@loro-extended/change 1.1.0 → 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();
@@ -301,6 +461,27 @@ var Shape = {
301
461
  _mutable: {},
302
462
  _placeholder: {}
303
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
+ }),
304
485
  // CRDTs are represented by Loro Containers--they converge on state using Loro's
305
486
  // various CRDT algorithms
306
487
  counter: () => {
@@ -459,12 +640,63 @@ var Shape = {
459
640
  _mutable: void 0,
460
641
  _placeholder: void 0
461
642
  }),
462
- 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: () => ({
463
695
  _type: "value",
464
- valueType: "uint8array",
465
- _plain: new Uint8Array(),
466
- _mutable: new Uint8Array(),
467
- _placeholder: new Uint8Array()
696
+ valueType: "any",
697
+ _plain: void 0,
698
+ _mutable: void 0,
699
+ _placeholder: void 0
468
700
  }),
469
701
  /**
470
702
  * Creates a struct value shape for plain objects with fixed keys.
@@ -1468,6 +1700,11 @@ var RecordRef = class extends TypedRef {
1468
1700
  if (placeholder === void 0) {
1469
1701
  placeholder = deriveShapePlaceholder(shape);
1470
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
+ }
1471
1708
  const LoroContainer = containerConstructor[shape._type];
1472
1709
  return {
1473
1710
  shape,
@@ -1610,6 +1847,11 @@ var StructRef = class extends TypedRef {
1610
1847
  }
1611
1848
  getTypedRefParams(key, shape) {
1612
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
+ }
1613
1855
  const LoroContainer = containerConstructor[shape._type];
1614
1856
  return {
1615
1857
  shape,
@@ -1806,6 +2048,9 @@ var TextRef = class extends TypedRef {
1806
2048
  var TreeRef = class extends TypedRef {
1807
2049
  absorbPlainValues() {
1808
2050
  }
2051
+ toJSON() {
2052
+ return this.container.toJSON();
2053
+ }
1809
2054
  createNode(parent, index) {
1810
2055
  this.assertMutable();
1811
2056
  return this.container.createNode(parent, index);
@@ -1838,6 +2083,9 @@ var containerConstructor = {
1838
2083
  text: LoroText2,
1839
2084
  tree: LoroTree
1840
2085
  };
2086
+ function hasContainerConstructor(type) {
2087
+ return type in containerConstructor;
2088
+ }
1841
2089
  function unwrapReadonlyPrimitive(ref, shape) {
1842
2090
  if (shape._type === "counter") {
1843
2091
  return ref.value;
@@ -1977,7 +2225,13 @@ var DocRef = class extends TypedRef {
1977
2225
  this.createLazyProperties();
1978
2226
  }
1979
2227
  getTypedRefParams(key, shape) {
1980
- 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);
1981
2235
  return {
1982
2236
  shape,
1983
2237
  placeholder: this.requiredPlaceholder[key],
@@ -2032,6 +2286,9 @@ function validateValue(value, schema, path = "") {
2032
2286
  throw new Error(`Invalid schema at path ${path}: missing _type`);
2033
2287
  }
2034
2288
  const currentPath = path || "root";
2289
+ if (schema._type === "any") {
2290
+ return value;
2291
+ }
2035
2292
  if (schema._type === "text") {
2036
2293
  if (typeof value !== "string") {
2037
2294
  throw new Error(
@@ -2099,6 +2356,9 @@ function validateValue(value, schema, path = "") {
2099
2356
  if (schema._type === "value") {
2100
2357
  const valueSchema = schema;
2101
2358
  switch (valueSchema.valueType) {
2359
+ // AnyValueShape - no validation, accept anything
2360
+ case "any":
2361
+ return value;
2102
2362
  case "string": {
2103
2363
  if (typeof value !== "string") {
2104
2364
  throw new Error(
@@ -2412,85 +2672,19 @@ function createTypedDoc(shape, existingDoc) {
2412
2672
  internal.proxy = proxy;
2413
2673
  return proxy;
2414
2674
  }
2415
-
2416
- // src/typed-presence.ts
2417
- var TypedPresence = class {
2418
- constructor(shape, presence) {
2419
- this.shape = shape;
2420
- this.presence = presence;
2421
- this.placeholder = deriveShapePlaceholder(shape);
2422
- }
2423
- placeholder;
2424
- /**
2425
- * Get the current peer's presence state with placeholder values merged in.
2426
- */
2427
- get self() {
2428
- return mergeValue(
2429
- this.shape,
2430
- this.presence.self,
2431
- this.placeholder
2432
- );
2433
- }
2434
- /**
2435
- * Get other peers' presence states with placeholder values merged in.
2436
- * Does NOT include self. Use this for iterating over remote peers.
2437
- */
2438
- get peers() {
2439
- const result = /* @__PURE__ */ new Map();
2440
- for (const [peerId, value] of this.presence.peers) {
2441
- result.set(
2442
- peerId,
2443
- mergeValue(this.shape, value, this.placeholder)
2444
- );
2445
- }
2446
- return result;
2447
- }
2448
- /**
2449
- * Get all peers' presence states with placeholder values merged in.
2450
- * @deprecated Use `peers` and `self` separately. This property is synthesized
2451
- * from `peers` and `self` for backward compatibility.
2452
- */
2453
- get all() {
2454
- const result = {};
2455
- const all = this.presence.all;
2456
- for (const peerId of Object.keys(all)) {
2457
- result[peerId] = mergeValue(
2458
- this.shape,
2459
- all[peerId],
2460
- this.placeholder
2461
- );
2462
- }
2463
- return result;
2464
- }
2465
- /**
2466
- * Set presence values for the current peer.
2467
- */
2468
- set(value) {
2469
- this.presence.set(value);
2470
- }
2471
- /**
2472
- * Subscribe to presence changes.
2473
- * The callback is called immediately with the current state, then on each change.
2474
- *
2475
- * @param cb Callback that receives the typed presence state
2476
- * @returns Unsubscribe function
2477
- */
2478
- subscribe(cb) {
2479
- cb({ self: this.self, peers: this.peers, all: this.all });
2480
- return this.presence.subscribe(() => {
2481
- cb({ self: this.self, peers: this.peers, all: this.all });
2482
- });
2483
- }
2484
- };
2485
2675
  export {
2486
2676
  Shape,
2487
- TypedPresence,
2488
2677
  change,
2678
+ compileToJsonPath,
2679
+ createPathBuilder,
2489
2680
  createPlaceholderProxy,
2490
2681
  createTypedDoc,
2491
2682
  derivePlaceholder,
2492
2683
  deriveShapePlaceholder,
2684
+ evaluatePath,
2685
+ evaluatePathOnValue,
2493
2686
  getLoroDoc,
2687
+ hasWildcard,
2494
2688
  mergeValue,
2495
2689
  overlayPlaceholder,
2496
2690
  validatePlaceholder