@tldraw/sync-core 5.3.0-internal.d205573d66cb → 5.3.0-internal.fba91ed55f6c
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.
- package/dist-cjs/index.d.ts +78 -0
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TLSocketRoom.js +1 -0
- package/dist-cjs/lib/TLSocketRoom.js.map +2 -2
- package/dist-cjs/lib/TLSyncRoom.js +117 -5
- package/dist-cjs/lib/TLSyncRoom.js.map +3 -3
- package/dist-esm/index.d.mts +78 -0
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TLSocketRoom.mjs +1 -0
- package/dist-esm/lib/TLSocketRoom.mjs.map +2 -2
- package/dist-esm/lib/TLSyncRoom.mjs +117 -5
- package/dist-esm/lib/TLSyncRoom.mjs.map +3 -3
- package/package.json +6 -6
- package/src/index.ts +2 -0
- package/src/lib/TLSocketRoom.ts +8 -1
- package/src/lib/TLSyncRoom.ts +218 -6
- package/src/test/objectStore.test.ts +244 -1
- package/src/test/upgradeDowngrade.test.ts +97 -2
|
@@ -173,8 +173,13 @@ class TestInstance {
|
|
|
173
173
|
|
|
174
174
|
hasLoaded = false
|
|
175
175
|
|
|
176
|
-
constructor(
|
|
177
|
-
|
|
176
|
+
constructor(
|
|
177
|
+
snapshot?: RoomSnapshot,
|
|
178
|
+
oldSchema = schemaV1,
|
|
179
|
+
newSchema = schemaV2,
|
|
180
|
+
roomOpts?: ConstructorParameters<typeof TestServer<RV2>>[2]
|
|
181
|
+
) {
|
|
182
|
+
this.server = new TestServer(newSchema, snapshot, roomOpts)
|
|
178
183
|
this.oldSocketPair = new TestSocketPair('test_upgrade_old', this.server)
|
|
179
184
|
this.newSocketPair = new TestSocketPair('test_upgrade_new', this.server)
|
|
180
185
|
|
|
@@ -1232,3 +1237,93 @@ describe('when the client is the same version', () => {
|
|
|
1232
1237
|
} satisfies TLSocketServerSentEvent<RV2>)
|
|
1233
1238
|
})
|
|
1234
1239
|
})
|
|
1240
|
+
|
|
1241
|
+
describe('record authorizers see server-schema records', () => {
|
|
1242
|
+
function makeInstance(authorizeUser: (args: any) => any) {
|
|
1243
|
+
return new TestInstance(undefined, schemaV1, schemaV2, {
|
|
1244
|
+
authorizeRecord: { user: authorizeUser },
|
|
1245
|
+
})
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
it('a put from an old client reaches the authorizer up-migrated', () => {
|
|
1249
|
+
const seen: any[] = []
|
|
1250
|
+
const t = makeInstance((args: any) => {
|
|
1251
|
+
seen.push(structuredClone({ type: args.type, prev: args.prev, next: args.next }))
|
|
1252
|
+
return args.next
|
|
1253
|
+
})
|
|
1254
|
+
t.oldSocketPair.connect()
|
|
1255
|
+
t.flush()
|
|
1256
|
+
|
|
1257
|
+
const user = UserV1.create({ name: 'bob', age: 10 })
|
|
1258
|
+
t.oldClient.store.put([user])
|
|
1259
|
+
t.flush()
|
|
1260
|
+
|
|
1261
|
+
expect(seen).toHaveLength(1)
|
|
1262
|
+
expect(seen[0].type).toBe('create')
|
|
1263
|
+
expect(seen[0].next).toMatchObject({ name: 'bob', birthdate: null })
|
|
1264
|
+
expect(seen[0].next).not.toHaveProperty('age')
|
|
1265
|
+
})
|
|
1266
|
+
|
|
1267
|
+
it('a stamp applied on create survives — no migration runs after the authorizer', () => {
|
|
1268
|
+
const t = makeInstance((args: any) =>
|
|
1269
|
+
args.type === 'create' ? { ...args.next, birthdate: '2001-02-03' } : args.next
|
|
1270
|
+
)
|
|
1271
|
+
t.oldSocketPair.connect()
|
|
1272
|
+
t.flush()
|
|
1273
|
+
|
|
1274
|
+
const user = UserV1.create({ name: 'bob', age: 10 })
|
|
1275
|
+
t.oldClient.store.put([user])
|
|
1276
|
+
t.flush()
|
|
1277
|
+
|
|
1278
|
+
expect(t.server.storage.documents.get(user.id)?.state).toMatchObject({
|
|
1279
|
+
name: 'bob',
|
|
1280
|
+
birthdate: '2001-02-03',
|
|
1281
|
+
})
|
|
1282
|
+
})
|
|
1283
|
+
|
|
1284
|
+
it('a patch from an old client reaches the authorizer as the upgraded committed candidate', () => {
|
|
1285
|
+
const seen: any[] = []
|
|
1286
|
+
const t = makeInstance((args: any) => {
|
|
1287
|
+
seen.push(structuredClone({ type: args.type, prev: args.prev, next: args.next }))
|
|
1288
|
+
return args.next
|
|
1289
|
+
})
|
|
1290
|
+
t.oldSocketPair.connect()
|
|
1291
|
+
t.newSocketPair.connect()
|
|
1292
|
+
t.flush()
|
|
1293
|
+
|
|
1294
|
+
const user = UserV2.create({ name: 'bob', birthdate: '2022-01-09' })
|
|
1295
|
+
t.newClient.store.put([user])
|
|
1296
|
+
t.flush()
|
|
1297
|
+
seen.length = 0
|
|
1298
|
+
|
|
1299
|
+
t.oldClient.store.update(user.id as any, (u: any) => ({ ...u, name: 'bobby' }))
|
|
1300
|
+
t.flush()
|
|
1301
|
+
|
|
1302
|
+
expect(seen).toHaveLength(1)
|
|
1303
|
+
expect(seen[0].type).toBe('update')
|
|
1304
|
+
expect(seen[0].prev).toMatchObject({ name: 'bob', birthdate: '2022-01-09' })
|
|
1305
|
+
// The down→patch→up round-trip through the lossy v1 schema resets birthdate to null.
|
|
1306
|
+
// The authorizer must see exactly what will be committed — not a preview mixing the
|
|
1307
|
+
// server record with the raw v1 diff (which would still show the old birthdate).
|
|
1308
|
+
expect(seen[0].next).toMatchObject({ name: 'bobby', birthdate: null })
|
|
1309
|
+
expect(seen[0].next).not.toHaveProperty('age')
|
|
1310
|
+
})
|
|
1311
|
+
|
|
1312
|
+
it('vetoes based on the server-schema record', () => {
|
|
1313
|
+
const t = makeInstance((args: any) =>
|
|
1314
|
+
args.type === 'update' && args.next.name === 'forged' ? null : args.next
|
|
1315
|
+
)
|
|
1316
|
+
t.oldSocketPair.connect()
|
|
1317
|
+
t.newSocketPair.connect()
|
|
1318
|
+
t.flush()
|
|
1319
|
+
|
|
1320
|
+
const user = UserV2.create({ name: 'bob', birthdate: '2022-01-09' })
|
|
1321
|
+
t.newClient.store.put([user])
|
|
1322
|
+
t.flush()
|
|
1323
|
+
|
|
1324
|
+
t.oldClient.store.update(user.id as any, (u: any) => ({ ...u, name: 'forged' }))
|
|
1325
|
+
t.flush()
|
|
1326
|
+
|
|
1327
|
+
expect(t.server.storage.documents.get(user.id)?.state).toMatchObject({ name: 'bob' })
|
|
1328
|
+
})
|
|
1329
|
+
})
|