@tldraw/store 5.2.0-next.ee0fa4d6244f → 5.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.
Files changed (64) hide show
  1. package/DOCS.md +790 -0
  2. package/README.md +9 -1
  3. package/dist-cjs/index.js +1 -1
  4. package/dist-cjs/lib/ImmutableMap.js +0 -13
  5. package/dist-cjs/lib/ImmutableMap.js.map +2 -2
  6. package/dist-cjs/lib/RecordType.js +1 -1
  7. package/dist-cjs/lib/RecordType.js.map +2 -2
  8. package/dist-cjs/lib/RecordsDiff.js +26 -14
  9. package/dist-cjs/lib/RecordsDiff.js.map +2 -2
  10. package/dist-cjs/lib/Store.js +6 -5
  11. package/dist-cjs/lib/Store.js.map +2 -2
  12. package/dist-cjs/lib/StoreSchema.js +1 -1
  13. package/dist-cjs/lib/StoreSchema.js.map +2 -2
  14. package/dist-cjs/lib/executeQuery.js +1 -1
  15. package/dist-cjs/lib/executeQuery.js.map +2 -2
  16. package/dist-esm/index.mjs +1 -1
  17. package/dist-esm/lib/ImmutableMap.mjs +0 -13
  18. package/dist-esm/lib/ImmutableMap.mjs.map +2 -2
  19. package/dist-esm/lib/RecordType.mjs +1 -1
  20. package/dist-esm/lib/RecordType.mjs.map +2 -2
  21. package/dist-esm/lib/RecordsDiff.mjs +26 -14
  22. package/dist-esm/lib/RecordsDiff.mjs.map +2 -2
  23. package/dist-esm/lib/Store.mjs +7 -6
  24. package/dist-esm/lib/Store.mjs.map +2 -2
  25. package/dist-esm/lib/StoreSchema.mjs +1 -1
  26. package/dist-esm/lib/StoreSchema.mjs.map +2 -2
  27. package/dist-esm/lib/executeQuery.mjs +1 -1
  28. package/dist-esm/lib/executeQuery.mjs.map +2 -2
  29. package/package.json +9 -5
  30. package/src/lib/{test/AtomMap.test.ts → AtomMap.test.ts} +77 -111
  31. package/src/lib/AtomSet.test.ts +116 -0
  32. package/src/lib/BaseRecord.test.ts +10 -22
  33. package/src/lib/ImmutableMap.test.ts +114 -71
  34. package/src/lib/ImmutableMap.ts +1 -0
  35. package/src/lib/IncrementalSetConstructor.test.ts +76 -81
  36. package/src/lib/RecordType.test.ts +216 -0
  37. package/src/lib/RecordType.ts +1 -1
  38. package/src/lib/RecordsDiff.test.ts +112 -106
  39. package/src/lib/RecordsDiff.ts +43 -18
  40. package/src/lib/Store.test.ts +570 -630
  41. package/src/lib/Store.ts +9 -10
  42. package/src/lib/StoreListeners.test.ts +462 -0
  43. package/src/lib/StoreQueries.test.ts +586 -434
  44. package/src/lib/StoreSchema.test.ts +1012 -174
  45. package/src/lib/StoreSchema.ts +1 -1
  46. package/src/lib/StoreSideEffects.test.ts +546 -158
  47. package/src/lib/devFreeze.test.ts +94 -124
  48. package/src/lib/executeQuery.test.ts +77 -31
  49. package/src/lib/executeQuery.ts +3 -1
  50. package/src/lib/migrate.test.ts +273 -296
  51. package/src/lib/setUtils.test.ts +38 -79
  52. package/src/lib/test/createMigrations.test.ts +0 -75
  53. package/src/lib/test/dependsOn.test.ts +0 -166
  54. package/src/lib/test/getMigrationsSince.test.ts +0 -121
  55. package/src/lib/test/migrate.test.ts +0 -118
  56. package/src/lib/test/migratePersistedRecord.test.ts +0 -265
  57. package/src/lib/test/migrationCaching.test.ts +0 -209
  58. package/src/lib/test/recordStore.test.ts +0 -1567
  59. package/src/lib/test/recordStoreQueries.test.ts +0 -814
  60. package/src/lib/test/recordType.test.ts +0 -19
  61. package/src/lib/test/sortMigrations.test.ts +0 -83
  62. package/src/lib/test/upgradeSchema.test.ts +0 -80
  63. package/src/lib/test/validate.test.ts +0 -178
  64. package/src/lib/test/validateMigrations.test.ts +0 -165
@@ -1,814 +0,0 @@
1
- import { atom, RESET_VALUE } from '@tldraw/state'
2
- import { BaseRecord, RecordId } from '../BaseRecord'
3
- import { createRecordType } from '../RecordType'
4
- import { Store } from '../Store'
5
- import { StoreSchema } from '../StoreSchema'
6
-
7
- interface Author extends BaseRecord<'author', RecordId<Author>> {
8
- name: string
9
- age: number
10
- }
11
- const Author = createRecordType<Author>('author', {
12
- validator: {
13
- validate(value) {
14
- const author = value as Author
15
- if (author.typeName !== 'author') throw Error()
16
- if (!author.id.startsWith('author:')) throw Error()
17
- if (!Number.isFinite(author.age)) throw Error()
18
- if (author.age < 0) throw Error()
19
- return author
20
- },
21
- },
22
- scope: 'document',
23
- }).withDefaultProperties(() => ({ age: 23 }))
24
-
25
- interface Book extends BaseRecord<'book', RecordId<Book>> {
26
- title: string
27
- authorId: RecordId<Author>
28
- }
29
- const Book = createRecordType<Book>('book', {
30
- validator: {
31
- validate(value) {
32
- const book = value as Book
33
- if (!book.id.startsWith('book:')) throw Error()
34
- if (book.typeName !== 'book') throw Error()
35
- if (typeof book.title !== 'string') throw Error()
36
- if (!book.authorId.startsWith('author')) throw Error()
37
- return book
38
- },
39
- },
40
- scope: 'document',
41
- })
42
- const authors = {
43
- tolkein: Author.create({ name: 'J.R.R. Tolkein' }),
44
- bradbury: Author.create({ name: 'Ray Bradbury' }),
45
- davidMitchellSerious: Author.create({ name: 'David Mitchell' }),
46
- davidMitchellFunny: Author.create({ name: 'David Mitchell' }),
47
- }
48
-
49
- const books = {
50
- cloudAtlas: Book.create({ title: 'Cloud Atlas', authorId: authors.davidMitchellSerious.id }),
51
- myLifeInComedy: Book.create({
52
- title: 'My Life in Comedy',
53
- authorId: authors.davidMitchellFunny.id,
54
- }),
55
- lotr: Book.create({ title: 'Lord of the Rings', authorId: authors.tolkein.id }),
56
- farenheit: Book.create({ title: 'Farenheit 451', authorId: authors.bradbury.id }),
57
- }
58
-
59
- let store: Store<Author | Book>
60
-
61
- beforeEach(() => {
62
- store = new Store({
63
- props: {},
64
- schema: StoreSchema.create<Author | Book>({
65
- author: Author,
66
- book: Book,
67
- }),
68
- })
69
- store.put([
70
- authors.tolkein,
71
- authors.bradbury,
72
- books.lotr,
73
- books.farenheit,
74
- authors.davidMitchellFunny,
75
- authors.davidMitchellSerious,
76
- books.cloudAtlas,
77
- books.myLifeInComedy,
78
- ])
79
- })
80
-
81
- describe('indexes', () => {
82
- it('are cached', () => {
83
- const authorNameIndex1 = store.query.index('author', 'name')
84
- const authorNameIndex2 = store.query.index('author', 'name')
85
- expect(authorNameIndex1).toBe(authorNameIndex2)
86
- })
87
- it('can be made on any property', () => {
88
- const authorNameIndex = store.query.index('author', 'name')
89
-
90
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
91
- expect(authorNameIndex.get().get('David Mitchell')).toEqual(
92
- new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
93
- )
94
-
95
- const bookTitleIndex = store.query.index('book', 'title')
96
- expect(bookTitleIndex.get().get('Cloud Atlas')).toEqual(new Set([books.cloudAtlas.id]))
97
- expect(bookTitleIndex.get().get('Lord of the Rings')).toEqual(new Set([books.lotr.id]))
98
- })
99
-
100
- it('can have things added when records are added', () => {
101
- const authorNameIndex = store.query.index('author', 'name')
102
- // deref to make it compute once
103
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
104
-
105
- let lastChangedEpoch = authorNameIndex.lastChangedEpoch
106
- const newAuthor = Author.create({ name: 'New Author' })
107
-
108
- store.put([newAuthor])
109
-
110
- expect(authorNameIndex.get().get('New Author')).toEqual(new Set([newAuthor.id]))
111
-
112
- const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
113
- if (diff === RESET_VALUE) throw new Error('should not be reset')
114
- expect(diff).toHaveLength(1)
115
- expect(diff[0].get('New Author')).toEqual({
116
- added: new Set([newAuthor.id]),
117
- })
118
- expect(diff[0].size).toBe(1)
119
-
120
- const moreNewAuthors = [
121
- Author.create({ name: 'New Author' }),
122
- Author.create({ name: 'New Author' }),
123
- Author.create({ name: 'New Author' }),
124
- ]
125
-
126
- lastChangedEpoch = authorNameIndex.lastChangedEpoch
127
- store.put(moreNewAuthors)
128
-
129
- expect(authorNameIndex.get().get('New Author')).toEqual(
130
- new Set([newAuthor.id, ...moreNewAuthors.map((a) => a.id)])
131
- )
132
-
133
- const diff2 = authorNameIndex.getDiffSince(lastChangedEpoch)
134
-
135
- if (diff2 === RESET_VALUE) throw new Error('should not be reset')
136
-
137
- expect(diff2).toHaveLength(1)
138
- expect(diff2[0].get('New Author')).toEqual({ added: new Set(moreNewAuthors.map((a) => a.id)) })
139
- })
140
-
141
- it('can have things added when records are updated', () => {
142
- const authorNameIndex = store.query.index('author', 'name')
143
-
144
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
145
-
146
- let lastChangedEpoch = authorNameIndex.lastChangedEpoch
147
-
148
- store.put([{ ...authors.bradbury, name: 'J.R.R. Tolkein' }])
149
-
150
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(
151
- new Set([authors.tolkein.id, authors.bradbury.id])
152
- )
153
-
154
- const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
155
- if (diff === RESET_VALUE) throw new Error('should not be reset')
156
- expect(diff).toHaveLength(1)
157
- expect(diff[0].size).toBe(2)
158
- expect(diff[0].get('J.R.R. Tolkein')).toEqual({
159
- added: new Set([authors.bradbury.id]),
160
- })
161
-
162
- expect(diff[0].get('Ray Bradbury')).toEqual({
163
- removed: new Set([authors.bradbury.id]),
164
- })
165
-
166
- lastChangedEpoch = authorNameIndex.lastChangedEpoch
167
- store.put([
168
- { ...authors.davidMitchellFunny, name: 'J.R.R. Tolkein' },
169
- { ...authors.davidMitchellSerious, name: 'J.R.R. Tolkein' },
170
- ])
171
-
172
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(
173
- new Set([
174
- authors.tolkein.id,
175
- authors.bradbury.id,
176
- authors.davidMitchellFunny.id,
177
- authors.davidMitchellSerious.id,
178
- ])
179
- )
180
-
181
- const diff2 = authorNameIndex.getDiffSince(lastChangedEpoch)
182
-
183
- if (diff2 === RESET_VALUE) throw new Error('should not be reset')
184
-
185
- expect(diff2).toHaveLength(1)
186
- expect(diff2[0].size).toBe(2)
187
-
188
- expect(diff2[0].get('J.R.R. Tolkein')).toEqual({
189
- added: new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id]),
190
- })
191
-
192
- expect(diff2[0].get('David Mitchell')).toEqual({
193
- removed: new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id]),
194
- })
195
- })
196
-
197
- it('can have things removed when records are removed', () => {
198
- const authorNameIndex = store.query.index('author', 'name')
199
-
200
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
201
-
202
- let lastChangedEpoch = authorNameIndex.lastChangedEpoch
203
-
204
- store.remove([authors.tolkein.id])
205
-
206
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(undefined)
207
-
208
- const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
209
- if (diff === RESET_VALUE) throw new Error('should not be reset')
210
- expect(diff).toHaveLength(1)
211
- expect(diff[0].size).toBe(1)
212
- expect(diff[0].get('J.R.R. Tolkein')).toEqual({
213
- removed: new Set([authors.tolkein.id]),
214
- })
215
-
216
- lastChangedEpoch = authorNameIndex.lastChangedEpoch
217
- store.remove([
218
- authors.bradbury.id,
219
- authors.davidMitchellFunny.id,
220
- authors.davidMitchellSerious.id,
221
- ])
222
-
223
- expect(authorNameIndex.get().get('Ray Bradbury')).toEqual(undefined)
224
- expect(authorNameIndex.get().get('David Mitchell')).toEqual(undefined)
225
-
226
- const diff2 = authorNameIndex.getDiffSince(lastChangedEpoch)
227
-
228
- if (diff2 === RESET_VALUE) throw new Error('should not be reset')
229
-
230
- expect(diff2).toHaveLength(1)
231
- expect(diff2[0].size).toBe(2)
232
-
233
- expect(diff2[0].get('Ray Bradbury')).toEqual({
234
- removed: new Set([authors.bradbury.id]),
235
- })
236
-
237
- expect(diff2[0].get('David Mitchell')).toEqual({
238
- removed: new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id]),
239
- })
240
- })
241
-
242
- it('can have things removed when records are updated', () => {
243
- const authorNameIndex = store.query.index('author', 'name')
244
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
245
-
246
- let lastChangedEpoch = authorNameIndex.lastChangedEpoch
247
-
248
- store.put([{ ...authors.tolkein, name: 'Ray Bradbury' }])
249
-
250
- expect(authorNameIndex.get().get('Ray Bradbury')).toEqual(
251
- new Set([authors.tolkein.id, authors.bradbury.id])
252
- )
253
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(undefined)
254
-
255
- const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
256
- if (diff === RESET_VALUE) throw new Error('should not be reset')
257
- expect(diff).toHaveLength(1)
258
- expect(diff[0].size).toBe(2)
259
- expect(diff[0].get('J.R.R. Tolkein')).toEqual({
260
- removed: new Set([authors.tolkein.id]),
261
- })
262
-
263
- expect(diff[0].get('Ray Bradbury')).toEqual({
264
- added: new Set([authors.tolkein.id]),
265
- })
266
-
267
- lastChangedEpoch = authorNameIndex.lastChangedEpoch
268
- store.put([
269
- { ...authors.davidMitchellFunny, name: 'Ray Bradbury' },
270
- { ...authors.davidMitchellSerious, name: 'Ray Bradbury' },
271
- ])
272
-
273
- expect(authorNameIndex.get().get('Ray Bradbury')).toEqual(
274
- new Set([
275
- authors.tolkein.id,
276
- authors.bradbury.id,
277
- authors.davidMitchellFunny.id,
278
- authors.davidMitchellSerious.id,
279
- ])
280
- )
281
- expect(authorNameIndex.get().get('David Mitchell')).toEqual(undefined)
282
-
283
- const diff2 = authorNameIndex.getDiffSince(lastChangedEpoch)
284
-
285
- if (diff2 === RESET_VALUE) throw new Error('should not be reset')
286
-
287
- expect(diff2).toHaveLength(1)
288
- expect(diff2[0].size).toBe(2)
289
-
290
- expect(diff2[0].get('David Mitchell')).toEqual({
291
- removed: new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id]),
292
- })
293
-
294
- expect(diff2[0].get('Ray Bradbury')).toEqual({
295
- added: new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id]),
296
- })
297
- })
298
-
299
- it('handles things being removed and added for the same value at the same time', () => {
300
- const authorNameIndex = store.query.index('author', 'name')
301
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
302
-
303
- let lastChangedEpoch = authorNameIndex.lastChangedEpoch
304
-
305
- // do remove first
306
- store.remove([authors.tolkein.id])
307
- const newAuthor = Author.create({ name: 'J.R.R. Tolkein' })
308
- store.put([newAuthor])
309
-
310
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([newAuthor.id]))
311
-
312
- const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
313
-
314
- if (diff === RESET_VALUE) throw new Error('should not be reset')
315
-
316
- expect(diff).toHaveLength(1)
317
- expect(diff[0].size).toBe(1)
318
-
319
- expect(diff[0].get('J.R.R. Tolkein')).toEqual({
320
- removed: new Set([authors.tolkein.id]),
321
- added: new Set([newAuthor.id]),
322
- })
323
-
324
- lastChangedEpoch = authorNameIndex.lastChangedEpoch
325
- // do updates
326
- store.put([{ ...authors.davidMitchellFunny, name: 'Ray Bradbury' }])
327
- store.put([{ ...authors.bradbury, name: 'David Mitchell' }])
328
-
329
- expect(authorNameIndex.get().get('Ray Bradbury')).toEqual(
330
- new Set([authors.davidMitchellFunny.id])
331
- )
332
- expect(authorNameIndex.get().get('David Mitchell')).toEqual(
333
- new Set([authors.bradbury.id, authors.davidMitchellSerious.id])
334
- )
335
-
336
- const diff2 = authorNameIndex.getDiffSince(lastChangedEpoch)
337
-
338
- if (diff2 === RESET_VALUE) throw new Error('should not be reset')
339
-
340
- expect(diff2).toHaveLength(1)
341
- expect(diff2[0].size).toBe(2)
342
-
343
- expect(diff2[0].get('Ray Bradbury')).toEqual({
344
- added: new Set([authors.davidMitchellFunny.id]),
345
- removed: new Set([authors.bradbury.id]),
346
- })
347
-
348
- expect(diff2[0].get('David Mitchell')).toEqual({
349
- added: new Set([authors.bradbury.id]),
350
- removed: new Set([authors.davidMitchellFunny.id]),
351
- })
352
- })
353
-
354
- it('has the same value if nothing changed', () => {
355
- const authorNameIndex = store.query.index('author', 'name')
356
-
357
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
358
-
359
- const lastChangedEpoch = authorNameIndex.lastChangedEpoch
360
-
361
- store.put([{ ...authors.tolkein, age: 23 }])
362
-
363
- expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
364
-
365
- expect(lastChangedEpoch).toBe(authorNameIndex.lastChangedEpoch)
366
- })
367
- })
368
-
369
- describe('queries for ids', () => {
370
- it('can query for all values of a given type', () => {
371
- const bookQuery = store.query.ids('book')
372
-
373
- expect(bookQuery.get()).toEqual(
374
- new Set([books.cloudAtlas.id, books.farenheit.id, books.lotr.id, books.myLifeInComedy.id])
375
- )
376
-
377
- const authorQuery = store.query.ids('author')
378
-
379
- expect(authorQuery.get()).toEqual(
380
- new Set([
381
- authors.bradbury.id,
382
- authors.davidMitchellFunny.id,
383
- authors.davidMitchellSerious.id,
384
- authors.tolkein.id,
385
- ])
386
- )
387
-
388
- const newAuthor = Author.create({ name: 'J.R.R. Tolkein' })
389
- const newBook = Book.create({ title: 'The Hobbit', authorId: newAuthor.id })
390
- store.put([newAuthor, newBook])
391
-
392
- expect(bookQuery.get()).toEqual(
393
- new Set([
394
- books.cloudAtlas.id,
395
- books.farenheit.id,
396
- books.lotr.id,
397
- books.myLifeInComedy.id,
398
- newBook.id,
399
- ])
400
- )
401
-
402
- expect(authorQuery.get()).toEqual(
403
- new Set([
404
- authors.bradbury.id,
405
- authors.davidMitchellFunny.id,
406
- authors.davidMitchellSerious.id,
407
- authors.tolkein.id,
408
- newAuthor.id,
409
- ])
410
- )
411
- })
412
-
413
- it('can query for a single value', () => {
414
- const jrr = store.query.ids('author', () => ({
415
- name: { eq: 'J.R.R. Tolkein' },
416
- }))
417
-
418
- expect(jrr.get()).toEqual(new Set([authors.tolkein.id]))
419
-
420
- const mitchell = store.query.ids('author', () => ({
421
- name: { eq: 'David Mitchell' },
422
- }))
423
-
424
- expect(mitchell.get()).toEqual(
425
- new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
426
- )
427
- })
428
-
429
- it('can query for multiple values', () => {
430
- store.put([{ ...authors.davidMitchellFunny, age: 30 }])
431
-
432
- const mitchell30 = store.query.ids('author', () => ({
433
- name: { eq: 'David Mitchell' },
434
- age: { eq: 30 },
435
- }))
436
-
437
- expect(mitchell30.get()).toEqual(new Set([authors.davidMitchellFunny.id]))
438
- })
439
-
440
- it('can use a reactive query', () => {
441
- store.put([{ ...authors.davidMitchellFunny, age: 30 }])
442
-
443
- const currentAuthor = atom('currentAuthor', 'David Mitchell')
444
- const currentAge = atom('currentAge', 30)
445
-
446
- const mitchell30 = store.query.ids('author', () => ({
447
- name: { eq: currentAuthor.get() },
448
- age: { eq: currentAge.get() },
449
- }))
450
-
451
- expect(mitchell30.get()).toEqual(new Set([authors.davidMitchellFunny.id]))
452
-
453
- let lastChangedEpoch = mitchell30.lastChangedEpoch
454
- currentAge.set(23)
455
-
456
- expect(mitchell30.get()).toEqual(new Set([authors.davidMitchellSerious.id]))
457
-
458
- const diff = mitchell30.getDiffSince(lastChangedEpoch)
459
-
460
- if (diff === RESET_VALUE) throw new Error('should not be reset')
461
-
462
- expect(diff).toHaveLength(1)
463
- expect(diff[0]).toEqual({
464
- added: new Set([authors.davidMitchellSerious.id]),
465
- removed: new Set([authors.davidMitchellFunny.id]),
466
- })
467
-
468
- currentAuthor.set('J.R.R. Tolkein')
469
-
470
- lastChangedEpoch = mitchell30.lastChangedEpoch
471
-
472
- expect(mitchell30.get()).toEqual(new Set([authors.tolkein.id]))
473
-
474
- const diff2 = mitchell30.getDiffSince(lastChangedEpoch)
475
-
476
- if (diff2 === RESET_VALUE) throw new Error('should not be reset')
477
-
478
- expect(diff2).toHaveLength(1)
479
-
480
- expect(diff2[0]).toEqual({
481
- added: new Set([authors.tolkein.id]),
482
- removed: new Set([authors.davidMitchellSerious.id]),
483
- })
484
- })
485
-
486
- it('supports not-equals matches', () => {
487
- store.put([{ ...authors.davidMitchellFunny, age: 30 }])
488
- const mitchell = store.query.ids('author', () => ({
489
- name: { neq: 'David Mitchell' },
490
- }))
491
-
492
- expect(mitchell.get()).toEqual(new Set([authors.tolkein.id, authors.bradbury.id]))
493
-
494
- const ageNot23 = store.query.ids('author', () => ({
495
- age: { neq: 23 },
496
- }))
497
-
498
- expect(ageNot23.get()).toEqual(new Set([authors.davidMitchellFunny.id]))
499
- })
500
-
501
- it('supports records being added', () => {
502
- const mitchell = store.query.ids('author', () => ({
503
- name: { eq: 'David Mitchell' },
504
- }))
505
-
506
- expect(mitchell.get()).toEqual(
507
- new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
508
- )
509
-
510
- const newAuthor = Author.create({ name: 'David Mitchell' })
511
- store.put([newAuthor])
512
-
513
- expect(mitchell.get()).toEqual(
514
- new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id, newAuthor.id])
515
- )
516
- })
517
-
518
- it('supports records being removed', () => {
519
- const mitchell = store.query.ids('author', () => ({
520
- name: { eq: 'David Mitchell' },
521
- }))
522
-
523
- expect(mitchell.get()).toEqual(
524
- new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
525
- )
526
-
527
- store.remove([authors.davidMitchellFunny.id])
528
-
529
- expect(mitchell.get()).toEqual(new Set([authors.davidMitchellSerious.id]))
530
- })
531
-
532
- it('supports records being updated', () => {
533
- const mitchell = store.query.ids('author', () => ({
534
- name: { eq: 'David Mitchell' },
535
- age: { neq: 30 },
536
- }))
537
-
538
- expect(mitchell.get()).toEqual(
539
- new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
540
- )
541
-
542
- store.put([{ ...authors.davidMitchellFunny, age: 30 }])
543
-
544
- expect(mitchell.get()).toEqual(new Set([authors.davidMitchellSerious.id]))
545
-
546
- store.put([{ ...authors.davidMitchellFunny, age: 23 }])
547
-
548
- expect(mitchell.get()).toEqual(
549
- new Set([authors.davidMitchellSerious.id, authors.davidMitchellFunny.id])
550
- )
551
- })
552
-
553
- it('does not update if unrelated records are added, upated, or removed', () => {
554
- const mitchell = store.query.ids('author', () => ({
555
- name: { eq: 'David Mitchell' },
556
- }))
557
-
558
- expect(mitchell.get()).toEqual(
559
- new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
560
- )
561
-
562
- const lastChangedEpoch = mitchell.lastChangedEpoch
563
-
564
- const newAuthor = Author.create({ name: 'William Shakespeare' })
565
- store.put([newAuthor])
566
-
567
- mitchell.get()
568
-
569
- expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
570
-
571
- store.remove([authors.tolkein.id])
572
-
573
- mitchell.get()
574
-
575
- expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
576
- })
577
-
578
- it('doesnt change if related records are updated', () => {
579
- const mitchell = store.query.ids('author', () => ({
580
- name: { eq: 'David Mitchell' },
581
- }))
582
-
583
- expect(mitchell.get()).toEqual(
584
- new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
585
- )
586
-
587
- const lastChangedEpoch = mitchell.lastChangedEpoch
588
-
589
- store.put([{ ...authors.davidMitchellFunny, age: 30 }])
590
-
591
- mitchell.get()
592
-
593
- expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
594
-
595
- // make a change that does affect the query just to check
596
- store.put([{ ...authors.davidMitchellFunny, name: 'steve' }])
597
-
598
- mitchell.get()
599
- expect(mitchell.lastChangedEpoch).toBeGreaterThan(lastChangedEpoch)
600
- })
601
-
602
- it('supports items being removed and then added back', () => {
603
- const mitchell = store.query.ids('author', () => ({
604
- name: { eq: 'David Mitchell' },
605
- }))
606
-
607
- expect(mitchell.get()).toEqual(
608
- new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
609
- )
610
-
611
- const lastChangedEpoch = mitchell.lastChangedEpoch
612
-
613
- store.remove([authors.davidMitchellFunny.id])
614
- store.put([authors.davidMitchellFunny])
615
-
616
- mitchell.get()
617
-
618
- expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
619
- })
620
-
621
- it('supports items being added and then removed', () => {
622
- const mitchell = store.query.ids('author', () => ({
623
- name: { eq: 'David Mitchell' },
624
- }))
625
-
626
- expect(mitchell.get()).toEqual(
627
- new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
628
- )
629
-
630
- const lastChangedEpoch = mitchell.lastChangedEpoch
631
-
632
- const newMitchell = Author.create({ name: 'David Mitchell' })
633
-
634
- store.put([newMitchell])
635
- store.remove([newMitchell.id])
636
-
637
- mitchell.get()
638
-
639
- expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
640
- })
641
- })
642
-
643
- const bookComparator = (a: Book, b: Book) => a.title.localeCompare(b.title)
644
-
645
- describe('queries for records', () => {
646
- it('can query for all values of a given type', () => {
647
- const allBooks = store.query.records('book')
648
-
649
- expect(allBooks.get().sort(bookComparator)).toEqual(
650
- [books.cloudAtlas, books.farenheit, books.lotr, books.myLifeInComedy].sort(bookComparator)
651
- )
652
-
653
- const newBook = Book.create({ title: 'The Hobbit', authorId: authors.tolkein.id })
654
-
655
- store.put([newBook])
656
-
657
- expect(allBooks.get().sort(bookComparator)).toEqual(
658
- [books.cloudAtlas, books.farenheit, books.lotr, books.myLifeInComedy, newBook].sort(
659
- bookComparator
660
- )
661
- )
662
- })
663
-
664
- it('can query for a single value', () => {
665
- const farenheit = store.query.records('book', () => ({
666
- title: { eq: 'Farenheit 451' },
667
- }))
668
-
669
- expect(farenheit.get()).toEqual([books.farenheit])
670
- })
671
-
672
- it('can query for multiple values', () => {
673
- const funnyGuide = Book.create({
674
- title: 'How to be a funny man',
675
- authorId: authors.davidMitchellFunny.id,
676
- })
677
- store.put([funnyGuide])
678
- const mitchell = store.query.records('book', () => ({
679
- authorId: { eq: authors.davidMitchellFunny.id },
680
- }))
681
-
682
- expect(mitchell.get().sort(bookComparator)).toEqual(
683
- [books.myLifeInComedy, funnyGuide].sort(bookComparator)
684
- )
685
- })
686
-
687
- it('supports reactive queries', () => {
688
- const currentAuthor = atom('currentAuthor', authors.davidMitchellFunny.id)
689
- const booksQuery = store.query.records('book', () => ({
690
- authorId: { eq: currentAuthor.get() },
691
- }))
692
-
693
- expect(booksQuery.get()).toEqual([books.myLifeInComedy])
694
-
695
- currentAuthor.set(authors.tolkein.id)
696
-
697
- expect(booksQuery.get()).toEqual([books.lotr])
698
- })
699
- })
700
-
701
- describe('filtering history', () => {
702
- it('caches filters', () => {
703
- const filter = store.query.filterHistory('author')
704
- const filter2 = store.query.filterHistory('author')
705
-
706
- expect(filter).toBe(filter2)
707
- })
708
- it('allows filtering history', () => {
709
- const authorHistory = store.query.filterHistory('author')
710
-
711
- authorHistory.get()
712
-
713
- let lastChangedEpoch = authorHistory.lastChangedEpoch
714
-
715
- expect(authorHistory.getDiffSince(lastChangedEpoch - 1)).toBe(RESET_VALUE)
716
-
717
- // updating an author should change the history
718
- store.put([{ ...authors.davidMitchellFunny, age: 30 }])
719
-
720
- authorHistory.get()
721
-
722
- expect(lastChangedEpoch).toBeLessThan(authorHistory.lastChangedEpoch)
723
-
724
- expect(authorHistory.getDiffSince(lastChangedEpoch)).toMatchObject([
725
- { updated: { [authors.davidMitchellFunny.id]: [{ age: 23 }, { age: 30 }] } },
726
- ])
727
-
728
- lastChangedEpoch = authorHistory.lastChangedEpoch
729
-
730
- // updating a book should not change the history
731
-
732
- store.put([
733
- { ...books.lotr, title: 'The Lord of the Rings Part I: The Fellowship of the Ring' },
734
- ])
735
-
736
- authorHistory.get()
737
-
738
- expect(authorHistory.lastChangedEpoch).toEqual(lastChangedEpoch)
739
- expect(authorHistory.getDiffSince(lastChangedEpoch)).toMatchObject([])
740
- })
741
-
742
- it('should not update if changes in a window of time cancel each other out', () => {
743
- const authorHistory = store.query.filterHistory('author')
744
-
745
- const epoch = authorHistory.get()
746
- const newAuthor = Author.create({ name: 'Stanley Briggs' })
747
-
748
- store.put([newAuthor])
749
- store.put([{ ...newAuthor, age: 38 }])
750
- store.remove([newAuthor.id])
751
-
752
- expect(authorHistory.get()).toEqual(epoch)
753
-
754
- store.remove([authors.tolkein.id])
755
- store.put([authors.tolkein])
756
-
757
- expect(authorHistory.get()).toEqual(epoch)
758
- })
759
-
760
- it('removes update entries if a thing was deleted', () => {
761
- const authorHistory = store.query.filterHistory('author')
762
-
763
- authorHistory.get()
764
-
765
- const lastChangedEpoch = authorHistory.lastChangedEpoch
766
-
767
- store.put([{ ...authors.davidMitchellFunny, age: 38 }])
768
- store.put([{ ...authors.davidMitchellFunny, age: 343 }])
769
- store.remove([authors.davidMitchellFunny.id])
770
-
771
- expect(authorHistory.getDiffSince(lastChangedEpoch)).toMatchObject([
772
- {
773
- // shows the original, not the updated version
774
- removed: { [authors.davidMitchellFunny.id]: { age: 23 } },
775
- },
776
- ])
777
- })
778
-
779
- it('collapses multiple updated entries into one', () => {
780
- const authorHistory = store.query.filterHistory('author')
781
-
782
- authorHistory.get()
783
-
784
- const lastChangedEpoch = authorHistory.lastChangedEpoch
785
-
786
- store.put([{ ...authors.davidMitchellFunny, age: 38 }])
787
- store.put([{ ...authors.davidMitchellFunny, age: 343 }])
788
-
789
- expect(authorHistory.getDiffSince(lastChangedEpoch)).toMatchObject([
790
- {
791
- updated: { [authors.davidMitchellFunny.id]: [{ age: 23 }, { age: 343 }] },
792
- },
793
- ])
794
- })
795
-
796
- it('collapeses an add + update entry into just an add entry', () => {
797
- const authorHistory = store.query.filterHistory('author')
798
-
799
- authorHistory.get()
800
-
801
- const lastChangedEpoch = authorHistory.lastChangedEpoch
802
-
803
- const newAuthor = Author.create({ name: 'Stanley Briggs' })
804
-
805
- store.put([newAuthor])
806
- store.put([{ ...newAuthor, age: 38 }])
807
-
808
- expect(authorHistory.getDiffSince(lastChangedEpoch)).toMatchObject([
809
- {
810
- added: { [newAuthor.id]: { age: 38 } },
811
- },
812
- ])
813
- })
814
- })