@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.
Files changed (113) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/EntityMap.js +2 -2
  3. package/dist/cjs/EntityMap.js.map +1 -1
  4. package/dist/cjs/NetworkManager.js +105 -0
  5. package/dist/cjs/NetworkManager.js.map +1 -0
  6. package/dist/cjs/QueryClient.js +390 -76
  7. package/dist/cjs/QueryClient.js.map +1 -1
  8. package/dist/cjs/QueryStore.js +295 -3
  9. package/dist/cjs/QueryStore.js.map +1 -1
  10. package/dist/cjs/index.js +16 -1
  11. package/dist/cjs/index.js.map +1 -1
  12. package/dist/cjs/package.json +3 -0
  13. package/dist/cjs/parseEntities.js +3 -0
  14. package/dist/cjs/parseEntities.js.map +1 -1
  15. package/dist/cjs/proxy.js +19 -0
  16. package/dist/cjs/proxy.js.map +1 -1
  17. package/dist/cjs/query.js +40 -2
  18. package/dist/cjs/query.js.map +1 -1
  19. package/dist/cjs/stores/async.js +6 -0
  20. package/dist/cjs/stores/async.js.map +1 -0
  21. package/dist/cjs/stores/sync.js +7 -0
  22. package/dist/cjs/stores/sync.js.map +1 -0
  23. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  24. package/dist/cjs/type-utils.js +3 -0
  25. package/dist/cjs/type-utils.js.map +1 -0
  26. package/dist/cjs/types.js +19 -1
  27. package/dist/cjs/types.js.map +1 -1
  28. package/dist/esm/EntityMap.js +3 -3
  29. package/dist/esm/EntityMap.js.map +1 -1
  30. package/dist/esm/NetworkManager.d.ts +48 -0
  31. package/dist/esm/NetworkManager.d.ts.map +1 -0
  32. package/dist/esm/NetworkManager.js +101 -0
  33. package/dist/esm/NetworkManager.js.map +1 -0
  34. package/dist/esm/QueryClient.d.ts +81 -25
  35. package/dist/esm/QueryClient.d.ts.map +1 -1
  36. package/dist/esm/QueryClient.js +390 -76
  37. package/dist/esm/QueryClient.js.map +1 -1
  38. package/dist/esm/QueryStore.d.ts +64 -2
  39. package/dist/esm/QueryStore.d.ts.map +1 -1
  40. package/dist/esm/QueryStore.js +293 -2
  41. package/dist/esm/QueryStore.js.map +1 -1
  42. package/dist/esm/index.d.ts +5 -3
  43. package/dist/esm/index.d.ts.map +1 -1
  44. package/dist/esm/index.js +3 -1
  45. package/dist/esm/index.js.map +1 -1
  46. package/dist/esm/parseEntities.d.ts.map +1 -1
  47. package/dist/esm/parseEntities.js +3 -0
  48. package/dist/esm/parseEntities.js.map +1 -1
  49. package/dist/esm/proxy.d.ts +6 -0
  50. package/dist/esm/proxy.d.ts.map +1 -1
  51. package/dist/esm/proxy.js +18 -0
  52. package/dist/esm/proxy.js.map +1 -1
  53. package/dist/esm/query.d.ts +30 -29
  54. package/dist/esm/query.d.ts.map +1 -1
  55. package/dist/esm/query.js +39 -3
  56. package/dist/esm/query.js.map +1 -1
  57. package/dist/esm/stores/async.d.ts +2 -0
  58. package/dist/esm/stores/async.d.ts.map +1 -0
  59. package/dist/esm/stores/async.js +2 -0
  60. package/dist/esm/stores/async.js.map +1 -0
  61. package/dist/esm/stores/sync.d.ts +2 -0
  62. package/dist/esm/stores/sync.d.ts.map +1 -0
  63. package/dist/esm/stores/sync.js +2 -0
  64. package/dist/esm/stores/sync.js.map +1 -0
  65. package/dist/esm/type-utils.d.ts +12 -0
  66. package/dist/esm/type-utils.d.ts.map +1 -0
  67. package/dist/esm/type-utils.js +2 -0
  68. package/dist/esm/type-utils.js.map +1 -0
  69. package/dist/esm/types.d.ts +62 -5
  70. package/dist/esm/types.d.ts.map +1 -1
  71. package/dist/esm/types.js +18 -0
  72. package/dist/esm/types.js.map +1 -1
  73. package/index.d.ts +1 -0
  74. package/package.json +25 -4
  75. package/stores/async.d.ts +1 -0
  76. package/stores/async.js +15 -0
  77. package/stores/sync.d.ts +1 -0
  78. package/stores/sync.js +15 -0
  79. package/.turbo/turbo-build.log +0 -12
  80. package/ENTITY_STORE_DESIGN.md +0 -386
  81. package/dist/tsconfig.esm.tsbuildinfo +0 -1
  82. package/src/EntityMap.ts +0 -63
  83. package/src/QueryClient.ts +0 -482
  84. package/src/QueryStore.ts +0 -322
  85. package/src/__tests__/caching-persistence.test.ts +0 -983
  86. package/src/__tests__/entity-system.test.ts +0 -556
  87. package/src/__tests__/gc-time.test.ts +0 -327
  88. package/src/__tests__/mock-fetch.test.ts +0 -186
  89. package/src/__tests__/parse-entities.test.ts +0 -425
  90. package/src/__tests__/path-interpolation.test.ts +0 -225
  91. package/src/__tests__/reactivity.test.ts +0 -424
  92. package/src/__tests__/refetch-interval.test.ts +0 -262
  93. package/src/__tests__/rest-query-api.test.ts +0 -568
  94. package/src/__tests__/stale-time.test.ts +0 -357
  95. package/src/__tests__/type-to-string.test.ts +0 -129
  96. package/src/__tests__/utils.ts +0 -258
  97. package/src/__tests__/validation-edge-cases.test.ts +0 -821
  98. package/src/errors.ts +0 -124
  99. package/src/index.ts +0 -7
  100. package/src/parseEntities.ts +0 -213
  101. package/src/pathInterpolator.ts +0 -74
  102. package/src/proxy.ts +0 -257
  103. package/src/query.ts +0 -164
  104. package/src/react/__tests__/basic.test.tsx +0 -926
  105. package/src/react/__tests__/component.test.tsx +0 -984
  106. package/src/react/__tests__/utils.tsx +0 -71
  107. package/src/typeDefs.ts +0 -351
  108. package/src/types.ts +0 -132
  109. package/src/utils.ts +0 -66
  110. package/tsconfig.cjs.json +0 -14
  111. package/tsconfig.esm.json +0 -13
  112. package/tsconfig.json +0 -20
  113. 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
- }