Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework builders #83

Merged
merged 13 commits into from
Jul 9, 2022
12 changes: 12 additions & 0 deletions .idea/runConfigurations/build__aegis_.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions .idea/runConfigurations/watch__aegis_.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions packages/aegis/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jujulego/aegis",
"version": "1.0.0-alpha.1",
"version": "1.0.0-alpha.2",
"license": "MIT",
"author": "Julien Capellari <julien.capellari@google.com>",
"repository": {
Expand Down Expand Up @@ -60,5 +60,6 @@
"regenerator-runtime": "0.13.9",
"ts-node": "10.8.2",
"typescript": "4.7.4"
}
},
"stableVersion": "1.0.0-alpha.1"
}
137 changes: 0 additions & 137 deletions packages/aegis/src/entity.builder.ts

This file was deleted.

61 changes: 61 additions & 0 deletions packages/aegis/src/entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Entity, Query, RefreshStrategy, Store } from '@jujulego/aegis-core';

import { $item, AegisItem, AegisUnknownItem } from './item';
import { $list, AegisList } from './list';
import { AegisId, AegisIdExtractor, AegisProtocol, Fetcher, Refreshable } from './utils';

// Types
export type AegisEntity<T, I extends AegisId, P extends AegisProtocol> = P & {
readonly $entity: Entity<T>;

$item(id: I): AegisItem<T, I>;
$queryItem<N extends string, A extends unknown[]>(name: N, fetcher: Fetcher<A, Query<T>>): AegisEntity<T, I, P & Record<N, Fetcher<A, AegisUnknownItem<T, I>>>>;
$queryItem<N extends string, A extends unknown[]>(name: N, fetcher: Fetcher<A, Query<T>>, id: AegisIdExtractor<A, I>, strategy?: RefreshStrategy): AegisEntity<T, I, P & Record<N, Fetcher<A, AegisItem<T, I> & Refreshable<T>>>>;

$list(key: string): AegisList<T>;
$queryList<N extends string, A extends unknown[]>(name: N, fetcher: Fetcher<A, Query<T[]>>, strategy?: RefreshStrategy): AegisEntity<T, I, P & Record<N, Fetcher<[string, ...A], AegisList<T>>>>;
}

// Entity builder
// eslint-disable-next-line @typescript-eslint/ban-types
export function $entity<T, I extends AegisId>(name: string, store: Store, extractor: (itm: T) => I): AegisEntity<T, I, {}> {
const entity = new Entity<T>(name, store, (itm) => JSON.stringify(extractor(itm)));

return {
$entity: entity,

$item(id: I): AegisItem<T, I> {
return $item(entity, id);
},

$queryItem<N extends string, A extends unknown[]>(name: N, fetcher: Fetcher<A, Query<T>>, id?: AegisIdExtractor<A, I>, strategy: RefreshStrategy = 'keep') {
this[name] = (...args: A) => {
if (!id) {
return $item<T, I>(entity, fetcher(...args));
} else {
const item = $item(entity, id(...args), () => fetcher(...args));
item.$item.refresh(() => fetcher(...args), strategy);

return item;
}
};

return this;
},

$list(key: string): AegisList<T> {
return $list(entity, key);
},

$queryList<N extends string, A extends unknown[]>(name: N, fetcher: Fetcher<A, Query<T[]>>, strategy: RefreshStrategy = 'keep') {
this[name] = (key: string, ...args: A) => {
const list = $list(entity, key, () => fetcher(...args));
list.$list.refresh(() => fetcher(...args), strategy);

return list;
};

return this;
}
};
}
7 changes: 5 additions & 2 deletions packages/aegis/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export * from '@jujulego/aegis-core';

export * from './entity.builder';
export * from './store.builder';
export * from './entity';
export * from './item';
export * from './list';
export * from './store';
export * from './utils';
128 changes: 128 additions & 0 deletions packages/aegis/src/item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import {
Entity,
EventListener,
EventListenerOptions, EventSource, EventType,
EventUnsubscribe, ExtractKey, Item,
PartialKey, Query, QueryManagerEventMap, RefreshStrategy,
StoreEventMap
} from '@jujulego/aegis-core';

import { AegisId, Refreshable } from './utils';

// Types
export interface AegisUnknownItem<T, I extends AegisId = AegisId> {
readonly $id?: I;
readonly $item?: Item<T>;
readonly $entity: Entity<T>;

readonly isLoading: boolean;
data?: T;

subscribe(
key: 'update',
listener: EventListener<StoreEventMap<T>, `update.${string}.${string}`>,
opts?: EventListenerOptions
): EventUnsubscribe;
subscribe<T extends PartialKey<EventType<QueryManagerEventMap<T>>>>(
type: T,
listener: EventListener<QueryManagerEventMap<T>, ExtractKey<EventType<QueryManagerEventMap<T>>, T>>,
opts?: EventListenerOptions
): EventUnsubscribe;
}

export interface AegisItem<T, I extends AegisId = AegisId> extends AegisUnknownItem<T, I> {
readonly $id: I;
readonly $item: Item<T>;
}

// Item builder
export function $item<T, I extends AegisId>(entity: Entity<T>, id: I): AegisItem<T, I>;
export function $item<T, I extends AegisId>(entity: Entity<T>, id: I, refresh: () => Query<T>): AegisItem<T, I> & Refreshable<T>;
export function $item<T, I extends AegisId>(entity: Entity<T>, query: Query<T>): AegisUnknownItem<T, I>;

export function $item<T, I extends AegisId>(entity: Entity<T>, arg1: I | Query<T>, refresh?: () => Query<T>) {
if (arg1 instanceof Query) {
const events = new EventSource<StoreEventMap<T> & QueryManagerEventMap<T>>();

const item: AegisUnknownItem<T, I> = {
$entity: entity,

get subscribe() {
return events.subscribe.bind(events);
},

get isLoading() {
return this.$item?.isLoading ?? (arg1.status === 'pending');
},

get data() {
return this.$item?.data;
},
set data(value: T | undefined) {
if (!this.$item) {
throw new Error('Cannot update unknown item');
}

this.$item.data = value;
}
};

// Events
arg1.subscribe('update.failed', (state, mtd) => {
events.emit('query.failed', state, { source: mtd.source });
});

arg1.subscribe('update.completed', ({ result }, mtd) => {
const id = entity.storeItem(result);
const $item = entity.item(id);

Object.assign(item, {
$id: JSON.parse(id),
$item
});

$item.subscribe('update', (data, mtd) => {
events.emit(mtd.type, data, { source: mtd.source });
});

$item.subscribe('query', (data, mtd) => {
events.emit(mtd.type, data, { source: mtd.source });
});

events.emit('query.completed', { status: 'completed', result: result }, { source: mtd.source });
});

return item;
} else {
const item: AegisItem<T, I> = {
$id: arg1,
$item: entity.item(JSON.stringify(arg1)),
$entity: entity,

subscribe(...args: unknown[]) {
return this.$item.subscribe(...args);
},

get isLoading() {
return this.$item.isLoading;
},

get data() {
return this.$item.data;
},
set data(value: T | undefined) {
this.$item.data = value;
}
};

if (refresh) {
return Object.assign(item, {
refresh(strategy: RefreshStrategy = 'keep') {
return item.$item.refresh(refresh, strategy);
}
});
}

return item;
}
}
Loading