@haklex/rich-collab-loro 0.62.1 → 0.63.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.
package/dist/binding.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { LexicalEditor, NodeKey } from 'lexical';
2
2
  import { LoroDoc, TreeID } from 'loro-crdt';
3
3
  export declare const COLLAB_TAG = "collab";
4
+ export declare const AGENT_WRITE_TAG = "agent-write";
4
5
  export declare const TREE_NAME = "lexical";
5
6
  export interface LoroBinding {
6
7
  dispose: () => void;
@@ -9,6 +10,7 @@ export interface LoroBinding {
9
10
  keyOf: (id: TreeID) => NodeKey | undefined;
10
11
  }
11
12
  export interface LoroBindingOptions {
13
+ agentLiveWindowSeconds?: number;
12
14
  commitMessage?: string;
13
15
  }
14
16
  export declare function createLoroBinding(editor: LexicalEditor, doc: LoroDoc, options?: LoroBindingOptions): LoroBinding;
@@ -1 +1 @@
1
- {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,aAAa,EAElB,KAAK,OAAO,EAGb,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,KAAK,OAAO,EAA+B,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAMnF,eAAO,MAAM,UAAU,WAAW,CAAC;AACnC,eAAO,MAAM,SAAS,YAAY,CAAC;AAGnC,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IAC3C,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACpC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,SAAS,CAAC;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAmBD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,OAAO,EACZ,OAAO,GAAE,kBAAuB,GAC/B,WAAW,CA4Nb"}
1
+ {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,aAAa,EAElB,KAAK,OAAO,EAGb,MAAM,SAAS,CAAC;AACjB,OAAO,EAEL,KAAK,OAAO,EAGZ,KAAK,MAAM,EACZ,MAAM,WAAW,CAAC;AAMnB,eAAO,MAAM,UAAU,WAAW,CAAC;AACnC,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAG7C,eAAO,MAAM,SAAS,YAAY,CAAC;AAGnC,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IAC3C,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACpC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,SAAS,CAAC;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAmBD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,OAAO,EACZ,OAAO,GAAE,kBAAuB,GAC/B,WAAW,CAiOb"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { COLLAB_TAG, createLoroBinding, type LoroBinding, type LoroBindingOptions, TREE_NAME, } from './binding';
1
+ export { AGENT_WRITE_TAG, COLLAB_TAG, createLoroBinding, type LoroBinding, type LoroBindingOptions, TREE_NAME, } from './binding';
2
2
  export { $reconcileRoot, editAtVersion } from './reconcile';
3
3
  export { type Frame, type FrameFocus, planFrames, streamAtVersion, type StreamStep, } from './stream';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,SAAS,GACV,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EACL,KAAK,KAAK,EACV,KAAK,UAAU,EACf,UAAU,EACV,eAAe,EACf,KAAK,UAAU,GAChB,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,SAAS,GACV,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EACL,KAAK,KAAK,EACV,KAAK,UAAU,EACf,UAAU,EACV,eAAe,EACf,KAAK,UAAU,GAChB,MAAM,UAAU,CAAC"}
package/dist/index.mjs CHANGED
@@ -140,6 +140,9 @@ function editAtVersion(doc, frontiers, createEditor, target, commitMessage) {
140
140
  //#endregion
141
141
  //#region src/binding.ts
142
142
  var COLLAB_TAG = "collab";
143
+ var AGENT_WRITE_TAG = "agent-write";
144
+ var AGENT_MESSAGE_PREFIX = "agent:";
145
+ var AGENT_LIVE_WINDOW_SECONDS = 15;
143
146
  var TREE_NAME = "lexical";
144
147
  var TEXT_KEY = "#text";
145
148
  function sameValue(a, b) {
@@ -306,7 +309,7 @@ function createLoroBinding(editor, doc, options = {}) {
306
309
  if ($isElementNode(child)) $collectDescendants(child, out);
307
310
  }
308
311
  };
309
- const pullFromDoc = () => {
312
+ const pullFromDoc = (agent = false) => {
310
313
  editor.update(() => {
311
314
  const rootTreeNode = tree.roots()[0];
312
315
  if (!rootTreeNode) return;
@@ -321,7 +324,7 @@ function createLoroBinding(editor, doc, options = {}) {
321
324
  for (const node of all) if (!visited.has(node.getKey()) && node.isAttached()) node.remove();
322
325
  }, {
323
326
  discrete: true,
324
- tag: COLLAB_TAG
327
+ tag: agent ? [COLLAB_TAG, AGENT_WRITE_TAG] : COLLAB_TAG
325
328
  });
326
329
  };
327
330
  if (tree.roots().length > 0) pullFromDoc();
@@ -334,14 +337,28 @@ function createLoroBinding(editor, doc, options = {}) {
334
337
  idOf: (key) => keyToId.get(key),
335
338
  keyOf: (id) => idToKey.get(id),
336
339
  import: (bytes) => {
337
- doc.import(bytes);
338
- pullFromDoc();
340
+ pullFromDoc(hasLiveAgentChange(doc, doc.import(bytes), options.agentLiveWindowSeconds ?? AGENT_LIVE_WINDOW_SECONDS));
339
341
  }
340
342
  };
341
343
  }
342
344
  function $isTextLike(props) {
343
345
  return typeof props.format === "number" && typeof props.detail === "number" && "mode" in props;
344
346
  }
347
+ function hasLiveAgentChange(doc, status, windowSeconds) {
348
+ const oldest = Date.now() / 1e3 - windowSeconds;
349
+ for (const [peer, span] of status.success) {
350
+ let counter = span.start;
351
+ while (counter < span.end) {
352
+ const change = doc.getChangeAt({
353
+ peer,
354
+ counter
355
+ });
356
+ if (change.message?.startsWith(AGENT_MESSAGE_PREFIX) && change.timestamp >= oldest) return true;
357
+ counter = change.counter + change.length;
358
+ }
359
+ }
360
+ return false;
361
+ }
345
362
  //#endregion
346
363
  //#region src/stream.ts
347
364
  var TYPING_FRAME_BUDGET = 120;
@@ -390,6 +407,7 @@ function pairGap(dels, adds) {
390
407
  };
391
408
  });
392
409
  }
410
+ /** @deprecated Agent writes are now played client-side by `@haklex/rich-plugin-agent-stream`; write once with `editAtVersion`. */
393
411
  function planFrames(base, target) {
394
412
  const before = base.children ?? [];
395
413
  const after = target.children ?? [];
@@ -482,6 +500,7 @@ function $locate(focus) {
482
500
  offset: 0
483
501
  };
484
502
  }
503
+ /** @deprecated Agent writes are now played client-side by `@haklex/rich-plugin-agent-stream`; write once with `editAtVersion`. */
485
504
  function streamAtVersion(doc, frontiers, createEditor, base, target, commitMessage) {
486
505
  const fork = doc.forkAt(frontiers);
487
506
  const editor = createEditor();
@@ -512,4 +531,4 @@ function streamAtVersion(doc, frontiers, createEditor, base, target, commitMessa
512
531
  };
513
532
  }
514
533
  //#endregion
515
- export { $reconcileRoot, COLLAB_TAG, TREE_NAME, createLoroBinding, editAtVersion, planFrames, streamAtVersion };
534
+ export { $reconcileRoot, AGENT_WRITE_TAG, COLLAB_TAG, TREE_NAME, createLoroBinding, editAtVersion, planFrames, streamAtVersion };
package/dist/stream.d.ts CHANGED
@@ -16,7 +16,9 @@ export interface StreamStep {
16
16
  } | null;
17
17
  update: Uint8Array;
18
18
  }
19
+ /** @deprecated Agent writes are now played client-side by `@haklex/rich-plugin-agent-stream`; write once with `editAtVersion`. */
19
20
  export declare function planFrames(base: Json, target: Json): Frame[];
21
+ /** @deprecated Agent writes are now played client-side by `@haklex/rich-plugin-agent-stream`; write once with `editAtVersion`. */
20
22
  export declare function streamAtVersion(doc: LoroDoc, frontiers: Frontiers, createEditor: () => LexicalEditor, base: SerializedEditorState, target: SerializedEditorState, commitMessage?: string): {
21
23
  frontiers: Frontiers;
22
24
  steps: StreamStep[];
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,aAAa,EAElB,KAAK,qBAAqB,EAC3B,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGjD,OAAO,EAAkB,KAAK,SAAS,EAAE,KAAK,IAAI,EAAiB,MAAM,aAAa,CAAC;AAGvF,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9C,MAAM,EAAE,UAAU,CAAC;CACpB;AA6DD,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,KAAK,EAAE,CA6E5D;AAoBD,wBAAgB,eAAe,CAC7B,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,MAAM,aAAa,EACjC,IAAI,EAAE,qBAAqB,EAC3B,MAAM,EAAE,qBAAqB,EAC7B,aAAa,CAAC,EAAE,MAAM,GACrB;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,CAgB/C"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,aAAa,EAElB,KAAK,qBAAqB,EAC3B,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGjD,OAAO,EAAkB,KAAK,SAAS,EAAE,KAAK,IAAI,EAAiB,MAAM,aAAa,CAAC;AAGvF,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9C,MAAM,EAAE,UAAU,CAAC;CACpB;AA6DD,kIAAkI;AAClI,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,KAAK,EAAE,CA6E5D;AAoBD,kIAAkI;AAClI,wBAAgB,eAAe,CAC7B,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,MAAM,aAAa,EACjC,IAAI,EAAE,qBAAqB,EAC3B,MAAM,EAAE,qBAAqB,EAC7B,aAAa,CAAC,EAAE,MAAM,GACrB;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,CAgB/C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haklex/rich-collab-loro",
3
- "version": "0.62.1",
3
+ "version": "0.63.0",
4
4
  "description": "Loro CRDT binding for Lexical — bidirectional LoroTree sync for real-time collaboration and history",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,8 +21,8 @@
21
21
  "src"
22
22
  ],
23
23
  "devDependencies": {
24
- "@haklex/rich-headless": "0.62.1",
25
- "@haklex/rich-litexml": "0.62.1",
24
+ "@haklex/rich-headless": "0.63.0",
25
+ "@haklex/rich-litexml": "0.63.0",
26
26
  "@lexical/headless": "^0.53.0",
27
27
  "lexical": "^0.53.0",
28
28
  "loro-crdt": "^1.16.4",
package/src/binding.ts CHANGED
@@ -13,13 +13,22 @@ import {
13
13
  type SerializedLexicalNode,
14
14
  type TextNode,
15
15
  } from 'lexical';
16
- import { type LoroDoc, LoroText, type LoroTreeNode, type TreeID } from 'loro-crdt';
16
+ import {
17
+ type ImportStatus,
18
+ type LoroDoc,
19
+ LoroText,
20
+ type LoroTreeNode,
21
+ type TreeID,
22
+ } from 'loro-crdt';
17
23
 
18
24
  import { transformOffset } from './offset';
19
25
  import { $updateInPlace } from './reconcile';
20
26
  import { stable } from './stable';
21
27
 
22
28
  export const COLLAB_TAG = 'collab';
29
+ export const AGENT_WRITE_TAG = 'agent-write';
30
+ const AGENT_MESSAGE_PREFIX = 'agent:';
31
+ const AGENT_LIVE_WINDOW_SECONDS = 15;
23
32
  export const TREE_NAME = 'lexical';
24
33
  const TEXT_KEY = '#text';
25
34
 
@@ -31,6 +40,7 @@ export interface LoroBinding {
31
40
  }
32
41
 
33
42
  export interface LoroBindingOptions {
43
+ agentLiveWindowSeconds?: number;
34
44
  commitMessage?: string;
35
45
  }
36
46
 
@@ -235,7 +245,7 @@ export function createLoroBinding(
235
245
  }
236
246
  };
237
247
 
238
- const pullFromDoc = () => {
248
+ const pullFromDoc = (agent = false) => {
239
249
  editor.update(
240
250
  () => {
241
251
  const rootTreeNode = tree.roots()[0];
@@ -254,7 +264,7 @@ export function createLoroBinding(
254
264
  if (!visited.has(node.getKey()) && node.isAttached()) node.remove();
255
265
  }
256
266
  },
257
- { discrete: true, tag: COLLAB_TAG },
267
+ { discrete: true, tag: agent ? [COLLAB_TAG, AGENT_WRITE_TAG] : COLLAB_TAG },
258
268
  );
259
269
  };
260
270
 
@@ -271,8 +281,13 @@ export function createLoroBinding(
271
281
  idOf: (key) => keyToId.get(key),
272
282
  keyOf: (id) => idToKey.get(id),
273
283
  import: (bytes) => {
274
- doc.import(bytes);
275
- pullFromDoc();
284
+ pullFromDoc(
285
+ hasLiveAgentChange(
286
+ doc,
287
+ doc.import(bytes),
288
+ options.agentLiveWindowSeconds ?? AGENT_LIVE_WINDOW_SECONDS,
289
+ ),
290
+ );
276
291
  },
277
292
  };
278
293
  }
@@ -280,3 +295,18 @@ export function createLoroBinding(
280
295
  function $isTextLike(props: Props): boolean {
281
296
  return typeof props.format === 'number' && typeof props.detail === 'number' && 'mode' in props;
282
297
  }
298
+
299
+ function hasLiveAgentChange(doc: LoroDoc, status: ImportStatus, windowSeconds: number): boolean {
300
+ const oldest = Date.now() / 1000 - windowSeconds;
301
+ for (const [peer, span] of status.success) {
302
+ let counter = span.start;
303
+ while (counter < span.end) {
304
+ const change = doc.getChangeAt({ peer, counter });
305
+ if (change.message?.startsWith(AGENT_MESSAGE_PREFIX) && change.timestamp >= oldest) {
306
+ return true;
307
+ }
308
+ counter = change.counter + change.length;
309
+ }
310
+ }
311
+ return false;
312
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export {
2
+ AGENT_WRITE_TAG,
2
3
  COLLAB_TAG,
3
4
  createLoroBinding,
4
5
  type LoroBinding,
package/src/stream.ts CHANGED
@@ -86,6 +86,7 @@ function pairGap(dels: Json[], adds: Json[]): Item[] {
86
86
  });
87
87
  }
88
88
 
89
+ /** @deprecated Agent writes are now played client-side by `@haklex/rich-plugin-agent-stream`; write once with `editAtVersion`. */
89
90
  export function planFrames(base: Json, target: Json): Frame[] {
90
91
  const before = base.children ?? [];
91
92
  const after = target.children ?? [];
@@ -183,6 +184,7 @@ function $locate(focus: FrameFocus | null): { key: NodeKey; offset: number } | n
183
184
  : { key: block.getKey(), offset: 0 };
184
185
  }
185
186
 
187
+ /** @deprecated Agent writes are now played client-side by `@haklex/rich-plugin-agent-stream`; write once with `editAtVersion`. */
186
188
  export function streamAtVersion(
187
189
  doc: LoroDoc,
188
190
  frontiers: Frontiers,