@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
@@ -1,357 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unused-expressions */
2
- import { describe, it, expect, beforeEach, vi } from 'vitest';
3
- import { SyncQueryStore, MemoryPersistentStore, updatedAtKeyFor } from '../QueryStore.js';
4
- import { QueryClient } from '../QueryClient.js';
5
- import { query } from '../query.js';
6
- import { createMockFetch, testWithClient, sleep } from './utils.js';
7
-
8
- /**
9
- * StaleTime Tests
10
- *
11
- * Tests staleTime behavior: serving cached data while refetching in background
12
- */
13
-
14
- describe('StaleTime', () => {
15
- let client: QueryClient;
16
- let mockFetch: ReturnType<typeof createMockFetch>;
17
- let kv: any;
18
- let store: any;
19
-
20
- beforeEach(() => {
21
- client?.destroy();
22
- kv = new MemoryPersistentStore();
23
- store = new SyncQueryStore(kv);
24
- mockFetch = createMockFetch();
25
- client = new QueryClient(store, { fetch: mockFetch as any });
26
- });
27
-
28
- describe('Fresh Data', () => {
29
- it('should not refetch when data is fresh (within staleTime)', async () => {
30
- // Set up query with 10 second staleTime
31
- const getItem = query(t => ({
32
- path: '/item',
33
- response: { value: t.string },
34
- cache: { staleTime: 10000 }, // 10 seconds
35
- }));
36
-
37
- mockFetch.get('/item', { value: 'first' });
38
-
39
- await testWithClient(client, async () => {
40
- // First fetch
41
- const relay1 = getItem();
42
- await relay1;
43
- expect(relay1.value).toEqual({ value: 'first' });
44
- expect(mockFetch.calls).toHaveLength(1);
45
-
46
- // Second access immediately (data is fresh)
47
- mockFetch.get('/item', { value: 'second' });
48
- const relay2 = getItem();
49
-
50
- // Force evaluation
51
- relay2.value;
52
- await sleep(50);
53
-
54
- // Should use cached data without refetch
55
- expect(relay2.value).toEqual({ value: 'first' });
56
- expect(mockFetch.calls).toHaveLength(1); // Still only one call
57
- });
58
- });
59
-
60
- it('should use fresh data from disk cache without refetch', async () => {
61
- const getItem = query(t => ({
62
- path: '/item',
63
- response: { data: t.number },
64
- cache: { staleTime: 5000 },
65
- }));
66
-
67
- mockFetch.get('/item', { data: 42 });
68
-
69
- await testWithClient(client, async () => {
70
- const relay1 = getItem();
71
- await relay1;
72
- expect(mockFetch.calls).toHaveLength(1);
73
- });
74
-
75
- // Create new client with same store (simulating app restart)
76
- mockFetch.reset();
77
- mockFetch.get('/item', { data: 99 }, { delay: 50 });
78
- const client2 = new QueryClient(store, { fetch: mockFetch as any });
79
-
80
- await testWithClient(client2, async () => {
81
- const relay = getItem();
82
-
83
- // Should immediately have cached value
84
- relay.value;
85
- await sleep(10);
86
- expect(relay.value).toEqual({ data: 42 });
87
-
88
- // Should not refetch since data is still fresh
89
- await sleep(100);
90
- expect(mockFetch.calls).toHaveLength(0);
91
- });
92
- });
93
- });
94
-
95
- describe('Stale Data', () => {
96
- it('should serve stale data immediately while refetching in background', async () => {
97
- const getItem = query(t => ({
98
- path: '/item',
99
- response: { count: t.number },
100
- staleTime: 100, // 100ms
101
- }));
102
-
103
- mockFetch.get('/item', { count: 1 });
104
-
105
- await testWithClient(client, async () => {
106
- // Initial fetch
107
- const relay1 = getItem();
108
- await relay1;
109
- expect(relay1.value).toEqual({ count: 1 });
110
- });
111
-
112
- // Wait for data to become stale, and unwatch query entirely
113
- await sleep(200);
114
-
115
- await testWithClient(client, async () => {
116
- // Set up new response
117
- mockFetch.get('/item', { count: 2 }, { delay: 50 });
118
-
119
- // Access again - should serve stale data immediately
120
- const relay2 = getItem();
121
- relay2.value;
122
- await sleep(10);
123
-
124
- // Should have stale data immediately
125
- expect(relay2.value).toEqual({ count: 1 });
126
-
127
- // Wait for background refetch to complete
128
- await sleep(100);
129
-
130
- // Should now have fresh data
131
- expect(relay2.value).toEqual({ count: 2 });
132
- expect(mockFetch.calls).toHaveLength(2);
133
- });
134
- });
135
-
136
- it('should refetch stale data from disk cache', async () => {
137
- const getItem = query(t => ({
138
- path: '/data',
139
- response: { version: t.number },
140
- staleTime: 100,
141
- }));
142
-
143
- mockFetch.get('/data', { version: 1 });
144
-
145
- await testWithClient(client, async () => {
146
- const relay = getItem();
147
- await relay;
148
- });
149
-
150
- // Wait for data to become stale
151
- await sleep(150);
152
-
153
- // Create new client
154
- mockFetch.reset();
155
- mockFetch.get('/data', { version: 2 }, { delay: 50 });
156
- const client2 = new QueryClient(store, { fetch: mockFetch as any });
157
-
158
- await testWithClient(client2, async () => {
159
- const relay = getItem();
160
-
161
- // Should have cached value immediately
162
- relay.value;
163
- await sleep(10);
164
- expect(relay.value).toEqual({ version: 1 });
165
-
166
- // Should trigger background refetch
167
- await sleep(100);
168
-
169
- expect(relay.value).toEqual({ version: 2 });
170
- expect(mockFetch.calls).toHaveLength(1);
171
- });
172
- });
173
-
174
- it('should handle no staleTime (always refetch)', async () => {
175
- const getItem = query(t => ({
176
- path: '/item',
177
- response: { value: t.string },
178
- // No staleTime configured
179
- }));
180
-
181
- mockFetch.get('/item', { value: 'first' });
182
-
183
- await testWithClient(client, async () => {
184
- const relay1 = getItem();
185
- await relay1;
186
- expect(mockFetch.calls).toHaveLength(1);
187
- });
188
-
189
- // Access again immediately
190
- await testWithClient(client, async () => {
191
- mockFetch.get('/item', { value: 'second' }, { delay: 50 });
192
- const relay2 = getItem();
193
-
194
- relay2.value;
195
- await sleep(10);
196
-
197
- // Should have cached value
198
- expect(relay2.value).toEqual({ value: 'first' });
199
-
200
- // But should refetch in background
201
- await sleep(100);
202
-
203
- expect(relay2.value).toEqual({ value: 'second' });
204
- expect(mockFetch.calls).toHaveLength(2);
205
- });
206
- });
207
- });
208
-
209
- describe('Edge Cases', () => {
210
- it('should handle staleTime of 0 (always stale)', async () => {
211
- const getItem = query(t => ({
212
- path: '/item',
213
- response: { n: t.number },
214
- cache: { staleTime: 0 },
215
- }));
216
-
217
- mockFetch.get('/item', { n: 1 });
218
-
219
- await testWithClient(client, async () => {
220
- const relay1 = getItem();
221
- await relay1;
222
- });
223
-
224
- await testWithClient(client, async () => {
225
- mockFetch.get('/item', { n: 2 }, { delay: 50 });
226
- const relay2 = getItem();
227
-
228
- // Should serve cached but refetch immediately
229
- relay2.value;
230
- await sleep(10);
231
- expect(relay2.value).toEqual({ n: 1 });
232
-
233
- await sleep(100);
234
- expect(relay2.value).toEqual({ n: 2 });
235
- });
236
- });
237
-
238
- it('should handle very long staleTime', async () => {
239
- vi.useFakeTimers();
240
-
241
- try {
242
- const getItem = query(t => ({
243
- path: '/item',
244
- response: { data: t.string },
245
- cache: { staleTime: 1000 * 60 * 60 }, // 1 hour
246
- }));
247
-
248
- mockFetch.get('/item', { data: 'cached' });
249
-
250
- // First subscription - fetch initial data
251
- await testWithClient(client, async () => {
252
- const relay1 = getItem();
253
- await relay1;
254
- expect(relay1.value).toEqual({ data: 'cached' });
255
- expect(mockFetch.calls).toHaveLength(1);
256
- });
257
-
258
- // Unsubscribed now (testWithClient ended)
259
-
260
- // Second subscription - should still use cache (data is fresh)
261
- mockFetch.reset();
262
- mockFetch.get('/item', { data: 'fresh1' });
263
- await testWithClient(client, async () => {
264
- const relay2 = getItem();
265
- relay2.value;
266
- await vi.advanceTimersByTimeAsync(100);
267
-
268
- // Should use cached data without refetch (still fresh)
269
- expect(relay2.value).toEqual({ data: 'cached' });
270
- expect(mockFetch.calls).toHaveLength(0);
271
- });
272
-
273
- // Advance time by 30 minutes - still within 1 hour staleTime
274
- await vi.advanceTimersByTimeAsync(30 * 60 * 1000);
275
-
276
- // Third subscription - data should still be fresh
277
- mockFetch.reset();
278
- mockFetch.get('/item', { data: 'fresh2' });
279
- await testWithClient(client, async () => {
280
- const relay3 = getItem();
281
- relay3.value;
282
- await vi.advanceTimersByTimeAsync(100);
283
-
284
- // Should still use cached data (within 1 hour)
285
- expect(relay3.value).toEqual({ data: 'cached' });
286
- expect(mockFetch.calls).toHaveLength(0);
287
- });
288
-
289
- // Advance time past the 1 hour mark (31 more minutes = 61 minutes total)
290
- await vi.advanceTimersByTimeAsync(31 * 60 * 1000);
291
-
292
- // Fourth subscription - data should now be stale and trigger refetch
293
- mockFetch.reset();
294
- mockFetch.get('/item', { data: 'fresh-after-hour' }, { delay: 100 });
295
- await testWithClient(client, async () => {
296
- const relay4 = getItem();
297
-
298
- // Should serve stale data immediately
299
- relay4.value;
300
- await vi.advanceTimersByTimeAsync(10);
301
- expect(relay4.value).toEqual({ data: 'cached' });
302
-
303
- // Wait for background refetch to complete
304
- await vi.advanceTimersByTimeAsync(100);
305
-
306
- // Should now have fresh data
307
- expect(relay4.value).toEqual({ data: 'fresh-after-hour' });
308
- expect(mockFetch.calls).toHaveLength(1);
309
- });
310
- } finally {
311
- vi.useRealTimers();
312
- }
313
- });
314
-
315
- it('should handle concurrent access to stale data', async () => {
316
- const getItem = query(t => ({
317
- path: '/item',
318
- response: { id: t.number },
319
- cache: { staleTime: 50 },
320
- }));
321
-
322
- mockFetch.get('/item', { id: 1 });
323
-
324
- await testWithClient(client, async () => {
325
- const relay1 = getItem();
326
- await relay1;
327
-
328
- await sleep(100); // Make stale
329
- });
330
-
331
- await testWithClient(client, async () => {
332
- mockFetch.get('/item', { id: 2 }, { delay: 100 });
333
-
334
- // Multiple concurrent accesses
335
- const relay2 = getItem();
336
- const relay3 = getItem();
337
- const relay4 = getItem();
338
-
339
- // All should be the same relay
340
- expect(relay2).toBe(relay3);
341
- expect(relay3).toBe(relay4);
342
-
343
- // Should serve stale data immediately
344
- relay2.value;
345
- await sleep(10);
346
- expect(relay2.value).toEqual({ id: 1 });
347
-
348
- // Wait for refetch
349
- await sleep(100);
350
- expect(relay2.value).toEqual({ id: 2 });
351
-
352
- // Should only refetch once
353
- expect(mockFetch.calls).toHaveLength(2);
354
- });
355
- });
356
- });
357
- });
@@ -1,129 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { typeToString } from '../errors.js';
3
- import { t } from '../typeDefs.js';
4
-
5
- /**
6
- * Unit tests for typeToString function
7
- * Tests the debug logging representation of TypeDef types
8
- */
9
-
10
- describe('typeToString', () => {
11
- describe('Primitive Types', () => {
12
- it('should convert string type to "string"', () => {
13
- expect(typeToString(t.string)).toBe('string');
14
- });
15
-
16
- it('should convert number type to "number"', () => {
17
- expect(typeToString(t.number)).toBe('number');
18
- });
19
-
20
- it('should convert boolean type to "boolean"', () => {
21
- expect(typeToString(t.boolean)).toBe('boolean');
22
- });
23
-
24
- it('should convert null type to "null"', () => {
25
- expect(typeToString(t.null)).toBe('null');
26
- });
27
-
28
- it('should convert undefined type to "undefined"', () => {
29
- expect(typeToString(t.undefined)).toBe('undefined');
30
- });
31
- });
32
-
33
- describe('Constant Values', () => {
34
- it('should convert string constant to quoted string', () => {
35
- expect(typeToString(t.const('user'))).toBe('"user"');
36
- });
37
-
38
- it('should convert boolean constant to boolean string', () => {
39
- expect(typeToString(t.const(true))).toBe('true');
40
- expect(typeToString(t.const(false))).toBe('false');
41
- });
42
-
43
- it('should convert number constant to number string', () => {
44
- const numConst = t.const(42);
45
- expect(typeToString(numConst)).toBe('42');
46
- });
47
- });
48
-
49
- describe('Union Types', () => {
50
- it('should convert primitive union types', () => {
51
- const unionType = t.union(t.string, t.number);
52
- const result = typeToString(unionType);
53
- // Union of primitives should show both types
54
- expect(result).toMatch(/string.*number|number.*string/);
55
- });
56
-
57
- it('should convert union with null', () => {
58
- const unionType = t.union(t.string, t.null);
59
- const result = typeToString(unionType);
60
- expect(result).toContain('null');
61
- expect(result).toContain('string');
62
- });
63
-
64
- it('should convert union with undefined', () => {
65
- const unionType = t.union(t.number, t.undefined);
66
- const result = typeToString(unionType);
67
- expect(result).toContain('undefined');
68
- expect(result).toContain('number');
69
- });
70
-
71
- it('should convert value union with types', () => {
72
- // Union with both types and values
73
- const unionType = t.union(t.string, t.const('admin'));
74
- const result = typeToString(unionType);
75
- });
76
- });
77
-
78
- describe('Array Types', () => {
79
- it('should convert array of primitives', () => {
80
- expect(typeToString(t.array(t.string))).toBe('Array<string>');
81
- expect(typeToString(t.array(t.number))).toBe('Array<number>');
82
- });
83
-
84
- it('should convert nested arrays', () => {
85
- const nestedArray = t.array(t.array(t.string));
86
- expect(typeToString(nestedArray)).toBe('Array<Array<string>>');
87
- });
88
- });
89
-
90
- describe('Record Types', () => {
91
- it('should convert record of primitives', () => {
92
- expect(typeToString(t.record(t.string))).toBe('Record<string, string>');
93
- expect(typeToString(t.record(t.number))).toBe('Record<string, number>');
94
- });
95
-
96
- it('should convert record of arrays', () => {
97
- const recordOfArrays = t.record(t.array(t.string));
98
- expect(typeToString(recordOfArrays)).toBe('Record<string, Array<string>>');
99
- });
100
- });
101
-
102
- describe('Object Types', () => {
103
- it('should convert object without typename', () => {
104
- const obj = t.object({ name: t.string, age: t.number });
105
- expect(typeToString(obj)).toBe('object');
106
- });
107
- });
108
-
109
- describe('Complex Nested Types', () => {
110
- it('should handle array of records', () => {
111
- const type = t.array(t.record(t.string));
112
- expect(typeToString(type)).toBe('Array<Record<string, string>>');
113
- });
114
-
115
- it('should handle record of arrays', () => {
116
- const type = t.record(t.array(t.number));
117
- expect(typeToString(type)).toBe('Record<string, Array<number>>');
118
- });
119
-
120
- it('should handle union of primitives with null and undefined', () => {
121
- const type = t.union(t.string, t.number, t.null, t.undefined);
122
- const result = typeToString(type);
123
- expect(result).toContain('string');
124
- expect(result).toContain('number');
125
- expect(result).toContain('null');
126
- expect(result).toContain('undefined');
127
- });
128
- });
129
- });