@syncular/client-plugin-yjs 0.0.0 → 0.0.6-159

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @syncular/client-plugin-crdt-yjs
1
+ # @syncular/client-plugin-yjs
2
2
 
3
3
  Yjs-first CRDT plugin for Syncular client integration.
4
4
 
@@ -45,7 +45,7 @@ The plugin converts this to:
45
45
  ## Example
46
46
 
47
47
  ```ts
48
- import { createYjsClientPlugin } from '@syncular/client-plugin-crdt-yjs';
48
+ import { createYjsClientPlugin } from '@syncular/client-plugin-yjs';
49
49
 
50
50
  const yjs = createYjsClientPlugin({
51
51
  rules: [
@@ -0,0 +1,88 @@
1
+ import { type SyncClientPlugin } from '@syncular/client';
2
+ export declare const YJS_PAYLOAD_KEY = "__yjs";
3
+ export type YjsFieldKind = 'text' | 'xml-fragment' | 'prosemirror';
4
+ export interface YjsClientFieldRule {
5
+ table: string;
6
+ field: string;
7
+ /**
8
+ * Column that stores canonical serialized Yjs state.
9
+ * Example: "content_yjs_state"
10
+ */
11
+ stateColumn: string;
12
+ /**
13
+ * Container key inside the Yjs document. Defaults to `field`.
14
+ */
15
+ containerKey?: string;
16
+ /**
17
+ * Snapshot row id column. Defaults to `id`.
18
+ */
19
+ rowIdField?: string;
20
+ /**
21
+ * CRDT container type.
22
+ */
23
+ kind?: YjsFieldKind;
24
+ }
25
+ export interface YjsClientUpdateEnvelope {
26
+ updateId: string;
27
+ updateBase64: string;
28
+ }
29
+ export type YjsClientUpdateInput = YjsClientUpdateEnvelope | readonly YjsClientUpdateEnvelope[];
30
+ export interface YjsClientPayloadEnvelope {
31
+ [field: string]: YjsClientUpdateInput;
32
+ }
33
+ export interface BuildYjsTextUpdateArgs {
34
+ previousStateBase64?: string | Uint8Array | null;
35
+ nextText: string;
36
+ containerKey?: string;
37
+ updateId?: string;
38
+ }
39
+ export interface BuildYjsTextUpdateResult {
40
+ update: YjsClientUpdateEnvelope;
41
+ nextStateBase64: string;
42
+ nextText: string;
43
+ }
44
+ export interface ApplyYjsTextUpdatesArgs {
45
+ previousStateBase64?: string | Uint8Array | null;
46
+ updates: readonly YjsClientUpdateEnvelope[];
47
+ containerKey?: string;
48
+ }
49
+ export interface ApplyYjsTextUpdatesResult {
50
+ nextStateBase64: string;
51
+ text: string;
52
+ }
53
+ export interface CreateYjsClientPluginOptions {
54
+ name?: string;
55
+ rules: readonly YjsClientFieldRule[];
56
+ envelopeKey?: string;
57
+ priority?: number;
58
+ /**
59
+ * Maximum cached row-field Yjs states to keep in memory.
60
+ * Prevents unbounded growth in long-lived sessions.
61
+ * @default 10_000
62
+ */
63
+ maxTrackedRows?: number;
64
+ /**
65
+ * Throw when envelope payload references fields without matching rules.
66
+ * @default true
67
+ */
68
+ strict?: boolean;
69
+ /**
70
+ * Remove the Yjs envelope key from outgoing/incoming records.
71
+ * @default true
72
+ */
73
+ stripEnvelope?: boolean;
74
+ /**
75
+ * Remove the Yjs envelope key from push payloads.
76
+ * Default inherits from `stripEnvelope`.
77
+ */
78
+ stripEnvelopeBeforePush?: boolean;
79
+ /**
80
+ * Remove the Yjs envelope key from local optimistic mutation payloads.
81
+ * Default inherits from `stripEnvelope`.
82
+ */
83
+ stripEnvelopeBeforeApplyLocalMutations?: boolean;
84
+ }
85
+ export declare function applyYjsTextUpdates(args: ApplyYjsTextUpdatesArgs): ApplyYjsTextUpdatesResult;
86
+ export declare function buildYjsTextUpdate(args: BuildYjsTextUpdateArgs): BuildYjsTextUpdateResult;
87
+ export declare function createYjsClientPlugin(options: CreateYjsClientPluginOptions): SyncClientPlugin;
88
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,gBAAgB,EAKtB,MAAM,kBAAkB,CAAC;AAG1B,eAAO,MAAM,eAAe,UAAU,CAAC;AAKvC,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,cAAc,GAAG,aAAa,CAAC;AAEnE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB;AAQD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,oBAAoB,GAC5B,uBAAuB,GACvB,SAAS,uBAAuB,EAAE,CAAC;AAEvC,MAAM,WAAW,wBAAwB;IACvC,CAAC,KAAK,EAAE,MAAM,GAAG,oBAAoB,CAAC;CACvC;AAED,MAAM,WAAW,sBAAsB;IACrC,mBAAmB,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,uBAAuB,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,mBAAmB,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACjD,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,yBAAyB;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,4BAA4B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;OAGG;IACH,sCAAsC,CAAC,EAAE,OAAO,CAAC;CAClD;AA0SD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,uBAAuB,GAC5B,yBAAyB,CAa3B;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,sBAAsB,GAC3B,wBAAwB,CAqB1B;AAsSD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,gBAAgB,CA0HlB"}
package/dist/index.js ADDED
@@ -0,0 +1,592 @@
1
+ import { PluginPriority, } from '@syncular/client';
2
+ import * as Y from 'yjs';
3
+ export const YJS_PAYLOAD_KEY = '__yjs';
4
+ const BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
5
+ function isRecord(value) {
6
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
7
+ }
8
+ function readString(value) {
9
+ return typeof value === 'string' && value.length > 0 ? value : null;
10
+ }
11
+ function bytesToBase64(bytes) {
12
+ if (typeof Buffer !== 'undefined') {
13
+ return Buffer.from(bytes).toString('base64');
14
+ }
15
+ let binary = '';
16
+ for (const byte of bytes) {
17
+ binary += String.fromCharCode(byte);
18
+ }
19
+ return btoa(binary);
20
+ }
21
+ function tryReadBase64TextFromBytes(bytes) {
22
+ if (bytes.length === 0)
23
+ return null;
24
+ try {
25
+ const decoded = new TextDecoder().decode(bytes).trim();
26
+ if (!decoded || !BASE64_PATTERN.test(decoded))
27
+ return null;
28
+ return decoded;
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ function base64ToBytes(base64) {
35
+ if (!BASE64_PATTERN.test(base64)) {
36
+ throw new Error('Invalid base64 string');
37
+ }
38
+ if (typeof Buffer !== 'undefined') {
39
+ return new Uint8Array(Buffer.from(base64, 'base64'));
40
+ }
41
+ const binary = atob(base64);
42
+ const out = new Uint8Array(binary.length);
43
+ for (let i = 0; i < binary.length; i++) {
44
+ out[i] = binary.charCodeAt(i);
45
+ }
46
+ return out;
47
+ }
48
+ function stateValueToBytes(value) {
49
+ if (value instanceof Uint8Array) {
50
+ const encoded = tryReadBase64TextFromBytes(value);
51
+ if (encoded)
52
+ return base64ToBytes(encoded);
53
+ return value;
54
+ }
55
+ const str = readString(value);
56
+ if (!str)
57
+ return null;
58
+ return base64ToBytes(str);
59
+ }
60
+ function stateValueToBase64(value) {
61
+ const str = readString(value);
62
+ if (str)
63
+ return str;
64
+ if (value instanceof Uint8Array) {
65
+ const encoded = tryReadBase64TextFromBytes(value);
66
+ if (encoded)
67
+ return encoded;
68
+ return bytesToBase64(value);
69
+ }
70
+ return null;
71
+ }
72
+ function createDocFromState(stateValue) {
73
+ const doc = new Y.Doc();
74
+ const bytes = stateValueToBytes(stateValue);
75
+ if (bytes && bytes.length > 0) {
76
+ Y.applyUpdate(doc, bytes);
77
+ }
78
+ return doc;
79
+ }
80
+ function exportSnapshotBase64(doc) {
81
+ return bytesToBase64(Y.encodeStateAsUpdate(doc));
82
+ }
83
+ function replaceText(doc, containerKey, nextText) {
84
+ const text = doc.getText(containerKey);
85
+ const currentLength = text.length;
86
+ doc.transact(() => {
87
+ if (currentLength > 0) {
88
+ text.delete(0, currentLength);
89
+ }
90
+ if (nextText.length > 0) {
91
+ text.insert(0, nextText);
92
+ }
93
+ });
94
+ }
95
+ function patchText(doc, containerKey, nextText) {
96
+ const text = doc.getText(containerKey);
97
+ const currentText = text.toString();
98
+ if (currentText === nextText)
99
+ return;
100
+ const minLength = Math.min(currentText.length, nextText.length);
101
+ let prefixLength = 0;
102
+ while (prefixLength < minLength &&
103
+ currentText.charCodeAt(prefixLength) === nextText.charCodeAt(prefixLength)) {
104
+ prefixLength += 1;
105
+ }
106
+ let currentSuffixStart = currentText.length;
107
+ let nextSuffixStart = nextText.length;
108
+ while (currentSuffixStart > prefixLength &&
109
+ nextSuffixStart > prefixLength &&
110
+ currentText.charCodeAt(currentSuffixStart - 1) ===
111
+ nextText.charCodeAt(nextSuffixStart - 1)) {
112
+ currentSuffixStart -= 1;
113
+ nextSuffixStart -= 1;
114
+ }
115
+ const deleteLength = currentSuffixStart - prefixLength;
116
+ const insertSegment = nextText.slice(prefixLength, nextSuffixStart);
117
+ doc.transact(() => {
118
+ if (deleteLength > 0) {
119
+ text.delete(prefixLength, deleteLength);
120
+ }
121
+ if (insertSegment.length > 0) {
122
+ text.insert(prefixLength, insertSegment);
123
+ }
124
+ });
125
+ }
126
+ function ensureTextContainer(doc, containerKey) {
127
+ return doc.getText(containerKey).toString();
128
+ }
129
+ function ensureXmlFragmentContainer(doc, containerKey) {
130
+ return doc.getXmlFragment(containerKey).toString();
131
+ }
132
+ function materializeRuleValue(doc, rule) {
133
+ if (rule.kind === 'text') {
134
+ return ensureTextContainer(doc, rule.containerKey);
135
+ }
136
+ return ensureXmlFragmentContainer(doc, rule.containerKey);
137
+ }
138
+ function seedRuleValueFromPayload(doc, rule, source) {
139
+ if (rule.kind !== 'text')
140
+ return;
141
+ const initialText = readString(source[rule.field]);
142
+ if (initialText) {
143
+ replaceText(doc, rule.containerKey, initialText);
144
+ }
145
+ }
146
+ function buildRuleIndex(rules) {
147
+ const index = new Map();
148
+ const normalizedRules = [];
149
+ const seen = new Set();
150
+ for (const rule of rules) {
151
+ if (!rule.table.trim()) {
152
+ throw new Error('YjsClientFieldRule.table cannot be empty');
153
+ }
154
+ if (!rule.field.trim()) {
155
+ throw new Error('YjsClientFieldRule.field cannot be empty');
156
+ }
157
+ if (!rule.stateColumn.trim()) {
158
+ throw new Error('YjsClientFieldRule.stateColumn cannot be empty');
159
+ }
160
+ const key = `${rule.table}\u001f${rule.field}`;
161
+ if (seen.has(key)) {
162
+ throw new Error(`Duplicate Yjs client rule for table "${rule.table}", field "${rule.field}"`);
163
+ }
164
+ seen.add(key);
165
+ const resolved = {
166
+ ...rule,
167
+ containerKey: rule.containerKey ?? rule.field,
168
+ rowIdField: rule.rowIdField ?? 'id',
169
+ kind: rule.kind ?? 'text',
170
+ };
171
+ normalizedRules.push(resolved);
172
+ const tableRules = index.get(resolved.table) ?? new Map();
173
+ tableRules.set(resolved.field, resolved);
174
+ index.set(resolved.table, tableRules);
175
+ }
176
+ return { index, normalizedRules };
177
+ }
178
+ function rowFieldCacheKey(table, rowId, field) {
179
+ return `${table}\u001f${rowId}\u001f${field}`;
180
+ }
181
+ function touchLruState(map, key, value, maxTrackedRows) {
182
+ map.delete(key);
183
+ map.set(key, value);
184
+ if (map.size <= maxTrackedRows)
185
+ return;
186
+ const oldest = map.keys().next().value;
187
+ if (oldest) {
188
+ map.delete(oldest);
189
+ }
190
+ }
191
+ function clearRowStateCache(args) {
192
+ const tableRules = args.index.get(args.table);
193
+ if (!tableRules)
194
+ return;
195
+ for (const rule of tableRules.values()) {
196
+ args.stateByRowField.delete(rowFieldCacheKey(args.table, args.rowId, rule.field));
197
+ }
198
+ }
199
+ function resolveSnapshotRowId(row, rule) {
200
+ const candidate = row[rule.rowIdField];
201
+ return typeof candidate === 'string' && candidate.length > 0
202
+ ? candidate
203
+ : null;
204
+ }
205
+ function normalizeUpdateEnvelope(value, context) {
206
+ if (!isRecord(value)) {
207
+ throw new Error(`${context} must be an object`);
208
+ }
209
+ const updateId = readString(value.updateId);
210
+ const updateBase64 = readString(value.updateBase64);
211
+ if (!updateId) {
212
+ throw new Error(`${context}.updateId must be a non-empty string`);
213
+ }
214
+ if (!updateBase64) {
215
+ throw new Error(`${context}.updateBase64 must be a non-empty base64 string`);
216
+ }
217
+ return { updateId, updateBase64 };
218
+ }
219
+ function normalizeUpdateEnvelopes(value, context) {
220
+ if (Array.isArray(value)) {
221
+ return value.map((entry, i) => normalizeUpdateEnvelope(entry, `${context}[${i}]`));
222
+ }
223
+ return [normalizeUpdateEnvelope(value, context)];
224
+ }
225
+ function createUpdateId() {
226
+ const randomUUID = globalThis.crypto?.randomUUID;
227
+ if (typeof randomUUID === 'function') {
228
+ return randomUUID.call(globalThis.crypto);
229
+ }
230
+ const ts = Date.now().toString(36);
231
+ const rnd = Math.random().toString(36).slice(2, 12);
232
+ return `yjs-${ts}-${rnd}`;
233
+ }
234
+ export function applyYjsTextUpdates(args) {
235
+ const containerKey = args.containerKey ?? 'text';
236
+ const doc = createDocFromState(args.previousStateBase64);
237
+ try {
238
+ for (const update of args.updates) {
239
+ Y.applyUpdate(doc, base64ToBytes(update.updateBase64));
240
+ }
241
+ const text = ensureTextContainer(doc, containerKey);
242
+ const nextStateBase64 = exportSnapshotBase64(doc);
243
+ return { nextStateBase64, text };
244
+ }
245
+ finally {
246
+ doc.destroy();
247
+ }
248
+ }
249
+ export function buildYjsTextUpdate(args) {
250
+ const containerKey = args.containerKey ?? 'text';
251
+ const doc = createDocFromState(args.previousStateBase64);
252
+ try {
253
+ const from = Y.encodeStateVector(doc);
254
+ patchText(doc, containerKey, args.nextText);
255
+ const update = bytesToBase64(Y.encodeStateAsUpdate(doc, from));
256
+ const nextText = ensureTextContainer(doc, containerKey);
257
+ const nextStateBase64 = exportSnapshotBase64(doc);
258
+ return {
259
+ update: {
260
+ updateId: args.updateId ?? createUpdateId(),
261
+ updateBase64: update,
262
+ },
263
+ nextStateBase64,
264
+ nextText,
265
+ };
266
+ }
267
+ finally {
268
+ doc.destroy();
269
+ }
270
+ }
271
+ function materializeRowFromState(args) {
272
+ const tableRules = args.index.get(args.table);
273
+ if (!tableRules) {
274
+ if (args.stripEnvelope && args.envelopeKey in args.row) {
275
+ const next = { ...args.row };
276
+ delete next[args.envelopeKey];
277
+ return next;
278
+ }
279
+ return args.row;
280
+ }
281
+ let nextRow = null;
282
+ const ensureRow = () => {
283
+ if (nextRow)
284
+ return nextRow;
285
+ nextRow = { ...args.row };
286
+ return nextRow;
287
+ };
288
+ for (const rule of tableRules.values()) {
289
+ const source = nextRow ?? args.row;
290
+ const stateBase64 = stateValueToBase64(source[rule.stateColumn]);
291
+ if (!stateBase64)
292
+ continue;
293
+ const doc = createDocFromState(stateBase64);
294
+ try {
295
+ const nextValue = materializeRuleValue(doc, rule);
296
+ if (source[rule.field] !== nextValue) {
297
+ ensureRow()[rule.field] = nextValue;
298
+ }
299
+ if (args.rowId) {
300
+ touchLruState(args.stateByRowField, rowFieldCacheKey(args.table, args.rowId, rule.field), stateBase64, args.maxTrackedRows);
301
+ }
302
+ }
303
+ finally {
304
+ doc.destroy();
305
+ }
306
+ }
307
+ if (args.stripEnvelope) {
308
+ const source = nextRow ?? args.row;
309
+ if (args.envelopeKey in source) {
310
+ const target = ensureRow();
311
+ delete target[args.envelopeKey];
312
+ }
313
+ }
314
+ return nextRow ?? args.row;
315
+ }
316
+ function transformPushPayload(args) {
317
+ const tableRules = args.index.get(args.table);
318
+ const rawEnvelope = args.payload[args.envelopeKey];
319
+ if (!tableRules) {
320
+ if (rawEnvelope !== undefined && args.strict) {
321
+ throw new Error(`Yjs envelope provided for table "${args.table}" without matching rules`);
322
+ }
323
+ if (args.stripEnvelope && rawEnvelope !== undefined) {
324
+ const next = { ...args.payload };
325
+ delete next[args.envelopeKey];
326
+ return next;
327
+ }
328
+ return args.payload;
329
+ }
330
+ let nextPayload = null;
331
+ const ensurePayload = () => {
332
+ if (nextPayload)
333
+ return nextPayload;
334
+ nextPayload = { ...args.payload };
335
+ return nextPayload;
336
+ };
337
+ const sourceEnvelope = rawEnvelope;
338
+ if (sourceEnvelope !== undefined && !isRecord(sourceEnvelope)) {
339
+ throw new Error(`Yjs payload key "${args.envelopeKey}" must be an object for table "${args.table}"`);
340
+ }
341
+ for (const rule of tableRules.values()) {
342
+ const source = nextPayload ?? args.payload;
343
+ const stateBase64 = stateValueToBase64(source[rule.stateColumn]);
344
+ if (stateBase64) {
345
+ touchLruState(args.stateByRowField, rowFieldCacheKey(args.table, args.rowId, rule.field), stateBase64, args.maxTrackedRows);
346
+ }
347
+ }
348
+ if (sourceEnvelope) {
349
+ for (const [field, rawUpdateInput] of Object.entries(sourceEnvelope)) {
350
+ const rule = tableRules.get(field);
351
+ if (!rule) {
352
+ if (args.strict) {
353
+ throw new Error(`No Yjs rule found for envelope field "${field}" on table "${args.table}"`);
354
+ }
355
+ continue;
356
+ }
357
+ const updates = normalizeUpdateEnvelopes(rawUpdateInput, `yjs.${args.table}.${field}`);
358
+ const cacheKey = rowFieldCacheKey(args.table, args.rowId, rule.field);
359
+ const source = nextPayload ?? args.payload;
360
+ const baseState = stateValueToBase64(source[rule.stateColumn]) ??
361
+ args.stateByRowField.get(cacheKey) ??
362
+ null;
363
+ const doc = createDocFromState(baseState);
364
+ try {
365
+ if (!baseState) {
366
+ seedRuleValueFromPayload(doc, rule, source);
367
+ }
368
+ for (const update of updates) {
369
+ Y.applyUpdate(doc, base64ToBytes(update.updateBase64));
370
+ }
371
+ const nextValue = materializeRuleValue(doc, rule);
372
+ const nextStateBase64 = exportSnapshotBase64(doc);
373
+ const target = ensurePayload();
374
+ target[rule.field] = nextValue;
375
+ target[rule.stateColumn] = nextStateBase64;
376
+ touchLruState(args.stateByRowField, cacheKey, nextStateBase64, args.maxTrackedRows);
377
+ }
378
+ finally {
379
+ doc.destroy();
380
+ }
381
+ }
382
+ }
383
+ if (args.stripEnvelope) {
384
+ const source = nextPayload ?? args.payload;
385
+ if (args.envelopeKey in source) {
386
+ const target = ensurePayload();
387
+ delete target[args.envelopeKey];
388
+ }
389
+ }
390
+ return nextPayload ?? args.payload;
391
+ }
392
+ function transformPullSubscription(args) {
393
+ const nextSnapshots = (args.sub.snapshots ?? []).map((snapshot) => ({
394
+ ...snapshot,
395
+ rows: (snapshot.rows ?? []).map((row) => {
396
+ if (!isRecord(row))
397
+ return row;
398
+ const tableRules = args.index.get(snapshot.table);
399
+ if (!tableRules) {
400
+ return materializeRowFromState({
401
+ table: snapshot.table,
402
+ rowId: null,
403
+ row,
404
+ index: args.index,
405
+ stateByRowField: args.stateByRowField,
406
+ maxTrackedRows: args.maxTrackedRows,
407
+ envelopeKey: args.envelopeKey,
408
+ stripEnvelope: args.stripEnvelope,
409
+ });
410
+ }
411
+ let rowId = null;
412
+ for (const rule of tableRules.values()) {
413
+ rowId = resolveSnapshotRowId(row, rule);
414
+ if (rowId)
415
+ break;
416
+ }
417
+ return materializeRowFromState({
418
+ table: snapshot.table,
419
+ rowId,
420
+ row,
421
+ index: args.index,
422
+ stateByRowField: args.stateByRowField,
423
+ maxTrackedRows: args.maxTrackedRows,
424
+ envelopeKey: args.envelopeKey,
425
+ stripEnvelope: args.stripEnvelope,
426
+ });
427
+ }),
428
+ }));
429
+ const nextCommits = (args.sub.commits ?? []).map((commit) => ({
430
+ ...commit,
431
+ changes: (commit.changes ?? []).map((change) => {
432
+ if (change.op === 'delete') {
433
+ clearRowStateCache({
434
+ table: change.table,
435
+ rowId: change.row_id,
436
+ index: args.index,
437
+ stateByRowField: args.stateByRowField,
438
+ });
439
+ return change;
440
+ }
441
+ if (change.op !== 'upsert' || !isRecord(change.row_json))
442
+ return change;
443
+ const nextRow = materializeRowFromState({
444
+ table: change.table,
445
+ rowId: change.row_id,
446
+ row: change.row_json,
447
+ index: args.index,
448
+ stateByRowField: args.stateByRowField,
449
+ maxTrackedRows: args.maxTrackedRows,
450
+ envelopeKey: args.envelopeKey,
451
+ stripEnvelope: args.stripEnvelope,
452
+ });
453
+ if (nextRow === change.row_json)
454
+ return change;
455
+ return { ...change, row_json: nextRow };
456
+ }),
457
+ }));
458
+ return {
459
+ ...args.sub,
460
+ snapshots: nextSnapshots,
461
+ commits: nextCommits,
462
+ };
463
+ }
464
+ function transformWsChanges(args) {
465
+ return args.changes.map((change) => {
466
+ if (change.op === 'delete') {
467
+ clearRowStateCache({
468
+ table: change.table,
469
+ rowId: change.row_id,
470
+ index: args.index,
471
+ stateByRowField: args.stateByRowField,
472
+ });
473
+ return change;
474
+ }
475
+ if (change.op !== 'upsert' || !isRecord(change.row_json))
476
+ return change;
477
+ const nextRow = materializeRowFromState({
478
+ table: change.table,
479
+ rowId: change.row_id,
480
+ row: change.row_json,
481
+ index: args.index,
482
+ stateByRowField: args.stateByRowField,
483
+ maxTrackedRows: args.maxTrackedRows,
484
+ envelopeKey: args.envelopeKey,
485
+ stripEnvelope: args.stripEnvelope,
486
+ });
487
+ if (nextRow === change.row_json)
488
+ return change;
489
+ return { ...change, row_json: nextRow };
490
+ });
491
+ }
492
+ export function createYjsClientPlugin(options) {
493
+ if (options.rules.length === 0) {
494
+ throw new Error('createYjsClientPlugin requires at least one table/field rule');
495
+ }
496
+ const envelopeKey = options.envelopeKey ?? YJS_PAYLOAD_KEY;
497
+ const strict = options.strict ?? true;
498
+ const stripEnvelope = options.stripEnvelope ?? true;
499
+ const stripEnvelopeBeforePush = options.stripEnvelopeBeforePush ?? stripEnvelope;
500
+ const stripEnvelopeBeforeApplyLocalMutations = options.stripEnvelopeBeforeApplyLocalMutations ?? stripEnvelope;
501
+ const maxTrackedRows = Math.max(1, Math.min(1_000_000, options.maxTrackedRows ?? 10_000));
502
+ const { index } = buildRuleIndex(options.rules);
503
+ const stateByRowField = new Map();
504
+ return {
505
+ name: options.name ?? 'crdt-yjs-client',
506
+ priority: options.priority ?? PluginPriority.DEFAULT,
507
+ beforePush(_ctx, request) {
508
+ const nextOperations = request.operations.map((op) => {
509
+ if (op.op !== 'upsert')
510
+ return op;
511
+ if (!isRecord(op.payload))
512
+ return op;
513
+ const nextPayload = transformPushPayload({
514
+ table: op.table,
515
+ rowId: op.row_id,
516
+ payload: op.payload,
517
+ index,
518
+ stateByRowField,
519
+ maxTrackedRows,
520
+ envelopeKey,
521
+ stripEnvelope: stripEnvelopeBeforePush,
522
+ strict,
523
+ });
524
+ if (nextPayload === op.payload)
525
+ return op;
526
+ return { ...op, payload: nextPayload };
527
+ });
528
+ return { ...request, operations: nextOperations };
529
+ },
530
+ beforeApplyLocalMutations(_ctx, args) {
531
+ const nextOperations = args.operations.map((op) => {
532
+ if (op.op === 'delete') {
533
+ clearRowStateCache({
534
+ table: op.table,
535
+ rowId: op.row_id,
536
+ index,
537
+ stateByRowField,
538
+ });
539
+ return op;
540
+ }
541
+ if (op.op !== 'upsert')
542
+ return op;
543
+ if (!isRecord(op.payload))
544
+ return op;
545
+ const nextPayload = transformPushPayload({
546
+ table: op.table,
547
+ rowId: op.row_id,
548
+ payload: op.payload,
549
+ index,
550
+ stateByRowField,
551
+ maxTrackedRows,
552
+ envelopeKey,
553
+ stripEnvelope: stripEnvelopeBeforeApplyLocalMutations,
554
+ strict,
555
+ });
556
+ if (nextPayload === op.payload)
557
+ return op;
558
+ return { ...op, payload: nextPayload };
559
+ });
560
+ return { ...args, operations: nextOperations };
561
+ },
562
+ afterPull(_ctx, args) {
563
+ const nextSubscriptions = args.response.subscriptions.map((sub) => transformPullSubscription({
564
+ sub,
565
+ index,
566
+ stateByRowField,
567
+ maxTrackedRows,
568
+ envelopeKey,
569
+ stripEnvelope,
570
+ }));
571
+ return {
572
+ ...args.response,
573
+ subscriptions: nextSubscriptions,
574
+ };
575
+ },
576
+ beforeApplyWsChanges(_ctx, args) {
577
+ const nextChanges = transformWsChanges({
578
+ changes: args.changes,
579
+ index,
580
+ stateByRowField,
581
+ maxTrackedRows,
582
+ envelopeKey,
583
+ stripEnvelope,
584
+ });
585
+ return {
586
+ ...args,
587
+ changes: nextChanges,
588
+ };
589
+ },
590
+ };
591
+ }
592
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/client-plugin-yjs",
3
- "version": "0.0.0",
3
+ "version": "0.0.6-159",
4
4
  "description": "Yjs CRDT plugin primitives for Syncular client integration",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -44,7 +44,7 @@
44
44
  "release": "bunx syncular-publish"
45
45
  },
46
46
  "dependencies": {
47
- "@syncular/client": "0.0.0",
47
+ "@syncular/client": "0.0.6-159",
48
48
  "yjs": "^13.6.29"
49
49
  },
50
50
  "devDependencies": {
package/src/index.test.ts CHANGED
@@ -317,6 +317,189 @@ describe('@syncular/client-plugin-crdt-yjs', () => {
317
317
  expect(typeof payload.content_yjs_state).toBe('string');
318
318
  });
319
319
 
320
+ it('invalidates cached row state after pull commit delete before row-id reuse', async () => {
321
+ const plugin = createYjsClientPlugin({
322
+ rules: [
323
+ {
324
+ table: 'tasks',
325
+ field: 'content',
326
+ stateColumn: 'content_yjs_state',
327
+ },
328
+ ],
329
+ });
330
+
331
+ const old = createUpdate('old');
332
+ await callAfterPull(plugin, {
333
+ ok: true,
334
+ subscriptions: [
335
+ {
336
+ id: 'tasks',
337
+ status: 'active',
338
+ scopes: {},
339
+ bootstrap: true,
340
+ nextCursor: 1,
341
+ bootstrapState: null,
342
+ commits: [],
343
+ snapshots: [
344
+ {
345
+ table: 'tasks',
346
+ rows: [
347
+ {
348
+ id: 'task-1',
349
+ content: 'stale',
350
+ content_yjs_state: old.state,
351
+ },
352
+ ],
353
+ isFirstPage: true,
354
+ isLastPage: true,
355
+ },
356
+ ],
357
+ },
358
+ ],
359
+ });
360
+
361
+ await callAfterPull(plugin, {
362
+ ok: true,
363
+ subscriptions: [
364
+ {
365
+ id: 'tasks',
366
+ status: 'active',
367
+ scopes: {},
368
+ bootstrap: false,
369
+ nextCursor: 2,
370
+ bootstrapState: null,
371
+ snapshots: [],
372
+ commits: [
373
+ {
374
+ commitSeq: 2,
375
+ createdAt: '2026-03-01T00:00:00.000Z',
376
+ actorId: 'actor-2',
377
+ changes: [
378
+ {
379
+ table: 'tasks',
380
+ row_id: 'task-1',
381
+ op: 'delete',
382
+ row_json: null,
383
+ row_version: null,
384
+ scopes: {},
385
+ },
386
+ ],
387
+ },
388
+ ],
389
+ },
390
+ ],
391
+ });
392
+
393
+ const fresh = createUpdate('new');
394
+ const pushed = await callBeforePush(plugin, {
395
+ clientId: 'client-1',
396
+ clientCommitId: 'commit-reuse-after-delete',
397
+ schemaVersion: 1,
398
+ operations: [
399
+ {
400
+ table: 'tasks',
401
+ row_id: 'task-1',
402
+ op: 'upsert',
403
+ payload: {
404
+ [YJS_PAYLOAD_KEY]: {
405
+ content: fresh.update,
406
+ },
407
+ },
408
+ base_version: null,
409
+ },
410
+ ],
411
+ });
412
+
413
+ const payload = pushed.operations[0]?.payload as
414
+ | Record<string, unknown>
415
+ | undefined;
416
+ if (!payload) {
417
+ throw new Error('Expected transformed push payload');
418
+ }
419
+
420
+ expect(payload.content).toBe('new');
421
+ expect(String(payload.content)).not.toContain('old');
422
+ });
423
+
424
+ it('evicts least-recently-used cached row state when maxTrackedRows is reached', async () => {
425
+ const plugin = createYjsClientPlugin({
426
+ rules: [
427
+ {
428
+ table: 'tasks',
429
+ field: 'content',
430
+ stateColumn: 'content_yjs_state',
431
+ },
432
+ ],
433
+ maxTrackedRows: 1,
434
+ });
435
+
436
+ const rowOne = createUpdate('old');
437
+ const rowTwo = createUpdate('other');
438
+
439
+ await callAfterPull(plugin, {
440
+ ok: true,
441
+ subscriptions: [
442
+ {
443
+ id: 'tasks',
444
+ status: 'active',
445
+ scopes: {},
446
+ bootstrap: true,
447
+ nextCursor: 1,
448
+ bootstrapState: null,
449
+ commits: [],
450
+ snapshots: [
451
+ {
452
+ table: 'tasks',
453
+ rows: [
454
+ {
455
+ id: 'task-1',
456
+ content: 'stale',
457
+ content_yjs_state: rowOne.state,
458
+ },
459
+ {
460
+ id: 'task-2',
461
+ content: 'stale',
462
+ content_yjs_state: rowTwo.state,
463
+ },
464
+ ],
465
+ isFirstPage: true,
466
+ isLastPage: true,
467
+ },
468
+ ],
469
+ },
470
+ ],
471
+ });
472
+
473
+ const fullFromEmpty = createUpdate('new');
474
+ const pushed = await callBeforePush(plugin, {
475
+ clientId: 'client-1',
476
+ clientCommitId: 'commit-eviction',
477
+ schemaVersion: 1,
478
+ operations: [
479
+ {
480
+ table: 'tasks',
481
+ row_id: 'task-1',
482
+ op: 'upsert',
483
+ payload: {
484
+ [YJS_PAYLOAD_KEY]: {
485
+ content: fullFromEmpty.update,
486
+ },
487
+ },
488
+ base_version: null,
489
+ },
490
+ ],
491
+ });
492
+
493
+ const payload = pushed.operations[0]?.payload as
494
+ | Record<string, unknown>
495
+ | undefined;
496
+ if (!payload) {
497
+ throw new Error('Expected transformed push payload');
498
+ }
499
+
500
+ expect(payload.content).toBe('new');
501
+ });
502
+
320
503
  it('materializes websocket inline changes through beforeApplyWsChanges', async () => {
321
504
  const plugin = createYjsClientPlugin({
322
505
  rules: [
package/src/index.ts CHANGED
@@ -87,6 +87,12 @@ export interface CreateYjsClientPluginOptions {
87
87
  rules: readonly YjsClientFieldRule[];
88
88
  envelopeKey?: string;
89
89
  priority?: number;
90
+ /**
91
+ * Maximum cached row-field Yjs states to keep in memory.
92
+ * Prevents unbounded growth in long-lived sessions.
93
+ * @default 10_000
94
+ */
95
+ maxTrackedRows?: number;
90
96
  /**
91
97
  * Throw when envelope payload references fields without matching rules.
92
98
  * @default true
@@ -323,6 +329,36 @@ function rowFieldCacheKey(table: string, rowId: string, field: string): string {
323
329
  return `${table}\u001f${rowId}\u001f${field}`;
324
330
  }
325
331
 
332
+ function touchLruState(
333
+ map: Map<string, string>,
334
+ key: string,
335
+ value: string,
336
+ maxTrackedRows: number
337
+ ): void {
338
+ map.delete(key);
339
+ map.set(key, value);
340
+ if (map.size <= maxTrackedRows) return;
341
+ const oldest = map.keys().next().value as string | undefined;
342
+ if (oldest) {
343
+ map.delete(oldest);
344
+ }
345
+ }
346
+
347
+ function clearRowStateCache(args: {
348
+ table: string;
349
+ rowId: string;
350
+ index: RuleIndex;
351
+ stateByRowField: Map<string, string>;
352
+ }): void {
353
+ const tableRules = args.index.get(args.table);
354
+ if (!tableRules) return;
355
+ for (const rule of tableRules.values()) {
356
+ args.stateByRowField.delete(
357
+ rowFieldCacheKey(args.table, args.rowId, rule.field)
358
+ );
359
+ }
360
+ }
361
+
326
362
  function resolveSnapshotRowId(
327
363
  row: Record<string, unknown>,
328
364
  rule: ResolvedYjsClientFieldRule
@@ -423,6 +459,7 @@ function materializeRowFromState(args: {
423
459
  row: Record<string, unknown>;
424
460
  index: RuleIndex;
425
461
  stateByRowField: Map<string, string>;
462
+ maxTrackedRows: number;
426
463
  envelopeKey: string;
427
464
  stripEnvelope: boolean;
428
465
  }): Record<string, unknown> {
@@ -455,9 +492,11 @@ function materializeRowFromState(args: {
455
492
  ensureRow()[rule.field] = nextValue;
456
493
  }
457
494
  if (args.rowId) {
458
- args.stateByRowField.set(
495
+ touchLruState(
496
+ args.stateByRowField,
459
497
  rowFieldCacheKey(args.table, args.rowId, rule.field),
460
- stateBase64
498
+ stateBase64,
499
+ args.maxTrackedRows
461
500
  );
462
501
  }
463
502
  } finally {
@@ -482,6 +521,7 @@ function transformPushPayload(args: {
482
521
  payload: Record<string, unknown>;
483
522
  index: RuleIndex;
484
523
  stateByRowField: Map<string, string>;
524
+ maxTrackedRows: number;
485
525
  envelopeKey: string;
486
526
  stripEnvelope: boolean;
487
527
  strict: boolean;
@@ -521,9 +561,11 @@ function transformPushPayload(args: {
521
561
  const source = nextPayload ?? args.payload;
522
562
  const stateBase64 = stateValueToBase64(source[rule.stateColumn]);
523
563
  if (stateBase64) {
524
- args.stateByRowField.set(
564
+ touchLruState(
565
+ args.stateByRowField,
525
566
  rowFieldCacheKey(args.table, args.rowId, rule.field),
526
- stateBase64
567
+ stateBase64,
568
+ args.maxTrackedRows
527
569
  );
528
570
  }
529
571
  }
@@ -566,7 +608,12 @@ function transformPushPayload(args: {
566
608
  const target = ensurePayload();
567
609
  target[rule.field] = nextValue;
568
610
  target[rule.stateColumn] = nextStateBase64;
569
- args.stateByRowField.set(cacheKey, nextStateBase64);
611
+ touchLruState(
612
+ args.stateByRowField,
613
+ cacheKey,
614
+ nextStateBase64,
615
+ args.maxTrackedRows
616
+ );
570
617
  } finally {
571
618
  doc.destroy();
572
619
  }
@@ -588,6 +635,7 @@ function transformPullSubscription(args: {
588
635
  sub: SyncPullSubscriptionResponse;
589
636
  index: RuleIndex;
590
637
  stateByRowField: Map<string, string>;
638
+ maxTrackedRows: number;
591
639
  envelopeKey: string;
592
640
  stripEnvelope: boolean;
593
641
  }): SyncPullSubscriptionResponse {
@@ -603,6 +651,7 @@ function transformPullSubscription(args: {
603
651
  row,
604
652
  index: args.index,
605
653
  stateByRowField: args.stateByRowField,
654
+ maxTrackedRows: args.maxTrackedRows,
606
655
  envelopeKey: args.envelopeKey,
607
656
  stripEnvelope: args.stripEnvelope,
608
657
  });
@@ -620,6 +669,7 @@ function transformPullSubscription(args: {
620
669
  row,
621
670
  index: args.index,
622
671
  stateByRowField: args.stateByRowField,
672
+ maxTrackedRows: args.maxTrackedRows,
623
673
  envelopeKey: args.envelopeKey,
624
674
  stripEnvelope: args.stripEnvelope,
625
675
  });
@@ -629,6 +679,15 @@ function transformPullSubscription(args: {
629
679
  const nextCommits = (args.sub.commits ?? []).map((commit) => ({
630
680
  ...commit,
631
681
  changes: (commit.changes ?? []).map((change) => {
682
+ if (change.op === 'delete') {
683
+ clearRowStateCache({
684
+ table: change.table,
685
+ rowId: change.row_id,
686
+ index: args.index,
687
+ stateByRowField: args.stateByRowField,
688
+ });
689
+ return change;
690
+ }
632
691
  if (change.op !== 'upsert' || !isRecord(change.row_json)) return change;
633
692
  const nextRow = materializeRowFromState({
634
693
  table: change.table,
@@ -636,6 +695,7 @@ function transformPullSubscription(args: {
636
695
  row: change.row_json,
637
696
  index: args.index,
638
697
  stateByRowField: args.stateByRowField,
698
+ maxTrackedRows: args.maxTrackedRows,
639
699
  envelopeKey: args.envelopeKey,
640
700
  stripEnvelope: args.stripEnvelope,
641
701
  });
@@ -655,10 +715,20 @@ function transformWsChanges(args: {
655
715
  changes: SyncChange[];
656
716
  index: RuleIndex;
657
717
  stateByRowField: Map<string, string>;
718
+ maxTrackedRows: number;
658
719
  envelopeKey: string;
659
720
  stripEnvelope: boolean;
660
721
  }): SyncChange[] {
661
722
  return args.changes.map((change) => {
723
+ if (change.op === 'delete') {
724
+ clearRowStateCache({
725
+ table: change.table,
726
+ rowId: change.row_id,
727
+ index: args.index,
728
+ stateByRowField: args.stateByRowField,
729
+ });
730
+ return change;
731
+ }
662
732
  if (change.op !== 'upsert' || !isRecord(change.row_json)) return change;
663
733
  const nextRow = materializeRowFromState({
664
734
  table: change.table,
@@ -666,6 +736,7 @@ function transformWsChanges(args: {
666
736
  row: change.row_json,
667
737
  index: args.index,
668
738
  stateByRowField: args.stateByRowField,
739
+ maxTrackedRows: args.maxTrackedRows,
669
740
  envelopeKey: args.envelopeKey,
670
741
  stripEnvelope: args.stripEnvelope,
671
742
  });
@@ -690,6 +761,10 @@ export function createYjsClientPlugin(
690
761
  options.stripEnvelopeBeforePush ?? stripEnvelope;
691
762
  const stripEnvelopeBeforeApplyLocalMutations =
692
763
  options.stripEnvelopeBeforeApplyLocalMutations ?? stripEnvelope;
764
+ const maxTrackedRows = Math.max(
765
+ 1,
766
+ Math.min(1_000_000, options.maxTrackedRows ?? 10_000)
767
+ );
693
768
  const { index } = buildRuleIndex(options.rules);
694
769
  const stateByRowField = new Map<string, string>();
695
770
 
@@ -708,6 +783,7 @@ export function createYjsClientPlugin(
708
783
  payload: op.payload,
709
784
  index,
710
785
  stateByRowField,
786
+ maxTrackedRows,
711
787
  envelopeKey,
712
788
  stripEnvelope: stripEnvelopeBeforePush,
713
789
  strict,
@@ -725,6 +801,15 @@ export function createYjsClientPlugin(
725
801
  args: SyncClientLocalMutationArgs
726
802
  ): SyncClientLocalMutationArgs {
727
803
  const nextOperations = args.operations.map((op) => {
804
+ if (op.op === 'delete') {
805
+ clearRowStateCache({
806
+ table: op.table,
807
+ rowId: op.row_id,
808
+ index,
809
+ stateByRowField,
810
+ });
811
+ return op;
812
+ }
728
813
  if (op.op !== 'upsert') return op;
729
814
  if (!isRecord(op.payload)) return op;
730
815
 
@@ -734,6 +819,7 @@ export function createYjsClientPlugin(
734
819
  payload: op.payload,
735
820
  index,
736
821
  stateByRowField,
822
+ maxTrackedRows,
737
823
  envelopeKey,
738
824
  stripEnvelope: stripEnvelopeBeforeApplyLocalMutations,
739
825
  strict,
@@ -752,6 +838,7 @@ export function createYjsClientPlugin(
752
838
  sub,
753
839
  index,
754
840
  stateByRowField,
841
+ maxTrackedRows,
755
842
  envelopeKey,
756
843
  stripEnvelope,
757
844
  })
@@ -771,6 +858,7 @@ export function createYjsClientPlugin(
771
858
  changes: args.changes,
772
859
  index,
773
860
  stateByRowField,
861
+ maxTrackedRows,
774
862
  envelopeKey,
775
863
  stripEnvelope,
776
864
  });