@instantdb/core 0.22.164 → 0.22.165

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 (60) hide show
  1. package/__tests__/src/infiniteQuery.e2e.test.ts +384 -0
  2. package/__tests__/src/simple.e2e.test.ts +0 -1
  3. package/__tests__/src/utils/e2e.ts +1 -1
  4. package/dist/commonjs/Reactor.d.ts +1 -1
  5. package/dist/commonjs/Reactor.js +3 -3
  6. package/dist/commonjs/Reactor.js.map +1 -1
  7. package/dist/commonjs/index.d.ts +23 -2
  8. package/dist/commonjs/index.d.ts.map +1 -1
  9. package/dist/commonjs/index.js +25 -1
  10. package/dist/commonjs/index.js.map +1 -1
  11. package/dist/commonjs/infiniteQuery.d.ts +26 -0
  12. package/dist/commonjs/infiniteQuery.d.ts.map +1 -0
  13. package/dist/commonjs/infiniteQuery.js +422 -0
  14. package/dist/commonjs/infiniteQuery.js.map +1 -0
  15. package/dist/commonjs/instaql.d.ts.map +1 -1
  16. package/dist/commonjs/instaql.js +18 -5
  17. package/dist/commonjs/instaql.js.map +1 -1
  18. package/dist/commonjs/queryTypes.d.ts +2 -2
  19. package/dist/commonjs/queryTypes.d.ts.map +1 -1
  20. package/dist/commonjs/queryTypes.js.map +1 -1
  21. package/dist/commonjs/utils/Deferred.d.ts +5 -4
  22. package/dist/commonjs/utils/Deferred.d.ts.map +1 -1
  23. package/dist/commonjs/utils/Deferred.js.map +1 -1
  24. package/dist/commonjs/utils/weakHash.d.ts.map +1 -1
  25. package/dist/commonjs/utils/weakHash.js +4 -0
  26. package/dist/commonjs/utils/weakHash.js.map +1 -1
  27. package/dist/esm/Reactor.d.ts +1 -1
  28. package/dist/esm/Reactor.js +1 -1
  29. package/dist/esm/Reactor.js.map +1 -1
  30. package/dist/esm/index.d.ts +23 -2
  31. package/dist/esm/index.d.ts.map +1 -1
  32. package/dist/esm/index.js +25 -0
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/infiniteQuery.d.ts +26 -0
  35. package/dist/esm/infiniteQuery.d.ts.map +1 -0
  36. package/dist/esm/infiniteQuery.js +417 -0
  37. package/dist/esm/infiniteQuery.js.map +1 -0
  38. package/dist/esm/instaql.d.ts.map +1 -1
  39. package/dist/esm/instaql.js +18 -5
  40. package/dist/esm/instaql.js.map +1 -1
  41. package/dist/esm/queryTypes.d.ts +2 -2
  42. package/dist/esm/queryTypes.d.ts.map +1 -1
  43. package/dist/esm/queryTypes.js.map +1 -1
  44. package/dist/esm/utils/Deferred.d.ts +5 -4
  45. package/dist/esm/utils/Deferred.d.ts.map +1 -1
  46. package/dist/esm/utils/Deferred.js.map +1 -1
  47. package/dist/esm/utils/weakHash.d.ts.map +1 -1
  48. package/dist/esm/utils/weakHash.js +4 -0
  49. package/dist/esm/utils/weakHash.js.map +1 -1
  50. package/dist/standalone/index.js +1731 -1432
  51. package/dist/standalone/index.umd.cjs +3 -3
  52. package/package.json +2 -2
  53. package/src/Reactor.js +1 -1
  54. package/src/index.ts +49 -0
  55. package/src/infiniteQuery.ts +573 -0
  56. package/src/instaql.ts +25 -7
  57. package/src/queryTypes.ts +1 -2
  58. package/src/utils/{Deferred.js → Deferred.ts} +4 -4
  59. package/src/utils/weakHash.ts +4 -0
  60. package/vitest.config.ts +6 -0
@@ -0,0 +1,384 @@
1
+ import { describe, expect, vi } from 'vitest';
2
+ import { getInfiniteQueryInitialSnapshot, i, id } from '../../src';
3
+ import { makeE2ETest } from './utils/e2e';
4
+
5
+ async function addNumberItem(db: any, value: number) {
6
+ await db.transact(db.tx.items[id()].update({ value }));
7
+ }
8
+
9
+ async function addNumberItems(db: any, values: number[]) {
10
+ await db.transact(values.map((value) => db.tx.items[id()].update({ value })));
11
+ }
12
+
13
+ async function getItemIdByValue(db: any, value: number): Promise<string> {
14
+ const items = (
15
+ await db.queryOnce({
16
+ items: {
17
+ $: {
18
+ order: {
19
+ value: 'asc',
20
+ },
21
+ },
22
+ },
23
+ })
24
+ ).data.items;
25
+
26
+ const match = items.find((item: any) => item.value === value);
27
+ if (!match) {
28
+ throw new Error(`Expected to find item with value ${value}`);
29
+ }
30
+
31
+ return match.id;
32
+ }
33
+
34
+ const getLoadedValues = (response: Record<string, any>): number[] =>
35
+ (response.data?.items || []).map((item: any) => item.value);
36
+
37
+ const test = makeE2ETest({
38
+ schema: i.schema({
39
+ entities: {
40
+ items: i.entity({
41
+ value: i.number().indexed(),
42
+ }),
43
+ },
44
+ }),
45
+ });
46
+
47
+ describe('get initial data for useSyncExternalStore', () => {
48
+ test.skip('empty result', async ({ db }) => {
49
+ let response: Record<string, any> = {};
50
+ const callback = vi.fn<(response: any) => void>((resp) => {
51
+ response = resp;
52
+ });
53
+ const scrollSub = db.subscribeInfiniteQuery(
54
+ {
55
+ items: {
56
+ $: {
57
+ limit: 4,
58
+ order: {
59
+ value: 'asc',
60
+ },
61
+ },
62
+ },
63
+ },
64
+ callback,
65
+ );
66
+
67
+ await addNumberItem(db, 0);
68
+ await addNumberItem(db, 1);
69
+ await addNumberItem(db, 2);
70
+ await addNumberItem(db, 3);
71
+
72
+ await expect.poll(() => getLoadedValues(response)).toContain(3);
73
+ const result = getInfiniteQueryInitialSnapshot(db, {
74
+ items: {
75
+ $: {
76
+ limit: 4,
77
+ order: {
78
+ value: 'asc',
79
+ },
80
+ },
81
+ },
82
+ });
83
+ expect(result.data).toEqual(response.data);
84
+ scrollSub.unsubscribe();
85
+ });
86
+ });
87
+
88
+ describe('infinite scroll number line', () => {
89
+ test('adding new numbers', async ({ db }) => {
90
+ let response: Record<string, any> = {};
91
+ const callback = vi.fn<(response: any) => void>((resp) => {
92
+ response = resp;
93
+ });
94
+
95
+ const scrollSub = db.subscribeInfiniteQuery(
96
+ {
97
+ items: {
98
+ $: {
99
+ limit: 4,
100
+ order: {
101
+ value: 'asc',
102
+ },
103
+ },
104
+ },
105
+ },
106
+ callback,
107
+ );
108
+
109
+ await addNumberItem(db, 0);
110
+ await addNumberItem(db, 1);
111
+ await addNumberItem(db, 2);
112
+ await addNumberItem(db, 3);
113
+
114
+ await expect.poll(() => getLoadedValues(response)).toContain(3);
115
+ await addNumberItems(db, [5, 6, 7, 8]);
116
+ await expect.poll(() => getLoadedValues(response)).toEqual([0, 1, 2, 3]);
117
+ scrollSub.loadNextPage();
118
+ await expect
119
+ .poll(() => getLoadedValues(response))
120
+ .toEqual([0, 1, 2, 3, 5, 6, 7, 8]);
121
+ await expect.poll(() => response.canLoadNextPage).toEqual(false);
122
+ await addNumberItems(db, [9]);
123
+ await expect.poll(() => response.canLoadNextPage).toEqual(true);
124
+ scrollSub.loadNextPage();
125
+ await expect
126
+ .poll(() => getLoadedValues(response))
127
+ .toEqual([0, 1, 2, 3, 5, 6, 7, 8, 9]);
128
+ });
129
+
130
+ test('adding negative numbers', async ({ db }) => {
131
+ let response: Record<string, any> = {};
132
+ const callback = vi.fn<(response: any) => void>((resp) => {
133
+ response = resp;
134
+ });
135
+
136
+ const scrollSub = db.subscribeInfiniteQuery(
137
+ {
138
+ items: {
139
+ $: {
140
+ limit: 4,
141
+ order: {
142
+ value: 'asc',
143
+ },
144
+ },
145
+ },
146
+ },
147
+ callback,
148
+ );
149
+
150
+ await addNumberItems(db, [0, 1, 2, 3]);
151
+
152
+ await expect.poll(() => getLoadedValues(response)).toEqual([0, 1, 2, 3]);
153
+
154
+ await addNumberItem(db, -1);
155
+
156
+ await expect
157
+ .poll(() => getLoadedValues(response))
158
+ .toEqual([-1, 0, 1, 2, 3]);
159
+
160
+ await addNumberItems(db, [-4]);
161
+ await expect
162
+ .poll(() => getLoadedValues(response))
163
+ .toEqual([-4, -1, 0, 1, 2, 3]);
164
+ await addNumberItems(db, [-2]);
165
+ await expect
166
+ .poll(() => getLoadedValues(response))
167
+ .toEqual([-4, -2, -1, 0, 1, 2, 3]);
168
+
169
+ await addNumberItems(db, [4, 5, 6]);
170
+
171
+ await expect
172
+ .poll(() => getLoadedValues(response))
173
+ .toEqual([-4, -2, -1, 0, 1, 2, 3]);
174
+
175
+ await expect.poll(() => response.canLoadNextPage).toEqual(true);
176
+
177
+ scrollSub.loadNextPage();
178
+
179
+ await expect.poll(() => response.canLoadNextPage).toEqual(false);
180
+ });
181
+
182
+ test('add zero twice', async ({ db }) => {
183
+ let response: Record<string, any> = {};
184
+ const callback = vi.fn<(response: any) => void>((resp) => {
185
+ response = resp;
186
+ });
187
+
188
+ db.subscribeInfiniteQuery(
189
+ {
190
+ items: {
191
+ $: {
192
+ limit: 4,
193
+ order: {
194
+ value: 'desc',
195
+ },
196
+ },
197
+ },
198
+ },
199
+ callback,
200
+ );
201
+ addNumberItem(db, 0);
202
+ addNumberItem(db, 0);
203
+ await expect.poll(() => getLoadedValues(response)).toEqual([0, 0]);
204
+ });
205
+ });
206
+
207
+ describe('unique queries', () => {
208
+ test('descending', async ({ db }) => {
209
+ let response: Record<string, any> = {};
210
+ const callback = vi.fn<(response: any) => void>((resp) => {
211
+ response = resp;
212
+ });
213
+
214
+ const scrollSub = db.subscribeInfiniteQuery(
215
+ {
216
+ items: {
217
+ $: {
218
+ limit: 4,
219
+ order: {
220
+ value: 'desc',
221
+ },
222
+ },
223
+ },
224
+ },
225
+ callback,
226
+ );
227
+
228
+ await addNumberItems(db, [4, 5]);
229
+
230
+ await expect.poll(() => getLoadedValues(response)).toEqual([5, 4]);
231
+ await addNumberItems(db, [1]);
232
+ await expect.poll(() => getLoadedValues(response)).toEqual([5, 4, 1]);
233
+ await addNumberItems(db, [1, 2, 3]);
234
+ await expect.poll(() => getLoadedValues(response)).toEqual([5, 4, 3, 2]);
235
+ await expect.poll(() => response.canLoadNextPage).toEqual(true);
236
+ scrollSub.loadNextPage();
237
+ await expect
238
+ .poll(() => getLoadedValues(response))
239
+ .toEqual([5, 4, 3, 2, 1, 1]);
240
+ });
241
+
242
+ test('duplicate boundary values across pages (desc)', async ({ db }) => {
243
+ let response: Record<string, any> = {};
244
+ const callback = vi.fn<(response: any) => void>((resp) => {
245
+ response = resp;
246
+ });
247
+
248
+ const scrollSub = db.subscribeInfiniteQuery(
249
+ {
250
+ items: {
251
+ $: {
252
+ limit: 3,
253
+ order: {
254
+ value: 'desc',
255
+ },
256
+ },
257
+ },
258
+ },
259
+ callback,
260
+ );
261
+
262
+ await addNumberItems(db, [5, 4, 3, 2, 2, 2, 1]);
263
+
264
+ await expect.poll(() => getLoadedValues(response)).toEqual([5, 4, 3]);
265
+ await expect.poll(() => response.canLoadNextPage).toEqual(true);
266
+
267
+ scrollSub.loadNextPage();
268
+ await expect
269
+ .poll(() => getLoadedValues(response))
270
+ .toEqual([5, 4, 3, 2, 2, 2]);
271
+ await expect.poll(() => response.canLoadNextPage).toEqual(true);
272
+
273
+ scrollSub.loadNextPage();
274
+ await expect
275
+ .poll(() => getLoadedValues(response))
276
+ .toEqual([5, 4, 3, 2, 2, 2, 1]);
277
+ await expect.poll(() => response.canLoadNextPage).toEqual(false);
278
+ });
279
+
280
+ test('rapid loadNextPage calls do not duplicate pages', async ({ db }) => {
281
+ let response: Record<string, any> = {};
282
+
283
+ const scrollSub = db.subscribeInfiniteQuery(
284
+ {
285
+ items: {
286
+ $: {
287
+ limit: 2,
288
+ order: {
289
+ value: 'asc',
290
+ },
291
+ },
292
+ },
293
+ },
294
+ (resp: any) => {
295
+ response = resp;
296
+ },
297
+ );
298
+
299
+ await addNumberItems(db, [1, 2, 3, 4, 5, 6]);
300
+
301
+ await expect.poll(() => getLoadedValues(response)).toEqual([1, 2]);
302
+ await expect.poll(() => response.canLoadNextPage).toEqual(true);
303
+
304
+ scrollSub.loadNextPage();
305
+ scrollSub.loadNextPage();
306
+
307
+ await expect.poll(() => getLoadedValues(response)).toEqual([1, 2, 3, 4]);
308
+ await expect.poll(() => response.canLoadNextPage).toEqual(true);
309
+ });
310
+
311
+ test('deleting an item', async ({ db }) => {
312
+ let response: Record<string, any> = {};
313
+
314
+ const scrollSub = db.subscribeInfiniteQuery(
315
+ {
316
+ items: {
317
+ $: {
318
+ limit: 4,
319
+ order: {
320
+ value: 'asc',
321
+ },
322
+ },
323
+ },
324
+ },
325
+ (resp: any) => {
326
+ console.log(resp);
327
+ response = resp;
328
+ },
329
+ );
330
+
331
+ await addNumberItems(db, [1, 2, 3, 4, 5, 6]);
332
+ await expect.poll(() => getLoadedValues(response)).toEqual([1, 2, 3, 4]);
333
+
334
+ scrollSub.loadNextPage();
335
+ await expect
336
+ .poll(() => getLoadedValues(response))
337
+ .toEqual([1, 2, 3, 4, 5, 6]);
338
+ await expect.poll(() => response.canLoadNextPage).toEqual(false);
339
+
340
+ const threeId = await getItemIdByValue(db, 3);
341
+ await db.transact(db.tx.items[threeId].delete());
342
+
343
+ await expect.poll(() => getLoadedValues(response)).toEqual([1, 2, 4, 5, 6]);
344
+ await expect.poll(() => response.canLoadNextPage).toEqual(false);
345
+ });
346
+
347
+ test('updating an out-of-window item can reorder into visible chunk', async ({
348
+ db,
349
+ }) => {
350
+ let response: Record<string, any> = {};
351
+
352
+ const scrollSub = db.subscribeInfiniteQuery(
353
+ {
354
+ items: {
355
+ $: {
356
+ limit: 3,
357
+ order: {
358
+ value: 'asc',
359
+ },
360
+ },
361
+ },
362
+ },
363
+ (resp: any) => {
364
+ response = resp;
365
+ },
366
+ );
367
+
368
+ await addNumberItems(db, [10, 20, 30, 40, 50, 60]);
369
+ await expect.poll(() => getLoadedValues(response)).toEqual([10, 20, 30]);
370
+ await expect.poll(() => response.canLoadNextPage).toEqual(true);
371
+
372
+ const sixtyId = await getItemIdByValue(db, 60);
373
+ await db.transact(db.tx.items[sixtyId].update({ value: 15 }));
374
+
375
+ await expect.poll(() => getLoadedValues(response)).toEqual([10, 15, 20]);
376
+ await expect.poll(() => response.canLoadNextPage).toEqual(true);
377
+
378
+ scrollSub.loadNextPage();
379
+ await expect
380
+ .poll(() => getLoadedValues(response))
381
+ .toEqual([10, 15, 20, 30, 40, 50]);
382
+ await expect.poll(() => response.canLoadNextPage).toEqual(false);
383
+ });
384
+ });
@@ -5,7 +5,6 @@ test('can make a query', async ({ db }) => {
5
5
  const result = await db.queryOnce({ todos: {} });
6
6
 
7
7
  document.body.innerHTML = '<h1>Hello</h1>';
8
- console.log('Hello World');
9
8
 
10
9
  expect(result.data.todos.length).toBe(0);
11
10
  });
@@ -37,7 +37,7 @@ export function makeE2ETest<Schema extends InstantSchemaDef<any, any, any>>({
37
37
  body: JSON.stringify({ title: `e2e-${task.id}`, schema, rules }),
38
38
  headers: { 'Content-Type': 'application/json' },
39
39
  method: 'POST',
40
- signal,
40
+ signal: AbortSignal.any([signal, AbortSignal.timeout(4000)]),
41
41
  });
42
42
  if (!response.ok) {
43
43
  throw new Error(await response.text());
@@ -389,7 +389,7 @@ import * as s from './store.ts';
389
389
  import { PersistedObject } from './utils/PersistedObject.ts';
390
390
  import { SyncTable } from './SyncTable.ts';
391
391
  import { InstantStream } from './Stream.ts';
392
- import { Deferred } from './utils/Deferred.js';
392
+ import { Deferred } from './utils/Deferred.ts';
393
393
  import * as authAPI from './authAPI.ts';
394
394
  import * as StorageApi from './StorageAPI.ts';
395
395
  import IndexedDBStorage from './IndexedDBStorage.ts';
@@ -49,7 +49,7 @@ const authAPI = __importStar(require("./authAPI.js"));
49
49
  const StorageApi = __importStar(require("./StorageAPI.js"));
50
50
  const flags = __importStar(require("./utils/flags.js"));
51
51
  const presence_ts_1 = require("./presence.js");
52
- const Deferred_js_1 = require("./utils/Deferred.js");
52
+ const Deferred_ts_1 = require("./utils/Deferred.js");
53
53
  const PersistedObject_ts_1 = require("./utils/PersistedObject.js");
54
54
  const instaqlResult_js_1 = require("./model/instaqlResult.js");
55
55
  const object_js_1 = require("./utils/object.js");
@@ -949,7 +949,7 @@ class Reactor {
949
949
  if (opts && 'ruleParams' in opts) {
950
950
  q = { $$ruleParams: opts['ruleParams'], ...q };
951
951
  }
952
- const dfd = new Deferred_js_1.Deferred();
952
+ const dfd = new Deferred_ts_1.Deferred();
953
953
  if (!this._isOnline) {
954
954
  dfd.reject(new Error("We can't run `queryOnce`, because the device is offline."));
955
955
  return dfd.promise;
@@ -1240,7 +1240,7 @@ class Reactor {
1240
1240
  this._updatePendingMutations((prev) => {
1241
1241
  prev.set(eventId, mutation);
1242
1242
  });
1243
- const dfd = new Deferred_js_1.Deferred();
1243
+ const dfd = new Deferred_ts_1.Deferred();
1244
1244
  this.mutationDeferredStore.set(eventId, dfd);
1245
1245
  this._sendMutation(eventId, mutation);
1246
1246
  this.notifyAll();