@loro-extended/change 1.0.1 → 2.0.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/index.js CHANGED
@@ -8,6 +8,9 @@ function derivePlaceholder(schema) {
8
8
  }
9
9
  function deriveShapePlaceholder(shape) {
10
10
  switch (shape._type) {
11
+ // Any container - no placeholder (undefined)
12
+ case "any":
13
+ return void 0;
11
14
  // Leaf containers - use _placeholder directly
12
15
  case "text":
13
16
  return shape._placeholder;
@@ -36,6 +39,9 @@ function deriveShapePlaceholder(shape) {
36
39
  }
37
40
  function deriveValueShapePlaceholder(shape) {
38
41
  switch (shape.valueType) {
42
+ // Any value - no placeholder (undefined)
43
+ case "any":
44
+ return void 0;
39
45
  // Leaf values - use _placeholder directly
40
46
  case "string":
41
47
  return shape._placeholder;
@@ -146,6 +152,12 @@ function overlayPlaceholder(shape, crdtValue, placeholderValue) {
146
152
  return result;
147
153
  }
148
154
  function mergeValue(shape, crdtValue, placeholderValue) {
155
+ if (shape._type === "any") {
156
+ return crdtValue;
157
+ }
158
+ if (shape._type === "value" && shape.valueType === "any") {
159
+ return crdtValue;
160
+ }
149
161
  if (crdtValue === void 0 && placeholderValue === void 0) {
150
162
  throw new Error("either crdt or placeholder value must be defined");
151
163
  }
@@ -247,6 +259,154 @@ function mergeDiscriminatedUnion(shape, crdtValue, placeholderValue) {
247
259
  return mergeValue(variantShape, crdtValue, effectivePlaceholderValue);
248
260
  }
249
261
 
262
+ // src/path-builder.ts
263
+ function createPathSelector(segments) {
264
+ return {
265
+ __resultType: void 0,
266
+ __segments: segments
267
+ };
268
+ }
269
+ function createPathNode(shape, segments) {
270
+ const selector = createPathSelector(segments);
271
+ if (shape._type === "text" || shape._type === "counter") {
272
+ return selector;
273
+ }
274
+ if (shape._type === "value") {
275
+ return selector;
276
+ }
277
+ if (shape._type === "list" || shape._type === "movableList") {
278
+ return Object.assign(selector, {
279
+ get $each() {
280
+ return createPathNode(shape.shape, [...segments, { type: "each" }]);
281
+ },
282
+ $at(index) {
283
+ return createPathNode(shape.shape, [
284
+ ...segments,
285
+ { type: "index", index }
286
+ ]);
287
+ },
288
+ get $first() {
289
+ return createPathNode(shape.shape, [
290
+ ...segments,
291
+ { type: "index", index: 0 }
292
+ ]);
293
+ },
294
+ get $last() {
295
+ return createPathNode(shape.shape, [
296
+ ...segments,
297
+ { type: "index", index: -1 }
298
+ ]);
299
+ }
300
+ });
301
+ }
302
+ if (shape._type === "struct") {
303
+ const props = {};
304
+ for (const key in shape.shapes) {
305
+ Object.defineProperty(props, key, {
306
+ get() {
307
+ return createPathNode(shape.shapes[key], [
308
+ ...segments,
309
+ { type: "property", key }
310
+ ]);
311
+ },
312
+ enumerable: true
313
+ });
314
+ }
315
+ return Object.assign(selector, props);
316
+ }
317
+ if (shape._type === "record") {
318
+ return Object.assign(selector, {
319
+ get $each() {
320
+ return createPathNode(shape.shape, [...segments, { type: "each" }]);
321
+ },
322
+ $key(key) {
323
+ return createPathNode(shape.shape, [...segments, { type: "key", key }]);
324
+ }
325
+ });
326
+ }
327
+ return selector;
328
+ }
329
+ function createPathBuilder(docShape) {
330
+ const builder = {};
331
+ for (const key in docShape.shapes) {
332
+ Object.defineProperty(builder, key, {
333
+ get() {
334
+ return createPathNode(docShape.shapes[key], [{ type: "property", key }]);
335
+ },
336
+ enumerable: true
337
+ });
338
+ }
339
+ return builder;
340
+ }
341
+
342
+ // src/path-compiler.ts
343
+ function compileToJsonPath(segments) {
344
+ let path = "$";
345
+ for (const segment of segments) {
346
+ switch (segment.type) {
347
+ case "property":
348
+ if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(segment.key)) {
349
+ path += `.${segment.key}`;
350
+ } else {
351
+ path += `["${escapeJsonPathKey(segment.key)}"]`;
352
+ }
353
+ break;
354
+ case "each":
355
+ path += "[*]";
356
+ break;
357
+ case "index":
358
+ path += `[${segment.index}]`;
359
+ break;
360
+ case "key":
361
+ path += `["${escapeJsonPathKey(segment.key)}"]`;
362
+ break;
363
+ }
364
+ }
365
+ return path;
366
+ }
367
+ function escapeJsonPathKey(key) {
368
+ return key.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
369
+ }
370
+ function hasWildcard(segments) {
371
+ return segments.some((s) => s.type === "each");
372
+ }
373
+
374
+ // src/path-evaluator.ts
375
+ function evaluatePath(doc, selector) {
376
+ const json = doc.$.toJSON();
377
+ return evaluatePathOnValue(json, selector.__segments);
378
+ }
379
+ function evaluatePathOnValue(value, segments) {
380
+ if (segments.length === 0) {
381
+ return value;
382
+ }
383
+ const [segment, ...rest] = segments;
384
+ switch (segment.type) {
385
+ case "property":
386
+ case "key":
387
+ if (value == null) return void 0;
388
+ if (typeof value !== "object") return void 0;
389
+ return evaluatePathOnValue(
390
+ value[segment.key],
391
+ rest
392
+ );
393
+ case "index": {
394
+ if (!Array.isArray(value)) return void 0;
395
+ const index = segment.index < 0 ? value.length + segment.index : segment.index;
396
+ if (index < 0 || index >= value.length) return void 0;
397
+ return evaluatePathOnValue(value[index], rest);
398
+ }
399
+ case "each":
400
+ if (Array.isArray(value)) {
401
+ return value.map((item) => evaluatePathOnValue(item, rest));
402
+ }
403
+ if (typeof value === "object" && value !== null) {
404
+ return Object.values(value).map((item) => evaluatePathOnValue(item, rest));
405
+ }
406
+ return [];
407
+ }
408
+ }
409
+
250
410
  // src/placeholder-proxy.ts
251
411
  function createPlaceholderProxy(target) {
252
412
  const cache = /* @__PURE__ */ new Map();
@@ -270,6 +430,29 @@ function createPlaceholderProxy(target) {
270
430
  }
271
431
 
272
432
  // src/shape.ts
433
+ function makeNullable(shape) {
434
+ const nullShape = {
435
+ _type: "value",
436
+ valueType: "null",
437
+ _plain: null,
438
+ _mutable: null,
439
+ _placeholder: null
440
+ };
441
+ const base = {
442
+ _type: "value",
443
+ valueType: "union",
444
+ shapes: [nullShape, shape],
445
+ _plain: null,
446
+ _mutable: null,
447
+ _placeholder: null
448
+ // Default placeholder is null
449
+ };
450
+ return Object.assign(base, {
451
+ placeholder(value) {
452
+ return { ...base, _placeholder: value };
453
+ }
454
+ });
455
+ }
273
456
  var Shape = {
274
457
  doc: (shape) => ({
275
458
  _type: "doc",
@@ -278,6 +461,27 @@ var Shape = {
278
461
  _mutable: {},
279
462
  _placeholder: {}
280
463
  }),
464
+ /**
465
+ * Creates an "any" container shape - an escape hatch for untyped containers.
466
+ * Use this when integrating with external libraries that manage their own document structure.
467
+ *
468
+ * @example
469
+ * ```typescript
470
+ * // loro-prosemirror manages its own structure
471
+ * const ProseMirrorDocShape = Shape.doc({
472
+ * doc: Shape.any(), // opt out of typing for this container
473
+ * })
474
+ *
475
+ * const handle = repo.get(docId, ProseMirrorDocShape, CursorPresenceShape)
476
+ * // handle.doc.doc is typed as `unknown` - you're on your own
477
+ * ```
478
+ */
479
+ any: () => ({
480
+ _type: "any",
481
+ _plain: void 0,
482
+ _mutable: void 0,
483
+ _placeholder: void 0
484
+ }),
281
485
  // CRDTs are represented by Loro Containers--they converge on state using Loro's
282
486
  // various CRDT algorithms
283
487
  counter: () => {
@@ -382,6 +586,9 @@ var Shape = {
382
586
  return Object.assign(base, {
383
587
  placeholder(value) {
384
588
  return { ...base, _placeholder: value };
589
+ },
590
+ nullable() {
591
+ return makeNullable(base);
385
592
  }
386
593
  });
387
594
  },
@@ -396,6 +603,9 @@ var Shape = {
396
603
  return Object.assign(base, {
397
604
  placeholder(value) {
398
605
  return { ...base, _placeholder: value };
606
+ },
607
+ nullable() {
608
+ return makeNullable(base);
399
609
  }
400
610
  });
401
611
  },
@@ -410,6 +620,9 @@ var Shape = {
410
620
  return Object.assign(base, {
411
621
  placeholder(value) {
412
622
  return { ...base, _placeholder: value };
623
+ },
624
+ nullable() {
625
+ return makeNullable(base);
413
626
  }
414
627
  });
415
628
  },
@@ -427,12 +640,63 @@ var Shape = {
427
640
  _mutable: void 0,
428
641
  _placeholder: void 0
429
642
  }),
430
- uint8Array: () => ({
643
+ uint8Array: () => {
644
+ const base = {
645
+ _type: "value",
646
+ valueType: "uint8array",
647
+ _plain: new Uint8Array(),
648
+ _mutable: new Uint8Array(),
649
+ _placeholder: new Uint8Array()
650
+ };
651
+ return Object.assign(base, {
652
+ nullable() {
653
+ return makeNullable(base);
654
+ }
655
+ });
656
+ },
657
+ /**
658
+ * Alias for `uint8Array()` - creates a shape for binary data.
659
+ * Use this for better discoverability when working with binary data like cursor positions.
660
+ *
661
+ * @example
662
+ * ```typescript
663
+ * const CursorPresenceShape = Shape.plain.struct({
664
+ * anchor: Shape.plain.bytes().nullable(),
665
+ * focus: Shape.plain.bytes().nullable(),
666
+ * })
667
+ * ```
668
+ */
669
+ bytes: () => {
670
+ const base = {
671
+ _type: "value",
672
+ valueType: "uint8array",
673
+ _plain: new Uint8Array(),
674
+ _mutable: new Uint8Array(),
675
+ _placeholder: new Uint8Array()
676
+ };
677
+ return Object.assign(base, {
678
+ nullable() {
679
+ return makeNullable(base);
680
+ }
681
+ });
682
+ },
683
+ /**
684
+ * Creates an "any" value shape - an escape hatch for untyped values.
685
+ * Use this when you need to accept any valid Loro value type.
686
+ *
687
+ * @example
688
+ * ```typescript
689
+ * const FlexiblePresenceShape = Shape.plain.struct({
690
+ * metadata: Shape.plain.any(), // accept any value type
691
+ * })
692
+ * ```
693
+ */
694
+ any: () => ({
431
695
  _type: "value",
432
- valueType: "uint8array",
433
- _plain: new Uint8Array(),
434
- _mutable: new Uint8Array(),
435
- _placeholder: new Uint8Array()
696
+ valueType: "any",
697
+ _plain: void 0,
698
+ _mutable: void 0,
699
+ _placeholder: void 0
436
700
  }),
437
701
  /**
438
702
  * Creates a struct value shape for plain objects with fixed keys.
@@ -446,41 +710,69 @@ var Shape = {
446
710
  * })
447
711
  * ```
448
712
  */
449
- struct: (shape) => ({
450
- _type: "value",
451
- valueType: "struct",
452
- shape,
453
- _plain: {},
454
- _mutable: {},
455
- _placeholder: {}
456
- }),
713
+ struct: (shape) => {
714
+ const base = {
715
+ _type: "value",
716
+ valueType: "struct",
717
+ shape,
718
+ _plain: {},
719
+ _mutable: {},
720
+ _placeholder: {}
721
+ };
722
+ return Object.assign(base, {
723
+ nullable() {
724
+ return makeNullable(base);
725
+ }
726
+ });
727
+ },
457
728
  /**
458
729
  * @deprecated Use `Shape.plain.struct` instead. `Shape.plain.struct` will be removed in a future version.
459
730
  */
460
- object: (shape) => ({
461
- _type: "value",
462
- valueType: "struct",
463
- shape,
464
- _plain: {},
465
- _mutable: {},
466
- _placeholder: {}
467
- }),
468
- record: (shape) => ({
469
- _type: "value",
470
- valueType: "record",
471
- shape,
472
- _plain: {},
473
- _mutable: {},
474
- _placeholder: {}
475
- }),
476
- array: (shape) => ({
477
- _type: "value",
478
- valueType: "array",
479
- shape,
480
- _plain: [],
481
- _mutable: [],
482
- _placeholder: []
483
- }),
731
+ object: (shape) => {
732
+ const base = {
733
+ _type: "value",
734
+ valueType: "struct",
735
+ shape,
736
+ _plain: {},
737
+ _mutable: {},
738
+ _placeholder: {}
739
+ };
740
+ return Object.assign(base, {
741
+ nullable() {
742
+ return makeNullable(base);
743
+ }
744
+ });
745
+ },
746
+ record: (shape) => {
747
+ const base = {
748
+ _type: "value",
749
+ valueType: "record",
750
+ shape,
751
+ _plain: {},
752
+ _mutable: {},
753
+ _placeholder: {}
754
+ };
755
+ return Object.assign(base, {
756
+ nullable() {
757
+ return makeNullable(base);
758
+ }
759
+ });
760
+ },
761
+ array: (shape) => {
762
+ const base = {
763
+ _type: "value",
764
+ valueType: "array",
765
+ shape,
766
+ _plain: [],
767
+ _mutable: [],
768
+ _placeholder: []
769
+ };
770
+ return Object.assign(base, {
771
+ nullable() {
772
+ return makeNullable(base);
773
+ }
774
+ });
775
+ },
484
776
  // Special value type that helps make things like `string | null` representable
485
777
  // TODO(duane): should this be a more general type for containers too?
486
778
  union: (shapes) => {
@@ -1408,6 +1700,11 @@ var RecordRef = class extends TypedRef {
1408
1700
  if (placeholder === void 0) {
1409
1701
  placeholder = deriveShapePlaceholder(shape);
1410
1702
  }
1703
+ if (!hasContainerConstructor(shape._type)) {
1704
+ throw new Error(
1705
+ `Cannot create typed ref for shape type "${shape._type}". Use Shape.any() only at the document root level.`
1706
+ );
1707
+ }
1411
1708
  const LoroContainer = containerConstructor[shape._type];
1412
1709
  return {
1413
1710
  shape,
@@ -1550,6 +1847,11 @@ var StructRef = class extends TypedRef {
1550
1847
  }
1551
1848
  getTypedRefParams(key, shape) {
1552
1849
  const placeholder = this.placeholder?.[key];
1850
+ if (!hasContainerConstructor(shape._type)) {
1851
+ throw new Error(
1852
+ `Cannot create typed ref for shape type "${shape._type}". Use Shape.any() only at the document root level.`
1853
+ );
1854
+ }
1553
1855
  const LoroContainer = containerConstructor[shape._type];
1554
1856
  return {
1555
1857
  shape,
@@ -1746,6 +2048,9 @@ var TextRef = class extends TypedRef {
1746
2048
  var TreeRef = class extends TypedRef {
1747
2049
  absorbPlainValues() {
1748
2050
  }
2051
+ toJSON() {
2052
+ return this.container.toJSON();
2053
+ }
1749
2054
  createNode(parent, index) {
1750
2055
  this.assertMutable();
1751
2056
  return this.container.createNode(parent, index);
@@ -1778,6 +2083,9 @@ var containerConstructor = {
1778
2083
  text: LoroText2,
1779
2084
  tree: LoroTree
1780
2085
  };
2086
+ function hasContainerConstructor(type) {
2087
+ return type in containerConstructor;
2088
+ }
1781
2089
  function unwrapReadonlyPrimitive(ref, shape) {
1782
2090
  if (shape._type === "counter") {
1783
2091
  return ref.value;
@@ -1917,7 +2225,13 @@ var DocRef = class extends TypedRef {
1917
2225
  this.createLazyProperties();
1918
2226
  }
1919
2227
  getTypedRefParams(key, shape) {
1920
- const getter = this._doc[containerGetter[shape._type]].bind(this._doc);
2228
+ if (shape._type === "any") {
2229
+ throw new Error(
2230
+ `Cannot get typed ref params for "any" shape type. The "any" shape is an escape hatch for untyped containers and should be accessed directly via loroDoc.`
2231
+ );
2232
+ }
2233
+ const getterName = containerGetter[shape._type];
2234
+ const getter = this._doc[getterName].bind(this._doc);
1921
2235
  return {
1922
2236
  shape,
1923
2237
  placeholder: this.requiredPlaceholder[key],
@@ -1972,6 +2286,9 @@ function validateValue(value, schema, path = "") {
1972
2286
  throw new Error(`Invalid schema at path ${path}: missing _type`);
1973
2287
  }
1974
2288
  const currentPath = path || "root";
2289
+ if (schema._type === "any") {
2290
+ return value;
2291
+ }
1975
2292
  if (schema._type === "text") {
1976
2293
  if (typeof value !== "string") {
1977
2294
  throw new Error(
@@ -2039,6 +2356,9 @@ function validateValue(value, schema, path = "") {
2039
2356
  if (schema._type === "value") {
2040
2357
  const valueSchema = schema;
2041
2358
  switch (valueSchema.valueType) {
2359
+ // AnyValueShape - no validation, accept anything
2360
+ case "any":
2361
+ return value;
2042
2362
  case "string": {
2043
2363
  if (typeof value !== "string") {
2044
2364
  throw new Error(
@@ -2352,85 +2672,19 @@ function createTypedDoc(shape, existingDoc) {
2352
2672
  internal.proxy = proxy;
2353
2673
  return proxy;
2354
2674
  }
2355
-
2356
- // src/typed-presence.ts
2357
- var TypedPresence = class {
2358
- constructor(shape, presence) {
2359
- this.shape = shape;
2360
- this.presence = presence;
2361
- this.placeholder = deriveShapePlaceholder(shape);
2362
- }
2363
- placeholder;
2364
- /**
2365
- * Get the current peer's presence state with placeholder values merged in.
2366
- */
2367
- get self() {
2368
- return mergeValue(
2369
- this.shape,
2370
- this.presence.self,
2371
- this.placeholder
2372
- );
2373
- }
2374
- /**
2375
- * Get other peers' presence states with placeholder values merged in.
2376
- * Does NOT include self. Use this for iterating over remote peers.
2377
- */
2378
- get peers() {
2379
- const result = /* @__PURE__ */ new Map();
2380
- for (const [peerId, value] of this.presence.peers) {
2381
- result.set(
2382
- peerId,
2383
- mergeValue(this.shape, value, this.placeholder)
2384
- );
2385
- }
2386
- return result;
2387
- }
2388
- /**
2389
- * Get all peers' presence states with placeholder values merged in.
2390
- * @deprecated Use `peers` and `self` separately. This property is synthesized
2391
- * from `peers` and `self` for backward compatibility.
2392
- */
2393
- get all() {
2394
- const result = {};
2395
- const all = this.presence.all;
2396
- for (const peerId of Object.keys(all)) {
2397
- result[peerId] = mergeValue(
2398
- this.shape,
2399
- all[peerId],
2400
- this.placeholder
2401
- );
2402
- }
2403
- return result;
2404
- }
2405
- /**
2406
- * Set presence values for the current peer.
2407
- */
2408
- set(value) {
2409
- this.presence.set(value);
2410
- }
2411
- /**
2412
- * Subscribe to presence changes.
2413
- * The callback is called immediately with the current state, then on each change.
2414
- *
2415
- * @param cb Callback that receives the typed presence state
2416
- * @returns Unsubscribe function
2417
- */
2418
- subscribe(cb) {
2419
- cb({ self: this.self, peers: this.peers, all: this.all });
2420
- return this.presence.subscribe(() => {
2421
- cb({ self: this.self, peers: this.peers, all: this.all });
2422
- });
2423
- }
2424
- };
2425
2675
  export {
2426
2676
  Shape,
2427
- TypedPresence,
2428
2677
  change,
2678
+ compileToJsonPath,
2679
+ createPathBuilder,
2429
2680
  createPlaceholderProxy,
2430
2681
  createTypedDoc,
2431
2682
  derivePlaceholder,
2432
2683
  deriveShapePlaceholder,
2684
+ evaluatePath,
2685
+ evaluatePathOnValue,
2433
2686
  getLoroDoc,
2687
+ hasWildcard,
2434
2688
  mergeValue,
2435
2689
  overlayPlaceholder,
2436
2690
  validatePlaceholder