reptree 0.2.4 → 0.4.0-alpha.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/README.md CHANGED
@@ -26,7 +26,7 @@ npm install reptree
26
26
  ### Reactive vertex with Zod (optional)
27
27
 
28
28
  ```ts
29
- import { RepTree, bindVertex } from 'reptree';
29
+ import { RepTree } from 'reptree';
30
30
  import { z } from 'zod';
31
31
 
32
32
  const tree = new RepTree('peer1');
@@ -35,11 +35,7 @@ const v = root.newChild();
35
35
 
36
36
  const Person = z.object({ name: z.string(), age: z.number().int().min(0) });
37
37
 
38
- // Helper function form
39
- const person = bindVertex(tree, v.id, Person);
40
-
41
- // Or via instance method on Vertex
42
- // const person = v.bind(Person);
38
+ const person = v.bind(Person);
43
39
 
44
40
  person.name = 'Alice'; // validated and persisted
45
41
  person.age = 33; // validated and persisted
@@ -50,7 +46,7 @@ person.age = 33; // validated and persisted
50
46
  - `name` ↔ `_n`
51
47
  - `createdAt` ↔ `_c` (Date exposed, ISO stored)
52
48
 
53
- These aliases are applied by default when using `bindVertex` or `vertex.bind()`.
49
+ These aliases are applied by default when using `vertex.bind()`.
54
50
 
55
51
  ```ts
56
52
  person.name = 'Alice'; // writes _n
package/dist/index.cjs CHANGED
@@ -187,6 +187,12 @@ var VertexState = class {
187
187
  removeTransientProperty(key) {
188
188
  this.transientProperties = this.transientProperties.filter((p) => p.key !== key);
189
189
  }
190
+ getTransientProperties() {
191
+ return this.transientProperties;
192
+ }
193
+ clearAllTransientProperties() {
194
+ this.transientProperties = [];
195
+ }
190
196
  };
191
197
 
192
198
  // src/TreeState.ts
@@ -423,159 +429,286 @@ function buildAliasMaps(aliases) {
423
429
  }
424
430
  return { publicToInternal, internalToPublic };
425
431
  }
426
- function toPublicObject(tree, id, internalToPublic) {
427
- const obj = {};
428
- for (const { key, value } of tree.getVertexProperties(id)) {
429
- const rule = internalToPublic.get(key);
430
- if (rule) {
431
- const converted = rule.toPublic ? rule.toPublic(value) : value;
432
- obj[rule.publicKey] = converted;
433
- } else {
434
- obj[key] = value;
435
- }
436
- }
437
- return obj;
438
- }
439
- var RESERVED_METHOD_USE_TRANSIENT = "useTransient";
440
- var RESERVED_METHOD_COMMIT_TRANSIENTS = "commitTransients";
441
432
  function bindVertex(tree, id, schemaOrOptions) {
442
433
  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"));
443
434
  const options = isOptions ? schemaOrOptions : { schema: schemaOrOptions };
444
435
  const schema = options.schema;
445
436
  const aliases = options.aliases ?? defaultAliases;
446
- const includeInternalKeys = options.includeInternalKeys ?? false;
447
437
  const { publicToInternal, internalToPublic } = buildAliasMaps(aliases);
448
- return new Proxy({}, {
449
- get(_target, prop) {
450
- if (prop === RESERVED_METHOD_USE_TRANSIENT) {
451
- return (fn) => {
452
- const transientProxy = new Proxy({}, {
453
- get(_t, p) {
454
- if (typeof p !== "string") return void 0;
455
- const rule2 = publicToInternal.get(p);
456
- if (rule2) {
457
- const raw = tree.getVertexProperty(id, rule2.internalKey);
458
- return rule2.toPublic ? rule2.toPublic(raw) : raw;
459
- }
460
- return tree.getVertexProperty(id, p);
461
- },
462
- set(_t, p, value) {
463
- if (typeof p !== "string") return true;
464
- if (schema?.shape && schema.shape[p]) {
465
- const field = schema.shape[p];
466
- if (field.safeParse) {
467
- const res = field.safeParse(value);
468
- if (!res.success) throw new Error(`Invalid value for ${String(p)}`);
469
- value = res.data ?? value;
470
- }
471
- } else if (schema?.safeParse) {
472
- const next = { ...toPublicObject(tree, id, internalToPublic), [p]: value };
473
- const res = schema.safeParse(next);
474
- if (!res.success) throw new Error(`Invalid value for ${String(p)}`);
475
- const parsed = res.data;
476
- if (parsed && Object.prototype.hasOwnProperty.call(parsed, p)) {
477
- value = parsed[p];
478
- }
479
- }
480
- const rule2 = publicToInternal.get(p);
481
- if (rule2) {
482
- const converted = rule2.toInternal ? rule2.toInternal(value) : value;
483
- tree.setTransientVertexProperty(id, rule2.internalKey, converted);
484
- return true;
485
- }
486
- tree.setTransientVertexProperty(id, p, value);
487
- return true;
488
- },
489
- deleteProperty(_t, p) {
490
- if (typeof p !== "string") return true;
491
- const rule2 = publicToInternal.get(p);
492
- if (rule2) {
493
- tree.setTransientVertexProperty(id, rule2.internalKey, void 0);
494
- return true;
495
- }
496
- tree.setTransientVertexProperty(id, p, void 0);
497
- return true;
498
- }
499
- });
500
- fn(transientProxy);
501
- };
502
- }
503
- if (prop === RESERVED_METHOD_COMMIT_TRANSIENTS) {
504
- return () => {
505
- const entries = tree.getVertexProperties(id);
506
- const currentPublic = schema?.safeParse ? toPublicObject(tree, id, internalToPublic) : void 0;
507
- for (const { key: internalKey, value: overlayValue } of entries) {
508
- const persistentValue = tree.getVertexProperty(id, internalKey, false);
509
- if (overlayValue === persistentValue) continue;
510
- const aliasRule = internalToPublic.get(internalKey);
511
- const publicKey = aliasRule ? aliasRule.publicKey : internalKey;
512
- const publicValue = aliasRule && aliasRule.toPublic ? aliasRule.toPublic(overlayValue) : overlayValue;
513
- let valueToPersistInternal = overlayValue;
514
- if (schema?.shape && schema.shape[publicKey]) {
515
- const field = schema.shape[publicKey];
516
- if (field.safeParse) {
517
- const res = field.safeParse(publicValue);
518
- if (!res.success) throw new Error(`Invalid value for ${String(publicKey)}`);
519
- const coerced = res.data;
520
- const maybeInternal = aliasRule && aliasRule.toInternal ? aliasRule.toInternal(coerced) : coerced;
521
- valueToPersistInternal = maybeInternal;
522
- }
523
- } else if (schema?.safeParse && currentPublic) {
524
- const nextPublic = { ...currentPublic, [publicKey]: publicValue };
525
- const res = schema.safeParse(nextPublic);
526
- if (!res.success) throw new Error("Invalid values for commitTransients");
527
- const parsed = res.data;
528
- const parsedPublic = Object.prototype.hasOwnProperty.call(parsed, publicKey) ? parsed[publicKey] : publicValue;
529
- const maybeInternal = aliasRule && aliasRule.toInternal ? aliasRule.toInternal(parsedPublic) : parsedPublic;
530
- valueToPersistInternal = maybeInternal;
438
+ const obj = {};
439
+ let isObserverUpdate = false;
440
+ const propsToDefine = /* @__PURE__ */ new Set();
441
+ if (schema?.shape) {
442
+ Object.keys(schema.shape).forEach((key) => propsToDefine.add(key));
443
+ aliases.forEach((alias) => propsToDefine.add(alias.publicKey));
444
+ }
445
+ const localValues = /* @__PURE__ */ new Map();
446
+ propsToDefine.forEach((publicKey) => {
447
+ const rule = publicToInternal.get(publicKey);
448
+ const internalKey = rule?.internalKey ?? publicKey;
449
+ const rawValue = tree.getVertexProperty(id, internalKey);
450
+ const publicValue = rule?.toPublic ? rule.toPublic(rawValue) : rawValue;
451
+ localValues.set(publicKey, publicValue);
452
+ });
453
+ propsToDefine.forEach((publicKey) => {
454
+ const rule = publicToInternal.get(publicKey);
455
+ const storageKey = `_${publicKey}_value`;
456
+ obj[storageKey] = localValues.get(publicKey);
457
+ Object.defineProperty(obj, publicKey, {
458
+ get: function() {
459
+ const internalKey = rule?.internalKey ?? publicKey;
460
+ const rawValue = tree.getVertexProperty(id, internalKey, true);
461
+ const publicValue = rule?.toPublic ? rule.toPublic(rawValue) : rawValue;
462
+ if (this[storageKey] !== publicValue) {
463
+ this[storageKey] = publicValue;
464
+ }
465
+ return publicValue;
466
+ },
467
+ set: function(value) {
468
+ if (schema?.shape && schema.shape[publicKey]) {
469
+ const field = schema.shape[publicKey];
470
+ if (field.safeParse) {
471
+ const res = field.safeParse(value);
472
+ if (!res.success) throw new Error(`Invalid value for ${publicKey}`);
473
+ value = res.data;
474
+ }
475
+ }
476
+ this[storageKey] = value;
477
+ if (!isObserverUpdate) {
478
+ const internalKey = rule?.internalKey ?? publicKey;
479
+ const internalValue = rule?.toInternal ? rule.toInternal(value) : value;
480
+ tree.setVertexProperty(id, internalKey, internalValue);
481
+ }
482
+ },
483
+ enumerable: true,
484
+ configurable: true
485
+ });
486
+ });
487
+ const cachedMethods = /* @__PURE__ */ new Map();
488
+ const getCachedMethod = (name, factory) => {
489
+ if (!cachedMethods.has(name)) {
490
+ cachedMethods.set(name, factory());
491
+ }
492
+ return cachedMethods.get(name);
493
+ };
494
+ Object.defineProperties(obj, {
495
+ $id: {
496
+ get: () => id,
497
+ set: () => {
498
+ },
499
+ // No-op setter (silently ignore)
500
+ enumerable: false,
501
+ configurable: true
502
+ },
503
+ $parentId: {
504
+ get: () => tree.getVertex(id)?.parentId ?? null,
505
+ set: () => {
506
+ },
507
+ // No-op setter
508
+ enumerable: false,
509
+ configurable: true
510
+ },
511
+ $parent: {
512
+ get: () => tree.getVertex(id)?.parent,
513
+ set: () => {
514
+ },
515
+ // No-op setter
516
+ enumerable: false,
517
+ configurable: true
518
+ },
519
+ $children: {
520
+ get: () => tree.getChildren(id),
521
+ set: () => {
522
+ },
523
+ // No-op setter
524
+ enumerable: false,
525
+ configurable: true
526
+ },
527
+ $childrenIds: {
528
+ get: () => tree.getChildrenIds(id),
529
+ set: () => {
530
+ },
531
+ // No-op setter
532
+ enumerable: false,
533
+ configurable: true
534
+ },
535
+ $moveTo: {
536
+ get: () => getCachedMethod("$moveTo", () => (parent) => {
537
+ const parentId = typeof parent === "object" && parent !== null ? parent.id || parent.$id : parent;
538
+ tree.moveVertex(id, parentId);
539
+ }),
540
+ set: () => {
541
+ },
542
+ // No-op setter
543
+ enumerable: false,
544
+ configurable: true
545
+ },
546
+ $delete: {
547
+ get: () => getCachedMethod("$delete", () => () => tree.deleteVertex(id)),
548
+ set: () => {
549
+ },
550
+ // No-op setter
551
+ enumerable: false,
552
+ configurable: true
553
+ },
554
+ $observe: {
555
+ get: () => getCachedMethod("$observe", () => (listener) => tree.observe(id, listener)),
556
+ set: () => {
557
+ },
558
+ // No-op setter
559
+ enumerable: false,
560
+ configurable: true
561
+ },
562
+ $observeChildren: {
563
+ get: () => getCachedMethod("$observeChildren", () => (listener) => {
564
+ return tree.observe(id, (events) => {
565
+ if (events.some((e) => e.type === "children")) {
566
+ listener(tree.getChildren(id));
567
+ }
568
+ });
569
+ }),
570
+ set: () => {
571
+ },
572
+ // No-op setter
573
+ enumerable: false,
574
+ configurable: true
575
+ },
576
+ $newChild: {
577
+ get: () => getCachedMethod("$newChild", () => (props) => {
578
+ const vertex = tree.getVertex(id);
579
+ return vertex?.newChild(props);
580
+ }),
581
+ set: () => {
582
+ },
583
+ // No-op setter
584
+ enumerable: false,
585
+ configurable: true
586
+ },
587
+ $newNamedChild: {
588
+ get: () => getCachedMethod("$newNamedChild", () => (name, props) => {
589
+ const vertex = tree.getVertex(id);
590
+ return vertex?.newNamedChild(name, props);
591
+ }),
592
+ set: () => {
593
+ },
594
+ // No-op setter
595
+ enumerable: false,
596
+ configurable: true
597
+ },
598
+ useTransient: {
599
+ value: function(fn) {
600
+ const transientProxy = new Proxy({}, {
601
+ set(_, prop, value) {
602
+ if (typeof prop === "string") {
603
+ const rule = publicToInternal.get(prop);
604
+ const internalKey = rule?.internalKey ?? prop;
605
+ const internalValue = rule?.toInternal ? rule.toInternal(value) : value;
606
+ tree.setTransientVertexProperty(id, internalKey, internalValue);
531
607
  }
532
- tree.setVertexProperty(id, internalKey, valueToPersistInternal);
608
+ return true;
609
+ },
610
+ get(_, prop) {
611
+ if (typeof prop !== "string") return void 0;
612
+ const rule = publicToInternal.get(prop);
613
+ const internalKey = rule?.internalKey ?? prop;
614
+ const rawValue = tree.getVertexProperty(id, internalKey, true);
615
+ return rule?.toPublic ? rule.toPublic(rawValue) : rawValue;
533
616
  }
534
- };
617
+ });
618
+ fn(transientProxy);
619
+ },
620
+ enumerable: false,
621
+ configurable: true
622
+ },
623
+ commitTransients: {
624
+ value: function() {
625
+ tree.commitTransients(id);
626
+ },
627
+ enumerable: false,
628
+ configurable: true
629
+ },
630
+ equals: {
631
+ value: function(other) {
632
+ if (other && typeof other === "object" && "$id" in other) {
633
+ return other.$id === id;
634
+ }
635
+ return obj === other;
636
+ },
637
+ enumerable: false,
638
+ configurable: true
639
+ }
640
+ });
641
+ tree.observe(id, (events) => {
642
+ isObserverUpdate = true;
643
+ for (const e of events) {
644
+ if (e.type === "property") {
645
+ const propEvent = e;
646
+ const rule = internalToPublic.get(propEvent.key);
647
+ if (rule) {
648
+ const publicKey = rule.publicKey;
649
+ const publicValue = rule.toPublic ? rule.toPublic(propEvent.value) : propEvent.value;
650
+ const storageKey = `_${publicKey}_value`;
651
+ if (storageKey in obj) {
652
+ obj[storageKey] = publicValue;
653
+ }
654
+ }
655
+ }
656
+ }
657
+ isObserverUpdate = false;
658
+ });
659
+ if (schema) {
660
+ return obj;
661
+ }
662
+ const proxy = new Proxy(obj, {
663
+ get(target, prop) {
664
+ if (typeof prop !== "string") {
665
+ return target[prop];
666
+ }
667
+ if (prop in target || prop.startsWith("$") || prop === "equals") {
668
+ return target[prop];
535
669
  }
536
- if (typeof prop !== "string") return void 0;
537
670
  const rule = publicToInternal.get(prop);
538
671
  if (rule) {
539
- const raw = tree.getVertexProperty(id, rule.internalKey);
540
- return rule.toPublic ? rule.toPublic(raw) : raw;
672
+ const internalKey = rule.internalKey;
673
+ const rawValue2 = tree.getVertexProperty(id, internalKey);
674
+ const result = rule.toPublic ? rule.toPublic(rawValue2) : rawValue2;
675
+ return result;
541
676
  }
542
- return tree.getVertexProperty(id, prop);
677
+ const rawValue = tree.getVertexProperty(id, prop);
678
+ return rawValue;
543
679
  },
544
- set(_target, prop, value) {
545
- if (typeof prop !== "string") return true;
546
- if (prop === RESERVED_METHOD_USE_TRANSIENT || prop === RESERVED_METHOD_COMMIT_TRANSIENTS) {
680
+ set(target, prop, value) {
681
+ if (typeof prop !== "string") {
682
+ target[prop] = value;
547
683
  return true;
548
684
  }
549
- if (schema?.shape && schema.shape[prop]) {
550
- const field = schema.shape[prop];
551
- if (field.safeParse) {
552
- const res = field.safeParse(value);
553
- if (!res.success) throw new Error(`Invalid value for ${String(prop)}`);
554
- value = res.data ?? value;
555
- }
556
- } else if (schema?.safeParse) {
557
- const next = { ...toPublicObject(tree, id, internalToPublic), [prop]: value };
558
- const res = schema.safeParse(next);
559
- if (!res.success) throw new Error(`Invalid value for ${String(prop)}`);
560
- const parsed = res.data;
561
- if (parsed && Object.prototype.hasOwnProperty.call(parsed, prop)) {
562
- value = parsed[prop];
563
- }
685
+ const descriptor = Object.getOwnPropertyDescriptor(target, prop);
686
+ if (descriptor && descriptor.set) {
687
+ target[prop] = value;
688
+ return true;
564
689
  }
565
690
  const rule = publicToInternal.get(prop);
566
691
  if (rule) {
567
- const converted = rule.toInternal ? rule.toInternal(value) : value;
568
- tree.setVertexProperty(id, rule.internalKey, converted);
692
+ const internalKey = rule.internalKey;
693
+ const internalValue = rule.toInternal ? rule.toInternal(value) : value;
694
+ tree.setVertexProperty(id, internalKey, internalValue);
569
695
  return true;
570
696
  }
571
697
  tree.setVertexProperty(id, prop, value);
572
698
  return true;
573
699
  },
574
- deleteProperty(_target, prop) {
575
- if (typeof prop !== "string") return true;
576
- if (prop === RESERVED_METHOD_USE_TRANSIENT || prop === RESERVED_METHOD_COMMIT_TRANSIENTS) {
700
+ deleteProperty(target, prop) {
701
+ if (typeof prop !== "string") {
702
+ delete target[prop];
577
703
  return true;
578
704
  }
705
+ const storageKey = `_${prop}_value`;
706
+ if (storageKey in target) {
707
+ delete target[storageKey];
708
+ }
709
+ if (prop in target) {
710
+ delete target[prop];
711
+ }
579
712
  const rule = publicToInternal.get(prop);
580
713
  if (rule) {
581
714
  tree.setVertexProperty(id, rule.internalKey, void 0);
@@ -583,27 +716,9 @@ function bindVertex(tree, id, schemaOrOptions) {
583
716
  }
584
717
  tree.setVertexProperty(id, prop, void 0);
585
718
  return true;
586
- },
587
- has(_target, prop) {
588
- if (typeof prop !== "string") return false;
589
- if (schema?.shape && Object.prototype.hasOwnProperty.call(schema.shape, prop)) return true;
590
- if (includeInternalKeys) {
591
- return publicToInternal.has(prop) || internalToPublic.has(prop);
592
- }
593
- return false;
594
- },
595
- ownKeys() {
596
- const keys = /* @__PURE__ */ new Set();
597
- for (const k of Object.keys(schema?.shape ?? {})) keys.add(k);
598
- if (includeInternalKeys) {
599
- for (const rule of aliases) keys.add(rule.internalKey);
600
- }
601
- return Array.from(keys);
602
- },
603
- getOwnPropertyDescriptor() {
604
- return { enumerable: true, configurable: true };
605
719
  }
606
720
  });
721
+ return proxy;
607
722
  }
608
723
 
609
724
  // src/Vertex.ts
@@ -681,6 +796,9 @@ var Vertex = class _Vertex {
681
796
  }
682
797
  this.tree.setTransientVertexProperty(this.id, key, value);
683
798
  }
799
+ commitTransients() {
800
+ this.tree.commitTransients(this.id);
801
+ }
684
802
  setProperties(props) {
685
803
  for (const [key, value] of Object.entries(props)) {
686
804
  this.setProperty(key, value);
@@ -1157,6 +1275,20 @@ var _RepTree = class _RepTree {
1157
1275
  this.localOps.push(op);
1158
1276
  this.applyProperty(op);
1159
1277
  }
1278
+ commitTransients(vertexId) {
1279
+ const vertex = this.state.getVertex(vertexId);
1280
+ if (!vertex) {
1281
+ return;
1282
+ }
1283
+ const transientProps = vertex.getTransientProperties();
1284
+ for (const prop of transientProps) {
1285
+ this.setVertexProperty(vertexId, prop.key, prop.value);
1286
+ }
1287
+ for (const prop of transientProps) {
1288
+ this.transientPropertiesAndTheirOpIds.delete(`${prop.key}@${vertexId}`);
1289
+ }
1290
+ vertex.clearAllTransientProperties();
1291
+ }
1160
1292
  setVertexProperty(vertexId, key, value) {
1161
1293
  this.lamportClock++;
1162
1294
  let opValue;