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