reptree 0.2.3 → 0.2.4

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.d.ts CHANGED
@@ -107,13 +107,35 @@ type SchemaLike<T> = {
107
107
  parse?: (input: unknown) => T;
108
108
  shape?: Record<string, FieldSchemaLike>;
109
109
  };
110
+ type AliasRule = {
111
+ publicKey: string;
112
+ internalKey: string;
113
+ toPublic?: (value: unknown) => unknown;
114
+ toInternal?: (value: unknown) => unknown;
115
+ };
116
+ declare const defaultAliases: AliasRule[];
117
+ type BindOptions<T> = {
118
+ schema?: SchemaLike<T>;
119
+ aliases?: AliasRule[];
120
+ includeInternalKeys?: boolean;
121
+ };
122
+ type BindedVertex<T> = T & {
123
+ /**
124
+ * Create a transient proxy that can be used to write transient properties.
125
+ */
126
+ useTransient(fn: (t: T) => void): void;
127
+ /**
128
+ * Promote transient properties to persistent.
129
+ */
130
+ commitTransients(): void;
131
+ };
110
132
  /**
111
133
  * Returns a live object that proxies reads/writes to a vertex.
112
134
  * - Reads reflect the latest CRDT state.
113
135
  * - Writes persist to the CRDT.
114
136
  * - If a schema is provided, writes are validated. If a field schema exists in `schema.shape`, field-level validation is applied.
115
137
  */
116
- declare function bindVertex<T extends Record<string, unknown>>(tree: RepTree, id: string, schema?: SchemaLike<T>): T;
138
+ declare function bindVertex<T extends Record<string, unknown>>(tree: RepTree, id: string, schemaOrOptions?: SchemaLike<T> | BindOptions<T>): BindedVertex<T>;
117
139
 
118
140
  /**
119
141
  * A wrapper class for VertexState that provides a more convenient API
@@ -150,8 +172,15 @@ declare class Vertex {
150
172
  observeChildrenAsTypedArray<T>(listener: (children: T[]) => void): () => void;
151
173
  delete(): void;
152
174
  moveTo(parent: Vertex): void;
153
- /** Returns a live reactive object bound to this vertex. Optional schema validates writes. */
154
- bind<T extends Record<string, unknown>>(schema?: SchemaLike<T>): T;
175
+ /** Returns a live reactive object bound to this vertex. Accepts schema or options. */
176
+ bind<T extends Record<string, unknown>>(schemaOrOptions?: SchemaLike<T> | BindOptions<T>): BindedVertex<T>;
177
+ /**
178
+ * Normalizes an input props object for vertex creation:
179
+ * - Aliases name -> _n, createdAt -> _c (Date -> ISO string)
180
+ * - Filters unsupported field types with a console warning
181
+ * - When a name param is provided to newNamedChild, ignores conflicting name in props
182
+ */
183
+ private static normalizePropsForCreation;
155
184
  }
156
185
 
157
186
  /**
@@ -355,4 +384,4 @@ declare class StateVector {
355
384
 
356
385
  declare function uuid(): string;
357
386
 
358
- export { type CRDTType, type MoveVertex, type OpId, type OpIdRange, RepTree, type SetVertexProperty, StateVector, TreeState, type TreeVertexId, type TreeVertexProperty, Vertex, type VertexChangeEvent, type VertexChildrenChangeEvent, type VertexMoveEvent, type VertexOperation, type VertexPropertyChangeEvent, type VertexPropertyType, type VertexPropertyTypeInOperation, VertexState, bindVertex, isAnyPropertyOp, isLWWPropertyOp, isModifyPropertyOp, isMoveVertexOp, newMoveVertexOp, newSetTransientVertexPropertyOp, newSetVertexPropertyOp, uuid };
387
+ export { type AliasRule, type BindOptions, type BindedVertex, type CRDTType, type MoveVertex, type OpId, type OpIdRange, RepTree, type SchemaLike, type SetVertexProperty, StateVector, TreeState, type TreeVertexId, type TreeVertexProperty, Vertex, type VertexChangeEvent, type VertexChildrenChangeEvent, type VertexMoveEvent, type VertexOperation, type VertexPropertyChangeEvent, type VertexPropertyType, type VertexPropertyTypeInOperation, VertexState, bindVertex, defaultAliases, isAnyPropertyOp, isLWWPropertyOp, isModifyPropertyOp, isMoveVertexOp, newMoveVertexOp, newSetTransientVertexPropertyOp, newSetVertexPropertyOp, uuid };
package/dist/index.js CHANGED
@@ -355,19 +355,147 @@ function uuid() {
355
355
  }
356
356
 
357
357
  // src/reactive.ts
358
- function toObject(tree, id) {
358
+ var defaultAliases = [
359
+ { publicKey: "name", internalKey: "_n" },
360
+ {
361
+ publicKey: "createdAt",
362
+ internalKey: "_c",
363
+ toPublic: (v) => typeof v === "string" ? new Date(v) : v,
364
+ toInternal: (v) => v instanceof Date ? v.toISOString() : v
365
+ }
366
+ ];
367
+ function buildAliasMaps(aliases) {
368
+ const publicToInternal = /* @__PURE__ */ new Map();
369
+ const internalToPublic = /* @__PURE__ */ new Map();
370
+ for (const rule of aliases) {
371
+ publicToInternal.set(rule.publicKey, rule);
372
+ internalToPublic.set(rule.internalKey, rule);
373
+ }
374
+ return { publicToInternal, internalToPublic };
375
+ }
376
+ function toPublicObject(tree, id, internalToPublic) {
359
377
  const obj = {};
360
- for (const { key, value } of tree.getVertexProperties(id)) obj[key] = value;
378
+ for (const { key, value } of tree.getVertexProperties(id)) {
379
+ const rule = internalToPublic.get(key);
380
+ if (rule) {
381
+ const converted = rule.toPublic ? rule.toPublic(value) : value;
382
+ obj[rule.publicKey] = converted;
383
+ } else {
384
+ obj[key] = value;
385
+ }
386
+ }
361
387
  return obj;
362
388
  }
363
- function bindVertex(tree, id, schema) {
389
+ var RESERVED_METHOD_USE_TRANSIENT = "useTransient";
390
+ var RESERVED_METHOD_COMMIT_TRANSIENTS = "commitTransients";
391
+ function bindVertex(tree, id, schemaOrOptions) {
392
+ const isOptions = typeof schemaOrOptions === "object" && schemaOrOptions !== null && (Object.prototype.hasOwnProperty.call(schemaOrOptions, "aliases") || Object.prototype.hasOwnProperty.call(schemaOrOptions, "includeInternalKeys") || Object.prototype.hasOwnProperty.call(schemaOrOptions, "schema"));
393
+ const options = isOptions ? schemaOrOptions : { schema: schemaOrOptions };
394
+ const schema = options.schema;
395
+ const aliases = options.aliases ?? defaultAliases;
396
+ const includeInternalKeys = options.includeInternalKeys ?? false;
397
+ const { publicToInternal, internalToPublic } = buildAliasMaps(aliases);
364
398
  return new Proxy({}, {
365
399
  get(_target, prop) {
400
+ if (prop === RESERVED_METHOD_USE_TRANSIENT) {
401
+ return (fn) => {
402
+ const transientProxy = new Proxy({}, {
403
+ get(_t, p) {
404
+ if (typeof p !== "string") return void 0;
405
+ const rule2 = publicToInternal.get(p);
406
+ if (rule2) {
407
+ const raw = tree.getVertexProperty(id, rule2.internalKey);
408
+ return rule2.toPublic ? rule2.toPublic(raw) : raw;
409
+ }
410
+ return tree.getVertexProperty(id, p);
411
+ },
412
+ set(_t, p, value) {
413
+ if (typeof p !== "string") return true;
414
+ if (schema?.shape && schema.shape[p]) {
415
+ const field = schema.shape[p];
416
+ if (field.safeParse) {
417
+ const res = field.safeParse(value);
418
+ if (!res.success) throw new Error(`Invalid value for ${String(p)}`);
419
+ value = res.data ?? value;
420
+ }
421
+ } else if (schema?.safeParse) {
422
+ const next = { ...toPublicObject(tree, id, internalToPublic), [p]: value };
423
+ const res = schema.safeParse(next);
424
+ if (!res.success) throw new Error(`Invalid value for ${String(p)}`);
425
+ const parsed = res.data;
426
+ if (parsed && Object.prototype.hasOwnProperty.call(parsed, p)) {
427
+ value = parsed[p];
428
+ }
429
+ }
430
+ const rule2 = publicToInternal.get(p);
431
+ if (rule2) {
432
+ const converted = rule2.toInternal ? rule2.toInternal(value) : value;
433
+ tree.setTransientVertexProperty(id, rule2.internalKey, converted);
434
+ return true;
435
+ }
436
+ tree.setTransientVertexProperty(id, p, value);
437
+ return true;
438
+ },
439
+ deleteProperty(_t, p) {
440
+ if (typeof p !== "string") return true;
441
+ const rule2 = publicToInternal.get(p);
442
+ if (rule2) {
443
+ tree.setTransientVertexProperty(id, rule2.internalKey, void 0);
444
+ return true;
445
+ }
446
+ tree.setTransientVertexProperty(id, p, void 0);
447
+ return true;
448
+ }
449
+ });
450
+ fn(transientProxy);
451
+ };
452
+ }
453
+ if (prop === RESERVED_METHOD_COMMIT_TRANSIENTS) {
454
+ return () => {
455
+ const entries = tree.getVertexProperties(id);
456
+ const currentPublic = schema?.safeParse ? toPublicObject(tree, id, internalToPublic) : void 0;
457
+ for (const { key: internalKey, value: overlayValue } of entries) {
458
+ const persistentValue = tree.getVertexProperty(id, internalKey, false);
459
+ if (overlayValue === persistentValue) continue;
460
+ const aliasRule = internalToPublic.get(internalKey);
461
+ const publicKey = aliasRule ? aliasRule.publicKey : internalKey;
462
+ const publicValue = aliasRule && aliasRule.toPublic ? aliasRule.toPublic(overlayValue) : overlayValue;
463
+ let valueToPersistInternal = overlayValue;
464
+ if (schema?.shape && schema.shape[publicKey]) {
465
+ const field = schema.shape[publicKey];
466
+ if (field.safeParse) {
467
+ const res = field.safeParse(publicValue);
468
+ if (!res.success) throw new Error(`Invalid value for ${String(publicKey)}`);
469
+ const coerced = res.data;
470
+ const maybeInternal = aliasRule && aliasRule.toInternal ? aliasRule.toInternal(coerced) : coerced;
471
+ valueToPersistInternal = maybeInternal;
472
+ }
473
+ } else if (schema?.safeParse && currentPublic) {
474
+ const nextPublic = { ...currentPublic, [publicKey]: publicValue };
475
+ const res = schema.safeParse(nextPublic);
476
+ if (!res.success) throw new Error("Invalid values for commitTransients");
477
+ const parsed = res.data;
478
+ const parsedPublic = Object.prototype.hasOwnProperty.call(parsed, publicKey) ? parsed[publicKey] : publicValue;
479
+ const maybeInternal = aliasRule && aliasRule.toInternal ? aliasRule.toInternal(parsedPublic) : parsedPublic;
480
+ valueToPersistInternal = maybeInternal;
481
+ }
482
+ tree.setVertexProperty(id, internalKey, valueToPersistInternal);
483
+ }
484
+ };
485
+ }
366
486
  if (typeof prop !== "string") return void 0;
487
+ const rule = publicToInternal.get(prop);
488
+ if (rule) {
489
+ const raw = tree.getVertexProperty(id, rule.internalKey);
490
+ return rule.toPublic ? rule.toPublic(raw) : raw;
491
+ }
367
492
  return tree.getVertexProperty(id, prop);
368
493
  },
369
494
  set(_target, prop, value) {
370
495
  if (typeof prop !== "string") return true;
496
+ if (prop === RESERVED_METHOD_USE_TRANSIENT || prop === RESERVED_METHOD_COMMIT_TRANSIENTS) {
497
+ return true;
498
+ }
371
499
  if (schema?.shape && schema.shape[prop]) {
372
500
  const field = schema.shape[prop];
373
501
  if (field.safeParse) {
@@ -376,7 +504,7 @@ function bindVertex(tree, id, schema) {
376
504
  value = res.data ?? value;
377
505
  }
378
506
  } else if (schema?.safeParse) {
379
- const next = { ...toObject(tree, id), [prop]: value };
507
+ const next = { ...toPublicObject(tree, id, internalToPublic), [prop]: value };
380
508
  const res = schema.safeParse(next);
381
509
  if (!res.success) throw new Error(`Invalid value for ${String(prop)}`);
382
510
  const parsed = res.data;
@@ -384,20 +512,43 @@ function bindVertex(tree, id, schema) {
384
512
  value = parsed[prop];
385
513
  }
386
514
  }
515
+ const rule = publicToInternal.get(prop);
516
+ if (rule) {
517
+ const converted = rule.toInternal ? rule.toInternal(value) : value;
518
+ tree.setVertexProperty(id, rule.internalKey, converted);
519
+ return true;
520
+ }
387
521
  tree.setVertexProperty(id, prop, value);
388
522
  return true;
389
523
  },
390
524
  deleteProperty(_target, prop) {
391
525
  if (typeof prop !== "string") return true;
526
+ if (prop === RESERVED_METHOD_USE_TRANSIENT || prop === RESERVED_METHOD_COMMIT_TRANSIENTS) {
527
+ return true;
528
+ }
529
+ const rule = publicToInternal.get(prop);
530
+ if (rule) {
531
+ tree.setVertexProperty(id, rule.internalKey, void 0);
532
+ return true;
533
+ }
392
534
  tree.setVertexProperty(id, prop, void 0);
393
535
  return true;
394
536
  },
395
537
  has(_target, prop) {
396
538
  if (typeof prop !== "string") return false;
397
- return !!schema?.shape && Object.prototype.hasOwnProperty.call(schema.shape, prop);
539
+ if (schema?.shape && Object.prototype.hasOwnProperty.call(schema.shape, prop)) return true;
540
+ if (includeInternalKeys) {
541
+ return publicToInternal.has(prop) || internalToPublic.has(prop);
542
+ }
543
+ return false;
398
544
  },
399
545
  ownKeys() {
400
- return Object.keys(schema?.shape ?? {});
546
+ const keys = /* @__PURE__ */ new Set();
547
+ for (const k of Object.keys(schema?.shape ?? {})) keys.add(k);
548
+ if (includeInternalKeys) {
549
+ for (const rule of aliases) keys.add(rule.internalKey);
550
+ }
551
+ return Array.from(keys);
401
552
  },
402
553
  getOwnPropertyDescriptor() {
403
554
  return { enumerable: true, configurable: true };
@@ -406,7 +557,8 @@ function bindVertex(tree, id, schema) {
406
557
  }
407
558
 
408
559
  // src/Vertex.ts
409
- var Vertex = class {
560
+ import * as Y from "yjs";
561
+ var Vertex = class _Vertex {
410
562
  constructor(tree, state) {
411
563
  this.tree = tree;
412
564
  this.state = state;
@@ -452,12 +604,18 @@ var Vertex = class {
452
604
  return this.children.map((v) => v.getAsTypedObject());
453
605
  }
454
606
  newChild(props) {
455
- const typedProps = props;
456
- return this.tree.newVertex(this.id, typedProps);
607
+ if (props && typeof props === "object" && "children" in props) {
608
+ throw new Error("Passing children inside props is not supported at the moment");
609
+ }
610
+ const normalized = _Vertex.normalizePropsForCreation(props);
611
+ return this.tree.newVertex(this.id, normalized);
457
612
  }
458
613
  newNamedChild(name, props) {
459
- const typedProps = props;
460
- return this.tree.newNamedVertex(this.id, name, typedProps);
614
+ if (props && typeof props === "object" && "children" in props) {
615
+ throw new Error("Passing children inside props is not supported at the moment");
616
+ }
617
+ const normalized = _Vertex.normalizePropsForCreation(props, name);
618
+ return this.tree.newNamedVertex(this.id, name, normalized);
461
619
  }
462
620
  setProperty(key, value) {
463
621
  const existingValue = this.getProperty(key, false);
@@ -523,9 +681,67 @@ var Vertex = class {
523
681
  moveTo(parent) {
524
682
  this.tree.moveVertex(this.id, parent.id);
525
683
  }
526
- /** Returns a live reactive object bound to this vertex. Optional schema validates writes. */
527
- bind(schema) {
528
- return bindVertex(this.tree, this.id, schema);
684
+ /** Returns a live reactive object bound to this vertex. Accepts schema or options. */
685
+ bind(schemaOrOptions) {
686
+ return bindVertex(this.tree, this.id, schemaOrOptions);
687
+ }
688
+ /**
689
+ * Normalizes an input props object for vertex creation:
690
+ * - Aliases name -> _n, createdAt -> _c (Date -> ISO string)
691
+ * - Filters unsupported field types with a console warning
692
+ * - When a name param is provided to newNamedChild, ignores conflicting name in props
693
+ */
694
+ static normalizePropsForCreation(props, explicitName) {
695
+ if (!props) return null;
696
+ const input = props;
697
+ const out = {};
698
+ const skipped = [];
699
+ for (const [rawKey, rawValue] of Object.entries(input)) {
700
+ if (rawValue === void 0) {
701
+ continue;
702
+ }
703
+ if (rawKey === "children") continue;
704
+ let key = rawKey;
705
+ if (rawKey === "name") {
706
+ if (explicitName !== void 0) {
707
+ console.warn('newNamedChild: "name" in props is ignored because a name argument was provided');
708
+ continue;
709
+ }
710
+ key = "_n";
711
+ } else if (rawKey === "createdAt") {
712
+ key = "_c";
713
+ }
714
+ let value = rawValue;
715
+ if (key === "_c") {
716
+ if (value instanceof Date) {
717
+ value = value.toISOString();
718
+ } else if (typeof value === "string") {
719
+ } else {
720
+ skipped.push(rawKey);
721
+ continue;
722
+ }
723
+ }
724
+ const isPrimitive = (v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean";
725
+ if (Array.isArray(value)) {
726
+ if (!value.every(isPrimitive)) {
727
+ skipped.push(rawKey);
728
+ continue;
729
+ }
730
+ } else if (typeof value === "object" && value !== null) {
731
+ if (!(value instanceof Y.Doc)) {
732
+ skipped.push(rawKey);
733
+ continue;
734
+ }
735
+ } else if (!isPrimitive(value)) {
736
+ skipped.push(rawKey);
737
+ continue;
738
+ }
739
+ out[key] = value;
740
+ }
741
+ if (skipped.length > 0) {
742
+ console.warn(`Some fields were skipped due to unsupported types: ${skipped.join(", ")}`);
743
+ }
744
+ return Object.keys(out).length > 0 ? out : null;
529
745
  }
530
746
  };
531
747
 
@@ -715,7 +931,7 @@ var StateVector = class _StateVector {
715
931
  };
716
932
 
717
933
  // src/RepTree.ts
718
- import * as Y from "yjs";
934
+ import * as Y2 from "yjs";
719
935
  var _RepTree = class _RepTree {
720
936
  /**
721
937
  * @param peerId - The peer ID of the current client. Should be unique across all peers.
@@ -877,8 +1093,8 @@ var _RepTree = class _RepTree {
877
1093
  setTransientVertexProperty(vertexId, key, value) {
878
1094
  this.lamportClock++;
879
1095
  let opValue;
880
- if (value instanceof Y.Doc) {
881
- const state = Y.encodeStateAsUpdate(value);
1096
+ if (value instanceof Y2.Doc) {
1097
+ const state = Y2.encodeStateAsUpdate(value);
882
1098
  opValue = {
883
1099
  type: "yjs",
884
1100
  value: state
@@ -894,8 +1110,8 @@ var _RepTree = class _RepTree {
894
1110
  setVertexProperty(vertexId, key, value) {
895
1111
  this.lamportClock++;
896
1112
  let opValue;
897
- if (value instanceof Y.Doc) {
898
- const state = Y.encodeStateAsUpdate(value);
1113
+ if (value instanceof Y2.Doc) {
1114
+ const state = Y2.encodeStateAsUpdate(value);
899
1115
  opValue = {
900
1116
  type: "yjs",
901
1117
  value: state
@@ -1067,10 +1283,10 @@ var _RepTree = class _RepTree {
1067
1283
  if (!propB) {
1068
1284
  return false;
1069
1285
  }
1070
- if (propA.value instanceof Y.Doc && propB.value instanceof Y.Doc) {
1071
- const snapshotA = Y.snapshot(propA.value);
1072
- const snapshotB = Y.snapshot(propB.value);
1073
- if (!Y.equalSnapshots(snapshotA, snapshotB)) {
1286
+ if (propA.value instanceof Y2.Doc && propB.value instanceof Y2.Doc) {
1287
+ const snapshotA = Y2.snapshot(propA.value);
1288
+ const snapshotB = Y2.snapshot(propB.value);
1289
+ if (!Y2.equalSnapshots(snapshotA, snapshotB)) {
1074
1290
  return false;
1075
1291
  }
1076
1292
  } else if (propA.value !== propB.value) {
@@ -1174,7 +1390,7 @@ var _RepTree = class _RepTree {
1174
1390
  const propertyKey = `${key}@${vertexId}`;
1175
1391
  if (this.yjsObservers.has(propertyKey)) {
1176
1392
  const existingDoc = this.getVertexProperty(vertexId, key);
1177
- if (existingDoc instanceof Y.Doc) {
1393
+ if (existingDoc instanceof Y2.Doc) {
1178
1394
  existingDoc.off("update", this.yjsObservers.get(propertyKey));
1179
1395
  }
1180
1396
  this.yjsObservers.delete(propertyKey);
@@ -1254,13 +1470,13 @@ var _RepTree = class _RepTree {
1254
1470
  throw new Error("Unknown CRDT type");
1255
1471
  }
1256
1472
  const ydoc = targetVertex.getProperty(op.key);
1257
- if (ydoc instanceof Y.Doc) {
1258
- Y.applyUpdate(ydoc, crdtValue.value);
1473
+ if (ydoc instanceof Y2.Doc) {
1474
+ Y2.applyUpdate(ydoc, crdtValue.value);
1259
1475
  } else {
1260
- const newDoc = new Y.Doc();
1476
+ const newDoc = new Y2.Doc();
1261
1477
  this.setupYjsObserver(newDoc, op.targetId, op.key);
1262
1478
  this.state.setProperty(op.targetId, op.key, newDoc);
1263
- Y.applyUpdate(newDoc, crdtValue.value);
1479
+ Y2.applyUpdate(newDoc, crdtValue.value);
1264
1480
  }
1265
1481
  this.propertiesAndTheirOpIds.set(`${op.key}@${op.targetId}`, op.id);
1266
1482
  this.reportOpAsApplied(op);
@@ -1387,6 +1603,7 @@ export {
1387
1603
  Vertex,
1388
1604
  VertexState,
1389
1605
  bindVertex,
1606
+ defaultAliases,
1390
1607
  isAnyPropertyOp,
1391
1608
  isLWWPropertyOp,
1392
1609
  isModifyPropertyOp,