@tldraw/store 5.2.0-next.e2b8d10bf10e → 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,265 +0,0 @@
1
- import assert from 'assert'
2
- import { BaseRecord, RecordId } from '../BaseRecord'
3
- import { MigrationSequence } from '../migrate'
4
- import { createRecordType } from '../RecordType'
5
- import { SerializedSchemaV2, StoreSchema } from '../StoreSchema'
6
-
7
- const mockSequence = ({
8
- id,
9
- retroactive,
10
- versions,
11
- filter,
12
- }: {
13
- id: string
14
- retroactive: boolean
15
- versions: number
16
- filter?(r: TestRecordType): boolean
17
- }): MigrationSequence => ({
18
- sequenceId: id,
19
- retroactive,
20
- sequence: new Array(versions).fill(0).map((_, i) => ({
21
- id: `${id}/${i + 1}`,
22
- scope: 'record',
23
- filter: filter as any,
24
- up(r) {
25
- const record = r as TestRecordType
26
- record.versions[id] ??= 0
27
- record.versions[id]++
28
- // noop
29
- },
30
- down(r) {
31
- const record = r as TestRecordType
32
- record.versions[id]--
33
- },
34
- })),
35
- })
36
-
37
- interface TestRecordType extends BaseRecord<'test', RecordId<TestRecordType>> {
38
- versions: Record<string, number>
39
- }
40
- const TestRecordType = createRecordType<TestRecordType>('test', {
41
- scope: 'document',
42
- })
43
-
44
- const makeSchema = (migrations: MigrationSequence[]) => {
45
- return StoreSchema.create({ test: TestRecordType }, { migrations })
46
- }
47
-
48
- const makePersistedSchema = (...args: Array<[migrations: MigrationSequence, version: number]>) => {
49
- return {
50
- schemaVersion: 2,
51
- sequences: Object.fromEntries(args.map(([m, v]) => [m.sequenceId, v])),
52
- } satisfies SerializedSchemaV2
53
- }
54
-
55
- const makeTestRecord = (persistedSchema: SerializedSchemaV2) => {
56
- return TestRecordType.create({
57
- versions: Object.fromEntries(
58
- Object.keys(persistedSchema.sequences).map((id) => [id, persistedSchema.sequences[id]])
59
- ),
60
- })
61
- }
62
-
63
- test('going up from 0', () => {
64
- const foo = mockSequence({ id: 'foo', retroactive: false, versions: 2 })
65
- const bar = mockSequence({ id: 'bar', retroactive: false, versions: 3 })
66
- const schema = makeSchema([foo, bar])
67
- const persistedSchema = makePersistedSchema([foo, 0], [bar, 0])
68
-
69
- const r = makeTestRecord(persistedSchema)
70
- expect(r.versions).toEqual({ foo: 0, bar: 0 })
71
- const update = schema.migratePersistedRecord(r, persistedSchema)
72
- assert(update.type === 'success', 'the update should be successful')
73
-
74
- // the original record did not change
75
- expect(r.versions).toEqual({ foo: 0, bar: 0 })
76
-
77
- // the updated record has the new versions
78
- expect((update.value as TestRecordType).versions).toEqual({ foo: 2, bar: 3 })
79
- })
80
-
81
- test('going up with a retroactive: true and a retroactive: false', () => {
82
- const foo = mockSequence({ id: 'foo', retroactive: true, versions: 2 })
83
- const bar = mockSequence({ id: 'bar', retroactive: false, versions: 3 })
84
- const schema = makeSchema([foo, bar])
85
- const persistedSchema = makePersistedSchema()
86
-
87
- const r = makeTestRecord(persistedSchema)
88
- expect(r.versions).toEqual({})
89
- const update = schema.migratePersistedRecord(r, persistedSchema)
90
- assert(update.type === 'success', 'the update should be successful')
91
-
92
- // the original record did not change
93
- expect(r.versions).toEqual({})
94
-
95
- // the updated record has the new versions
96
- expect((update.value as TestRecordType).versions).toEqual({ foo: 2 })
97
- })
98
-
99
- test('going down to 0s', () => {
100
- const foo = mockSequence({ id: 'foo', retroactive: false, versions: 2 })
101
- const bar = mockSequence({ id: 'bar', retroactive: false, versions: 3 })
102
- const schema = makeSchema([foo, bar])
103
- const persistedSchema = makePersistedSchema([foo, 0], [bar, 0])
104
-
105
- const r = makeTestRecord(schema.serialize())
106
- expect(r.versions).toEqual({ foo: 2, bar: 3 })
107
- const downgrade = schema.migratePersistedRecord(r, persistedSchema, 'down')
108
- assert(downgrade.type === 'success', 'the downgrade should be successful')
109
-
110
- // the original record did not change
111
- expect(r.versions).toEqual({ foo: 2, bar: 3 })
112
-
113
- // the downgraded record has the new versions
114
- expect((downgrade.value as TestRecordType).versions).toEqual({ foo: 0, bar: 0 })
115
- })
116
-
117
- test('going down with a retroactive: true and a retroactive: false', () => {
118
- const foo = mockSequence({ id: 'foo', retroactive: true, versions: 2 })
119
- const bar = mockSequence({ id: 'bar', retroactive: false, versions: 3 })
120
- const schema = makeSchema([foo, bar])
121
- const persistedSchema = makePersistedSchema()
122
-
123
- const r = makeTestRecord(schema.serialize())
124
- expect(r.versions).toEqual({ foo: 2, bar: 3 })
125
- const downgrade = schema.migratePersistedRecord(r, persistedSchema, 'down')
126
- assert(downgrade.type === 'success', 'the downgrade should be successful')
127
-
128
- // the original record did not change
129
- expect(r.versions).toEqual({ foo: 2, bar: 3 })
130
-
131
- // only the foo migrations were undone
132
- expect((downgrade.value as TestRecordType).versions).toEqual({ foo: 0, bar: 3 })
133
- })
134
-
135
- test('going up with no changes', () => {
136
- const foo = mockSequence({ id: 'foo', retroactive: false, versions: 2 })
137
- const bar = mockSequence({ id: 'bar', retroactive: false, versions: 3 })
138
- const schema = makeSchema([foo, bar])
139
- const persistedSchema = makePersistedSchema([foo, 2], [bar, 3])
140
-
141
- const r = makeTestRecord(persistedSchema)
142
- expect(r.versions).toEqual({ foo: 2, bar: 3 })
143
- const update = schema.migratePersistedRecord(r, persistedSchema)
144
- assert(update.type === 'success', 'the update should be successful')
145
-
146
- // the returned record should be the the input record, i.e. it should not have allocated a new record
147
- expect(r).toBe(update.value)
148
- })
149
-
150
- test('going down with no changes', () => {
151
- const foo = mockSequence({ id: 'foo', retroactive: false, versions: 2 })
152
- const bar = mockSequence({ id: 'bar', retroactive: false, versions: 3 })
153
- const schema = makeSchema([foo, bar])
154
- const persistedSchema = makePersistedSchema([foo, 2], [bar, 3])
155
-
156
- const r = makeTestRecord(persistedSchema)
157
- expect(r.versions).toEqual({ foo: 2, bar: 3 })
158
- const update = schema.migratePersistedRecord(r, persistedSchema, 'down')
159
- assert(update.type === 'success', 'the update should be successful')
160
-
161
- // the returned record should be the the input record, i.e. it should not have allocated a new record
162
- expect(r).toBe(update.value)
163
- })
164
-
165
- test('respects filters', () => {
166
- const foo = mockSequence({
167
- id: 'foo',
168
- retroactive: false,
169
- versions: 2,
170
- filter: (r) => (r as any).foo === true,
171
- })
172
- const bar = mockSequence({ id: 'bar', retroactive: false, versions: 3 })
173
- const schema = makeSchema([foo, bar])
174
- const persistedSchema = makePersistedSchema([foo, 0], [bar, 0])
175
-
176
- const r = makeTestRecord(persistedSchema)
177
- const update = schema.migratePersistedRecord(r, persistedSchema, 'up')
178
- assert(update.type === 'success', 'the update should be successful')
179
-
180
- // foo migrations shouldn't have been applied
181
- expect((update.value as TestRecordType).versions).toEqual({ foo: 0, bar: 3 })
182
-
183
- const r2 = { ...r, foo: true }
184
- const update2 = schema.migratePersistedRecord(r2, persistedSchema, 'up')
185
- assert(update2.type === 'success', 'the update should be successful')
186
-
187
- // foo migrations should have been applied
188
- expect((update2.value as TestRecordType).versions).toEqual({ foo: 2, bar: 3 })
189
- })
190
-
191
- test('does not go up or down if theres a store migration in the path', () => {
192
- const foo = mockSequence({ id: 'foo', retroactive: false, versions: 3 })
193
- foo.sequence[1] = {
194
- id: 'foo/2',
195
- scope: 'store',
196
- up() {
197
- // noop
198
- },
199
- down() {
200
- // noop
201
- },
202
- }
203
- const schema = makeSchema([foo])
204
- const v0Schema = makePersistedSchema([foo, 0])
205
-
206
- const r0 = makeTestRecord(v0Schema)
207
- const r3 = makeTestRecord(schema.serialize())
208
- const update = schema.migratePersistedRecord(r0, v0Schema, 'up')
209
- expect(update.type).toBe('error')
210
- const update2 = schema.migratePersistedRecord(r3, v0Schema, 'down')
211
- expect(update2.type).toBe('error')
212
-
213
- // snapshot migration up should still work
214
- const update3 = schema.migrateStoreSnapshot({
215
- schema: v0Schema,
216
- store: { [r0.id]: r0 },
217
- })
218
-
219
- assert(update3.type === 'success', 'the update should be successful')
220
- expect((update3.value[r0.id] as TestRecordType).versions).toEqual({ foo: 2 })
221
- })
222
-
223
- test('does not go down if theres a migrations without the down migrator in the path', () => {
224
- const foo = mockSequence({ id: 'foo', retroactive: false, versions: 3 })
225
- delete (foo.sequence[1] as any).down
226
- const schema = makeSchema([foo])
227
- const v0Schema = makePersistedSchema([foo, 0])
228
-
229
- // going up still works
230
- const r0 = makeTestRecord(v0Schema)
231
- const update = schema.migratePersistedRecord(r0, v0Schema, 'up')
232
- expect(update.type).toBe('success')
233
-
234
- // going down does not
235
- const r3 = makeTestRecord(schema.serialize())
236
- const update2 = schema.migratePersistedRecord(r3, v0Schema, 'down')
237
- expect(update2.type).toBe('error')
238
- })
239
-
240
- test('allows returning a new record from the migrator fn', () => {
241
- const foo = mockSequence({ id: 'foo', retroactive: false, versions: 3 })
242
- foo.sequence[1] = {
243
- id: 'foo/2',
244
- scope: 'record',
245
- up(r) {
246
- const record = r as TestRecordType
247
- return { ...record, versions: { ...record.versions, foo: 2 } }
248
- },
249
- down(r) {
250
- const record = r as TestRecordType
251
- return { ...record, versions: { ...record.versions, foo: 1 } }
252
- },
253
- }
254
- const schema = makeSchema([foo])
255
- const v0Schema = makePersistedSchema([foo, 0])
256
-
257
- const r0 = makeTestRecord(v0Schema)
258
- const r3 = makeTestRecord(schema.serialize())
259
- const update = schema.migratePersistedRecord(r0, v0Schema, 'up')
260
- assert(update.type === 'success', 'the update should be successful')
261
- expect((update.value as TestRecordType).versions).toEqual({ foo: 3 })
262
- const update2 = schema.migratePersistedRecord(r3, v0Schema, 'down')
263
- assert(update2.type === 'success', 'the update should be successful')
264
- expect((update2.value as TestRecordType).versions).toEqual({ foo: 0 })
265
- })
@@ -1,209 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-deprecated */
2
- import { assert } from '@tldraw/utils'
3
- import {
4
- BaseRecord,
5
- Migration,
6
- RecordId,
7
- createMigrationIds,
8
- createMigrationSequence,
9
- createRecordType,
10
- } from '../../'
11
- import { StoreSchema } from '../StoreSchema'
12
-
13
- interface TestRecord extends BaseRecord<'test', RecordId<TestRecord>> {
14
- name: string
15
- version: number
16
- }
17
-
18
- describe('StoreSchema migration caching', () => {
19
- // Create migration IDs
20
- const TestVersions = createMigrationIds('com.tldraw.test', {
21
- AddVersion: 1,
22
- UpdateVersion: 2,
23
- })
24
-
25
- // Create a simple schema with migrations
26
- const createTestSchema = (version: number) => {
27
- const TestRecordType = createRecordType<TestRecord>('test', {
28
- scope: 'document',
29
- })
30
-
31
- const sequence: Migration[] = []
32
-
33
- if (version > 1) {
34
- sequence.push({
35
- id: TestVersions.AddVersion,
36
- scope: 'record',
37
- up: (record: any) => {
38
- // Mutate the record in place
39
- record.version = 2
40
- // Don't return anything
41
- },
42
- down: (record: any) => {
43
- record.version = 1
44
- // Don't return anything
45
- },
46
- })
47
- }
48
-
49
- if (version > 2) {
50
- sequence.push({
51
- id: TestVersions.UpdateVersion,
52
- scope: 'record',
53
- up: (record: any) => {
54
- record.version = 3
55
- // Don't return anything
56
- },
57
- down: (record: any) => {
58
- record.version = 2
59
- // Don't return anything
60
- },
61
- })
62
- }
63
-
64
- const schema = StoreSchema.create(
65
- {
66
- test: TestRecordType,
67
- },
68
- {
69
- migrations: [createMigrationSequence({ sequenceId: 'com.tldraw.test', sequence })],
70
- }
71
- )
72
-
73
- return schema
74
- }
75
-
76
- it('should cache migration results and return same array reference', () => {
77
- const schema = createTestSchema(3)
78
- const oldSchema = schema.serializeEarliestVersion()
79
-
80
- // First call should create the migrations array
81
- const migrations1 = schema.getMigrationsSince(oldSchema)
82
- assert(migrations1.ok)
83
- expect(migrations1.value).toHaveLength(2)
84
-
85
- // Second call should return the same array reference (cached)
86
- const migrations2 = schema.getMigrationsSince(oldSchema)
87
- assert(migrations2.ok)
88
- expect(migrations2.value).toBe(migrations1.value) // Same array reference
89
-
90
- // Third call should also return the same array reference
91
- const migrations3 = schema.getMigrationsSince(oldSchema)
92
- assert(migrations3.ok)
93
- expect(migrations3.value).toBe(migrations1.value)
94
- })
95
-
96
- it('should not cache when schema versions are different', () => {
97
- const schema = createTestSchema(3)
98
- const oldSchema = schema.serializeEarliestVersion()
99
-
100
- // Call with original schema
101
- const migrations1 = schema.getMigrationsSince(oldSchema)
102
- expect(migrations1.ok).toBe(true)
103
- if (!migrations1.ok) throw new Error('Expected migrations1 to be ok')
104
-
105
- // Create a different schema version by using a schema with version 2
106
- const schemaV2 = createTestSchema(2)
107
- const schemaV2Serialized = schemaV2.serializeEarliestVersion()
108
- const migrations2 = schema.getMigrationsSince(schemaV2Serialized)
109
- expect(migrations2.ok).toBe(true)
110
- if (!migrations2.ok) throw new Error('Expected migrations2 to be ok')
111
-
112
- // Should be different arrays (no cache hit)
113
- expect(migrations2.value).not.toBe(migrations1.value)
114
- })
115
-
116
- it('should handle mutateInputStore: true with migrators that return void', () => {
117
- const schema = createTestSchema(3)
118
- const oldSchema = schema.serializeEarliestVersion()
119
-
120
- const store = {
121
- test1: {
122
- id: 'test1',
123
- name: 'Test 1',
124
- version: 1,
125
- typeName: 'test',
126
- },
127
- test2: {
128
- id: 'test2',
129
- name: 'Test 2',
130
- version: 1,
131
- typeName: 'test',
132
- },
133
- }
134
-
135
- // Test with mutateInputStore: true
136
- const result1 = schema.migrateStoreSnapshot(
137
- { store, schema: oldSchema },
138
- { mutateInputStore: true }
139
- )
140
-
141
- assert(result1.type === 'success')
142
- expect((result1.value as any).test1.version).toBe(3)
143
- expect((result1.value as any).test2.version).toBe(3)
144
-
145
- // The input store should be mutated in place
146
- expect(result1.value).toBe(store)
147
- })
148
-
149
- it('should handle mutateInputStore: false with migrators that return void', () => {
150
- const schema = createTestSchema(3)
151
- const oldSchema = schema.serializeEarliestVersion()
152
-
153
- const store = {
154
- test1: {
155
- id: 'test1',
156
- name: 'Test 1',
157
- version: 1,
158
- typeName: 'test',
159
- },
160
- }
161
-
162
- // Test with mutateInputStore: false (default)
163
- const result = schema.migrateStoreSnapshot({ store, schema: oldSchema })
164
-
165
- assert(result.type === 'success')
166
- expect((result.value as any).test1.version).toBe(3)
167
-
168
- // The input store should NOT be mutated
169
- expect(store.test1.version).toBe(1)
170
- })
171
-
172
- it('should handle empty migration list caching', () => {
173
- const schema = createTestSchema(1) // No migrations
174
- const oldSchema = schema.serializeEarliestVersion()
175
-
176
- // First call
177
- const migrations1 = schema.getMigrationsSince(oldSchema)
178
- assert(migrations1.ok)
179
-
180
- expect(migrations1.value).toHaveLength(0)
181
-
182
- // Second call should return same array reference
183
- const migrations2 = schema.getMigrationsSince(oldSchema)
184
- assert(migrations2.ok)
185
- expect(migrations2.value).toBe(migrations1.value)
186
- expect(migrations2.value).toHaveLength(0)
187
- })
188
-
189
- it('should handle incompatible schema caching', () => {
190
- const schema = createTestSchema(3)
191
- const incompatibleSchema = {
192
- schemaVersion: 1 as const,
193
- storeVersion: 1,
194
- recordVersions: {
195
- test: {
196
- version: 999, // Much higher version than what we support
197
- },
198
- },
199
- }
200
-
201
- // First call should fail
202
- const migrations1 = schema.getMigrationsSince(incompatibleSchema)
203
- expect(migrations1.ok).toBe(false)
204
-
205
- // Second call should also fail (but might be cached)
206
- const migrations2 = schema.getMigrationsSince(incompatibleSchema)
207
- expect(migrations2.ok).toBe(false)
208
- })
209
- })