entity-repository 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react.d.ts +2 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +27 -0
- package/dist/record-query.d.ts +2 -2
- package/dist/record-query.d.ts.map +1 -1
- package/dist/repository.d.ts +18 -3
- package/dist/repository.d.ts.map +1 -1
- package/dist/repository.js +67 -2
- package/package.json +5 -1
package/dist/react.d.ts
CHANGED
|
@@ -9,8 +9,9 @@ export declare function createRepositoryContext<Definitions extends EntityDefini
|
|
|
9
9
|
children: ReactNode;
|
|
10
10
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
11
11
|
useRepository: () => Repository<Definitions, Config>;
|
|
12
|
-
useRepositoryQuery: <Table extends keyof Definitions>(table: Table, id: EntityIdTuple<Definitions, Config, Table>, fetcher: (id: EntityIdTuple<Definitions, Config, Table>) => Promise<Definitions[Table]>) => RepositoryQuery<Definitions[Table]>;
|
|
12
|
+
useRepositoryQuery: <Table extends keyof Definitions>(table: Table, id: EntityIdTuple<Definitions, Config, Table>, fetcher: (id: EntityIdTuple<Definitions, Config, Table>) => Promise<Definitions[Table] | null>) => RepositoryQuery<Definitions[Table]>;
|
|
13
13
|
useRepositoryListQuery: <Table extends keyof Definitions, Param>(table: Table, param: Param, options: ListQueryOptions<Definitions[Table]>, fetcher: (param: Param) => Promise<Definitions[Table][]>) => ListQueryState<Definitions[Table]>;
|
|
14
|
+
useObservableList: <Table extends keyof Definitions, Key>(table: Table, key: Key, options: ListQueryOptions<Definitions[Table]>) => Definitions[Table][];
|
|
14
15
|
useSubscribedState: <Value>(observable: Observable<Value>, getSnapshot: () => Value) => Value;
|
|
15
16
|
};
|
|
16
17
|
//# sourceMappingURL=react.d.ts.map
|
package/dist/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,SAAS,EAA6E,MAAM,OAAO,CAAC;AAE5H,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/F,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,wBAAgB,uBAAuB,CACrC,WAAW,SAAS,iBAAiB,EACrC,MAAM,SAAS,YAAY,CAAC,WAAW,CAAC,GAAG,YAAY,CAAC,WAAW,CAAC;oDAOjE;QACD,UAAU,EAAE,UAAU,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAC5C,QAAQ,EAAE,SAAS,CAAC;KACrB;;yBAyC2B,KAAK,SAAS,MAAM,WAAW,SAClD,KAAK,MACR,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,WACpC,CAAC,EAAE,EAAE,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,SAAS,EAA6E,MAAM,OAAO,CAAC;AAE5H,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/F,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,wBAAgB,uBAAuB,CACrC,WAAW,SAAS,iBAAiB,EACrC,MAAM,SAAS,YAAY,CAAC,WAAW,CAAC,GAAG,YAAY,CAAC,WAAW,CAAC;oDAOjE;QACD,UAAU,EAAE,UAAU,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAC5C,QAAQ,EAAE,SAAS,CAAC;KACrB;;yBAyC2B,KAAK,SAAS,MAAM,WAAW,SAClD,KAAK,MACR,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,WACpC,CAAC,EAAE,EAAE,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAC7F,eAAe,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;6BAmDN,KAAK,SAAS,MAAM,WAAW,EAAE,KAAK,SAC7D,KAAK,SACL,KAAK,WACH,gBAAgB,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,WACpC,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,KACvD,cAAc,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;wBAlCV,KAAK,SAAS,MAAM,WAAW,EAAE,GAAG,SACtD,KAAK,OACP,GAAG,WACC,gBAAgB,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,KAC5C,WAAW,CAAC,KAAK,CAAC,EAAE;yBAvDK,KAAK,cAAc,UAAU,CAAC,KAAK,CAAC,eAAe,MAAM,KAAK;EAmH3F"}
|
package/dist/react.js
CHANGED
|
@@ -37,6 +37,32 @@ export function createRepositoryContext() {
|
|
|
37
37
|
const recordQuery = useMemo(() => repository.recordQuery(table, id, fetcher), [repository, JSON.stringify(id)]);
|
|
38
38
|
return useSubscribedState(recordQuery.$state, () => recordQuery.$state.value);
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Subscribes to a filtered/ordered observable view of the cache. Reads the
|
|
42
|
+
* current matching snapshot on mount, then re-emits whenever an
|
|
43
|
+
* insert/update/delete on the table changes the matching set. Unlike
|
|
44
|
+
* useRepositoryListQuery, there's no fetcher — this hook is the
|
|
45
|
+
* cold-start-safe read for data that's seeded elsewhere (typically by a
|
|
46
|
+
* sibling listQuery).
|
|
47
|
+
*
|
|
48
|
+
* `key` keys the memoization (use any value derived from the filter
|
|
49
|
+
* inputs, e.g. a task id).
|
|
50
|
+
*/
|
|
51
|
+
function useObservableList(table, key, options) {
|
|
52
|
+
const repository = useRepository();
|
|
53
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- table/options intentionally ignored (stable for a given key)
|
|
54
|
+
const observable = useMemo(() => repository.observableList(table, options), [repository, JSON.stringify(key)]);
|
|
55
|
+
return useSubscribedState(observable, () => {
|
|
56
|
+
// Pull the current snapshot via a synchronous one-shot subscribe.
|
|
57
|
+
// observableList emits its first value synchronously.
|
|
58
|
+
let snapshot = [];
|
|
59
|
+
const sub = observable.subscribe((value) => {
|
|
60
|
+
snapshot = value;
|
|
61
|
+
});
|
|
62
|
+
sub.unsubscribe();
|
|
63
|
+
return snapshot;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
40
66
|
/**
|
|
41
67
|
* Subscribes to a list query keyed only by `param`.
|
|
42
68
|
*
|
|
@@ -58,6 +84,7 @@ export function createRepositoryContext() {
|
|
|
58
84
|
useRepository,
|
|
59
85
|
useRepositoryQuery,
|
|
60
86
|
useRepositoryListQuery,
|
|
87
|
+
useObservableList,
|
|
61
88
|
useSubscribedState,
|
|
62
89
|
};
|
|
63
90
|
}
|
package/dist/record-query.d.ts
CHANGED
|
@@ -8,8 +8,8 @@ export declare class RecordQuery<Definitions extends EntityDefinitions, Config e
|
|
|
8
8
|
private table;
|
|
9
9
|
private id;
|
|
10
10
|
private fetcher;
|
|
11
|
-
constructor(repository: Repository<Definitions, Config>, table: Table, id: EntityIdTuple<Definitions, Config, Table>, fetcher: (id: EntityIdTuple<Definitions, Config, Table>) => Promise<Definitions[Table]>);
|
|
12
|
-
fetch(): Promise<Definitions[Table]>;
|
|
11
|
+
constructor(repository: Repository<Definitions, Config>, table: Table, id: EntityIdTuple<Definitions, Config, Table>, fetcher: (id: EntityIdTuple<Definitions, Config, Table>) => Promise<Definitions[Table] | null>);
|
|
12
|
+
fetch(): Promise<Definitions[Table] | null>;
|
|
13
13
|
dispose(): void;
|
|
14
14
|
}
|
|
15
15
|
//# sourceMappingURL=record-query.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-query.d.ts","sourceRoot":"","sources":["../src/record-query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAgB,MAAM,MAAM,CAAC;AAErD,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,eAAe,EAChB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,qBAAa,WAAW,CACtB,WAAW,SAAS,iBAAiB,EACrC,MAAM,SAAS,YAAY,CAAC,WAAW,CAAC,EACxC,KAAK,SAAS,MAAM,WAAW;IAE/B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC,eAAe,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,EAAE,CAA4C;IACtD,OAAO,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"record-query.d.ts","sourceRoot":"","sources":["../src/record-query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAgB,MAAM,MAAM,CAAC;AAErD,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,eAAe,EAChB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,qBAAa,WAAW,CACtB,WAAW,SAAS,iBAAiB,EACrC,MAAM,SAAS,YAAY,CAAC,WAAW,CAAC,EACxC,KAAK,SAAS,MAAM,WAAW;IAE/B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC,eAAe,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,EAAE,CAA4C;IACtD,OAAO,CAAC,OAAO,CAAwF;gBAGrG,UAAU,EAAE,UAAU,CAAC,WAAW,EAAE,MAAM,CAAC,EAC3C,KAAK,EAAE,KAAK,EACZ,EAAE,EAAE,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAC7C,OAAO,EAAE,CAAC,EAAE,EAAE,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAuB1F,KAAK;IA2BX,OAAO;CAGR"}
|
package/dist/repository.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BehaviorSubject, Subject } from "rxjs";
|
|
1
|
+
import { BehaviorSubject, Observable, Subject } from "rxjs";
|
|
2
2
|
import type { EntityConfig, EntityDefinitions, EntityEvent, EntityIdTuple, RepositoryConfig } from "./types";
|
|
3
3
|
import { ListQuery, type ListQueryOptions } from "./list-query";
|
|
4
4
|
import { RecordQuery } from "./record-query";
|
|
@@ -9,10 +9,25 @@ export declare class Repository<Definitions extends EntityDefinitions, Config ex
|
|
|
9
9
|
set<Table extends keyof Definitions>(table: Table, entity: Definitions[Table]): void;
|
|
10
10
|
del<Table extends keyof Definitions>(table: Table, id: EntityIdTuple<Definitions, Config, Table>): void;
|
|
11
11
|
get<Table extends keyof Definitions>(table: Table, id: EntityIdTuple<Definitions, Config, Table>): Definitions[Table] | null;
|
|
12
|
-
fetch<Table extends keyof Definitions>(table: Table, id: EntityIdTuple<Definitions, Config, Table>, fetcher: (id: EntityIdTuple<Definitions, Config, Table>) => Promise<Definitions[Table]>): Promise<Definitions[Table]>;
|
|
12
|
+
fetch<Table extends keyof Definitions>(table: Table, id: EntityIdTuple<Definitions, Config, Table>, fetcher: (id: EntityIdTuple<Definitions, Config, Table>) => Promise<Definitions[Table] | null>): Promise<Definitions[Table] | null>;
|
|
13
13
|
getObservable<Table extends keyof Definitions>(table: Table, id: EntityIdTuple<Definitions, Config, Table>): BehaviorSubject<Definitions[Table] | null>;
|
|
14
14
|
getEvents<Table extends keyof Definitions>(table: Table): Subject<EntityEvent<Definitions[Table]>>;
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Observable view of the cache for `table`, optionally filtered and
|
|
17
|
+
* ordered. On subscribe, emits the current matching snapshot
|
|
18
|
+
* synchronously (no cold-start race against a non-replaying event
|
|
19
|
+
* Subject), then emits a new array whenever an insert/update/delete on
|
|
20
|
+
* the table changes the matching set.
|
|
21
|
+
*
|
|
22
|
+
* Use this when a subscriber needs to mount AFTER the data may already
|
|
23
|
+
* have been seeded — e.g. a per-row tag query mounted when the parent
|
|
24
|
+
* task row renders, where the parent's fetch already filled the cache.
|
|
25
|
+
*
|
|
26
|
+
* Returned arrays are immutable snapshots — each emission is a fresh
|
|
27
|
+
* array, so React subscribers can compare by reference.
|
|
28
|
+
*/
|
|
29
|
+
observableList<Table extends keyof Definitions>(table: Table, options?: ListQueryOptions<Definitions[Table]>): Observable<Definitions[Table][]>;
|
|
30
|
+
recordQuery<Table extends keyof Definitions>(table: Table, id: EntityIdTuple<Definitions, Config, Table>, fetcher: (id: EntityIdTuple<Definitions, Config, Table>) => Promise<Definitions[Table] | null>): RecordQuery<Definitions, Config, Table>;
|
|
16
31
|
getCacheKey<Table extends keyof Definitions>(table: Table, id: EntityIdTuple<Definitions, Config, Table>): string;
|
|
17
32
|
getEntityKey<Table extends keyof Definitions>(table: Table, entity: Definitions[Table]): string;
|
|
18
33
|
listQuery<Table extends keyof Definitions>(table: Table, options: ListQueryOptions<Definitions[Table]>, fetcher: () => Promise<Definitions[Table][]>): ListQuery<Definitions, Config, Table>;
|
package/dist/repository.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../src/repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../src/repository.ts"],"names":[],"mappings":"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"}
|
package/dist/repository.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BehaviorSubject, Subject } from "rxjs";
|
|
1
|
+
import { BehaviorSubject, Observable, Subject } from "rxjs";
|
|
2
2
|
import { ListQuery } from "./list-query";
|
|
3
3
|
import { RecordQuery } from "./record-query";
|
|
4
4
|
export class Repository {
|
|
@@ -68,7 +68,9 @@ export class Repository {
|
|
|
68
68
|
}
|
|
69
69
|
const request = (async () => {
|
|
70
70
|
const value = await fetcher(id);
|
|
71
|
-
|
|
71
|
+
if (value !== null) {
|
|
72
|
+
this.set(table, value);
|
|
73
|
+
}
|
|
72
74
|
return value;
|
|
73
75
|
})();
|
|
74
76
|
store.inflight.set(cacheKey, request);
|
|
@@ -93,6 +95,69 @@ export class Repository {
|
|
|
93
95
|
const store = this.getStore(table);
|
|
94
96
|
return store.events$;
|
|
95
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Observable view of the cache for `table`, optionally filtered and
|
|
100
|
+
* ordered. On subscribe, emits the current matching snapshot
|
|
101
|
+
* synchronously (no cold-start race against a non-replaying event
|
|
102
|
+
* Subject), then emits a new array whenever an insert/update/delete on
|
|
103
|
+
* the table changes the matching set.
|
|
104
|
+
*
|
|
105
|
+
* Use this when a subscriber needs to mount AFTER the data may already
|
|
106
|
+
* have been seeded — e.g. a per-row tag query mounted when the parent
|
|
107
|
+
* task row renders, where the parent's fetch already filled the cache.
|
|
108
|
+
*
|
|
109
|
+
* Returned arrays are immutable snapshots — each emission is a fresh
|
|
110
|
+
* array, so React subscribers can compare by reference.
|
|
111
|
+
*/
|
|
112
|
+
observableList(table, options = {}) {
|
|
113
|
+
const store = this.getStore(table);
|
|
114
|
+
const filterFn = options.filter ?? (() => true);
|
|
115
|
+
const orderFn = options.order ?? null;
|
|
116
|
+
const applyOrder = (records) => orderFn ? [...records].sort(orderFn) : records;
|
|
117
|
+
return new Observable((subscriber) => {
|
|
118
|
+
let current = applyOrder(Array.from(store.records.values()).filter(filterFn));
|
|
119
|
+
subscriber.next(current);
|
|
120
|
+
const eventsSub = store.events$.subscribe((event) => {
|
|
121
|
+
switch (event.type) {
|
|
122
|
+
case "insert":
|
|
123
|
+
case "update": {
|
|
124
|
+
const passes = filterFn(event.new);
|
|
125
|
+
const key = this.getEntityKey(table, event.new);
|
|
126
|
+
const index = current.findIndex((record) => this.getEntityKey(table, record) === key);
|
|
127
|
+
if (!passes) {
|
|
128
|
+
if (index === -1)
|
|
129
|
+
return;
|
|
130
|
+
const next = current.slice();
|
|
131
|
+
next.splice(index, 1);
|
|
132
|
+
current = applyOrder(next);
|
|
133
|
+
subscriber.next(current);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const next = current.slice();
|
|
137
|
+
if (index === -1)
|
|
138
|
+
next.push(event.new);
|
|
139
|
+
else
|
|
140
|
+
next[index] = event.new;
|
|
141
|
+
current = applyOrder(next);
|
|
142
|
+
subscriber.next(current);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
case "delete": {
|
|
146
|
+
const key = this.getEntityKey(table, event.old);
|
|
147
|
+
const index = current.findIndex((record) => this.getEntityKey(table, record) === key);
|
|
148
|
+
if (index === -1)
|
|
149
|
+
return;
|
|
150
|
+
const next = current.slice();
|
|
151
|
+
next.splice(index, 1);
|
|
152
|
+
current = applyOrder(next);
|
|
153
|
+
subscriber.next(current);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
return () => eventsSub.unsubscribe();
|
|
159
|
+
});
|
|
160
|
+
}
|
|
96
161
|
recordQuery(table, id, fetcher) {
|
|
97
162
|
return new RecordQuery(this, table, id, fetcher);
|
|
98
163
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "entity-repository",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Type-safe entity caching and state management with RxJS and React",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc",
|
|
19
19
|
"clean": "rm -rf dist",
|
|
20
|
+
"test": "node --import tsx --test tests/*.test.ts",
|
|
21
|
+
"prepare": "npm run build",
|
|
20
22
|
"prepublishOnly": "npm run clean && npm run build"
|
|
21
23
|
},
|
|
22
24
|
"keywords": [
|
|
@@ -50,7 +52,9 @@
|
|
|
50
52
|
}
|
|
51
53
|
},
|
|
52
54
|
"devDependencies": {
|
|
55
|
+
"@types/node": "^22.0.0",
|
|
53
56
|
"@types/react": "^19.2.8",
|
|
57
|
+
"tsx": "^4.19.0",
|
|
54
58
|
"typescript": "^5.9.3"
|
|
55
59
|
}
|
|
56
60
|
}
|