@signalium/query 0.1.0 → 1.0.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/CHANGELOG.md +15 -0
- package/dist/cjs/EntityMap.js +2 -2
- package/dist/cjs/EntityMap.js.map +1 -1
- package/dist/cjs/NetworkManager.js +105 -0
- package/dist/cjs/NetworkManager.js.map +1 -0
- package/dist/cjs/QueryClient.js +390 -76
- package/dist/cjs/QueryClient.js.map +1 -1
- package/dist/cjs/QueryStore.js +295 -3
- package/dist/cjs/QueryStore.js.map +1 -1
- package/dist/cjs/index.js +16 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/parseEntities.js +3 -0
- package/dist/cjs/parseEntities.js.map +1 -1
- package/dist/cjs/proxy.js +19 -0
- package/dist/cjs/proxy.js.map +1 -1
- package/dist/cjs/query.js +40 -2
- package/dist/cjs/query.js.map +1 -1
- package/dist/cjs/stores/async.js +6 -0
- package/dist/cjs/stores/async.js.map +1 -0
- package/dist/cjs/stores/sync.js +7 -0
- package/dist/cjs/stores/sync.js.map +1 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/cjs/type-utils.js +3 -0
- package/dist/cjs/type-utils.js.map +1 -0
- package/dist/cjs/types.js +19 -1
- package/dist/cjs/types.js.map +1 -1
- package/dist/esm/EntityMap.js +3 -3
- package/dist/esm/EntityMap.js.map +1 -1
- package/dist/esm/NetworkManager.d.ts +48 -0
- package/dist/esm/NetworkManager.d.ts.map +1 -0
- package/dist/esm/NetworkManager.js +101 -0
- package/dist/esm/NetworkManager.js.map +1 -0
- package/dist/esm/QueryClient.d.ts +81 -25
- package/dist/esm/QueryClient.d.ts.map +1 -1
- package/dist/esm/QueryClient.js +390 -76
- package/dist/esm/QueryClient.js.map +1 -1
- package/dist/esm/QueryStore.d.ts +64 -2
- package/dist/esm/QueryStore.d.ts.map +1 -1
- package/dist/esm/QueryStore.js +293 -2
- package/dist/esm/QueryStore.js.map +1 -1
- package/dist/esm/index.d.ts +5 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/parseEntities.d.ts.map +1 -1
- package/dist/esm/parseEntities.js +3 -0
- package/dist/esm/parseEntities.js.map +1 -1
- package/dist/esm/proxy.d.ts +6 -0
- package/dist/esm/proxy.d.ts.map +1 -1
- package/dist/esm/proxy.js +18 -0
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query.d.ts +30 -29
- package/dist/esm/query.d.ts.map +1 -1
- package/dist/esm/query.js +39 -3
- package/dist/esm/query.js.map +1 -1
- package/dist/esm/stores/async.d.ts +2 -0
- package/dist/esm/stores/async.d.ts.map +1 -0
- package/dist/esm/stores/async.js +2 -0
- package/dist/esm/stores/async.js.map +1 -0
- package/dist/esm/stores/sync.d.ts +2 -0
- package/dist/esm/stores/sync.d.ts.map +1 -0
- package/dist/esm/stores/sync.js +2 -0
- package/dist/esm/stores/sync.js.map +1 -0
- package/dist/esm/type-utils.d.ts +12 -0
- package/dist/esm/type-utils.d.ts.map +1 -0
- package/dist/esm/type-utils.js +2 -0
- package/dist/esm/type-utils.js.map +1 -0
- package/dist/esm/types.d.ts +62 -5
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/types.js +18 -0
- package/dist/esm/types.js.map +1 -1
- package/index.d.ts +1 -0
- package/package.json +25 -4
- package/stores/async.d.ts +1 -0
- package/stores/async.js +15 -0
- package/stores/sync.d.ts +1 -0
- package/stores/sync.js +15 -0
- package/.turbo/turbo-build.log +0 -12
- package/ENTITY_STORE_DESIGN.md +0 -386
- package/dist/tsconfig.esm.tsbuildinfo +0 -1
- package/src/EntityMap.ts +0 -63
- package/src/QueryClient.ts +0 -482
- package/src/QueryStore.ts +0 -322
- package/src/__tests__/caching-persistence.test.ts +0 -983
- package/src/__tests__/entity-system.test.ts +0 -556
- package/src/__tests__/gc-time.test.ts +0 -327
- package/src/__tests__/mock-fetch.test.ts +0 -186
- package/src/__tests__/parse-entities.test.ts +0 -425
- package/src/__tests__/path-interpolation.test.ts +0 -225
- package/src/__tests__/reactivity.test.ts +0 -424
- package/src/__tests__/refetch-interval.test.ts +0 -262
- package/src/__tests__/rest-query-api.test.ts +0 -568
- package/src/__tests__/stale-time.test.ts +0 -357
- package/src/__tests__/type-to-string.test.ts +0 -129
- package/src/__tests__/utils.ts +0 -258
- package/src/__tests__/validation-edge-cases.test.ts +0 -821
- package/src/errors.ts +0 -124
- package/src/index.ts +0 -7
- package/src/parseEntities.ts +0 -213
- package/src/pathInterpolator.ts +0 -74
- package/src/proxy.ts +0 -257
- package/src/query.ts +0 -164
- package/src/react/__tests__/basic.test.tsx +0 -926
- package/src/react/__tests__/component.test.tsx +0 -984
- package/src/react/__tests__/utils.tsx +0 -71
- package/src/typeDefs.ts +0 -351
- package/src/types.ts +0 -132
- package/src/utils.ts +0 -66
- package/tsconfig.cjs.json +0 -14
- package/tsconfig.esm.json +0 -13
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -65
package/src/QueryStore.ts
DELETED
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QueryStore - Minimal interface for query persistence
|
|
3
|
-
*
|
|
4
|
-
* Provides a clean abstraction over document storage, reference counting,
|
|
5
|
-
* and LRU cache management. Supports both synchronous (in-memory) and
|
|
6
|
-
* asynchronous (writer-backed) implementations.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { EntityStore } from './EntityMap.js';
|
|
10
|
-
import { QueryDefinition } from './QueryClient.js';
|
|
11
|
-
|
|
12
|
-
// -----------------------------------------------------------------------------
|
|
13
|
-
// QueryStore Interface
|
|
14
|
-
// -----------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
export interface CachedQuery {
|
|
17
|
-
value: unknown;
|
|
18
|
-
updatedAt: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface QueryStore {
|
|
22
|
-
/**
|
|
23
|
-
* Asynchronously retrieves a document by key.
|
|
24
|
-
* May return undefined if the document is not in the store.
|
|
25
|
-
*/
|
|
26
|
-
loadQuery(
|
|
27
|
-
queryDef: QueryDefinition<any, any>,
|
|
28
|
-
queryKey: number,
|
|
29
|
-
entityMap: EntityStore,
|
|
30
|
-
): MaybePromise<CachedQuery | undefined>;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Synchronously stores a document with optional reference IDs.
|
|
34
|
-
* This is fire-and-forget for async implementations.
|
|
35
|
-
*/
|
|
36
|
-
saveQuery(queryDef: QueryDefinition<any, any>, queryKey: number, value: unknown, refIds?: Set<number>): void;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Synchronously stores an entity with optional reference IDs.
|
|
40
|
-
* This is fire-and-forget for async implementations.
|
|
41
|
-
*/
|
|
42
|
-
saveEntity(entityKey: number, value: unknown, refIds?: Set<number>): void;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Marks a query as accessed, updating the LRU queue.
|
|
46
|
-
* Handles eviction internally when the cache is full.
|
|
47
|
-
*/
|
|
48
|
-
activateQuery(queryDef: QueryDefinition<any, any>, queryKey: number): void;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export type MaybePromise<T> = T | Promise<T>;
|
|
52
|
-
|
|
53
|
-
export interface SyncPersistentStore {
|
|
54
|
-
has(key: string): boolean;
|
|
55
|
-
|
|
56
|
-
getString(key: string): string | undefined;
|
|
57
|
-
setString(key: string, value: string): void;
|
|
58
|
-
|
|
59
|
-
getNumber(key: string): number | undefined;
|
|
60
|
-
setNumber(key: string, value: number): void;
|
|
61
|
-
|
|
62
|
-
getBuffer(key: string): Uint32Array | undefined;
|
|
63
|
-
setBuffer(key: string, value: Uint32Array): void;
|
|
64
|
-
|
|
65
|
-
delete(key: string): void;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const DEFAULT_MAX_COUNT = 50;
|
|
69
|
-
const DEFAULT_GC_TIME = 1000 * 60 * 60 * 24; // 24 hours
|
|
70
|
-
|
|
71
|
-
export class MemoryPersistentStore implements SyncPersistentStore {
|
|
72
|
-
private readonly kv: Record<string, unknown> = Object.create(null);
|
|
73
|
-
|
|
74
|
-
has(key: string): boolean {
|
|
75
|
-
return key in this.kv;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
getString(key: string): string | undefined {
|
|
79
|
-
return this.kv[key] as string | undefined;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
setString(key: string, value: string): void {
|
|
83
|
-
this.kv[key] = value;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
getNumber(key: string): number | undefined {
|
|
87
|
-
return this.kv[key] as number | undefined;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
setNumber(key: string, value: number): void {
|
|
91
|
-
this.kv[key] = value;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
getBuffer(key: string): Uint32Array | undefined {
|
|
95
|
-
return this.kv[key] as Uint32Array | undefined;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
setBuffer(key: string, value: Uint32Array): void {
|
|
99
|
-
this.kv[key] = value;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
delete(key: string): void {
|
|
103
|
-
delete this.kv[key];
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Query Instance keys
|
|
108
|
-
export const valueKeyFor = (id: number) => `sq:doc:value:${id}`;
|
|
109
|
-
export const refCountKeyFor = (id: number) => `sq:doc:refCount:${id}`;
|
|
110
|
-
export const refIdsKeyFor = (id: number) => `sq:doc:refIds:${id}`;
|
|
111
|
-
export const updatedAtKeyFor = (id: number) => `sq:doc:updatedAt:${id}`;
|
|
112
|
-
|
|
113
|
-
// Query Type keys
|
|
114
|
-
export const queueKeyFor = (queryDefId: string) => `sq:doc:queue:${queryDefId}`;
|
|
115
|
-
|
|
116
|
-
export class SyncQueryStore implements QueryStore {
|
|
117
|
-
queues: Map<string, Uint32Array> = new Map();
|
|
118
|
-
|
|
119
|
-
constructor(private readonly kv: SyncPersistentStore) {}
|
|
120
|
-
|
|
121
|
-
loadQuery(queryDef: QueryDefinition<any, any>, queryKey: number, entityMap: EntityStore): CachedQuery | undefined {
|
|
122
|
-
const updatedAt = this.kv.getNumber(updatedAtKeyFor(queryKey));
|
|
123
|
-
|
|
124
|
-
if (updatedAt === undefined || updatedAt < Date.now() - (queryDef.cache?.gcTime ?? DEFAULT_GC_TIME)) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const valueStr = this.kv.getString(valueKeyFor(queryKey));
|
|
129
|
-
|
|
130
|
-
if (valueStr === undefined) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const entityIds = this.kv.getBuffer(refIdsKeyFor(queryKey));
|
|
135
|
-
|
|
136
|
-
if (entityIds !== undefined) {
|
|
137
|
-
this.preloadEntities(entityIds, entityMap);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
this.activateQuery(queryDef, queryKey);
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
value: JSON.parse(valueStr) as Record<string, unknown>,
|
|
144
|
-
updatedAt,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private preloadEntities(entityIds: Uint32Array, entityMap: EntityStore): void {
|
|
149
|
-
for (const entityId of entityIds) {
|
|
150
|
-
const entityValue = this.kv.getString(valueKeyFor(entityId));
|
|
151
|
-
|
|
152
|
-
if (entityValue === undefined) {
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const entity = JSON.parse(entityValue) as Record<string, unknown>;
|
|
157
|
-
entityMap.setPreloadedEntity(entityId, entity);
|
|
158
|
-
|
|
159
|
-
const childIds = this.kv.getBuffer(refIdsKeyFor(entityId));
|
|
160
|
-
|
|
161
|
-
if (childIds === undefined) {
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
this.preloadEntities(childIds, entityMap);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
saveQuery(queryDef: QueryDefinition<any, any>, queryKey: number, value: unknown, refIds?: Set<number>): void {
|
|
170
|
-
this.setValue(queryKey, value, refIds);
|
|
171
|
-
this.kv.setNumber(updatedAtKeyFor(queryKey), Date.now());
|
|
172
|
-
this.activateQuery(queryDef, queryKey);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
saveEntity(entityKey: number, value: unknown, refIds?: Set<number>): void {
|
|
176
|
-
this.setValue(entityKey, value, refIds);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
activateQuery(queryDef: QueryDefinition<any, any>, queryKey: number): void {
|
|
180
|
-
if (!this.kv.has(valueKeyFor(queryKey))) {
|
|
181
|
-
// Query not in store, nothing to do. This can happen if the query has
|
|
182
|
-
// been evicted from the cache, but is still active in memory.
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
let queue = this.queues.get(queryDef.id);
|
|
187
|
-
|
|
188
|
-
if (queue === undefined) {
|
|
189
|
-
const maxCount = queryDef.cache?.maxCount ?? DEFAULT_MAX_COUNT;
|
|
190
|
-
queue = this.kv.getBuffer(queueKeyFor(queryDef.id));
|
|
191
|
-
|
|
192
|
-
if (queue === undefined) {
|
|
193
|
-
queue = new Uint32Array(maxCount);
|
|
194
|
-
this.kv.setBuffer(queueKeyFor(queryDef.id), queue);
|
|
195
|
-
} else if (queue.length !== maxCount) {
|
|
196
|
-
const newQueue = new Uint32Array(maxCount);
|
|
197
|
-
newQueue.set(queue);
|
|
198
|
-
queue = newQueue;
|
|
199
|
-
this.kv.setBuffer(queueKeyFor(queryDef.id), queue);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
this.queues.set(queryDef.id, queue);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const indexOfKey = queue.indexOf(queryKey);
|
|
206
|
-
|
|
207
|
-
// Item already in queue, move to front
|
|
208
|
-
if (indexOfKey >= 0) {
|
|
209
|
-
if (indexOfKey === 0) {
|
|
210
|
-
// Already at front, nothing to do
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
// Shift items right to make space at front
|
|
214
|
-
queue.copyWithin(1, 0, indexOfKey);
|
|
215
|
-
queue[0] = queryKey;
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Item not in queue, add to front and evict tail
|
|
220
|
-
const evicted = queue[queue.length - 1];
|
|
221
|
-
queue.copyWithin(1, 0, queue.length - 1);
|
|
222
|
-
queue[0] = queryKey;
|
|
223
|
-
|
|
224
|
-
if (evicted !== 0) {
|
|
225
|
-
this.deleteValue(evicted);
|
|
226
|
-
this.kv.delete(updatedAtKeyFor(evicted));
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
private setValue(id: number, value: unknown, refIds?: Set<number>): void {
|
|
231
|
-
const kv = this.kv;
|
|
232
|
-
|
|
233
|
-
kv.setString(valueKeyFor(id), JSON.stringify(value));
|
|
234
|
-
|
|
235
|
-
const refIdsKey = refIdsKeyFor(id);
|
|
236
|
-
|
|
237
|
-
const prevRefIds = kv.getBuffer(refIdsKey);
|
|
238
|
-
|
|
239
|
-
if (refIds === undefined || refIds.size === 0) {
|
|
240
|
-
kv.delete(refIdsKey);
|
|
241
|
-
|
|
242
|
-
// Decrement all previous refs
|
|
243
|
-
if (prevRefIds !== undefined) {
|
|
244
|
-
for (let i = 0; i < prevRefIds.length; i++) {
|
|
245
|
-
const refId = prevRefIds[i];
|
|
246
|
-
this.decrementRefCount(refId);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
} else {
|
|
250
|
-
// Convert the set to a Uint32Array and capture all the refIds before we
|
|
251
|
-
// delete previous ones from the set
|
|
252
|
-
const newRefIds = new Uint32Array(refIds);
|
|
253
|
-
|
|
254
|
-
if (prevRefIds !== undefined) {
|
|
255
|
-
// Process new refs: increment if not in old
|
|
256
|
-
for (let i = 0; i < prevRefIds.length; i++) {
|
|
257
|
-
const refId = prevRefIds[i];
|
|
258
|
-
|
|
259
|
-
if (refIds.has(refId)) {
|
|
260
|
-
refIds.delete(refId);
|
|
261
|
-
} else {
|
|
262
|
-
this.decrementRefCount(refId);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// No previous refs, increment all unique new refs
|
|
268
|
-
for (const refId of refIds) {
|
|
269
|
-
this.incrementRefCount(refId);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
kv.setBuffer(refIdsKey, newRefIds);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private deleteValue(id: number): void {
|
|
277
|
-
const kv = this.kv;
|
|
278
|
-
|
|
279
|
-
kv.delete(valueKeyFor(id));
|
|
280
|
-
kv.delete(refCountKeyFor(id));
|
|
281
|
-
|
|
282
|
-
const refIds = kv.getBuffer(refIdsKeyFor(id));
|
|
283
|
-
kv.delete(refIdsKeyFor(id)); // Clean up the refIds key
|
|
284
|
-
|
|
285
|
-
if (refIds === undefined) {
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Decrement ref counts for all referenced entities
|
|
290
|
-
for (const refId of refIds) {
|
|
291
|
-
if (refId !== 0) {
|
|
292
|
-
this.decrementRefCount(refId);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
private incrementRefCount(refId: number): void {
|
|
298
|
-
const refCountKey = refCountKeyFor(refId);
|
|
299
|
-
const currentCount = this.kv.getNumber(refCountKey) ?? 0;
|
|
300
|
-
const newCount = currentCount + 1;
|
|
301
|
-
this.kv.setNumber(refCountKey, newCount);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private decrementRefCount(refId: number): void {
|
|
305
|
-
const refCountKey = refCountKeyFor(refId);
|
|
306
|
-
const currentCount = this.kv.getNumber(refCountKey);
|
|
307
|
-
|
|
308
|
-
if (currentCount === undefined) {
|
|
309
|
-
// Already deleted or never existed
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const newCount = currentCount - 1;
|
|
314
|
-
|
|
315
|
-
if (newCount === 0) {
|
|
316
|
-
// Entity exists, cascade delete it
|
|
317
|
-
this.deleteValue(refId);
|
|
318
|
-
} else {
|
|
319
|
-
this.kv.setNumber(refCountKey, newCount);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|