Skip to content

Commit

Permalink
Merge pull request #292 from Jujulego/feat/add-common-defs
Browse files Browse the repository at this point in the history
Add common defs
  • Loading branch information
Jujulego committed May 10, 2023
2 parents 63f57e2 + 8ee2ff9 commit f2912c4
Show file tree
Hide file tree
Showing 19 changed files with 241 additions and 106 deletions.
3 changes: 1 addition & 2 deletions packages/aegis/.swcrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
"target": "es2022",
"baseUrl": "./",
"paths": {
"@/src/*": ["./src/*"],
"@/tools/*": ["./tools/*"]
"@/src/*": ["./src/*"]
}
},
"module": {
Expand Down
4 changes: 2 additions & 2 deletions packages/aegis/src/blade/b-ref.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { IListenable, inherit, IObservable, lazy, PrependEventMapKeys } from '@jujulego/event-tree';
import { Query } from '@jujulego/utils';

import { DRef, DRefEventMap, DVar, StoreEvent } from '@/src/data';
import { Fetcher, QRef, QRefEventMap, Strategy } from '@/src/query';
import { DRef, DRefEventMap, DVar, StoreEvent } from '../data';
import { Fetcher, QRef, QRefEventMap, Strategy } from '../query';

// Types
export type BRefEventMap<D> = DRefEventMap<D> & PrependEventMapKeys<'query', QRefEventMap<D>>;
Expand Down
9 changes: 5 additions & 4 deletions packages/aegis/src/blade/blade.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { KeyPart } from '@jujulego/event-tree';
import { Query } from '@jujulego/utils';

import { Fetcher, Manager, Strategy } from '@/src/query';
import { MemoryStore, Store } from '@/src/store';
import { BRef } from '@/src/blade/b-ref';
import { FRef } from '@/src/blade/f-ref';
import { Fetcher, Manager, Strategy } from '../query';
import { MemoryStore, Store } from '../store';

import { BRef } from './b-ref';
import { FRef } from './f-ref';

// Types
export type Extractor<D, K extends KeyPart = KeyPart> = (entity: D) => K;
Expand Down
7 changes: 4 additions & 3 deletions packages/aegis/src/blade/f-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {
} from '@jujulego/event-tree';
import { Query, QueryEventMap } from '@jujulego/utils';

import { DRef, DRefEventMap, StoreEvent } from '@/src/data';
import { QRef } from '@/src/query';
import { BRef } from '@/src/blade/b-ref';
import { DRef, DRefEventMap, StoreEvent } from '../data';
import { QRef } from '../query';

import { BRef } from './b-ref';

// Types
export type DGetter<D> = (data: D) => DRef<D>;
Expand Down
36 changes: 16 additions & 20 deletions packages/aegis/src/data/d-ref.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,49 @@
import { group, IListenable, IObservable, source, waitFor } from '@jujulego/event-tree';
import { source, waitFor } from '@jujulego/event-tree';

import { DataAccessor, StoreDeleteEvent, StoreEvent, StoreUpdateEvent } from './types';
import { Ref } from '../defs';

// Types
export type DRefEventMap<D> = {
update: StoreUpdateEvent<D>;
delete: StoreDeleteEvent<D>;
};
import { DataAccessor } from './types';

// Class
export class DRef<D> implements IObservable<StoreEvent<D>>, IListenable<DRefEventMap<D>> {
export class DRef<D = unknown> implements Ref<D> {
// Attributes
private readonly _events = group({
update: source<StoreUpdateEvent<D>>(),
delete: source<StoreDeleteEvent<D>>(),
});
private readonly _events = source<D>();

// Constructor
constructor(
private readonly accessor: DataAccessor<D>,
) {}

// Methods
readonly on = this._events.on;
readonly off = this._events.off;
readonly subscribe = this._events.subscribe;
readonly unsubscribe = this._events.unsubscribe;
readonly clear = this._events.clear;

async read(): Promise<D> {
let data = this.data;
const data = this.data;

if (data === undefined) {
const event = await waitFor(this._events, 'update');
data = event.data;
return waitFor(this._events);
}

return data;
}

update(data: D): void {
const old = this.accessor.read();
this.accessor.update(data);

this._events.emit('update', { data, old });
this._events.emit(data);
}

// Properties
get data(): D | undefined {
return this.accessor.read();
}

get isEmpty(): boolean {
if (this.accessor.isEmpty) {
return this.accessor.isEmpty();
}

return this.accessor.read() === undefined;
}
}
8 changes: 5 additions & 3 deletions packages/aegis/src/data/d-var.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { DRef } from './d-ref';

// Class
export class DVar<D> extends DRef<D> {
export class DVar<D = unknown> extends DRef<D> {
// Attributes
private _data: D | undefined;

// Constructor
constructor() {
constructor(initial?: D) {
super({
read: () => this._data,
update: (data: D) => {
this._data = data;
}
});

this._data = initial;
}
}
}
1 change: 1 addition & 0 deletions packages/aegis/src/data/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Types
export interface DataAccessor<D> {
isEmpty?: () => boolean;
read(): D | undefined;
update(data: D): void;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/aegis/src/defs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './readonly-ref';
export * from './ref';
13 changes: 13 additions & 0 deletions packages/aegis/src/defs/readonly-ref.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IObservable } from '@jujulego/event-tree';

/**
* Readonly reference
*/
export interface ReadonlyRef<D = unknown> extends IObservable<D> {
// Attributes
readonly isEmpty: boolean;
readonly data: D | undefined;

// Methods
read(): PromiseLike<D>;
}
9 changes: 9 additions & 0 deletions packages/aegis/src/defs/ref.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ReadonlyRef } from './readonly-ref';

/**
* Updatable reference
*/
export interface Ref<D> extends ReadonlyRef<D> {
// Methods
update(data: D): void;
}
2 changes: 1 addition & 1 deletion packages/aegis/src/query/manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IListenable, KeyPart, multiplexerMap, PrependEventMapKeys } from '@jujulego/event-tree';

import { WeakStore } from '@/src/utils';
import { WeakStore } from '../utils';

import { Fetcher, QRef, QRefEventMap, Strategy } from './q-ref';

Expand Down
86 changes: 56 additions & 30 deletions packages/aegis/src/query/q-ref.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,67 @@
import { group, IListenable, IObservable, OffGroup, offGroup, once, source } from '@jujulego/event-tree';
import { queryfy, Query, QueryState, QueryStateDone, QueryStateFailed, QueryStatePending } from '@jujulego/utils';
import {
IListenable, IMultiplexer,
Listener,
multiplexer,
OffGroup,
offGroup,
once,
source
} from '@jujulego/event-tree';
import { queryfy, Query, QueryState } from '@jujulego/utils';

import { ReadonlyRef } from '../defs';

// Types
export type Fetcher<D> = () => PromiseLike<D>;
export type Strategy = 'keep' | 'replace';

export type QRefEventMap<D> = {
pending: QueryStatePending;
done: QueryStateDone<D>;
failed: QueryStateFailed;
pending: true;
done: D;
failed: Error;
};

export interface QRefReadOpts {
/**
* Should throw if current query fails ?
*/
throws?: boolean;
}

// Class
export class QRef<D> implements IObservable<QueryState<D>>, IListenable<QRefEventMap<D>> {
/**
* Reference on data received by query
*/
export class QRef<D = unknown> implements ReadonlyRef<D>, IListenable<QRefEventMap<D>> {
// Attributes
private _off?: OffGroup;
private _query?: Query<D>;

private readonly _events = group({
'pending': source<QueryStatePending>(),
'done': source<QueryStateDone<D>>(),
'failed': source<QueryStateFailed>(),
});
private readonly _events = multiplexer({
'pending': source<true>(),
'done': source<D>(),
'failed': source<Error>(),
}) as IMultiplexer<QRefEventMap<D>, QRefEventMap<D>>;

// Methods
readonly on = this._events.on;
readonly off = this._events.off;
readonly subscribe = this._events.subscribe;
readonly unsubscribe = this._events.unsubscribe;
readonly clear = this._events.clear;

/**
* Subscribe to data updates
* @param listener
*/
readonly subscribe = (listener: Listener<D>) => this._events.on('done', listener);

/**
* Unsubscribe from data updates
* @param listener
*/
readonly unsubscribe = (listener: Listener<D>) => this._events.off('done', listener);

private _emitQueryState(state: QueryState<D>) {
if (state.status === 'pending') {
this._events.emit('pending', true);
} else if (state.status === 'done') {
this._events.emit('done', state.data);
} else {
this._events.emit('failed', state.error);
}
}

/**
* Triggers refresh of the q-ref. Will call fetcher to replace current Query according to its state and the selected
* strategy. If the current Query is completed (done or failed) it will call fetcher to initiate a new Query.
Expand All @@ -62,10 +87,10 @@ export class QRef<D> implements IObservable<QueryState<D>>, IListenable<QRefEven

if (this._query.status === 'pending') {
this._off = offGroup();
once(this._query, (state) => this._events.emit(state.status, state), { off: this._off });
once(this._query, (state) => this._emitQueryState(state), { off: this._off });
}

this._events.emit(this._query.state.status, this._query.state);
this._emitQueryState(this._query.state);

return this._query;
}
Expand All @@ -89,18 +114,15 @@ export class QRef<D> implements IObservable<QueryState<D>>, IListenable<QRefEven
/**
* Resolves to loaded data. Will return immediately if the current Query is done.
*/
async read(opts: QRefReadOpts = {}): Promise<D> {
let state = this._query?.state;
async read(): Promise<D> {
const state = this._query?.state;

if (state?.status !== 'done') {
state = await new Promise<QueryStateDone<D>>((resolve, reject) => {
return new Promise<D>((resolve, reject) => {
const off = offGroup();

once(this._events, 'done', resolve, { off });

if (opts.throws) {
once(this._events, 'failed', ({ error }) => reject(error), { off });
}
once(this._events, 'failed', (error) => reject(error), { off });
});
}

Expand All @@ -116,6 +138,10 @@ export class QRef<D> implements IObservable<QueryState<D>>, IListenable<QRefEven
return this._query?.data;
}

get isEmpty(): boolean {
return this._query?.status !== 'done';
}

get isLoading(): boolean {
return this._query?.status === 'pending';
}
Expand Down
4 changes: 2 additions & 2 deletions packages/aegis/src/store/storage.store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { KeyPart } from '@jujulego/event-tree';

import { WeakStore } from '@/src/utils';
import { WeakStore } from '../utils';

import { Store } from './store';

Expand Down Expand Up @@ -63,4 +63,4 @@ export class StorageStore<D, K extends KeyPart = KeyPart> extends Store<D, K> {
this.storage.setItem(this._storageKey(key), JSON.stringify(data));
this._cache.delete(key);
}
}
}
10 changes: 5 additions & 5 deletions packages/aegis/src/store/store.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { IListenable, KeyPart, multiplexerMap, PrependEventMapKeys } from '@jujulego/event-tree';
import { IListenable, KeyPart, multiplexerMap } from '@jujulego/event-tree';

import { DRef, DRefEventMap } from '@/src/data';
import { WeakStore } from '@/src/utils';
import { DRef } from '../data';
import { WeakStore } from '../utils';

// Types
export type StoreEventMap<D, K extends KeyPart = KeyPart> = PrependEventMapKeys<K, DRefEventMap<D>>;
export type StoreEventMap<D, K extends KeyPart = KeyPart> = Record<K, D>;

// Class
export abstract class Store<D, K extends KeyPart = KeyPart> implements IListenable<StoreEventMap<D, K>> {
Expand All @@ -23,7 +23,7 @@ export abstract class Store<D, K extends KeyPart = KeyPart> implements IListenab
private _createRef(key: K): DRef<D> {
return new DRef<D>({
read: () => this.get(key),
update: (data) => this.set(key, data),
update: (data: D) => this.set(key, data),
});
}

Expand Down
Loading

0 comments on commit f2912c4

Please sign in to comment.