@tldraw/sync-core 5.3.0-internal.fba91ed55f6c → 5.3.0-next.91d2a20b898c

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 (50) hide show
  1. package/dist-cjs/index.d.ts +3 -154
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/InMemorySyncStorage.js +20 -55
  5. package/dist-cjs/lib/InMemorySyncStorage.js.map +2 -2
  6. package/dist-cjs/lib/RoomSession.js.map +1 -1
  7. package/dist-cjs/lib/SQLiteSyncStorage.js +52 -115
  8. package/dist-cjs/lib/SQLiteSyncStorage.js.map +2 -2
  9. package/dist-cjs/lib/TLSocketRoom.js +2 -27
  10. package/dist-cjs/lib/TLSocketRoom.js.map +2 -2
  11. package/dist-cjs/lib/TLSyncClient.js +1 -6
  12. package/dist-cjs/lib/TLSyncClient.js.map +2 -2
  13. package/dist-cjs/lib/TLSyncRoom.js +10 -160
  14. package/dist-cjs/lib/TLSyncRoom.js.map +3 -3
  15. package/dist-cjs/lib/TLSyncStorage.js.map +2 -2
  16. package/dist-cjs/lib/protocol.js.map +2 -2
  17. package/dist-esm/index.d.mts +3 -154
  18. package/dist-esm/index.mjs +1 -1
  19. package/dist-esm/index.mjs.map +2 -2
  20. package/dist-esm/lib/InMemorySyncStorage.mjs +20 -55
  21. package/dist-esm/lib/InMemorySyncStorage.mjs.map +2 -2
  22. package/dist-esm/lib/RoomSession.mjs.map +1 -1
  23. package/dist-esm/lib/SQLiteSyncStorage.mjs +52 -115
  24. package/dist-esm/lib/SQLiteSyncStorage.mjs.map +2 -2
  25. package/dist-esm/lib/TLSocketRoom.mjs +2 -27
  26. package/dist-esm/lib/TLSocketRoom.mjs.map +2 -2
  27. package/dist-esm/lib/TLSyncClient.mjs +1 -6
  28. package/dist-esm/lib/TLSyncClient.mjs.map +2 -2
  29. package/dist-esm/lib/TLSyncRoom.mjs +10 -160
  30. package/dist-esm/lib/TLSyncRoom.mjs.map +3 -3
  31. package/dist-esm/lib/TLSyncStorage.mjs.map +2 -2
  32. package/dist-esm/lib/protocol.mjs.map +2 -2
  33. package/package.json +6 -6
  34. package/src/index.ts +0 -3
  35. package/src/lib/InMemorySyncStorage.ts +21 -74
  36. package/src/lib/RoomSession.ts +2 -7
  37. package/src/lib/SQLiteSyncStorage.test.ts +2 -29
  38. package/src/lib/SQLiteSyncStorage.ts +59 -158
  39. package/src/lib/TLSocketRoom.ts +3 -61
  40. package/src/lib/TLSyncClient.test.ts +2 -9
  41. package/src/lib/TLSyncClient.ts +3 -15
  42. package/src/lib/TLSyncRoom.ts +10 -294
  43. package/src/lib/TLSyncStorage.ts +0 -8
  44. package/src/lib/protocol.ts +0 -17
  45. package/src/test/TLSocketRoom.test.ts +2 -37
  46. package/src/test/TLSyncRoom.test.ts +0 -25
  47. package/src/test/TestServer.ts +4 -14
  48. package/src/test/storageContractSuite.ts +0 -79
  49. package/src/test/upgradeDowngrade.test.ts +2 -144
  50. package/src/test/objectStore.test.ts +0 -630
@@ -109,41 +109,14 @@ class TLSyncRoom {
109
109
  storage;
110
110
  serializedSchema;
111
111
  documentTypes;
112
- /**
113
- * Record types served by the object-store lane. Object records ride the same wire messages
114
- * as document records but are gated by the session's `objectAccess` instead of `isReadonly`,
115
- * and are excluded from `documentTypes` so hosts can persist them in a separate lane.
116
- */
117
- objectTypes;
118
112
  presenceType;
119
113
  log;
120
114
  schema;
121
- authorizeRecord;
122
115
  sessionIdleTimeout;
123
- /**
124
- * The authorizer registered for a record type, widened to the room's record union. Each entry in
125
- * `authorizeRecord` is typed to its specific record; the cast here is the one place we can't
126
- * statically correlate a runtime `typeName` with its record type, so it lives in the library
127
- * rather than in every consumer.
128
- */
129
- authorizerFor(typeName) {
130
- const authorize = this.authorizeRecord?.[typeName];
131
- if (!authorize) return void 0;
132
- return (args) => {
133
- try {
134
- return authorize(args);
135
- } catch (e) {
136
- this.log?.error?.("record authorizer threw; rejecting the write", e);
137
- return null;
138
- }
139
- };
140
- }
141
116
  constructor(opts) {
142
117
  this.schema = opts.schema;
143
118
  this.log = opts.log;
144
119
  this.onPresenceChange = opts.onPresenceChange;
145
- this.onCommittedChanges = opts.onCommittedChanges;
146
- this.authorizeRecord = opts.authorizeRecord;
147
120
  this.storage = opts.storage;
148
121
  this.sessionIdleTimeout = opts.clientTimeout ?? import_RoomSession.SESSION_IDLE_TIMEOUT;
149
122
  (0, import_utils.assert)(
@@ -151,17 +124,8 @@ class TLSyncRoom {
151
124
  "TLSyncRoom is supposed to run either on Cloudflare Workersor on a 18+ version of Node.js, which both support the native structuredClone API"
152
125
  );
153
126
  this.serializedSchema = JSON.parse(JSON.stringify(this.schema.serialize()));
154
- this.objectTypes = new Set(opts.objectTypes ?? []);
155
- for (const typeName of this.objectTypes) {
156
- const type = (0, import_utils.getOwnProperty)(this.schema.types, typeName);
157
- (0, import_utils.assert)(type, `TLSyncRoom: object type '${typeName}' is not registered in the schema`);
158
- (0, import_utils.assert)(
159
- type.scope === "document",
160
- `TLSyncRoom: object type '${typeName}' must have scope 'document', got '${type.scope}'`
161
- );
162
- }
163
127
  this.documentTypes = new Set(
164
- Object.values(this.schema.types).filter((t) => t.scope === "document" && !this.objectTypes.has(t.typeName)).map((t) => t.typeName)
128
+ Object.values(this.schema.types).filter((t) => t.scope === "document").map((t) => t.typeName)
165
129
  );
166
130
  const presenceTypes = new Set(
167
131
  Object.values(this.schema.types).filter((t) => t.scope === "presence")
@@ -172,12 +136,6 @@ class TLSyncRoom {
172
136
  );
173
137
  }
174
138
  this.presenceType = presenceTypes.values().next()?.value ?? null;
175
- if (this.presenceType && this.authorizeRecord) {
176
- (0, import_utils.assert)(
177
- !(0, import_utils.getOwnProperty)(this.authorizeRecord, this.presenceType.typeName),
178
- `TLSyncRoom: authorizeRecord['${this.presenceType.typeName}'] is a presence type; presence records are not authorized`
179
- );
180
- }
181
139
  const { documentClock } = this.storage.transaction((txn) => {
182
140
  this.schema.migrateStorage(txn);
183
141
  });
@@ -304,7 +262,6 @@ class TLSyncRoom {
304
262
  cancellationTime: Date.now(),
305
263
  meta: session.meta,
306
264
  isReadonly: session.isReadonly,
307
- objectAccess: session.objectAccess,
308
265
  requiresLegacyRejection: session.requiresLegacyRejection,
309
266
  supportsStringAppend: session.supportsStringAppend
310
267
  });
@@ -396,7 +353,7 @@ class TLSyncRoom {
396
353
  * @internal
397
354
  */
398
355
  handleNewSession(opts) {
399
- const { sessionId, socket, meta, isReadonly, objectAccess } = opts;
356
+ const { sessionId, socket, meta, isReadonly } = opts;
400
357
  const existing = this.sessions.get(sessionId);
401
358
  this.sessions.set(sessionId, {
402
359
  state: import_RoomSession.RoomSessionState.AwaitingConnectMessage,
@@ -406,7 +363,6 @@ class TLSyncRoom {
406
363
  sessionStartTime: Date.now(),
407
364
  meta,
408
365
  isReadonly: isReadonly ?? false,
409
- objectAccess: objectAccess ?? "write",
410
366
  // this gets set later during handleConnectMessage
411
367
  requiresLegacyRejection: false,
412
368
  supportsStringAppend: true
@@ -426,7 +382,6 @@ class TLSyncRoom {
426
382
  socket,
427
383
  meta,
428
384
  isReadonly,
429
- objectAccess,
430
385
  serializedSchema,
431
386
  presenceId,
432
387
  presenceRecord,
@@ -447,7 +402,6 @@ class TLSyncRoom {
447
402
  outstandingDataMessages: [],
448
403
  meta,
449
404
  isReadonly,
450
- objectAccess: objectAccess ?? "write",
451
405
  requiresLegacyRejection,
452
406
  supportsStringAppend
453
407
  });
@@ -708,7 +662,6 @@ class TLSyncRoom {
708
662
  supportsStringAppend: session.supportsStringAppend,
709
663
  meta: session.meta,
710
664
  isReadonly: session.isReadonly,
711
- objectAccess: session.objectAccess,
712
665
  requiresLegacyRejection: session.requiresLegacyRejection
713
666
  });
714
667
  this._unsafe_sendMessage(session.sessionId, msg);
@@ -753,8 +706,7 @@ class TLSyncRoom {
753
706
  schema: this.schema.serialize(),
754
707
  serverClock: txn.getClock(),
755
708
  diff: { ...presenceDiff.value, ...docDiff },
756
- isReadonly: session.isReadonly,
757
- objectAccess: session.objectAccess
709
+ isReadonly: session.isReadonly
758
710
  };
759
711
  });
760
712
  this.lastDocumentClock = documentClock;
@@ -788,18 +740,13 @@ class TLSyncRoom {
788
740
  (0, import_utils.exhaustiveSwitchError)(op[0]);
789
741
  }
790
742
  };
791
- const addDocument = (storage, changes2, id, _state, authorize) => {
743
+ const addDocument = (storage, changes2, id, _state) => {
792
744
  const res = session ? this.schema.migratePersistedRecord(_state, session.serializedSchema, "up") : { type: "success", value: _state };
793
745
  if (res.type === "error") {
794
746
  throw new import_TLSyncClient.TLSyncError(res.reason, import_TLSyncClient.TLSyncErrorCloseEventReason.CLIENT_TOO_OLD);
795
747
  }
796
- let { value: state } = res;
748
+ const { value: state } = res;
797
749
  const doc = storage.get(id);
798
- if (authorize) {
799
- const result2 = authorize(doc ?? null, state);
800
- if (!result2) return import_utils.Result.ok(void 0);
801
- if (!doc) state = result2;
802
- }
803
750
  if (doc) {
804
751
  const recordType = (0, import_utils.assertExists)((0, import_utils.getOwnProperty)(this.schema.types, doc.typeName));
805
752
  const diff = (0, import_recordDiff.diffAndValidateRecord)(doc, state, recordType);
@@ -815,7 +762,7 @@ class TLSyncRoom {
815
762
  }
816
763
  return import_utils.Result.ok(void 0);
817
764
  };
818
- const patchDocument = (storage, changes2, id, patch, authorize) => {
765
+ const patchDocument = (storage, changes2, id, patch) => {
819
766
  const doc = storage.get(id);
820
767
  if (!doc) return;
821
768
  const recordType = (0, import_utils.assertExists)((0, import_utils.getOwnProperty)(this.schema.types, doc.typeName));
@@ -826,7 +773,6 @@ class TLSyncRoom {
826
773
  if (downgraded.value === doc) {
827
774
  const diff = (0, import_recordDiff.applyAndDiffRecord)(doc, patch, recordType, legacyAppendMode);
828
775
  if (diff) {
829
- if (authorize && !authorize(doc, diff[1])) return;
830
776
  storage.set(id, diff[1]);
831
777
  propagateOp(changes2, id, [import_diff.RecordOpType.Patch, diff[0]], doc, diff[1]);
832
778
  }
@@ -838,7 +784,6 @@ class TLSyncRoom {
838
784
  }
839
785
  const diff = (0, import_recordDiff.diffAndValidateRecord)(doc, upgraded.value, recordType, legacyAppendMode);
840
786
  if (diff) {
841
- if (authorize && !authorize(doc, upgraded.value)) return;
842
787
  storage.set(id, upgraded.value);
843
788
  propagateOp(changes2, id, [import_diff.RecordOpType.Patch, diff], doc, upgraded.value);
844
789
  }
@@ -873,89 +818,21 @@ class TLSyncRoom {
873
818
  }
874
819
  }
875
820
  }
876
- const canWrite = (typeName) => !session || (this.objectTypes.has(typeName) ? session.objectAccess !== "read" : !session.isReadonly);
877
- if (message.diff) {
821
+ if (message.diff && !session?.isReadonly) {
878
822
  for (const [id, op] of (0, import_utils.objectMapEntriesIterable)(message.diff)) {
879
823
  switch (op[0]) {
880
824
  case import_diff.RecordOpType.Put: {
881
- if (!canWrite(op[1].typeName)) continue;
882
- if (!this.documentTypes.has(op[1].typeName) && !this.objectTypes.has(op[1].typeName)) {
825
+ if (!this.documentTypes.has(op[1].typeName)) {
883
826
  throw new import_TLSyncClient.TLSyncError(
884
827
  "invalid record",
885
828
  import_TLSyncClient.TLSyncErrorCloseEventReason.INVALID_RECORD
886
829
  );
887
830
  }
888
- const record = op[1];
889
- let authorize;
890
- if (session && this.authorizeRecord) {
891
- const prev = txn.get(id) ?? null;
892
- if (prev && prev.typeName !== record.typeName) {
893
- this.log?.warn?.(
894
- "skipping put that changes typeName",
895
- `${prev.typeName} -> ${record.typeName}`,
896
- id,
897
- "session:",
898
- session.sessionId
899
- );
900
- continue;
901
- }
902
- const authorizePut = this.authorizerFor(record.typeName);
903
- if (authorizePut) {
904
- authorize = (prevRec, next) => {
905
- const result2 = authorizePut(
906
- prevRec ? {
907
- session: { sessionId: session.sessionId, meta: session.meta },
908
- type: "update",
909
- prev: prevRec,
910
- next
911
- } : {
912
- session: { sessionId: session.sessionId, meta: session.meta },
913
- type: "create",
914
- prev: null,
915
- next
916
- }
917
- );
918
- if (!result2) {
919
- this.log?.warn?.(
920
- "authorizer vetoed put",
921
- record.typeName,
922
- id,
923
- "session:",
924
- session.sessionId
925
- );
926
- return null;
927
- }
928
- return prevRec ? next : result2;
929
- };
930
- }
931
- }
932
- addDocument(txn, docChanges, id, record, authorize);
831
+ addDocument(txn, docChanges, id, op[1]);
933
832
  break;
934
833
  }
935
834
  case import_diff.RecordOpType.Patch: {
936
- const doc = txn.get(id);
937
- if (!doc) continue;
938
- if (!canWrite(doc.typeName)) continue;
939
- const authorizePatch = session && this.authorizerFor(doc.typeName);
940
- const authorize = authorizePatch ? (prev, next) => {
941
- const result2 = authorizePatch({
942
- session: { sessionId: session.sessionId, meta: session.meta },
943
- type: "update",
944
- prev,
945
- next
946
- });
947
- if (!result2) {
948
- this.log?.warn?.(
949
- "authorizer vetoed patch",
950
- doc.typeName,
951
- id,
952
- "session:",
953
- session.sessionId
954
- );
955
- }
956
- return result2;
957
- } : void 0;
958
- patchDocument(txn, docChanges, id, op[1], authorize);
835
+ patchDocument(txn, docChanges, id, op[1]);
959
836
  break;
960
837
  }
961
838
  case import_diff.RecordOpType.Remove: {
@@ -963,23 +840,6 @@ class TLSyncRoom {
963
840
  if (!doc) {
964
841
  continue;
965
842
  }
966
- if (!canWrite(doc.typeName)) continue;
967
- const authorizeRemove = session && this.authorizerFor(doc.typeName);
968
- if (authorizeRemove && !authorizeRemove({
969
- session: { sessionId: session.sessionId, meta: session.meta },
970
- type: "delete",
971
- prev: doc,
972
- next: null
973
- })) {
974
- this.log?.warn?.(
975
- "authorizer vetoed delete",
976
- doc.typeName,
977
- id,
978
- "session:",
979
- session.sessionId
980
- );
981
- continue;
982
- }
983
843
  txn.delete(id);
984
844
  propagateOp(docChanges, id, op, doc, void 0);
985
845
  break;
@@ -1054,16 +914,6 @@ class TLSyncRoom {
1054
914
  this.onPresenceChange?.();
1055
915
  });
1056
916
  }
1057
- if (result.docChanges.diffs && this.onCommittedChanges) {
1058
- const diff = result.docChanges.diffs.diff;
1059
- queueMicrotask(() => {
1060
- try {
1061
- this.onCommittedChanges?.({ diff, documentClock });
1062
- } catch (e) {
1063
- this.log?.error?.("onCommittedChanges threw", e);
1064
- }
1065
- });
1066
- }
1067
917
  }
1068
918
  /**
1069
919
  * Handle the event when a client disconnects. Cleans up the session and