@korajs/core 1.0.0-beta.11 → 1.0.0-beta.13

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
@@ -1,23 +1,51 @@
1
1
  import {
2
2
  AppNotReadyError,
3
3
  ClockDriftError,
4
+ DEFAULT_OPERATION_HASH_VERSION,
4
5
  HybridLogicalClock,
5
6
  InvalidTimestampError,
6
7
  KORA_ERROR_FIX_SUGGESTIONS,
7
8
  KoraError,
8
9
  MAX_LOGICAL,
9
10
  MergeConflictError,
11
+ NonCanonicalValueError,
10
12
  OperationError,
13
+ OperationTooLargeError,
11
14
  RemoteClockDriftError,
12
15
  SchemaValidationError,
13
16
  StorageError,
14
17
  SyncError,
18
+ base64ToBytes,
19
+ bytesToBase64,
20
+ canonicalValue,
21
+ canonicalize,
22
+ canonicalizeLegacyOperation,
23
+ canonicalizeOperationBody,
24
+ canonicalizeProvenLegacyClear,
25
+ collectionIndexName,
26
+ compareStamps,
27
+ computeOperationId,
15
28
  createOperation,
29
+ decodeBytesFromOpData,
30
+ encodeBytesForOpData,
31
+ generateFullDDL,
32
+ generateSQL,
16
33
  getKoraErrorFix,
34
+ isAfter,
35
+ isKoraBytesValue,
36
+ isLegacyNumericByteObject,
17
37
  isValidOperation,
38
+ maxStamp,
39
+ minStamp,
40
+ planField,
41
+ quoteIdent,
42
+ sqlDefaultLiteral,
43
+ stampOf,
44
+ stampTimestamp,
18
45
  topologicalSort,
46
+ verifyOperationId,
19
47
  verifyOperationIntegrity
20
- } from "./chunk-5IICSH6H.js";
48
+ } from "./chunk-QUD3DMSD.js";
21
49
 
22
50
  // src/types.ts
23
51
  var MERGE_STRATEGIES = [
@@ -463,6 +491,44 @@ function toAtomicOp(sentinel) {
463
491
  }
464
492
 
465
493
  // src/operations/replay-record.ts
494
+ function mergeArraySet(local, remote, base) {
495
+ const serialize = (v) => JSON.stringify(v);
496
+ const baseSet = new Set(base.map(serialize));
497
+ const localSet = new Set(local.map(serialize));
498
+ const remoteSet = new Set(remote.map(serialize));
499
+ const keep = (s) => localSet.has(s) && remoteSet.has(s) || !baseSet.has(s) && (localSet.has(s) || remoteSet.has(s));
500
+ const seen = /* @__PURE__ */ new Set();
501
+ const result = [];
502
+ const addIfKept = (serialized, value) => {
503
+ if (!seen.has(serialized) && keep(serialized)) {
504
+ seen.add(serialized);
505
+ result.push(value);
506
+ }
507
+ };
508
+ for (const item of base) {
509
+ addIfKept(serialize(item), item);
510
+ }
511
+ const others = /* @__PURE__ */ new Map();
512
+ for (const item of [...local, ...remote]) {
513
+ const s = serialize(item);
514
+ if (!baseSet.has(s) && !others.has(s)) {
515
+ others.set(s, item);
516
+ }
517
+ }
518
+ for (const s of [...others.keys()].sort()) {
519
+ addIfKept(s, others.get(s));
520
+ }
521
+ return result;
522
+ }
523
+ function resolveArrayWrite(running, value, base) {
524
+ if (!Array.isArray(running) || !Array.isArray(value) || !Array.isArray(base)) {
525
+ return value;
526
+ }
527
+ if (canonicalize(running) === canonicalize(base)) {
528
+ return value;
529
+ }
530
+ return mergeArraySet(running, value, base);
531
+ }
466
532
  function replayOperationsForRecord(ops) {
467
533
  let record = null;
468
534
  let deleted = false;
@@ -485,6 +551,8 @@ function replayOperationsForRecord(ops) {
485
551
  const atomicOp = op2.atomicOps?.[field];
486
552
  if (atomicOp && lastWriterKind[field] === `atomic:${atomicOp.type}`) {
487
553
  next[field] = applyAtomicOp(next[field], atomicOp);
554
+ } else if (!atomicOp && op2.previousData && field in op2.previousData) {
555
+ next[field] = resolveArrayWrite(next[field], value, op2.previousData[field]);
488
556
  } else {
489
557
  next[field] = value;
490
558
  }
@@ -499,107 +567,1567 @@ function replayOperationsForRecord(ops) {
499
567
  break;
500
568
  }
501
569
  }
502
- return deleted ? null : record;
570
+ return deleted ? null : record;
571
+ }
572
+ function replayFieldVersionsForRecord(ops) {
573
+ let fields = null;
574
+ let deleted = false;
575
+ const first = ops[0];
576
+ const last = ops[ops.length - 1];
577
+ if (!first || !last) return null;
578
+ for (const op2 of ops) {
579
+ switch (op2.type) {
580
+ case "insert":
581
+ if (op2.data) {
582
+ fields = {};
583
+ for (const field of Object.keys(op2.data)) fields[field] = { ...op2.timestamp };
584
+ deleted = false;
585
+ }
586
+ break;
587
+ case "update":
588
+ if (op2.data) {
589
+ const next = { ...fields ?? {} };
590
+ for (const field of Object.keys(op2.data)) next[field] = { ...op2.timestamp };
591
+ fields = next;
592
+ deleted = false;
593
+ }
594
+ break;
595
+ case "delete":
596
+ deleted = true;
597
+ break;
598
+ }
599
+ }
600
+ if (deleted || fields === null) return null;
601
+ return { fields, created: { ...first.timestamp }, latest: { ...last.timestamp } };
602
+ }
603
+ function expandFieldVersionedOperations(ops) {
604
+ const expanded = [];
605
+ for (const op2 of ops) {
606
+ if (op2.type !== "insert" || !op2.fieldVersions || !op2.data) {
607
+ expanded.push(op2);
608
+ continue;
609
+ }
610
+ const { fieldVersions, ...base } = op2;
611
+ expanded.push(base);
612
+ const groups = /* @__PURE__ */ new Map();
613
+ let newest = op2.timestamp;
614
+ for (const v of Object.values(fieldVersions)) {
615
+ if (HybridLogicalClock.compare(v, newest) > 0) newest = v;
616
+ }
617
+ for (const [field, value] of Object.entries(op2.data)) {
618
+ const version = fieldVersions[field] ?? newest;
619
+ if (HybridLogicalClock.compare(version, op2.timestamp) <= 0) continue;
620
+ const key = `${version.wallTime}:${version.logical}:${version.nodeId}`;
621
+ const group = groups.get(key) ?? { timestamp: version, data: {} };
622
+ group.data[field] = value;
623
+ groups.set(key, group);
624
+ }
625
+ let index = 0;
626
+ for (const group of groups.values()) {
627
+ expanded.push({
628
+ ...base,
629
+ id: `${op2.id}#field-version-${index++}`,
630
+ type: "update",
631
+ data: group.data,
632
+ previousData: null,
633
+ timestamp: { ...group.timestamp }
634
+ });
635
+ }
636
+ }
637
+ return expanded.sort((a, b) => HybridLogicalClock.compare(a.timestamp, b.timestamp));
638
+ }
639
+
640
+ // src/migration/apply-operation-transforms.ts
641
+ var MAX_TRANSFORM_STEPS = 32;
642
+ function applyOperationTransforms(operation, targetSchemaVersion, transforms) {
643
+ let current = operation;
644
+ for (let step = 0; step < MAX_TRANSFORM_STEPS; step++) {
645
+ if (current.schemaVersion === targetSchemaVersion) {
646
+ return current;
647
+ }
648
+ const transform = transforms.find(
649
+ (candidate) => candidate.fromVersion === current.schemaVersion
650
+ );
651
+ if (!transform) {
652
+ return null;
653
+ }
654
+ const next = transform.transform(current);
655
+ if (next === null) {
656
+ return null;
657
+ }
658
+ current = next;
659
+ }
660
+ return null;
661
+ }
662
+
663
+ // src/migration/operation-view.ts
664
+ var OperationTransformError = class extends KoraError {
665
+ constructor(message, context) {
666
+ super(message, "INVALID_OPERATION_TRANSFORM", {
667
+ ...context,
668
+ fix: "An OperationTransform may rewrite data, previousData, atomicOps and schemaVersion only, deterministically, with JSON values."
669
+ });
670
+ this.name = "OperationTransformError";
671
+ }
672
+ };
673
+ function operationSchemaView(op2, targetSchemaVersion, transforms) {
674
+ if (op2.schemaVersion === targetSchemaVersion) return op2;
675
+ if (transforms === void 0 || transforms.length === 0) return op2;
676
+ if (op2.encrypted !== void 0) return op2;
677
+ let view;
678
+ try {
679
+ view = applyOperationTransforms(op2, targetSchemaVersion, transforms);
680
+ } catch (error) {
681
+ throw new OperationTransformError(
682
+ `The schema transform of operation ${op2.id} (schema v${op2.schemaVersion} -> v${targetSchemaVersion}) threw: ${error instanceof Error ? error.message : String(error)}`,
683
+ { operationId: op2.id, fromVersion: op2.schemaVersion, toVersion: targetSchemaVersion }
684
+ );
685
+ }
686
+ if (view === null || view === op2) return view;
687
+ for (const key of ["id", "nodeId", "type", "collection", "recordId"]) {
688
+ if (view[key] !== op2[key]) {
689
+ throw new OperationTransformError(
690
+ `The schema transform of operation ${op2.id} changed its ${key} ("${String(op2[key])}" -> "${String(view[key])}"). A transform may rewrite data, previousData, atomicOps and schemaVersion only.`,
691
+ { operationId: op2.id, field: key }
692
+ );
693
+ }
694
+ }
695
+ const a = op2.timestamp;
696
+ const b = view.timestamp;
697
+ if (a.wallTime !== b.wallTime || a.logical !== b.logical || a.nodeId !== b.nodeId) {
698
+ throw new OperationTransformError(
699
+ `The schema transform of operation ${op2.id} changed its timestamp. A transform may rewrite data, previousData, atomicOps and schemaVersion only.`,
700
+ { operationId: op2.id, field: "timestamp" }
701
+ );
702
+ }
703
+ try {
704
+ return canonicalizeOperationBody(view);
705
+ } catch (error) {
706
+ throw new OperationTransformError(
707
+ `The schema transform of operation ${op2.id} produced a value with no JSON form: ${error instanceof Error ? error.message : String(error)}`,
708
+ { operationId: op2.id }
709
+ );
710
+ }
711
+ }
712
+ function hashText(text) {
713
+ let hash = 2166136261;
714
+ for (let i = 0; i < text.length; i++) {
715
+ hash ^= text.charCodeAt(i);
716
+ hash = Math.imul(hash, 16777619) >>> 0;
717
+ }
718
+ return hash.toString(16).padStart(8, "0");
719
+ }
720
+ function operationTransformsFingerprint(targetSchemaVersion, transforms) {
721
+ if (transforms === void 0 || transforms.length === 0) return "";
722
+ const parts = transforms.map((t2) => `${t2.fromVersion}>${t2.toVersion}:${hashText(String(t2.transform))}`).sort();
723
+ return `xf@v${targetSchemaVersion}(${parts.join(",")})`;
724
+ }
725
+ var OperationTransformCoverageError = class extends KoraError {
726
+ constructor(message, versions, targetSchemaVersion, context = {}) {
727
+ super(message, "OPERATION_TRANSFORM_MISSING", {
728
+ ...context,
729
+ versions: [...versions],
730
+ targetSchemaVersion,
731
+ fix: `Keep a transform path registered from every schema version in the operation log to v${targetSchemaVersion} (a transform must stay registered as long as operations of its source version exist, and the log is append-only), or register no transforms at all (operations then fold as written).`
732
+ });
733
+ this.versions = versions;
734
+ this.targetSchemaVersion = targetSchemaVersion;
735
+ this.name = "OperationTransformCoverageError";
736
+ }
737
+ versions;
738
+ targetSchemaVersion;
739
+ };
740
+ function missingTransformPaths(storedVersions, targetSchemaVersion, transforms) {
741
+ if (transforms === void 0 || transforms.length === 0) return [];
742
+ const missing = [];
743
+ for (const version of new Set(storedVersions)) {
744
+ if (version === targetSchemaVersion) continue;
745
+ let current = version;
746
+ let reached = false;
747
+ for (let step = 0; step < 32; step++) {
748
+ if (current === targetSchemaVersion) {
749
+ reached = true;
750
+ break;
751
+ }
752
+ const next = transforms.find((candidate) => candidate.fromVersion === current);
753
+ if (!next) break;
754
+ current = next.toVersion;
755
+ }
756
+ if (!reached) missing.push({ version, missingFrom: current });
757
+ }
758
+ return missing.sort((a, b) => a.version - b.version);
759
+ }
760
+ function assertOperationTransformCoverage(storedVersions, targetSchemaVersion, transforms, replica) {
761
+ const missing = missingTransformPaths(storedVersions, targetSchemaVersion, transforms);
762
+ if (missing.length === 0) return;
763
+ const versions = missing.map((entry) => entry.version);
764
+ const steps = [...new Set(missing.map((entry) => entry.missingFrom))];
765
+ const plural = versions.length > 1;
766
+ throw new OperationTransformCoverageError(
767
+ `The ${replica} holds operations of schema version${plural ? "s" : ""} ${versions.map((v) => `v${v}`).join(", ")}, but the registered operationTransforms have no path from ${plural ? "them" : "it"} to the current schema v${targetSchemaVersion} (no transform from ${steps.map((v) => `v${v}`).join(", ")}). Starting would silently drop those operations from their records, so it is refused. Keep the retired transform(s) registered: a transform may be removed only after every operation of its source version has been folded into snapshots by an explicit compaction.`,
768
+ versions,
769
+ targetSchemaVersion,
770
+ { replica, missingFrom: steps }
771
+ );
772
+ }
773
+
774
+ // src/fold/authority.ts
775
+ var RESERVED_NODE_ID_PREFIX = "kora:";
776
+ var SERVER_NODE_ID_PREFIX = "kora:server:";
777
+ function isReservedNodeId(nodeId) {
778
+ return nodeId.startsWith(RESERVED_NODE_ID_PREFIX);
779
+ }
780
+ function isServerNodeId(nodeId) {
781
+ return nodeId.startsWith(SERVER_NODE_ID_PREFIX) && nodeId.length > SERVER_NODE_ID_PREFIX.length;
782
+ }
783
+ function isAuthoritativeNodeId(nodeId, explicit) {
784
+ return isServerNodeId(nodeId) || (explicit?.has(nodeId) ?? false);
785
+ }
786
+
787
+ // src/fold/errors.ts
788
+ var FoldStateError = class extends KoraError {
789
+ constructor(message, context) {
790
+ super(message, "FOLD_STATE_INVALID", {
791
+ fix: "Rebuild the record state from its operation log with foldRecord(ops, schema).",
792
+ ...context
793
+ });
794
+ this.name = "FoldStateError";
795
+ }
796
+ };
797
+ var FoldConfigurationError = class extends KoraError {
798
+ constructor(message, context) {
799
+ super(message, "FOLD_CONFIGURATION", context);
800
+ this.name = "FoldConfigurationError";
801
+ }
802
+ };
803
+
804
+ // src/fold/values.ts
805
+ function isPlainObject(value) {
806
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
807
+ const proto = Object.getPrototypeOf(value);
808
+ return proto === null || proto === Object.prototype;
809
+ }
810
+ function normalizeValue(value) {
811
+ if (value instanceof Uint8Array) return { $koraBytes: bytesToBase64(value) };
812
+ if (value instanceof ArrayBuffer) return { $koraBytes: bytesToBase64(new Uint8Array(value)) };
813
+ if (Array.isArray(value)) return value.map(normalizeValue);
814
+ if (isPlainObject(value)) {
815
+ const out = {};
816
+ for (const [key, member] of Object.entries(value)) {
817
+ if (member !== void 0) out[key] = normalizeValue(member);
818
+ }
819
+ return out;
820
+ }
821
+ return value;
822
+ }
823
+ function canonicalKey(value) {
824
+ return canonicalize(normalizeValue(value));
825
+ }
826
+
827
+ // src/fold/field-states.ts
828
+ function emptyField(plan) {
829
+ switch (plan.kind) {
830
+ case "reg":
831
+ return { k: "reg", e: [], val: void 0 };
832
+ case "res":
833
+ return { k: "res", e: [], val: void 0 };
834
+ case "set":
835
+ return { k: "set", ao: plan.appendOnly, sh: null, clr: null, el: {} };
836
+ case "map":
837
+ return { k: "map", sh: null, clr: null, keys: {} };
838
+ case "ctr":
839
+ return { k: "ctr", base: null, d: [], val: void 0 };
840
+ case "max":
841
+ case "min":
842
+ return { k: plan.kind, best: null, reg: null };
843
+ case "rt":
844
+ return { k: "rt", reset: null, u: {} };
845
+ }
846
+ }
847
+ function entryKind(entry) {
848
+ return entry.a ? entry.a.type : "set";
849
+ }
850
+ function chainFold(entries) {
851
+ let value;
852
+ let last = null;
853
+ for (const entry of entries) {
854
+ value = entry.a && last === entry.a.type ? applyAtomicOp(value, entry.a) : entry.v;
855
+ last = entryKind(entry);
856
+ }
857
+ return value;
858
+ }
859
+ function insertionIndex(entries, stamp) {
860
+ let lo = 0;
861
+ let hi = entries.length;
862
+ while (lo < hi) {
863
+ const mid = lo + hi >>> 1;
864
+ const entry = entries[mid];
865
+ const cmp = compareStamps(entry.s, stamp);
866
+ if (cmp === 0) return -1;
867
+ if (cmp < 0) lo = mid + 1;
868
+ else hi = mid;
869
+ }
870
+ return lo;
871
+ }
872
+ function pruneChain(entries) {
873
+ for (let i = entries.length - 1; i > 0; i--) {
874
+ if (!entries[i].a) return entries.slice(i);
875
+ }
876
+ return entries;
877
+ }
878
+ function toEntry(write) {
879
+ return write.a ? { s: write.s, v: write.v, a: write.a } : { s: write.s, v: write.v };
880
+ }
881
+ function applyRegister(state, write) {
882
+ const entries = state.e;
883
+ const index = insertionIndex(entries, write.s);
884
+ if (index === -1) return { state, changed: false };
885
+ const first = entries[0];
886
+ if (index === 0 && first !== void 0 && !first.a) return { state, changed: false };
887
+ const entry = toEntry(write);
888
+ if (index === entries.length) {
889
+ if (!entry.a) return { state: { k: "reg", e: [entry], val: entry.v }, changed: true };
890
+ const last = entries[entries.length - 1];
891
+ const val = last !== void 0 && entryKind(last) === entry.a.type ? applyAtomicOp(state.val, entry.a) : entry.v;
892
+ return { state: { k: "reg", e: [...entries, entry], val }, changed: true };
893
+ }
894
+ const next = pruneChain([...entries.slice(0, index), entry, ...entries.slice(index)]);
895
+ return { state: { k: "reg", e: next, val: chainFold(next) }, changed: true };
896
+ }
897
+ function mergeLogs(a, b) {
898
+ const out = [];
899
+ let i = 0;
900
+ let j = 0;
901
+ while (i < a.length || j < b.length) {
902
+ const x = a[i];
903
+ const y = b[j];
904
+ if (y === void 0 || x !== void 0 && compareStamps(x.s, y.s) < 0) {
905
+ out.push(x);
906
+ i++;
907
+ } else if (x === void 0 || compareStamps(x.s, y.s) > 0) {
908
+ out.push(y);
909
+ j++;
910
+ } else {
911
+ out.push(x);
912
+ i++;
913
+ j++;
914
+ }
915
+ }
916
+ return out;
917
+ }
918
+ function resolveStep(plan, current, entry) {
919
+ try {
920
+ return { value: canonicalValue(plan.resolver?.(current, entry.v, entry.b), "resolver") };
921
+ } catch (error) {
922
+ return { value: entry.v, error: error instanceof Error ? error.message : String(error) };
923
+ }
924
+ }
925
+ function resolverFold(plan, entries) {
926
+ let value;
927
+ let error;
928
+ entries.forEach((entry, index) => {
929
+ if (index === 0) {
930
+ value = entry.v;
931
+ return;
932
+ }
933
+ const step = resolveStep(plan, value, entry);
934
+ value = step.value;
935
+ if (step.error !== void 0) error = step.error;
936
+ });
937
+ return error === void 0 ? { value } : { value, error };
938
+ }
939
+ function pruneResolverLog(entries) {
940
+ for (let i = entries.length - 1; i > 0; i--) {
941
+ if (entries[i].z === 1) return entries.slice(i);
942
+ }
943
+ return entries;
944
+ }
945
+ function resolverState(entries, result) {
946
+ return result.error === void 0 ? { k: "res", e: entries, val: result.value } : { k: "res", e: entries, val: result.value, err: result.error };
947
+ }
948
+ function applyResolver(state, write, plan) {
949
+ const index = insertionIndex(state.e, write.s);
950
+ if (index === -1) return { state, changed: false };
951
+ if (index === 0 && state.e[0]?.z === 1) return { state, changed: false };
952
+ const entry = { s: write.s, v: write.v };
953
+ if (write.insert) entry.b = null;
954
+ else if (write.hasPrev) entry.b = write.prev;
955
+ if (index === state.e.length) {
956
+ const entries2 = [...state.e, entry];
957
+ if (state.e.length === 0)
958
+ return { state: resolverState(entries2, { value: entry.v }), changed: true };
959
+ const step = resolveStep(plan, state.val, entry);
960
+ const next = step.error !== void 0 ? { k: "res", e: entries2, val: step.value, err: step.error } : state.err !== void 0 ? { k: "res", e: entries2, val: step.value, err: state.err } : { k: "res", e: entries2, val: step.value };
961
+ return {
962
+ state: next,
963
+ changed: true,
964
+ ...step.error !== void 0 ? { error: step.error } : {}
965
+ };
966
+ }
967
+ const entries = [...state.e.slice(0, index), entry, ...state.e.slice(index)];
968
+ const result = resolverFold(plan, entries);
969
+ return { state: resolverState(entries, result), changed: true };
970
+ }
971
+ function laterShape(current, next) {
972
+ return current === null || compareStamps(next.s, current.s) > 0 ? next : null;
973
+ }
974
+ function firstAddBefore(a, b) {
975
+ if (a === null) return true;
976
+ const cmp = compareStamps(b.s, a.s);
977
+ return cmp < 0 || cmp === 0 && b.i < a.i;
978
+ }
979
+ function elementKey(canonicalValue2, occurrence) {
980
+ return `${canonicalValue2}#${occurrence}`;
981
+ }
982
+ function elementWriter(base) {
983
+ let el = base;
984
+ let changed = false;
985
+ const read = (value, occurrence) => {
986
+ const canonical = canonicalKey(value);
987
+ const key = elementKey(canonical, occurrence);
988
+ const existing = el[key];
989
+ return [
990
+ key,
991
+ existing ? { ...existing } : { v: JSON.parse(canonical), n: occurrence, a: null, f: null, r: null }
992
+ ];
993
+ };
994
+ const store = (key, element) => {
995
+ if (el === base) el = { ...base };
996
+ el[key] = element;
997
+ changed = true;
998
+ };
999
+ return {
1000
+ add(value, occurrence, stamp, index) {
1001
+ const [key, element] = read(value, occurrence);
1002
+ let touched = false;
1003
+ if (isAfter(stamp, element.a)) {
1004
+ element.a = stamp;
1005
+ touched = true;
1006
+ }
1007
+ const first = { s: stamp, i: index };
1008
+ if (firstAddBefore(element.f, first)) {
1009
+ element.f = first;
1010
+ touched = true;
1011
+ }
1012
+ if (touched) store(key, element);
1013
+ },
1014
+ remove(value, occurrence, stamp) {
1015
+ const [key, element] = read(value, occurrence);
1016
+ if (isAfter(stamp, element.r)) {
1017
+ element.r = stamp;
1018
+ store(key, element);
1019
+ }
1020
+ },
1021
+ result: () => ({ el, changed })
1022
+ };
1023
+ }
1024
+ function countOccurrences(items) {
1025
+ const counts = /* @__PURE__ */ new Map();
1026
+ const occurrences = items.map((item) => {
1027
+ const canonical = canonicalKey(item);
1028
+ const k = counts.get(canonical) ?? 0;
1029
+ counts.set(canonical, k + 1);
1030
+ return { item, canonical, k };
1031
+ });
1032
+ return { counts, occurrences };
1033
+ }
1034
+ function applyElementSet(state, write) {
1035
+ const writer = elementWriter(state.el);
1036
+ let sh = state.sh;
1037
+ let clr = state.clr;
1038
+ let changed = false;
1039
+ const setShape = (arr, value) => {
1040
+ const next = arr ? { s: write.s, arr } : { s: write.s, arr, v: value };
1041
+ const later = laterShape(sh, next);
1042
+ if (later) {
1043
+ sh = later;
1044
+ changed = true;
1045
+ }
1046
+ };
1047
+ const atomic = write.a;
1048
+ const prev = !write.insert && Array.isArray(write.prev) ? write.prev : null;
1049
+ const atomicElement = atomic !== void 0 && (atomic.type === "append" || atomic.type === "remove");
1050
+ if (atomicElement && (prev === null || !Array.isArray(write.v))) {
1051
+ setShape(true);
1052
+ if (atomic.type === "append") writer.add(atomic.value, 0, write.s, 0);
1053
+ else if (!state.ao) writer.remove(atomic.value, 0, write.s);
1054
+ } else if (!Array.isArray(write.v)) {
1055
+ setShape(false, write.v);
1056
+ const nextClr = maxStamp(clr, write.s);
1057
+ if (nextClr !== clr) {
1058
+ clr = nextClr;
1059
+ changed = true;
1060
+ }
1061
+ } else {
1062
+ setShape(true);
1063
+ const next = countOccurrences(write.v);
1064
+ const before = prev ? countOccurrences(prev) : null;
1065
+ next.occurrences.forEach(({ item, canonical, k }, index) => {
1066
+ if (k >= (before?.counts.get(canonical) ?? 0)) writer.add(item, k, write.s, index);
1067
+ });
1068
+ if (before && !state.ao) {
1069
+ for (const { item, canonical, k } of before.occurrences) {
1070
+ if (k >= (next.counts.get(canonical) ?? 0)) writer.remove(item, k, write.s);
1071
+ }
1072
+ }
1073
+ }
1074
+ const elements = writer.result();
1075
+ if (!changed && !elements.changed) return { state, changed: false };
1076
+ return { state: { k: "set", ao: state.ao, sh, clr, el: elements.el }, changed: true };
1077
+ }
1078
+ function isElementPresent(element, state) {
1079
+ if (element.a === null || element.f === null) return false;
1080
+ if (state.ao) return true;
1081
+ return isAfter(element.a, element.r) && isAfter(element.a, state.clr);
1082
+ }
1083
+ function materializeElementSet(state) {
1084
+ if (state.sh === null) return void 0;
1085
+ if (!state.sh.arr) return state.sh.v;
1086
+ const present = Object.entries(state.el).filter(([, element]) => isElementPresent(element, state));
1087
+ present.sort(([ka, a], [kb, b]) => {
1088
+ const fa = a.f;
1089
+ const fb = b.f;
1090
+ return compareStamps(fa.s, fb.s) || fa.i - fb.i || (ka < kb ? -1 : ka > kb ? 1 : 0);
1091
+ });
1092
+ return present.map(([, element]) => element.v);
1093
+ }
1094
+ function applyKeyMap(state, write) {
1095
+ let sh = state.sh;
1096
+ let clr = state.clr;
1097
+ let keys = state.keys;
1098
+ let changed = false;
1099
+ const setKey = (key, next) => {
1100
+ const current = keys[key];
1101
+ if (current === void 0 || compareStamps(next.s, current.s) > 0) {
1102
+ if (keys === state.keys) keys = { ...state.keys };
1103
+ keys[key] = next;
1104
+ changed = true;
1105
+ }
1106
+ };
1107
+ if (!isPlainObject(write.v)) {
1108
+ const later = laterShape(sh, { s: write.s, obj: false, v: write.v });
1109
+ if (later) {
1110
+ sh = later;
1111
+ changed = true;
1112
+ }
1113
+ const nextClr = maxStamp(clr, write.s);
1114
+ if (nextClr !== clr) {
1115
+ clr = nextClr;
1116
+ changed = true;
1117
+ }
1118
+ } else {
1119
+ const later = laterShape(sh, { s: write.s, obj: true });
1120
+ if (later) {
1121
+ sh = later;
1122
+ changed = true;
1123
+ }
1124
+ const prev = !write.insert && isPlainObject(write.prev) ? write.prev : null;
1125
+ for (const [key, value] of Object.entries(write.v)) {
1126
+ if (prev && key in prev && canonicalKey(prev[key]) === canonicalKey(value)) continue;
1127
+ setKey(key, { s: write.s, del: false, v: value });
1128
+ }
1129
+ if (prev) {
1130
+ for (const key of Object.keys(prev)) {
1131
+ if (!(key in write.v)) setKey(key, { s: write.s, del: true });
1132
+ }
1133
+ }
1134
+ }
1135
+ if (!changed) return { state, changed: false };
1136
+ return { state: { k: "map", sh, clr, keys }, changed: true };
1137
+ }
1138
+ function materializeKeyMap(state) {
1139
+ if (state.sh === null) return void 0;
1140
+ if (!state.sh.obj) return state.sh.v;
1141
+ const out = {};
1142
+ for (const key of Object.keys(state.keys).sort()) {
1143
+ const entry = state.keys[key];
1144
+ if (entry.del || !isAfter(entry.s, state.clr)) continue;
1145
+ out[key] = entry.v;
1146
+ }
1147
+ return out;
1148
+ }
1149
+ function counterDelta(write) {
1150
+ if (write.a?.type === "increment" && typeof write.a.value === "number") return write.a.value;
1151
+ if (!write.insert && !write.a && typeof write.v === "number" && typeof write.prev === "number") {
1152
+ return write.v - write.prev;
1153
+ }
1154
+ return null;
1155
+ }
1156
+ function deltaIndex(deltas, stamp) {
1157
+ let lo = 0;
1158
+ let hi = deltas.length;
1159
+ while (lo < hi) {
1160
+ const mid = lo + hi >>> 1;
1161
+ const cmp = compareStamps(deltas[mid].s, stamp);
1162
+ if (cmp === 0) return -1;
1163
+ if (cmp < 0) lo = mid + 1;
1164
+ else hi = mid;
1165
+ }
1166
+ return lo;
1167
+ }
1168
+ function counterValue(base, deltas) {
1169
+ if (deltas.length === 0) return base?.v;
1170
+ let sum = typeof base?.v === "number" ? base.v : 0;
1171
+ for (const delta of deltas) sum += delta.n;
1172
+ return sum;
1173
+ }
1174
+ function counterState(base, d) {
1175
+ return { k: "ctr", base, d, val: counterValue(base, d) };
1176
+ }
1177
+ function pruneDeltas(d, base) {
1178
+ if (base === null) return [...d];
1179
+ return d.filter((delta) => compareStamps(delta.s, base.s) > 0);
1180
+ }
1181
+ function applyCounter(state, write) {
1182
+ const n = counterDelta(write);
1183
+ if (n !== null) {
1184
+ if (state.base !== null && compareStamps(write.s, state.base.s) < 0) {
1185
+ return { state, changed: false };
1186
+ }
1187
+ const index = deltaIndex(state.d, write.s);
1188
+ if (index === -1) return { state, changed: false };
1189
+ const delta = { s: write.s, n };
1190
+ if (index === state.d.length) {
1191
+ const val = state.d.length === 0 ? counterValue(state.base, [delta]) : state.val + n;
1192
+ return { state: { k: "ctr", base: state.base, d: [...state.d, delta], val }, changed: true };
1193
+ }
1194
+ const d = [...state.d.slice(0, index), delta, ...state.d.slice(index)];
1195
+ return { state: counterState(state.base, d), changed: true };
1196
+ }
1197
+ if (state.base !== null && compareStamps(write.s, state.base.s) <= 0) {
1198
+ return { state, changed: false };
1199
+ }
1200
+ const base = { s: write.s, v: write.v };
1201
+ return { state: counterState(base, pruneDeltas(state.d, base)), changed: true };
1202
+ }
1203
+ function mergeDeltas(a, b) {
1204
+ const out = [];
1205
+ let i = 0;
1206
+ let j = 0;
1207
+ while (i < a.length || j < b.length) {
1208
+ const x = a[i];
1209
+ const y = b[j];
1210
+ if (y === void 0 || x !== void 0 && compareStamps(x.s, y.s) < 0) {
1211
+ out.push(x);
1212
+ i++;
1213
+ } else if (x === void 0 || compareStamps(x.s, y.s) > 0) {
1214
+ out.push(y);
1215
+ j++;
1216
+ } else {
1217
+ out.push(x);
1218
+ i++;
1219
+ j++;
1220
+ }
1221
+ }
1222
+ return out;
1223
+ }
1224
+ function betterExtremum(kind, current, next) {
1225
+ if (current === null) return true;
1226
+ if (next.v !== current.v) return kind === "max" ? next.v > current.v : next.v < current.v;
1227
+ return compareStamps(next.s, current.s) > 0;
1228
+ }
1229
+ function applyExtremum(state, write) {
1230
+ if (typeof write.v === "number" && !Number.isNaN(write.v)) {
1231
+ const next = { s: write.s, v: write.v };
1232
+ if (!betterExtremum(state.k, state.best, next)) return { state, changed: false };
1233
+ return { state: { k: state.k, best: next, reg: state.reg }, changed: true };
1234
+ }
1235
+ if (state.reg !== null && compareStamps(write.s, state.reg.s) <= 0) {
1236
+ return { state, changed: false };
1237
+ }
1238
+ return { state: { k: state.k, best: state.best, reg: { s: write.s, v: write.v } }, changed: true };
1239
+ }
1240
+ function decodeRichtext(value) {
1241
+ if (value === null || value === void 0) return { reset: null };
1242
+ try {
1243
+ const decoded = decodeBytesFromOpData(value);
1244
+ if (typeof decoded === "string") return { reset: decoded };
1245
+ return { update: bytesToBase64(decoded) };
1246
+ } catch {
1247
+ return { reset: value };
1248
+ }
1249
+ }
1250
+ function applyRichtext(state, write, subsumes) {
1251
+ const decoded = decodeRichtext(write.v);
1252
+ if ("reset" in decoded) {
1253
+ if (state.reset !== null && compareStamps(write.s, state.reset.s) <= 0) {
1254
+ return { state, changed: false };
1255
+ }
1256
+ return {
1257
+ state: { k: "rt", reset: { s: write.s, v: decoded.reset }, u: state.u },
1258
+ changed: true
1259
+ };
1260
+ }
1261
+ const current = state.u[decoded.update];
1262
+ if (current !== void 0 && compareStamps(write.s, current) <= 0) {
1263
+ return { state, changed: false };
1264
+ }
1265
+ if (subsumes === void 0) {
1266
+ return {
1267
+ state: { k: "rt", reset: state.reset, u: { ...state.u, [decoded.update]: write.s } },
1268
+ changed: true
1269
+ };
1270
+ }
1271
+ const incoming = base64ToBytes(decoded.update);
1272
+ const u = {};
1273
+ for (const [update, stamp] of Object.entries(state.u)) {
1274
+ if (update === decoded.update) continue;
1275
+ const bytes = base64ToBytes(update);
1276
+ if (compareStamps(write.s, stamp) <= 0 && subsumes(incoming, bytes)) {
1277
+ return { state, changed: false };
1278
+ }
1279
+ if (compareStamps(stamp, write.s) <= 0 && subsumes(bytes, incoming)) continue;
1280
+ u[update] = stamp;
1281
+ }
1282
+ u[decoded.update] = write.s;
1283
+ return { state: { k: "rt", reset: state.reset, u }, changed: true };
1284
+ }
1285
+ function materializeRichtext(state, merger, field) {
1286
+ const live = Object.entries(state.u).filter(([, stamp]) => isAfter(stamp, state.reset?.s ?? null)).map(([update]) => update).sort();
1287
+ if (live.length === 0) return state.reset ? state.reset.v : void 0;
1288
+ if (live.length === 1) return { $koraBytes: live[0] };
1289
+ if (!merger) {
1290
+ throw new FoldConfigurationError(
1291
+ `Richtext field "${field}" has ${live.length} concurrent Yjs updates and no richtext merger was given.`,
1292
+ {
1293
+ field,
1294
+ updates: live.length,
1295
+ fix: "Pass { richtext: mergeYjsUpdates } (from @korajs/store) in the fold options."
1296
+ }
1297
+ );
1298
+ }
1299
+ return { $koraBytes: bytesToBase64(merger(live.map((update) => base64ToBytes(update)))) };
1300
+ }
1301
+ function applyFieldWrite(state, write, plan, subsumes) {
1302
+ switch (state.k) {
1303
+ case "reg":
1304
+ return applyRegister(state, write);
1305
+ case "res":
1306
+ return applyResolver(state, write, plan);
1307
+ case "set":
1308
+ return applyElementSet(state, write);
1309
+ case "map":
1310
+ return applyKeyMap(state, write);
1311
+ case "ctr":
1312
+ return applyCounter(state, write);
1313
+ case "max":
1314
+ case "min":
1315
+ return applyExtremum(state, write);
1316
+ case "rt":
1317
+ return applyRichtext(state, write, subsumes);
1318
+ }
1319
+ }
1320
+ function materializeField(state, field, merger) {
1321
+ switch (state.k) {
1322
+ case "reg":
1323
+ case "res":
1324
+ return state.e.length === 0 ? void 0 : state.val;
1325
+ case "set":
1326
+ return materializeElementSet(state);
1327
+ case "map":
1328
+ return materializeKeyMap(state);
1329
+ case "ctr":
1330
+ return state.val;
1331
+ case "max":
1332
+ case "min":
1333
+ return state.best !== null ? state.best.v : state.reg?.v;
1334
+ case "rt":
1335
+ return materializeRichtext(state, merger, field);
1336
+ }
1337
+ }
1338
+ function fieldStamp(state) {
1339
+ switch (state.k) {
1340
+ case "reg":
1341
+ case "res":
1342
+ return state.e[state.e.length - 1]?.s ?? null;
1343
+ case "set": {
1344
+ let newest = maxStamp(state.sh?.s ?? null, state.clr);
1345
+ for (const element of Object.values(state.el)) {
1346
+ newest = maxStamp(maxStamp(newest, element.a), element.r);
1347
+ }
1348
+ return newest;
1349
+ }
1350
+ case "map": {
1351
+ let newest = maxStamp(state.sh?.s ?? null, state.clr);
1352
+ for (const entry of Object.values(state.keys)) newest = maxStamp(newest, entry.s);
1353
+ return newest;
1354
+ }
1355
+ case "ctr": {
1356
+ return maxStamp(state.base?.s ?? null, state.d[state.d.length - 1]?.s ?? null);
1357
+ }
1358
+ case "max":
1359
+ case "min":
1360
+ return maxStamp(state.best?.s ?? null, state.reg?.s ?? null);
1361
+ case "rt": {
1362
+ let newest = state.reset?.s ?? null;
1363
+ for (const stamp of Object.values(state.u)) newest = maxStamp(newest, stamp);
1364
+ return newest;
1365
+ }
1366
+ }
1367
+ }
1368
+ function laterOf(a, b) {
1369
+ if (a === null) return b;
1370
+ if (b === null) return a;
1371
+ return compareStamps(a.s, b.s) >= 0 ? a : b;
1372
+ }
1373
+ function joinElements(a, b) {
1374
+ const out = { ...a };
1375
+ for (const [key, y] of Object.entries(b)) {
1376
+ const x = out[key];
1377
+ if (x === void 0) {
1378
+ out[key] = y;
1379
+ continue;
1380
+ }
1381
+ out[key] = {
1382
+ v: x.v,
1383
+ n: x.n,
1384
+ a: maxStamp(x.a, y.a),
1385
+ f: y.f !== null && firstAddBefore(x.f, y.f) ? y.f : x.f,
1386
+ r: maxStamp(x.r, y.r)
1387
+ };
1388
+ }
1389
+ return out;
1390
+ }
1391
+ function joinFieldStates(a, b, plan) {
1392
+ if (a.k !== b.k) return a;
1393
+ switch (a.k) {
1394
+ case "reg": {
1395
+ const entries = pruneChain(mergeLogs(a.e, b.e));
1396
+ return { k: "reg", e: entries, val: chainFold(entries) };
1397
+ }
1398
+ case "res": {
1399
+ const entries = pruneResolverLog(mergeLogs(a.e, b.e));
1400
+ return resolverState(entries, resolverFold(plan, entries));
1401
+ }
1402
+ case "set": {
1403
+ const other = b;
1404
+ return {
1405
+ k: "set",
1406
+ ao: a.ao,
1407
+ sh: laterOf(a.sh, other.sh),
1408
+ clr: maxStamp(a.clr, other.clr),
1409
+ el: joinElements(a.el, other.el)
1410
+ };
1411
+ }
1412
+ case "map": {
1413
+ const other = b;
1414
+ const keys = { ...a.keys };
1415
+ for (const [key, entry] of Object.entries(other.keys)) {
1416
+ const current = keys[key];
1417
+ if (current === void 0 || compareStamps(entry.s, current.s) > 0) keys[key] = entry;
1418
+ }
1419
+ return { k: "map", sh: laterOf(a.sh, other.sh), clr: maxStamp(a.clr, other.clr), keys };
1420
+ }
1421
+ case "ctr": {
1422
+ const other = b;
1423
+ const base = laterOf(a.base, other.base);
1424
+ return counterState(base, pruneDeltas(mergeDeltas(a.d, other.d), base));
1425
+ }
1426
+ case "max":
1427
+ case "min": {
1428
+ const other = b;
1429
+ let best = a.best;
1430
+ if (other.best !== null && betterExtremum(a.k, best, other.best)) best = other.best;
1431
+ return { k: a.k, best, reg: laterOf(a.reg, other.reg) };
1432
+ }
1433
+ case "rt": {
1434
+ const other = b;
1435
+ const u = { ...a.u };
1436
+ for (const [update, stamp] of Object.entries(other.u)) {
1437
+ u[update] = maxStamp(u[update] ?? null, stamp);
1438
+ }
1439
+ return { k: "rt", reset: laterOf(a.reset, other.reset), u };
1440
+ }
1441
+ }
1442
+ }
1443
+
1444
+ // src/fold/types.ts
1445
+ var FOLD_STATE_VERSION = 2;
1446
+
1447
+ // src/fold/serialize.ts
1448
+ var FIELD_KINDS = /* @__PURE__ */ new Set(["reg", "set", "map", "ctr", "max", "min", "res", "rt"]);
1449
+ function serializeFoldState(state) {
1450
+ return canonicalize(state);
1451
+ }
1452
+ function isRecord(value) {
1453
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1454
+ }
1455
+ function deserializeFoldState(json) {
1456
+ let parsed;
1457
+ try {
1458
+ parsed = JSON.parse(json);
1459
+ } catch (error) {
1460
+ throw new FoldStateError("Stored fold state is not valid JSON.", {
1461
+ reason: error instanceof Error ? error.message : String(error)
1462
+ });
1463
+ }
1464
+ if (!isRecord(parsed)) throw new FoldStateError("Stored fold state is not an object.");
1465
+ if (parsed.v !== FOLD_STATE_VERSION) {
1466
+ throw new FoldStateError(
1467
+ `Stored fold state has format version ${String(parsed.v)}; this build reads version ${FOLD_STATE_VERSION}.`,
1468
+ { found: parsed.v, expected: FOLD_STATE_VERSION }
1469
+ );
1470
+ }
1471
+ if (typeof parsed.c !== "string" || typeof parsed.r !== "string" || !isRecord(parsed.f)) {
1472
+ throw new FoldStateError("Stored fold state is missing its collection, record id or fields.");
1473
+ }
1474
+ for (const [field, state2] of Object.entries(parsed.f)) {
1475
+ if (!isRecord(state2) || typeof state2.k !== "string" || !FIELD_KINDS.has(state2.k)) {
1476
+ throw new FoldStateError(`Stored fold state has an unknown kind for field "${field}".`, {
1477
+ field
1478
+ });
1479
+ }
1480
+ }
1481
+ const state = parsed;
1482
+ for (const fieldState of Object.values(state.f)) {
1483
+ if ((fieldState.k === "reg" || fieldState.k === "res") && fieldState.e.length === 0) {
1484
+ fieldState.val = void 0;
1485
+ }
1486
+ if (fieldState.k === "ctr" && fieldState.d.length === 0 && fieldState.base === null) {
1487
+ fieldState.val = void 0;
1488
+ }
1489
+ }
1490
+ return state;
1491
+ }
1492
+
1493
+ // src/fold/fold.ts
1494
+ var FOLD_RECORD_TRACE_FIELD = "*";
1495
+ var SECRET_REDACTED = "[secret]";
1496
+ function createFoldState(collection, recordId) {
1497
+ return {
1498
+ v: FOLD_STATE_VERSION,
1499
+ c: collection,
1500
+ r: recordId,
1501
+ cr: null,
1502
+ w: null,
1503
+ d: null,
1504
+ u: null,
1505
+ f: {}
1506
+ };
1507
+ }
1508
+ function isExcluded(op2, exclude) {
1509
+ if (exclude === void 0) return false;
1510
+ return typeof exclude === "function" ? exclude(op2) : exclude.has(op2.id);
1511
+ }
1512
+ function samePlanKind(state, plan) {
1513
+ if (state.k !== plan.kind) return false;
1514
+ return state.k !== "set" || state.ao === plan.appendOnly;
1515
+ }
1516
+ function fieldPlanFor(collection, field, existing, state) {
1517
+ const plan = planField(collection, field);
1518
+ if (existing !== void 0 && !samePlanKind(existing, plan)) {
1519
+ throw new FoldStateError(
1520
+ `Field "${field}" of ${state.c}/${state.r} is stored as fold kind "${existing.k}" but the schema now folds it as "${plan.kind}".`,
1521
+ { collection: state.c, recordId: state.r, field, stored: existing.k, expected: plan.kind }
1522
+ );
1523
+ }
1524
+ return plan;
1525
+ }
1526
+ function fieldWriteStamp(op2, field, opStamp) {
1527
+ if (op2.type !== "insert" || op2.fieldVersions === void 0) return opStamp;
1528
+ const version = op2.fieldVersions[field];
1529
+ if (version !== void 0) return stampOf(version, op2.id);
1530
+ let oldest = opStamp;
1531
+ for (const candidate of Object.values(op2.fieldVersions)) {
1532
+ const stamp = stampOf(candidate, op2.id);
1533
+ if (compareStamps(stamp, oldest) < 0) oldest = stamp;
1534
+ }
1535
+ return oldest;
1536
+ }
1537
+ function classify(stamp, op2, field, plan, options) {
1538
+ if (!plan.authoritative) return stamp;
1539
+ const author = op2.type === "insert" ? op2.fieldVersions?.[field]?.nodeId ?? op2.nodeId : op2.nodeId;
1540
+ if (isAuthoritativeNodeId(author, options.authoritativeNodeIds)) {
1541
+ return { ...stamp, c: 1 };
1542
+ }
1543
+ return stamp;
1544
+ }
1545
+ function mergeCarriedState(state, op2, schema, options) {
1546
+ let carried;
1547
+ try {
1548
+ carried = deserializeFoldState(op2.foldState);
1549
+ } catch (error) {
1550
+ if (error instanceof FoldStateError) return null;
1551
+ throw error;
1552
+ }
1553
+ if (carried.c !== state.c || carried.r !== state.r) {
1554
+ throw new FoldStateError(
1555
+ `Operation ${op2.id} carries the fold state of ${carried.c}/${carried.r} but targets ${state.c}/${state.r}.`,
1556
+ { operationId: op2.id, collection: op2.collection, recordId: op2.recordId }
1557
+ );
1558
+ }
1559
+ let joined;
1560
+ try {
1561
+ joined = joinStates(state, carried, schema);
1562
+ } catch (error) {
1563
+ if (error instanceof FoldStateError) return null;
1564
+ throw error;
1565
+ }
1566
+ const changed = serializeFoldState(joined) !== serializeFoldState(state);
1567
+ const traces = [];
1568
+ if ((options.traces ?? "conflicts") !== "none") {
1569
+ const collection = schema.collections[state.c];
1570
+ for (const [field, incomingState] of Object.entries(carried.f)) {
1571
+ const mine = state.f[field];
1572
+ if (mine === void 0) continue;
1573
+ const prior = safeMaterializeField(mine, field, options);
1574
+ const incoming = safeMaterializeField(incomingState, field, options);
1575
+ if (canonicalKey(prior) === canonicalKey(incoming)) continue;
1576
+ const plan = planField(collection, field);
1577
+ const output = safeMaterializeField(joined.f[field], field, options);
1578
+ const redact = (value) => plan.secret ? SECRET_REDACTED : value;
1579
+ traces.push({
1580
+ field,
1581
+ strategy: `scope-entry-${plan.strategy}`,
1582
+ inputA: redact(prior),
1583
+ inputB: redact(incoming),
1584
+ base: null,
1585
+ output: redact(output),
1586
+ tier: plan.tier,
1587
+ constraintViolated: null,
1588
+ duration: 0,
1589
+ operation: plan.secret ? redactOperation(op2, field) : op2,
1590
+ priorOperationId: fieldStamp(mine)?.o ?? null,
1591
+ conflict: true
1592
+ });
1593
+ }
1594
+ }
1595
+ return { state: changed ? joined : state, traces, changed };
1596
+ }
1597
+ function buildWrite(op2, field, raw, stamp) {
1598
+ if (raw === void 0) return null;
1599
+ const insert = op2.type === "insert";
1600
+ const v = normalizeValue(raw);
1601
+ const atomic = op2.atomicOps?.[field];
1602
+ const a = atomic ? { type: atomic.type, value: normalizeValue(atomic.value) } : void 0;
1603
+ const prevRaw = insert ? void 0 : op2.previousData?.[field];
1604
+ const hasPrev = !insert && op2.previousData !== null && field in op2.previousData && prevRaw !== void 0;
1605
+ const prev = hasPrev ? normalizeValue(prevRaw) : void 0;
1606
+ if (!insert && a === void 0 && hasPrev && canonicalKey(prev) === canonicalKey(v)) return null;
1607
+ return a ? { s: stamp, insert, v, prev, hasPrev, a } : { s: stamp, insert, v, prev, hasPrev };
1608
+ }
1609
+ function safeMaterializeField(state, field, options) {
1610
+ if (state === void 0) return void 0;
1611
+ try {
1612
+ return materializeField(state, field, options.richtext);
1613
+ } catch (error) {
1614
+ if (error instanceof FoldConfigurationError) return void 0;
1615
+ throw error;
1616
+ }
1617
+ }
1618
+ function redactOperation(op2, field) {
1619
+ const redact = (data) => data !== null && field in data ? { ...data, [field]: SECRET_REDACTED } : data;
1620
+ return { ...op2, data: redact(op2.data), previousData: redact(op2.previousData) };
1621
+ }
1622
+ function fieldTrace(op2, field, plan, write, prior, priorStamp, output, newest, error) {
1623
+ const lost = newest !== null && compareStamps(newest, write.s) > 0;
1624
+ const baseMismatch = prior !== void 0 && !write.insert && canonicalKey(prior) !== canonicalKey(write.prev);
1625
+ const insertOnto = write.insert && prior !== void 0;
1626
+ const strategy = plan.kind === "reg" && write.a ? `atomic-${write.a.type}` : plan.strategy;
1627
+ const trace = {
1628
+ field,
1629
+ strategy,
1630
+ inputA: plan.secret ? SECRET_REDACTED : prior,
1631
+ inputB: plan.secret ? SECRET_REDACTED : write.v,
1632
+ base: write.hasPrev ? plan.secret ? SECRET_REDACTED : write.prev : null,
1633
+ output: plan.secret ? SECRET_REDACTED : output,
1634
+ tier: plan.tier,
1635
+ constraintViolated: null,
1636
+ // The fold is pure: callers time the whole mergeOp if they need a duration.
1637
+ duration: 0,
1638
+ operation: plan.secret ? redactOperation(op2, field) : op2,
1639
+ priorOperationId: priorStamp?.o ?? null,
1640
+ conflict: lost || baseMismatch || insertOnto
1641
+ };
1642
+ if (error !== void 0) trace.error = error;
1643
+ return trace;
1644
+ }
1645
+ function isAlive(state) {
1646
+ return state.cr !== null && isAfter(state.w, state.d);
1647
+ }
1648
+ function mergeOp(state, input, schema, options = {}) {
1649
+ if (isExcluded(input, options.exclude)) return { state, traces: [], changed: false };
1650
+ const op2 = operationSchemaView(input, schema.version, options.transforms);
1651
+ if (op2 === null) return { state, traces: [], changed: false };
1652
+ if (op2.collection !== state.c || op2.recordId !== state.r) {
1653
+ throw new FoldStateError(
1654
+ `Operation ${op2.id} targets ${op2.collection}/${op2.recordId} but the fold state is for ${state.c}/${state.r}.`,
1655
+ { operationId: op2.id, collection: op2.collection, recordId: op2.recordId }
1656
+ );
1657
+ }
1658
+ if (op2.foldState !== void 0 && op2.type === "insert") {
1659
+ const carried = mergeCarriedState(state, op2, schema, options);
1660
+ if (carried !== null) return carried;
1661
+ }
1662
+ const mode = options.traces ?? "conflicts";
1663
+ const traces = [];
1664
+ const stamp = stampOf(op2.timestamp, op2.id);
1665
+ const next = { ...state };
1666
+ let changed = false;
1667
+ const setRecordStamp = (key, value) => {
1668
+ if (value !== next[key]) {
1669
+ next[key] = value;
1670
+ changed = true;
1671
+ }
1672
+ };
1673
+ setRecordStamp("u", maxStamp(state.u, stamp));
1674
+ if (op2.type === "delete") {
1675
+ setRecordStamp("d", maxStamp(state.d, stamp));
1676
+ if (mode !== "none" && state.cr !== null && isAfter(state.w, stamp)) {
1677
+ traces.push(recordTrace(op2, true, true));
1678
+ }
1679
+ return { state: changed ? next : state, traces, changed };
1680
+ }
1681
+ if (op2.data === null) {
1682
+ if (op2.encrypted !== void 0) {
1683
+ if (op2.type === "insert") setRecordStamp("cr", minStamp(state.cr, stamp));
1684
+ setRecordStamp("w", maxStamp(state.w, stamp));
1685
+ }
1686
+ return { state: changed ? next : state, traces, changed };
1687
+ }
1688
+ if (op2.type === "insert") setRecordStamp("cr", minStamp(state.cr, stamp));
1689
+ setRecordStamp("w", maxStamp(state.w, stamp));
1690
+ if (mode !== "none" && isAfter(state.d, stamp)) traces.push(recordTrace(op2, false, false));
1691
+ const collection = schema.collections[op2.collection];
1692
+ let fields = state.f;
1693
+ for (const [field, raw] of Object.entries(op2.data)) {
1694
+ if (raw === void 0) continue;
1695
+ const existing = fields[field];
1696
+ const plan = fieldPlanFor(collection, field, existing, state);
1697
+ const write = buildWrite(
1698
+ op2,
1699
+ field,
1700
+ raw,
1701
+ classify(fieldWriteStamp(op2, field, stamp), op2, field, plan, options)
1702
+ );
1703
+ if (write === null) continue;
1704
+ const tracing = mode !== "none";
1705
+ const prior = tracing ? safeMaterializeField(existing, field, options) : void 0;
1706
+ const priorStamp = existing ? fieldStamp(existing) : null;
1707
+ const result = applyFieldWrite(
1708
+ existing ?? emptyField(plan),
1709
+ write,
1710
+ plan,
1711
+ options.richtextSubsumes
1712
+ );
1713
+ if (result.changed) {
1714
+ if (fields === state.f) fields = { ...state.f };
1715
+ fields[field] = result.state;
1716
+ changed = true;
1717
+ }
1718
+ if (tracing) {
1719
+ const trace = fieldTrace(
1720
+ op2,
1721
+ field,
1722
+ plan,
1723
+ write,
1724
+ prior,
1725
+ priorStamp,
1726
+ safeMaterializeField(result.state, field, options),
1727
+ fieldStamp(result.state),
1728
+ result.error
1729
+ );
1730
+ if (mode === "all" || trace.conflict || trace.error !== void 0) traces.push(trace);
1731
+ }
1732
+ }
1733
+ next.f = fields;
1734
+ return { state: changed ? next : state, traces, changed };
1735
+ }
1736
+ function recordTrace(op2, isDelete, lost) {
1737
+ return {
1738
+ field: FOLD_RECORD_TRACE_FIELD,
1739
+ strategy: "delete-tombstone-lww",
1740
+ inputA: isDelete ? "live" : "deleted",
1741
+ inputB: isDelete ? "deleted" : "live",
1742
+ base: null,
1743
+ // A delete that lost keeps the record live; a write older than the newest
1744
+ // delete stays hidden.
1745
+ output: isDelete ? lost ? "live" : "deleted" : "deleted",
1746
+ tier: 1,
1747
+ constraintViolated: null,
1748
+ duration: 0,
1749
+ operation: op2,
1750
+ priorOperationId: null,
1751
+ conflict: true
1752
+ };
1753
+ }
1754
+ function foldRecord(ops, schema, options = {}) {
1755
+ const kept = ops.filter((op2) => !isExcluded(op2, options.exclude));
1756
+ const first = kept[0];
1757
+ if (first === void 0) return { state: null, traces: [] };
1758
+ const ordered = kept.map((op2) => ({ op: op2, stamp: stampOf(op2.timestamp, op2.id) })).sort((a, b) => compareStamps(a.stamp, b.stamp));
1759
+ let state = createFoldState(first.collection, first.recordId);
1760
+ const traces = [];
1761
+ const mergeOptions = {
1762
+ ...options,
1763
+ exclude: void 0,
1764
+ traces: options.traces ?? "none"
1765
+ };
1766
+ for (const { op: op2 } of ordered) {
1767
+ const result = mergeOp(state, op2, schema, mergeOptions);
1768
+ state = result.state;
1769
+ traces.push(...result.traces);
1770
+ }
1771
+ return { state, traces };
1772
+ }
1773
+ function materialize(state, options = {}) {
1774
+ if (!isAlive(state)) return null;
1775
+ const record = {};
1776
+ for (const field of Object.keys(state.f).sort()) {
1777
+ const value = materializeField(state.f[field], field, options.richtext);
1778
+ if (value !== void 0) record[field] = value;
1779
+ }
1780
+ return record;
1781
+ }
1782
+ function isFoldStateLive(state) {
1783
+ return isAlive(state);
1784
+ }
1785
+ function joinStates(a, b, schema) {
1786
+ if (a.c !== b.c || a.r !== b.r) {
1787
+ throw new FoldStateError(`Cannot join fold states of ${a.c}/${a.r} and ${b.c}/${b.r}.`, {
1788
+ left: `${a.c}/${a.r}`,
1789
+ right: `${b.c}/${b.r}`
1790
+ });
1791
+ }
1792
+ const collection = schema.collections[a.c];
1793
+ const fields = { ...a.f };
1794
+ for (const [field, other] of Object.entries(b.f)) {
1795
+ const mine = fields[field];
1796
+ if (mine === void 0) {
1797
+ fieldPlanFor(collection, field, other, b);
1798
+ fields[field] = other;
1799
+ continue;
1800
+ }
1801
+ const plan = fieldPlanFor(collection, field, mine, a);
1802
+ fieldPlanFor(collection, field, other, b);
1803
+ fields[field] = joinFieldStates(mine, other, plan);
1804
+ }
1805
+ return {
1806
+ v: FOLD_STATE_VERSION,
1807
+ c: a.c,
1808
+ r: a.r,
1809
+ cr: minStamp(a.cr, b.cr),
1810
+ w: maxStamp(a.w, b.w),
1811
+ d: maxStamp(a.d, b.d),
1812
+ u: maxStamp(a.u, b.u),
1813
+ f: fields
1814
+ };
1815
+ }
1816
+ function getFoldFieldVersionStrings(state) {
1817
+ const out = {};
1818
+ for (const field of Object.keys(state.f).sort()) {
1819
+ const stamp = fieldStamp(state.f[field]);
1820
+ if (stamp !== null) out[field] = stamp.t;
1821
+ }
1822
+ return out;
1823
+ }
1824
+ function getFoldFieldVersions(state) {
1825
+ if (!isAlive(state) || state.cr === null || state.u === null) return null;
1826
+ const fields = {};
1827
+ for (const [field, fieldState] of Object.entries(state.f)) {
1828
+ const stamp = fieldStamp(fieldState);
1829
+ if (stamp !== null) fields[field] = stampTimestamp(stamp);
1830
+ }
1831
+ return { fields, created: stampTimestamp(state.cr), latest: stampTimestamp(state.u) };
1832
+ }
1833
+
1834
+ // src/fold/plan.ts
1835
+ function hashText2(text) {
1836
+ let hash = 2166136261;
1837
+ for (let i = 0; i < text.length; i++) {
1838
+ hash ^= text.charCodeAt(i);
1839
+ hash = Math.imul(hash, 16777619) >>> 0;
1840
+ }
1841
+ return hash.toString(16).padStart(8, "0");
1842
+ }
1843
+ function collectionFoldPlanFingerprint(name, collection) {
1844
+ const fields = Object.keys(collection.fields).sort().map((field) => {
1845
+ const descriptor = collection.fields[field];
1846
+ const resolver = collection.resolvers?.[field];
1847
+ return `${field}:${descriptor?.kind ?? ""}:${descriptor?.mergeStrategy ?? ""}:${resolver ? hashText2(String(resolver)) : ""}`;
1848
+ });
1849
+ const resolverOnly = Object.keys(collection.resolvers ?? {}).filter((field) => !(field in collection.fields)).sort().map((field) => `${field}:resolver:${hashText2(String(collection.resolvers?.[field]))}`);
1850
+ return `${name}(${[...fields, ...resolverOnly].join(",")})`;
1851
+ }
1852
+ function foldPlanFingerprint(schema, transforms) {
1853
+ const parts = [`fold-v${FOLD_STATE_VERSION}`];
1854
+ for (const name of Object.keys(schema.collections).sort()) {
1855
+ const collection = schema.collections[name];
1856
+ if (!collection) continue;
1857
+ parts.push(collectionFoldPlanFingerprint(name, collection));
1858
+ }
1859
+ const xf = operationTransformsFingerprint(schema.version, transforms);
1860
+ if (xf !== "") parts.push(xf);
1861
+ return parts.join("|");
1862
+ }
1863
+ function foldPlanFingerprints(schema, transforms) {
1864
+ const out = {};
1865
+ const xf = operationTransformsFingerprint(schema.version, transforms);
1866
+ for (const name of Object.keys(schema.collections).sort()) {
1867
+ const collection = schema.collections[name];
1868
+ if (!collection) continue;
1869
+ out[name] = `fold-v${FOLD_STATE_VERSION}|${collectionFoldPlanFingerprint(name, collection)}${xf !== "" ? `|${xf}` : ""}`;
1870
+ }
1871
+ return out;
1872
+ }
1873
+ function fieldStateMatchesPlan(collection, field, state) {
1874
+ const plan = planField(collection, field);
1875
+ if (state.k !== plan.kind) return false;
1876
+ return state.k !== "set" || state.ao === plan.appendOnly;
1877
+ }
1878
+ function mismatchedFoldFields(state, schema) {
1879
+ const collection = schema.collections[state.c];
1880
+ return Object.keys(state.f).filter((field) => !fieldStateMatchesPlan(collection, field, state.f[field])).sort();
1881
+ }
1882
+
1883
+ // src/fold/snapshot.ts
1884
+ var SNAPSHOT_CLEAR_ID = "\uFFFFkora-snapshot:0";
1885
+ var SNAPSHOT_WRITE_ID = "\uFFFFkora-snapshot:1";
1886
+ function snapshotField(plan, raw, clear, write) {
1887
+ const v = normalizeValue(raw);
1888
+ switch (plan.kind) {
1889
+ case "reg":
1890
+ return { k: "reg", e: [{ s: write, v }], val: v };
1891
+ case "res":
1892
+ return { k: "res", e: [{ s: write, v, z: 1 }], val: v };
1893
+ case "ctr":
1894
+ return { k: "ctr", base: { s: write, v }, d: [], val: v };
1895
+ case "max":
1896
+ case "min":
1897
+ return typeof v === "number" && !Number.isNaN(v) ? { k: plan.kind, best: { s: write, v }, reg: null } : { k: plan.kind, best: null, reg: { s: write, v } };
1898
+ case "set": {
1899
+ if (!Array.isArray(v)) {
1900
+ return {
1901
+ k: "set",
1902
+ ao: plan.appendOnly,
1903
+ sh: { s: write, arr: false, v },
1904
+ clr: write,
1905
+ el: {}
1906
+ };
1907
+ }
1908
+ const el = {};
1909
+ const counts = /* @__PURE__ */ new Map();
1910
+ v.forEach((item, index) => {
1911
+ const canonical = canonicalKey(item);
1912
+ const n = counts.get(canonical) ?? 0;
1913
+ counts.set(canonical, n + 1);
1914
+ el[elementKey(canonical, n)] = {
1915
+ v: JSON.parse(canonical),
1916
+ n,
1917
+ a: write,
1918
+ f: { s: write, i: index },
1919
+ r: null
1920
+ };
1921
+ });
1922
+ return { k: "set", ao: plan.appendOnly, sh: { s: write, arr: true }, clr: clear, el };
1923
+ }
1924
+ case "map": {
1925
+ if (!isPlainObject(v)) {
1926
+ return { k: "map", sh: { s: write, obj: false, v }, clr: write, keys: {} };
1927
+ }
1928
+ const keys = {};
1929
+ for (const [key, member] of Object.entries(v)) keys[key] = { s: write, del: false, v: member };
1930
+ return { k: "map", sh: { s: write, obj: true }, clr: clear, keys };
1931
+ }
1932
+ case "rt": {
1933
+ if (v === null || typeof v === "string" || !isPlainObject(v) || !("$koraBytes" in v)) {
1934
+ return { k: "rt", reset: { s: write, v }, u: {} };
1935
+ }
1936
+ const update = String(v.$koraBytes);
1937
+ return { k: "rt", reset: { s: clear, v: null }, u: { [update]: write } };
1938
+ }
1939
+ }
1940
+ }
1941
+ function createSnapshotState(input, schema, options = {}) {
1942
+ const state = createFoldState(input.collection, input.recordId);
1943
+ const collection = schema.collections[input.collection];
1944
+ const seed = options.seed && options.seed.c === input.collection && options.seed.r === input.recordId ? options.seed : null;
1945
+ const fields = {};
1946
+ for (const [field, raw] of Object.entries(input.values)) {
1947
+ if (raw === void 0) continue;
1948
+ const seeded = seed?.f[field];
1949
+ if (seeded !== void 0 && fieldStateMatchesPlan(collection, field, seeded)) {
1950
+ fields[field] = seeded;
1951
+ continue;
1952
+ }
1953
+ const version = input.fieldVersions[field] ?? input.latest;
1954
+ const plan = planField(collection, field);
1955
+ const authoritative = plan.authoritative === true && isAuthoritativeNodeId(version.nodeId, options.authoritativeNodeIds);
1956
+ fields[field] = snapshotField(
1957
+ plan,
1958
+ raw,
1959
+ classed(stampOf(version, SNAPSHOT_CLEAR_ID), authoritative),
1960
+ classed(stampOf(version, SNAPSHOT_WRITE_ID), authoritative)
1961
+ );
1962
+ }
1963
+ if (seed !== null && seed.cr !== null && seed.u !== null) {
1964
+ return { ...state, cr: seed.cr, w: seed.w, d: seed.d, u: seed.u, f: fields };
1965
+ }
1966
+ const latestClear = stampOf(input.latest, SNAPSHOT_CLEAR_ID);
1967
+ const latestWrite = stampOf(input.latest, SNAPSHOT_WRITE_ID);
1968
+ return {
1969
+ ...state,
1970
+ cr: stampOf(input.created, SNAPSHOT_WRITE_ID),
1971
+ w: input.deleted ? latestClear : latestWrite,
1972
+ d: input.deleted ? latestWrite : null,
1973
+ u: latestWrite,
1974
+ f: fields
1975
+ };
1976
+ }
1977
+ function classed(stamp, authoritative) {
1978
+ return authoritative ? { ...stamp, c: 1 } : stamp;
1979
+ }
1980
+ function adaptFoldState(state, schema, options = {}) {
1981
+ const mismatched = mismatchedFoldFields(state, schema);
1982
+ if (mismatched.length === 0) return { state, adapted: [] };
1983
+ const collection = schema.collections[state.c];
1984
+ const fields = { ...state.f };
1985
+ for (const field of mismatched) {
1986
+ const old = fields[field];
1987
+ delete fields[field];
1988
+ const stamp = fieldStamp(old);
1989
+ if (stamp === null) continue;
1990
+ let value;
1991
+ try {
1992
+ value = materializeField(old, field, options.richtext);
1993
+ } catch (error) {
1994
+ if (error instanceof FoldConfigurationError) continue;
1995
+ throw error;
1996
+ }
1997
+ if (value === void 0) continue;
1998
+ const plan = planField(collection, field);
1999
+ const authoritative = stamp.c === 1 && plan.authoritative === true;
2000
+ fields[field] = snapshotField(
2001
+ plan,
2002
+ value,
2003
+ classed({ t: stamp.t, o: SNAPSHOT_CLEAR_ID }, authoritative),
2004
+ classed({ t: stamp.t, o: SNAPSHOT_WRITE_ID }, authoritative)
2005
+ );
2006
+ }
2007
+ return { state: { ...state, f: fields }, adapted: mismatched };
503
2008
  }
504
2009
 
505
- // src/operations/op-data-binary.ts
506
- function isKoraBytesValue(value) {
507
- if (typeof value !== "object" || value === null) {
508
- return false;
509
- }
510
- const record = value;
511
- return Object.keys(record).length === 1 && typeof record.$koraBytes === "string";
2010
+ // src/fold/trace.ts
2011
+ function toMergeTrace(trace, operationA) {
2012
+ return {
2013
+ operationA: operationA ?? trace.operation,
2014
+ operationB: trace.operation,
2015
+ field: trace.field,
2016
+ strategy: trace.strategy,
2017
+ inputA: trace.inputA,
2018
+ inputB: trace.inputB,
2019
+ base: trace.base,
2020
+ output: trace.output,
2021
+ tier: trace.tier,
2022
+ constraintViolated: trace.constraintViolated,
2023
+ duration: trace.duration
2024
+ };
512
2025
  }
513
- function isLegacyNumericByteObject(value) {
514
- if (typeof value !== "object" || value === null || ArrayBuffer.isView(value)) {
515
- return false;
516
- }
517
- const record = value;
518
- const keys = Object.keys(record);
519
- if (keys.length === 0) {
520
- return false;
521
- }
522
- for (let i = 0; i < keys.length; i++) {
523
- const byte = record[String(i)];
524
- if (typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255) {
525
- return false;
526
- }
527
- }
528
- return true;
2026
+
2027
+ // src/text/stored-text.ts
2028
+ var STORED_TEXT_ESCAPE = "\uFFFF";
2029
+ var NEEDS_ENCODING = /[\u0000￿\ud800-\udfff]/;
2030
+ function needsStoredTextEncoding(s) {
2031
+ return NEEDS_ENCODING.test(s);
529
2032
  }
530
- var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
531
- var BASE64_REVERSE = new Map(
532
- Array.from(BASE64_ALPHABET, (char, index) => [char, index])
533
- );
534
- function bytesToBase64(bytes) {
2033
+ function encodeStoredText(s) {
2034
+ if (!NEEDS_ENCODING.test(s)) return s;
535
2035
  let out = "";
536
- for (let i = 0; i < bytes.length; i += 3) {
537
- const b0 = bytes[i] ?? 0;
538
- const b1 = bytes[i + 1] ?? 0;
539
- const b2 = bytes[i + 2] ?? 0;
540
- const triple = b0 << 16 | b1 << 8 | b2;
541
- out += BASE64_ALPHABET[triple >> 18 & 63] ?? "";
542
- out += BASE64_ALPHABET[triple >> 12 & 63] ?? "";
543
- out += i + 1 < bytes.length ? BASE64_ALPHABET[triple >> 6 & 63] ?? "" : "=";
544
- out += i + 2 < bytes.length ? BASE64_ALPHABET[triple & 63] ?? "" : "=";
2036
+ for (let i = 0; i < s.length; i++) {
2037
+ const code = s.charCodeAt(i);
2038
+ if (code === 0) {
2039
+ out += `${STORED_TEXT_ESCAPE}0`;
2040
+ } else if (code === 65535) {
2041
+ out += `${STORED_TEXT_ESCAPE}F`;
2042
+ } else if (code >= 55296 && code <= 56319) {
2043
+ const next = s.charCodeAt(i + 1);
2044
+ if (next >= 56320 && next <= 57343) {
2045
+ out += s.slice(i, i + 2);
2046
+ i++;
2047
+ } else {
2048
+ out += `${STORED_TEXT_ESCAPE}s${code.toString(16)}`;
2049
+ }
2050
+ } else if (code >= 56320 && code <= 57343) {
2051
+ out += `${STORED_TEXT_ESCAPE}s${code.toString(16)}`;
2052
+ } else {
2053
+ out += s.charAt(i);
2054
+ }
545
2055
  }
546
2056
  return out;
547
2057
  }
548
- function base64ToBytes(base64) {
549
- const cleaned = base64.replace(/=+$/, "");
550
- const out = new Uint8Array(Math.floor(cleaned.length * 6 / 8));
551
- let buffer = 0;
552
- let bits = 0;
553
- let index = 0;
554
- for (const char of cleaned) {
555
- const value = BASE64_REVERSE.get(char);
556
- if (value === void 0) {
557
- throw new OperationError(`Invalid base64 character "${char}" in tagged binary value.`, {
558
- char
559
- });
2058
+ function decodeStoredText(s) {
2059
+ if (!s.includes(STORED_TEXT_ESCAPE)) return s;
2060
+ let out = "";
2061
+ for (let i = 0; i < s.length; i++) {
2062
+ const char = s[i];
2063
+ if (char !== STORED_TEXT_ESCAPE) {
2064
+ out += char;
2065
+ continue;
560
2066
  }
561
- buffer = buffer << 6 | value;
562
- bits += 6;
563
- if (bits >= 8) {
564
- bits -= 8;
565
- out[index] = buffer >> bits & 255;
566
- index += 1;
2067
+ const tag = s[i + 1];
2068
+ if (tag === "0") {
2069
+ out += "\0";
2070
+ i += 1;
2071
+ } else if (tag === "F") {
2072
+ out += STORED_TEXT_ESCAPE;
2073
+ i += 1;
2074
+ } else if (tag === "s") {
2075
+ const hex = s.slice(i + 2, i + 6);
2076
+ const code = Number.parseInt(hex, 16);
2077
+ if (hex.length === 4 && Number.isInteger(code) && code >= 55296 && code <= 57343) {
2078
+ out += String.fromCharCode(code);
2079
+ i += 5;
2080
+ } else {
2081
+ out += char;
2082
+ }
2083
+ } else {
2084
+ out += char;
567
2085
  }
568
2086
  }
569
2087
  return out;
570
2088
  }
571
- function encodeBytesForOpData(value) {
572
- if (typeof value === "string") {
573
- return value;
574
- }
575
- const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
576
- return { $koraBytes: bytesToBase64(bytes) };
2089
+ function encodeStoredJsonValue(value) {
2090
+ return mapStrings(value, encodeStoredText);
577
2091
  }
578
- function decodeBytesFromOpData(value) {
579
- if (typeof value === "string") {
580
- return value;
581
- }
582
- if (value instanceof Uint8Array) {
583
- return value;
584
- }
585
- if (value instanceof ArrayBuffer) {
586
- return new Uint8Array(value);
587
- }
588
- if (isKoraBytesValue(value)) {
589
- return base64ToBytes(value.$koraBytes);
590
- }
591
- if (isLegacyNumericByteObject(value)) {
592
- const keys = Object.keys(value);
593
- const bytes = new Uint8Array(keys.length);
594
- for (let i = 0; i < keys.length; i++) {
595
- bytes[i] = value[String(i)] ?? 0;
2092
+ function decodeStoredJsonValue(value) {
2093
+ return mapStrings(value, decodeStoredText);
2094
+ }
2095
+ function mapStrings(value, map) {
2096
+ if (typeof value === "string") return map(value);
2097
+ if (Array.isArray(value)) return value.map((member) => mapStrings(member, map));
2098
+ if (value !== null && typeof value === "object" && !ArrayBuffer.isView(value)) {
2099
+ const out = {};
2100
+ for (const [key, member] of Object.entries(value)) out[map(key)] = mapStrings(member, map);
2101
+ return out;
2102
+ }
2103
+ return value;
2104
+ }
2105
+
2106
+ // src/fold/side-effect-id.ts
2107
+ var SIDE_EFFECT_DOMAIN = "kora/side-effect/v1";
2108
+ async function deriveSideEffectOpId(parentOpId, ruleId, targetRecordId) {
2109
+ for (const [name, value] of [
2110
+ ["parentOpId", parentOpId],
2111
+ ["ruleId", ruleId],
2112
+ ["targetRecordId", targetRecordId]
2113
+ ]) {
2114
+ if (typeof value !== "string" || value.length === 0) {
2115
+ throw new OperationError(`deriveSideEffectOpId: ${name} must be a non-empty string`, {
2116
+ [name]: value
2117
+ });
596
2118
  }
597
- return bytes;
598
2119
  }
599
- throw new OperationError(
600
- "Binary op-data value must be a string, Uint8Array, ArrayBuffer, tagged { $koraBytes } object, or legacy numeric-key byte object.",
601
- { receivedType: typeof value }
2120
+ const canonical = canonicalize({
2121
+ domain: SIDE_EFFECT_DOMAIN,
2122
+ parent: parentOpId,
2123
+ rule: ruleId,
2124
+ target: targetRecordId
2125
+ });
2126
+ const digest = await globalThis.crypto.subtle.digest(
2127
+ "SHA-256",
2128
+ new TextEncoder().encode(canonical)
602
2129
  );
2130
+ return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
603
2131
  }
604
2132
 
605
2133
  // src/scopes/sync-scope-bindings.ts
@@ -884,6 +2412,21 @@ function buildCollection(name, input) {
884
2412
  validateStateMachineDefinition(name, input.stateMachine, fields);
885
2413
  stateMachine = { ...input.stateMachine };
886
2414
  }
2415
+ if (!stateMachine) {
2416
+ const transitionFields = Object.entries(fields).filter(
2417
+ ([, descriptor]) => descriptor.kind === "enum" && descriptor.transitions
2418
+ );
2419
+ const only = transitionFields.length === 1 ? transitionFields[0] : void 0;
2420
+ if (only?.[1].transitions) {
2421
+ const derived = {
2422
+ field: only[0],
2423
+ transitions: { ...only[1].transitions },
2424
+ onInvalidTransition: "reject"
2425
+ };
2426
+ validateStateMachineDefinition(name, derived, fields);
2427
+ stateMachine = derived;
2428
+ }
2429
+ }
887
2430
  return { fields, indexes, constraints, resolvers, scope, stateMachine };
888
2431
  }
889
2432
  function buildSyncRules(syncInput, collections) {
@@ -1009,170 +2552,6 @@ function validateRelation(name, relation, collections) {
1009
2552
  }
1010
2553
  }
1011
2554
 
1012
- // src/schema/quote-ident.ts
1013
- function quoteIdent(name) {
1014
- return `"${name.replace(/"/g, '""')}"`;
1015
- }
1016
-
1017
- // src/schema/sql-gen.ts
1018
- function generateSQL(collectionName, collection, relations) {
1019
- const statements = [];
1020
- const columns = ["id TEXT PRIMARY KEY NOT NULL"];
1021
- const indexedFields = new Set(collection.indexes);
1022
- const fkFields = [];
1023
- for (const [fieldName, descriptor] of Object.entries(collection.fields)) {
1024
- let colDef = columnDefinition(fieldName, descriptor);
1025
- if (relations) {
1026
- for (const rel of Object.values(relations)) {
1027
- if (rel.from === collectionName && rel.field === fieldName) {
1028
- colDef += ` REFERENCES ${quoteIdent(rel.to)}(id)`;
1029
- fkFields.push(fieldName);
1030
- break;
1031
- }
1032
- }
1033
- }
1034
- columns.push(colDef);
1035
- }
1036
- columns.push("_created_at INTEGER NOT NULL");
1037
- columns.push("_updated_at INTEGER NOT NULL");
1038
- columns.push("_version TEXT NOT NULL DEFAULT ''");
1039
- columns.push("_field_versions TEXT NOT NULL DEFAULT '{}'");
1040
- columns.push("_deleted INTEGER NOT NULL DEFAULT 0");
1041
- statements.push(
1042
- `CREATE TABLE IF NOT EXISTS ${quoteIdent(collectionName)} (
1043
- ${columns.join(",\n ")}
1044
- )`
1045
- );
1046
- for (const [fieldName, descriptor] of Object.entries(collection.fields)) {
1047
- const colDef = columnDefinition(fieldName, descriptor);
1048
- statements.push(
1049
- `--kora:safe-alter
1050
- ALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN ${colDef}`
1051
- );
1052
- }
1053
- statements.push(
1054
- `--kora:safe-alter
1055
- ALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`
1056
- );
1057
- statements.push(
1058
- `--kora:safe-alter
1059
- ALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN _field_versions TEXT NOT NULL DEFAULT '{}'`
1060
- );
1061
- for (const indexField of collection.indexes) {
1062
- statements.push(
1063
- `CREATE INDEX IF NOT EXISTS idx_${collectionName}_${indexField} ON ${quoteIdent(collectionName)} (${quoteIdent(indexField)})`
1064
- );
1065
- }
1066
- for (const fkField of fkFields) {
1067
- if (!indexedFields.has(fkField)) {
1068
- statements.push(
1069
- `CREATE INDEX IF NOT EXISTS idx_${collectionName}_${fkField} ON ${quoteIdent(collectionName)} (${quoteIdent(fkField)})`
1070
- );
1071
- }
1072
- }
1073
- statements.push(
1074
- `CREATE TABLE IF NOT EXISTS ${quoteIdent(`_kora_ops_${collectionName}`)} (
1075
- id TEXT PRIMARY KEY NOT NULL,
1076
- node_id TEXT NOT NULL,
1077
- type TEXT NOT NULL,
1078
- record_id TEXT NOT NULL,
1079
- data TEXT,
1080
- previous_data TEXT,
1081
- timestamp TEXT NOT NULL,
1082
- sequence_number INTEGER NOT NULL,
1083
- causal_deps TEXT NOT NULL,
1084
- schema_version INTEGER NOT NULL
1085
- )`
1086
- );
1087
- statements.push(
1088
- `CREATE INDEX IF NOT EXISTS idx_kora_ops_${collectionName}_record_id ON ${quoteIdent(`_kora_ops_${collectionName}`)} (record_id)`
1089
- );
1090
- return statements;
1091
- }
1092
- function generateFullDDL(schema) {
1093
- const statements = [];
1094
- statements.push(
1095
- "CREATE TABLE IF NOT EXISTS _kora_meta (\n key TEXT PRIMARY KEY NOT NULL,\n value TEXT NOT NULL\n)"
1096
- );
1097
- statements.push(
1098
- "CREATE TABLE IF NOT EXISTS _kora_version_vector (\n node_id TEXT PRIMARY KEY NOT NULL,\n sequence_number INTEGER NOT NULL\n)"
1099
- );
1100
- statements.push(
1101
- "CREATE TABLE IF NOT EXISTS _kora_sequences (\n name TEXT NOT NULL,\n scope TEXT NOT NULL DEFAULT '',\n node_id TEXT NOT NULL,\n counter INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (name, scope, node_id)\n)"
1102
- );
1103
- statements.push(
1104
- "CREATE TABLE IF NOT EXISTS _kora_sync_queue (\n id TEXT PRIMARY KEY NOT NULL,\n payload TEXT NOT NULL\n)"
1105
- );
1106
- statements.push(
1107
- "CREATE TABLE IF NOT EXISTS _kora_sync_rejected (\n operation_id TEXT PRIMARY KEY NOT NULL,\n collection TEXT NOT NULL,\n record_id TEXT NOT NULL,\n code TEXT NOT NULL,\n message TEXT NOT NULL,\n retriable INTEGER NOT NULL,\n rejected_at INTEGER NOT NULL\n)"
1108
- );
1109
- statements.push(
1110
- "CREATE TABLE IF NOT EXISTS _kora_audit_traces (\n id TEXT PRIMARY KEY NOT NULL,\n recorded_at INTEGER NOT NULL,\n event_type TEXT NOT NULL,\n collection TEXT NOT NULL,\n record_id TEXT NOT NULL,\n field TEXT NOT NULL,\n strategy TEXT NOT NULL,\n tier INTEGER NOT NULL,\n constraint_name TEXT,\n trace_json TEXT NOT NULL\n)"
1111
- );
1112
- statements.push(
1113
- "CREATE INDEX IF NOT EXISTS idx_kora_audit_traces_recorded_at ON _kora_audit_traces (recorded_at)"
1114
- );
1115
- statements.push(
1116
- "CREATE INDEX IF NOT EXISTS idx_kora_audit_traces_collection ON _kora_audit_traces (collection)"
1117
- );
1118
- for (const [name, collection] of Object.entries(schema.collections)) {
1119
- statements.push(...generateSQL(name, collection, schema.relations));
1120
- }
1121
- return statements;
1122
- }
1123
- function columnDefinition(fieldName, descriptor) {
1124
- const sqlType = mapFieldType(descriptor);
1125
- const parts = [quoteIdent(fieldName), sqlType];
1126
- if (descriptor.required && descriptor.defaultValue === void 0 && !descriptor.auto) {
1127
- parts.push("NOT NULL");
1128
- }
1129
- if (descriptor.defaultValue !== void 0) {
1130
- parts.push(`DEFAULT ${sqlDefault(descriptor.defaultValue)}`);
1131
- }
1132
- if (descriptor.kind === "enum" && descriptor.enumValues) {
1133
- const values = descriptor.enumValues.map((v) => `'${v}'`).join(", ");
1134
- parts.push(`CHECK (${quoteIdent(fieldName)} IN (${values}))`);
1135
- }
1136
- return parts.join(" ");
1137
- }
1138
- function mapFieldType(descriptor) {
1139
- switch (descriptor.kind) {
1140
- case "string":
1141
- return "TEXT";
1142
- case "number":
1143
- return "REAL";
1144
- case "boolean":
1145
- return "INTEGER";
1146
- case "enum":
1147
- return "TEXT";
1148
- case "timestamp":
1149
- return "INTEGER";
1150
- case "array":
1151
- return "TEXT";
1152
- // JSON-serialized
1153
- case "object":
1154
- return "TEXT";
1155
- // JSON-serialized structured object
1156
- case "json":
1157
- return "TEXT";
1158
- // JSON-serialized dynamic-key value
1159
- case "blob":
1160
- return "TEXT";
1161
- // JSON-serialized content-addressed BlobRef (bytes stored out of band)
1162
- case "secret":
1163
- return "TEXT";
1164
- case "richtext":
1165
- return "BLOB";
1166
- }
1167
- }
1168
- function sqlDefault(value) {
1169
- if (value === null) return "NULL";
1170
- if (typeof value === "string") return `'${value}'`;
1171
- if (typeof value === "number") return String(value);
1172
- if (typeof value === "boolean") return value ? "1" : "0";
1173
- return `'${JSON.stringify(value)}'`;
1174
- }
1175
-
1176
2555
  // src/schema/types.ts
1177
2556
  var FieldBuilder = class _FieldBuilder {
1178
2557
  _kind;
@@ -1187,15 +2566,21 @@ var FieldBuilder = class _FieldBuilder {
1187
2566
  this._auto = auto;
1188
2567
  this._mergeStrategy = mergeStrategy;
1189
2568
  }
1190
- /** Mark this field as optional (not required on insert) */
2569
+ /** Mark this field as optional (not required on insert; reads may return `null`). */
1191
2570
  optional() {
1192
2571
  return new _FieldBuilder(this._kind, false, this._defaultValue, this._auto, this._mergeStrategy);
1193
2572
  }
1194
- /** Set a default value for this field. Implicitly makes the field optional. */
2573
+ /**
2574
+ * Set a default value for this field. Implicitly makes the field optional on insert.
2575
+ * The value must be of the field's type (`t.number().default('x')` is a type error).
2576
+ */
1195
2577
  default(value) {
1196
2578
  return new _FieldBuilder(this._kind, false, value, this._auto, this._mergeStrategy);
1197
2579
  }
1198
- /** Mark this field as auto-populated (e.g., createdAt timestamps). Developers cannot set auto fields. */
2580
+ /**
2581
+ * Mark this field as auto-populated (e.g., createdAt timestamps). Developers cannot
2582
+ * set auto fields; the framework fills `t.timestamp().auto()` with the insert time.
2583
+ */
1199
2584
  auto() {
1200
2585
  return new _FieldBuilder(this._kind, false, void 0, true, this._mergeStrategy);
1201
2586
  }
@@ -1213,13 +2598,7 @@ var FieldBuilder = class _FieldBuilder {
1213
2598
  * - `'server-authoritative'`: Always prefer the remote/server value
1214
2599
  */
1215
2600
  merge(strategy) {
1216
- return new _FieldBuilder(
1217
- this._kind,
1218
- this._required,
1219
- this._defaultValue,
1220
- this._auto,
1221
- strategy
1222
- );
2601
+ return new _FieldBuilder(this._kind, this._required, this._defaultValue, this._auto, strategy);
1223
2602
  }
1224
2603
  /** @internal Build the final FieldDescriptor. Used by defineSchema(). */
1225
2604
  _build() {
@@ -1235,6 +2614,16 @@ var FieldBuilder = class _FieldBuilder {
1235
2614
  };
1236
2615
  }
1237
2616
  };
2617
+ function assertStorableEnumValues(values) {
2618
+ for (const value of values) {
2619
+ if (typeof value !== "string" || encodeStoredText(value) !== value) {
2620
+ throw new SchemaValidationError(
2621
+ `Enum value ${JSON.stringify(value)} cannot be stored: enum values must be well-formed strings without U+0000 or U+FFFF.`,
2622
+ { value: String(value) }
2623
+ );
2624
+ }
2625
+ }
2626
+ }
1238
2627
  var EnumFieldBuilder = class _EnumFieldBuilder extends FieldBuilder {
1239
2628
  _enumValues;
1240
2629
  _transitions;
@@ -1340,14 +2729,14 @@ var EnumFieldBuilder = class _EnumFieldBuilder extends FieldBuilder {
1340
2729
  }
1341
2730
  };
1342
2731
  var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
1343
- _itemKind;
2732
+ _item;
1344
2733
  constructor(itemBuilder, required = true, defaultValue = void 0, auto = false, mergeStrategy = null) {
1345
2734
  super("array", required, defaultValue, auto, mergeStrategy);
1346
- this._itemKind = itemBuilder._build().kind;
2735
+ this._item = itemBuilder;
1347
2736
  }
1348
2737
  optional() {
1349
2738
  return new _ArrayFieldBuilder(
1350
- new FieldBuilder(this._itemKind),
2739
+ this._item,
1351
2740
  false,
1352
2741
  this._defaultValue,
1353
2742
  this._auto,
@@ -1355,26 +2744,14 @@ var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
1355
2744
  );
1356
2745
  }
1357
2746
  default(value) {
1358
- return new _ArrayFieldBuilder(
1359
- new FieldBuilder(this._itemKind),
1360
- false,
1361
- value,
1362
- this._auto,
1363
- this._mergeStrategy
1364
- );
2747
+ return new _ArrayFieldBuilder(this._item, false, value, this._auto, this._mergeStrategy);
1365
2748
  }
1366
2749
  auto() {
1367
- return new _ArrayFieldBuilder(
1368
- new FieldBuilder(this._itemKind),
1369
- false,
1370
- void 0,
1371
- true,
1372
- this._mergeStrategy
1373
- );
2750
+ return new _ArrayFieldBuilder(this._item, false, void 0, true, this._mergeStrategy);
1374
2751
  }
1375
2752
  merge(strategy) {
1376
2753
  return new _ArrayFieldBuilder(
1377
- new FieldBuilder(this._itemKind),
2754
+ this._item,
1378
2755
  this._required,
1379
2756
  this._defaultValue,
1380
2757
  this._auto,
@@ -1388,7 +2765,7 @@ var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
1388
2765
  defaultValue: this._defaultValue,
1389
2766
  auto: this._auto,
1390
2767
  enumValues: null,
1391
- itemKind: this._itemKind,
2768
+ itemKind: this._item._build().kind,
1392
2769
  mergeStrategy: this._mergeStrategy,
1393
2770
  transitions: null
1394
2771
  };
@@ -1522,6 +2899,9 @@ var SecretFieldBuilder = class _SecretFieldBuilder extends FieldBuilder {
1522
2899
  this._mergeStrategy
1523
2900
  );
1524
2901
  }
2902
+ default(value) {
2903
+ return new _SecretFieldBuilder(this._secretMode, false, value, this._auto, this._mergeStrategy);
2904
+ }
1525
2905
  auto() {
1526
2906
  return new _SecretFieldBuilder(this._secretMode, false, void 0, true, this._mergeStrategy);
1527
2907
  }
@@ -1548,42 +2928,220 @@ var SecretFieldBuilder = class _SecretFieldBuilder extends FieldBuilder {
1548
2928
  };
1549
2929
  }
1550
2930
  };
2931
+ function scalar(kind) {
2932
+ return new FieldBuilder(kind, true, void 0, false);
2933
+ }
1551
2934
  var t = {
2935
+ /** A string field. */
1552
2936
  string() {
1553
- return new FieldBuilder("string", true, void 0, false);
2937
+ return scalar("string");
1554
2938
  },
2939
+ /** A finite-number field. */
1555
2940
  number() {
1556
- return new FieldBuilder("number", true, void 0, false);
2941
+ return scalar("number");
1557
2942
  },
2943
+ /** A boolean field. */
1558
2944
  boolean() {
1559
- return new FieldBuilder("boolean", true, void 0, false);
2945
+ return scalar("boolean");
1560
2946
  },
2947
+ /** A timestamp field: integer milliseconds since the epoch (not a `Date`). */
1561
2948
  timestamp() {
1562
- return new FieldBuilder("timestamp", true, void 0, false);
2949
+ return scalar("timestamp");
1563
2950
  },
2951
+ /** A collaborative rich-text field (Yjs). Reads return the Yjs update bytes. */
1564
2952
  richtext() {
1565
- return new FieldBuilder("richtext", true, void 0, false);
2953
+ return scalar("richtext");
1566
2954
  },
2955
+ /** An enum field whose value is one of `values`. */
1567
2956
  enum(values) {
2957
+ assertStorableEnumValues(values);
1568
2958
  return new EnumFieldBuilder(values, true, void 0, false);
1569
2959
  },
2960
+ /** An array field whose items have the type of `itemBuilder`. */
1570
2961
  array(itemBuilder) {
1571
2962
  return new ArrayFieldBuilder(itemBuilder, true, void 0, false);
1572
2963
  },
2964
+ /** A structured object field whose keys are declared by nested builders. */
1573
2965
  object(fields) {
1574
2966
  return new ObjectFieldBuilder(fields, true, void 0, false);
1575
2967
  },
2968
+ /** A dynamic JSON field typed as `T` (compile-time only; not validated against `T`). */
1576
2969
  json() {
1577
2970
  return new JsonFieldBuilder("json", true, void 0, false);
1578
2971
  },
2972
+ /** A blob reference field (`BlobRef`); the bytes live in the blob store. */
1579
2973
  blob() {
1580
- return new FieldBuilder("blob", true, void 0, false);
2974
+ return scalar("blob");
1581
2975
  },
2976
+ /** A secret field (password, token): redacted from traces, hashed or encrypted at rest. */
1582
2977
  secret() {
1583
2978
  return new SecretFieldBuilder("encrypted", true, void 0, false);
1584
2979
  }
1585
2980
  };
1586
2981
 
2982
+ // src/schema/value-domain.ts
2983
+ var TIMESTAMP_MIN_MS = -864e13;
2984
+ var TIMESTAMP_MAX_MS = 864e13;
2985
+ var MAX_VALUE_DEPTH = 64;
2986
+ var DEFAULT_MAX_OPERATION_BYTES = 256 * 1024;
2987
+ function timestampDomainViolation(value) {
2988
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2989
+ return `must be a timestamp (integer milliseconds), got ${describe(value)}`;
2990
+ }
2991
+ if (!Number.isInteger(value)) {
2992
+ return `must be a timestamp in whole milliseconds, got ${String(value)}. Round it (Math.round) or use Date.getTime()`;
2993
+ }
2994
+ if (value < TIMESTAMP_MIN_MS || value > TIMESTAMP_MAX_MS) {
2995
+ return `must be a timestamp between ${TIMESTAMP_MIN_MS} and ${TIMESTAMP_MAX_MS} (the range of a JavaScript Date), got ${String(value)}`;
2996
+ }
2997
+ return null;
2998
+ }
2999
+ function valueDepthViolation(value, maxDepth = MAX_VALUE_DEPTH) {
3000
+ const walk = (current, depth, path) => {
3001
+ if (current === null || typeof current !== "object") return null;
3002
+ if (depth > maxDepth) return path;
3003
+ if (current instanceof Uint8Array || current instanceof ArrayBuffer) return null;
3004
+ const entries = Array.isArray(current) ? current.map((item, index) => [`[${index}]`, item]) : Object.entries(current).map(
3005
+ ([key, item]) => [`.${key}`, item]
3006
+ );
3007
+ for (const [suffix, item] of entries) {
3008
+ const found = walk(item, depth + 1, `${path}${suffix}`);
3009
+ if (found !== null) return found;
3010
+ }
3011
+ return null;
3012
+ };
3013
+ return walk(value, 1, "");
3014
+ }
3015
+ function protoKeyViolation(value) {
3016
+ const walk = (current, path) => {
3017
+ if (current === null || typeof current !== "object") return null;
3018
+ if (current instanceof Uint8Array || current instanceof ArrayBuffer) return null;
3019
+ if (Array.isArray(current)) {
3020
+ for (let index = 0; index < current.length; index++) {
3021
+ const found = walk(current[index], `${path}[${index}]`);
3022
+ if (found !== null) return found;
3023
+ }
3024
+ return null;
3025
+ }
3026
+ if (Object.prototype.hasOwnProperty.call(current, "__proto__")) return `${path}.__proto__`;
3027
+ for (const [key, item] of Object.entries(current)) {
3028
+ const found = walk(item, `${path}.${key}`);
3029
+ if (found !== null) return found;
3030
+ }
3031
+ return null;
3032
+ };
3033
+ return walk(value, "");
3034
+ }
3035
+ function operationValueViolation(descriptor, value) {
3036
+ if (value === null || value === void 0) return null;
3037
+ switch (descriptor.kind) {
3038
+ case "string":
3039
+ case "secret":
3040
+ return typeof value === "string" ? null : `must be a string, got ${describe(value)}`;
3041
+ case "enum":
3042
+ if (typeof value !== "string") return `must be a string (enum), got ${describe(value)}`;
3043
+ if (descriptor.enumValues && !descriptor.enumValues.includes(value)) {
3044
+ return `must be one of: ${descriptor.enumValues.join(", ")}. Got "${value}"`;
3045
+ }
3046
+ return null;
3047
+ case "number":
3048
+ return typeof value === "number" && Number.isFinite(value) ? null : `must be a finite number, got ${describe(value)}`;
3049
+ case "boolean":
3050
+ return typeof value === "boolean" ? null : `must be a boolean, got ${describe(value)}`;
3051
+ case "timestamp":
3052
+ return timestampDomainViolation(value);
3053
+ case "richtext":
3054
+ return typeof value === "string" || isKoraBytesValue(value) || value instanceof Uint8Array ? null : `must be richtext (Yjs bytes or a string), got ${describe(value)}`;
3055
+ case "blob":
3056
+ return isBlobRef(value) ? null : `must be a BlobRef, got ${describe(value)}`;
3057
+ case "array": {
3058
+ if (!Array.isArray(value)) return `must be an array, got ${describe(value)}`;
3059
+ const item = descriptor.itemKind;
3060
+ for (let index = 0; index < value.length; index++) {
3061
+ const element = value[index];
3062
+ if (item === "timestamp") {
3063
+ const violation = timestampDomainViolation(element);
3064
+ if (violation) return `[${index}] ${violation}`;
3065
+ } else if (item === "number" && (typeof element !== "number" || !Number.isFinite(element))) {
3066
+ return `[${index}] must be a finite number, got ${describe(element)}`;
3067
+ } else if ((item === "string" || item === "enum") && typeof element !== "string") {
3068
+ return `[${index}] must be a string, got ${describe(element)}`;
3069
+ } else if (item === "boolean" && typeof element !== "boolean") {
3070
+ return `[${index}] must be a boolean, got ${describe(element)}`;
3071
+ }
3072
+ }
3073
+ return structureViolation(value);
3074
+ }
3075
+ case "object": {
3076
+ if (typeof value !== "object" || Array.isArray(value)) {
3077
+ return `must be a plain object, got ${describe(value)}`;
3078
+ }
3079
+ for (const [name, nested] of Object.entries(descriptor.nestedFields ?? {})) {
3080
+ const violation = operationValueViolation(nested, value[name]);
3081
+ if (violation) return `.${name} ${violation}`;
3082
+ }
3083
+ return structureViolation(value);
3084
+ }
3085
+ case "json":
3086
+ return structureViolation(value);
3087
+ }
3088
+ }
3089
+ var WIRE_BYTES_KEY = "__kora_bytes__";
3090
+ function reservedWireShapeViolation(value) {
3091
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
3092
+ const keys = Object.keys(value);
3093
+ return keys.length === 1 && keys[0] === WIRE_BYTES_KEY ? `is an object whose only key is "${WIRE_BYTES_KEY}", which the sync wire reserves for binary values; add another key or rename it` : null;
3094
+ }
3095
+ function structureViolation(value) {
3096
+ const reserved = reservedWireShapeViolation(value);
3097
+ if (reserved !== null) return reserved;
3098
+ const deep = valueDepthViolation(value);
3099
+ if (deep !== null) return `nests deeper than ${MAX_VALUE_DEPTH} levels at "${deep}"`;
3100
+ const proto = protoKeyViolation(value);
3101
+ if (proto !== null) return `holds a "__proto__" key at "${proto}", which is not supported`;
3102
+ const walk = (current, path) => {
3103
+ if (typeof current === "number") {
3104
+ return Number.isFinite(current) ? null : `holds ${String(current)} at "${path}"`;
3105
+ }
3106
+ if (current === null || typeof current !== "object") return null;
3107
+ const entries = Array.isArray(current) ? current.map((item, index) => [`${path}[${index}]`, item]) : Object.entries(current).map(
3108
+ ([key, item]) => [`${path}.${key}`, item]
3109
+ );
3110
+ for (const [next, item] of entries) {
3111
+ const found = walk(item, next);
3112
+ if (found !== null) return found;
3113
+ }
3114
+ return null;
3115
+ };
3116
+ return walk(value, "");
3117
+ }
3118
+ function measureOperationBytes(op2) {
3119
+ return utf8Length(JSON.stringify(op2));
3120
+ }
3121
+ function utf8Length(text) {
3122
+ let bytes = 0;
3123
+ for (let i = 0; i < text.length; i++) {
3124
+ const code = text.charCodeAt(i);
3125
+ if (code < 128) bytes += 1;
3126
+ else if (code < 2048) bytes += 2;
3127
+ else if (code >= 55296 && code <= 56319 && i + 1 < text.length) {
3128
+ const next = text.charCodeAt(i + 1);
3129
+ if (next >= 56320 && next <= 57343) {
3130
+ bytes += 4;
3131
+ i++;
3132
+ } else bytes += 3;
3133
+ } else bytes += 3;
3134
+ }
3135
+ return bytes;
3136
+ }
3137
+ function describe(value) {
3138
+ if (value === null) return "null";
3139
+ if (Array.isArray(value)) return "array";
3140
+ if (typeof value === "number") return Number.isFinite(value) ? "number" : String(value);
3141
+ if (value instanceof Date) return "Date";
3142
+ return typeof value;
3143
+ }
3144
+
1587
3145
  // src/schema/validation.ts
1588
3146
  function validateRecord(collection, collectionDef, data, operationType) {
1589
3147
  if (operationType === "delete") {
@@ -1614,7 +3172,7 @@ function validateRecord(collection, collectionDef, data, operationType) {
1614
3172
  result[fieldName] = value;
1615
3173
  } else if (value !== void 0 && value !== null) {
1616
3174
  validateFieldValue(collection, fieldName, descriptor, value);
1617
- result[fieldName] = value;
3175
+ result[fieldName] = canonicalFieldValue(collection, fieldName, descriptor, value);
1618
3176
  } else {
1619
3177
  result[fieldName] = value;
1620
3178
  }
@@ -1638,10 +3196,29 @@ function validateRecord(collection, collectionDef, data, operationType) {
1638
3196
  continue;
1639
3197
  }
1640
3198
  validateFieldValue(collection, fieldName, descriptor, value);
1641
- result[fieldName] = value;
3199
+ result[fieldName] = canonicalFieldValue(collection, fieldName, descriptor, value);
1642
3200
  }
1643
3201
  return result;
1644
3202
  }
3203
+ function canonicalFieldValue(collection, fieldName, descriptor, value) {
3204
+ if (descriptor.kind === "richtext") return value;
3205
+ try {
3206
+ return canonicalValue(value, `${collection}.${fieldName}`);
3207
+ } catch (error) {
3208
+ if (error instanceof NonCanonicalValueError) {
3209
+ throw new SchemaValidationError(
3210
+ `Field "${fieldName}" in collection "${collection}": ${error.message}`,
3211
+ {
3212
+ collection,
3213
+ field: fieldName,
3214
+ path: error.path,
3215
+ receivedType: error.receivedType
3216
+ }
3217
+ );
3218
+ }
3219
+ throw error;
3220
+ }
3221
+ }
1645
3222
  function validateFieldValue(collection, fieldName, descriptor, value) {
1646
3223
  switch (descriptor.kind) {
1647
3224
  case "string": {
@@ -1654,9 +3231,9 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
1654
3231
  break;
1655
3232
  }
1656
3233
  case "number": {
1657
- if (typeof value !== "number" || Number.isNaN(value)) {
3234
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1658
3235
  throw new SchemaValidationError(
1659
- `Field "${fieldName}" in collection "${collection}" must be a number, got ${typeof value}`,
3236
+ `Field "${fieldName}" in collection "${collection}" must be a finite number, got ${typeof value === "number" ? String(value) : typeof value}`,
1660
3237
  { collection, field: fieldName, expectedType: "number", receivedType: typeof value }
1661
3238
  );
1662
3239
  }
@@ -1672,14 +3249,16 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
1672
3249
  break;
1673
3250
  }
1674
3251
  case "timestamp": {
1675
- if (typeof value !== "number" || !Number.isFinite(value)) {
3252
+ const violation = timestampDomainViolation(value);
3253
+ if (violation !== null) {
1676
3254
  throw new SchemaValidationError(
1677
- `Field "${fieldName}" in collection "${collection}" must be a timestamp (number), got ${typeof value}`,
3255
+ `Field "${fieldName}" in collection "${collection}" ${violation}`,
1678
3256
  {
1679
3257
  collection,
1680
3258
  field: fieldName,
1681
3259
  expectedType: "timestamp",
1682
- receivedType: typeof value
3260
+ receivedType: typeof value,
3261
+ received: value
1683
3262
  }
1684
3263
  );
1685
3264
  }
@@ -1716,6 +3295,14 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
1716
3295
  const expectedType = jsTypeForKind(descriptor.itemKind);
1717
3296
  for (let i = 0; i < value.length; i++) {
1718
3297
  const item = value[i];
3298
+ if ((descriptor.itemKind === "timestamp" || descriptor.itemKind === "number") && item !== null && item !== void 0) {
3299
+ validateFieldValue(
3300
+ collection,
3301
+ `${fieldName}[${i}]`,
3302
+ { ...descriptor, kind: descriptor.itemKind, itemKind: null },
3303
+ item
3304
+ );
3305
+ }
1719
3306
  if (!matchesJsType(item, expectedType)) {
1720
3307
  throw new SchemaValidationError(
1721
3308
  `Field "${fieldName}[${i}]" in collection "${collection}" must be a ${descriptor.itemKind}, got ${typeof item}`,
@@ -1729,6 +3316,7 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
1729
3316
  }
1730
3317
  }
1731
3318
  }
3319
+ assertStructureInDomain(collection, fieldName, value);
1732
3320
  break;
1733
3321
  }
1734
3322
  case "richtext": {
@@ -1746,7 +3334,7 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
1746
3334
  break;
1747
3335
  }
1748
3336
  case "object": {
1749
- if (!isPlainObject(value)) {
3337
+ if (!isPlainObject2(value)) {
1750
3338
  throw new SchemaValidationError(
1751
3339
  `Field "${fieldName}" in collection "${collection}" must be a plain object, got ${describeType(value)}`,
1752
3340
  {
@@ -1770,6 +3358,7 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
1770
3358
  }
1771
3359
  }
1772
3360
  }
3361
+ assertStructureInDomain(collection, fieldName, value);
1773
3362
  break;
1774
3363
  }
1775
3364
  case "json": {
@@ -1786,6 +3375,7 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
1786
3375
  }
1787
3376
  );
1788
3377
  }
3378
+ assertStructureInDomain(collection, fieldName, value);
1789
3379
  break;
1790
3380
  }
1791
3381
  case "blob": {
@@ -1813,7 +3403,34 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
1813
3403
  }
1814
3404
  }
1815
3405
  }
1816
- function isPlainObject(value) {
3406
+ function assertStructureInDomain(collection, fieldName, value) {
3407
+ const reserved = reservedWireShapeViolation(value);
3408
+ if (reserved !== null) {
3409
+ throw new SchemaValidationError(
3410
+ `Field "${fieldName}" in collection "${collection}" ${reserved}.`,
3411
+ { collection, field: fieldName }
3412
+ );
3413
+ }
3414
+ const deep = valueDepthViolation(value);
3415
+ if (deep !== null) {
3416
+ throw new SchemaValidationError(
3417
+ `Field "${fieldName}" in collection "${collection}" nests deeper than ${MAX_VALUE_DEPTH} levels (at "${fieldName}${deep}"). Flatten the value.`,
3418
+ { collection, field: fieldName, path: `${fieldName}${deep}`, maxDepth: MAX_VALUE_DEPTH }
3419
+ );
3420
+ }
3421
+ const proto = protoKeyViolation(value);
3422
+ if (proto !== null) {
3423
+ throw new SchemaValidationError(
3424
+ `Field "${fieldName}" in collection "${collection}" holds a "__proto__" key (at "${fieldName}${proto}"), which cannot be stored and synced unchanged. Rename the key.`,
3425
+ { collection, field: fieldName, path: `${fieldName}${proto}` }
3426
+ );
3427
+ }
3428
+ }
3429
+ function assertResolvedFieldValue(collection, fieldName, descriptor, value) {
3430
+ if (value === null || value === void 0 || descriptor.kind === "richtext") return;
3431
+ validateFieldValue(collection, fieldName, descriptor, value);
3432
+ }
3433
+ function isPlainObject2(value) {
1817
3434
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
1818
3435
  return false;
1819
3436
  }
@@ -1967,6 +3584,17 @@ function buildScopeMap(schema, scopeValues) {
1967
3584
  return result;
1968
3585
  }
1969
3586
 
3587
+ // src/scopes/scope-grant.ts
3588
+ var SCOPE_CLAIMS_KEY = "$claims";
3589
+ function claimScopes(claims, explicit) {
3590
+ return { ...explicit ?? {}, [SCOPE_CLAIMS_KEY]: { ...claims } };
3591
+ }
3592
+ function getScopeClaims(grant) {
3593
+ if (!grant) return null;
3594
+ const claims = grant[SCOPE_CLAIMS_KEY];
3595
+ return claims && typeof claims === "object" ? claims : null;
3596
+ }
3597
+
1970
3598
  // src/scopes/extract-scope-from-claims.ts
1971
3599
  function collectSchemaScopeFields(schema) {
1972
3600
  return collectSchemaScopeValueKeys(schema);
@@ -2021,29 +3649,6 @@ function defaultSequenceFormat(name) {
2021
3649
  return `${name}-{seq:4}`;
2022
3650
  }
2023
3651
 
2024
- // src/migration/apply-operation-transforms.ts
2025
- var MAX_TRANSFORM_STEPS = 32;
2026
- function applyOperationTransforms(operation, targetSchemaVersion, transforms) {
2027
- let current = operation;
2028
- for (let step = 0; step < MAX_TRANSFORM_STEPS; step++) {
2029
- if (current.schemaVersion === targetSchemaVersion) {
2030
- return current;
2031
- }
2032
- const transform = transforms.find(
2033
- (candidate) => candidate.fromVersion === current.schemaVersion
2034
- );
2035
- if (!transform) {
2036
- return null;
2037
- }
2038
- const next = transform.transform(current);
2039
- if (next === null) {
2040
- return null;
2041
- }
2042
- current = next;
2043
- }
2044
- return null;
2045
- }
2046
-
2047
3652
  // src/migrations/migration-builder.ts
2048
3653
  var RollbackBuilder = class {
2049
3654
  _steps = [];
@@ -2149,17 +3754,29 @@ var MigrationBuilder = class _MigrationBuilder {
2149
3754
  }
2150
3755
  /**
2151
3756
  * Backfill records in a collection using a transform function.
2152
- * The transform receives each record and returns the fields to update.
2153
- * Runs after structural changes (addField, renameField, etc.).
3757
+ * The transform receives each live record (typed values, as the app reads them) and
3758
+ * returns the fields to change. Runs after structural changes (addField, renameField,
3759
+ * etc.), in the same transaction as them and the schema version write.
3760
+ *
3761
+ * Changed records are written as update operations (mutation name `migration:v<N>`)
3762
+ * through the normal local write path, so backfilled values sync. Pass
3763
+ * `{ localOnly: true }` for device-local data that must not sync.
2154
3764
  *
2155
3765
  * @param collection - The collection name
2156
3766
  * @param transform - Forward transform function
2157
- * @param reverseTransform - Optional reverse transform for rollback support
3767
+ * @param reverseOrOptions - Reverse transform for rollback support, or options
2158
3768
  */
2159
- backfill(collection, transform, reverseTransform) {
3769
+ backfill(collection, transform, reverseOrOptions) {
3770
+ const options = typeof reverseOrOptions === "function" ? { reverseTransform: reverseOrOptions } : reverseOrOptions ?? {};
2160
3771
  return new _MigrationBuilder([
2161
3772
  ...this.steps,
2162
- { type: "backfill", collection, transform, reverseTransform }
3773
+ {
3774
+ type: "backfill",
3775
+ collection,
3776
+ transform,
3777
+ ...options.reverseTransform ? { reverseTransform: options.reverseTransform } : {},
3778
+ ...options.localOnly ? { localOnly: true } : {}
3779
+ }
2163
3780
  ]);
2164
3781
  }
2165
3782
  /**
@@ -2306,11 +3923,13 @@ function migrationStepsToSQL(steps) {
2306
3923
  break;
2307
3924
  case "addIndex":
2308
3925
  statements.push(
2309
- `CREATE INDEX IF NOT EXISTS idx_${step.collection}_${step.field} ON ${quoteIdent(step.collection)} (${quoteIdent(step.field)})`
3926
+ `CREATE INDEX IF NOT EXISTS ${quoteIdent(collectionIndexName(step.collection, step.field))} ON ${quoteIdent(step.collection)} (${quoteIdent(step.field)})`
2310
3927
  );
2311
3928
  break;
2312
3929
  case "removeIndex":
2313
- statements.push(`DROP INDEX IF EXISTS idx_${step.collection}_${step.field}`);
3930
+ statements.push(
3931
+ `DROP INDEX IF EXISTS ${quoteIdent(collectionIndexName(step.collection, step.field))}`
3932
+ );
2314
3933
  break;
2315
3934
  case "backfill":
2316
3935
  break;
@@ -2323,18 +3942,14 @@ function rollbackStepsToSQL(migration) {
2323
3942
  return migrationStepsToSQL(rollbackSteps);
2324
3943
  }
2325
3944
  function addFieldSQL(collection, field, descriptor) {
2326
- const sqlType = mapFieldType2(descriptor);
3945
+ const sqlType = mapFieldType(descriptor);
2327
3946
  const parts = [`ALTER TABLE ${quoteIdent(collection)} ADD COLUMN ${quoteIdent(field)}`, sqlType];
2328
3947
  if (descriptor.defaultValue !== void 0) {
2329
- parts.push(`DEFAULT ${sqlDefault2(descriptor.defaultValue)}`);
2330
- }
2331
- if (descriptor.kind === "enum" && descriptor.enumValues) {
2332
- const values = descriptor.enumValues.map((v) => `'${v}'`).join(", ");
2333
- parts.push(`CHECK (${quoteIdent(field)} IN (${values}))`);
3948
+ parts.push(`DEFAULT ${sqlDefaultLiteral(descriptor.defaultValue)}`);
2334
3949
  }
2335
3950
  return parts.join(" ");
2336
3951
  }
2337
- function mapFieldType2(descriptor) {
3952
+ function mapFieldType(descriptor) {
2338
3953
  switch (descriptor.kind) {
2339
3954
  case "string":
2340
3955
  return "TEXT";
@@ -2360,13 +3975,6 @@ function mapFieldType2(descriptor) {
2360
3975
  return "BLOB";
2361
3976
  }
2362
3977
  }
2363
- function sqlDefault2(value) {
2364
- if (value === null) return "NULL";
2365
- if (typeof value === "string") return `'${value}'`;
2366
- if (typeof value === "number") return String(value);
2367
- if (typeof value === "boolean") return value ? "1" : "0";
2368
- return `'${JSON.stringify(value)}'`;
2369
- }
2370
3978
 
2371
3979
  // src/codegen/proto-generator.ts
2372
3980
  var SCALAR_TYPE_MAP = {
@@ -2471,15 +4079,20 @@ var KORA_OPERATION_MESSAGE = `message KoraOperation {
2471
4079
  var OPERATION_BATCH_MESSAGE = `message OperationBatch {
2472
4080
  repeated KoraOperation operations = 1;
2473
4081
  bool is_final = 2;
4082
+ bytes retractions_json = 3;
2474
4083
  }`;
2475
4084
  var HANDSHAKE_MESSAGE = `message HandshakeMessage {
2476
4085
  map<string, int64> version_vector = 1;
2477
4086
  int32 schema_version = 2;
2478
4087
  string node_id = 3;
4088
+ string scope_exit_policy = 4;
2479
4089
  }`;
2480
4090
  var HANDSHAKE_RESPONSE_MESSAGE = `message HandshakeResponse {
2481
4091
  map<string, int64> version_vector = 1;
2482
4092
  int32 schema_version = 2;
4093
+ bytes accepted_scope_json = 3;
4094
+ bytes accepted_downlink_scopes_json = 4;
4095
+ bytes accepted_uplink_scopes_json = 5;
2483
4096
  }`;
2484
4097
  var ACKNOWLEDGMENT_MESSAGE = `message Acknowledgment {
2485
4098
  int64 sequence_number = 1;
@@ -2545,20 +4158,25 @@ function buildJsonDescriptor(schema) {
2545
4158
  nested.OperationBatch = {
2546
4159
  fields: {
2547
4160
  operations: { type: "KoraOperation", id: 1, rule: "repeated" },
2548
- is_final: { type: "bool", id: 2 }
4161
+ is_final: { type: "bool", id: 2 },
4162
+ retractions_json: { type: "bytes", id: 3 }
2549
4163
  }
2550
4164
  };
2551
4165
  nested.HandshakeMessage = {
2552
4166
  fields: {
2553
4167
  version_vector: { keyType: "string", type: "int64", id: 1 },
2554
4168
  schema_version: { type: "int32", id: 2 },
2555
- node_id: { type: "string", id: 3 }
4169
+ node_id: { type: "string", id: 3 },
4170
+ scope_exit_policy: { type: "string", id: 4 }
2556
4171
  }
2557
4172
  };
2558
4173
  nested.HandshakeResponse = {
2559
4174
  fields: {
2560
4175
  version_vector: { keyType: "string", type: "int64", id: 1 },
2561
- schema_version: { type: "int32", id: 2 }
4176
+ schema_version: { type: "int32", id: 2 },
4177
+ accepted_scope_json: { type: "bytes", id: 3 },
4178
+ accepted_downlink_scopes_json: { type: "bytes", id: 4 },
4179
+ accepted_uplink_scopes_json: { type: "bytes", id: 5 }
2562
4180
  }
2563
4181
  };
2564
4182
  nested.Acknowledgment = {
@@ -2613,50 +4231,91 @@ export {
2613
4231
  CONNECTION_QUALITIES,
2614
4232
  CausalTracker,
2615
4233
  ClockDriftError,
4234
+ DEFAULT_MAX_OPERATION_BYTES,
4235
+ DEFAULT_OPERATION_HASH_VERSION,
2616
4236
  EnumFieldBuilder,
4237
+ FOLD_RECORD_TRACE_FIELD,
4238
+ FOLD_STATE_VERSION,
2617
4239
  FieldBuilder,
4240
+ FoldConfigurationError,
4241
+ FoldStateError,
2618
4242
  HybridLogicalClock,
2619
4243
  InvalidTimestampError,
4244
+ JsonFieldBuilder,
2620
4245
  KORA_ERROR_FIX_SUGGESTIONS,
2621
4246
  KoraError,
2622
4247
  MAX_LOGICAL,
4248
+ MAX_VALUE_DEPTH,
2623
4249
  MERGE_STRATEGIES,
2624
4250
  MergeConflictError,
2625
4251
  MigrationBuilder,
2626
4252
  MigrationRollbackError,
2627
4253
  MissingSecretKeyError,
4254
+ NonCanonicalValueError,
4255
+ ObjectFieldBuilder,
2628
4256
  OperationError,
4257
+ OperationTooLargeError,
4258
+ OperationTransformCoverageError,
4259
+ OperationTransformError,
4260
+ RESERVED_NODE_ID_PREFIX,
2629
4261
  RemoteClockDriftError,
2630
4262
  RollbackBuilder,
4263
+ SCOPE_CLAIMS_KEY,
4264
+ SERVER_NODE_ID_PREFIX,
4265
+ STORED_TEXT_ESCAPE,
2631
4266
  SchemaValidationError,
4267
+ SecretFieldBuilder,
2632
4268
  StorageError,
2633
4269
  SyncError,
4270
+ TIMESTAMP_MAX_MS,
4271
+ TIMESTAMP_MIN_MS,
4272
+ adaptFoldState,
2634
4273
  advanceVector,
2635
4274
  applyAtomicOp,
2636
4275
  applyOperationTransforms,
4276
+ assertOperationTransformCoverage,
4277
+ assertResolvedFieldValue,
2637
4278
  base64ToBytes,
2638
4279
  buildScopeMap,
2639
4280
  buildStateMachineConstraints,
2640
4281
  bytesToBase64,
2641
4282
  canAutoRollback,
4283
+ canonicalValue,
4284
+ canonicalizeLegacyOperation,
4285
+ canonicalizeOperationBody,
4286
+ canonicalizeProvenLegacyClear,
4287
+ claimScopes,
2642
4288
  collectSchemaScopeFields,
2643
4289
  collectSchemaScopeValueKeys,
2644
4290
  computeDelta,
4291
+ computeOperationId,
2645
4292
  createBlobRef,
4293
+ createFoldState,
2646
4294
  createOperation,
2647
4295
  createReversibleMigration,
4296
+ createSnapshotState,
2648
4297
  createVersionVector,
2649
4298
  decodeBytesFromOpData,
4299
+ decodeStoredJsonValue,
4300
+ decodeStoredText,
2650
4301
  decryptSecret,
2651
4302
  defaultApplyFailureReason,
2652
4303
  defaultSequenceFormat,
2653
4304
  defineSchema,
4305
+ deriveSideEffectOpId,
4306
+ deserializeFoldState,
2654
4307
  deserializeVector,
2655
4308
  dominates,
2656
4309
  encodeBytesForOpData,
4310
+ encodeStoredJsonValue,
4311
+ encodeStoredText,
2657
4312
  encryptSecret,
4313
+ expandFieldVersionedOperations,
2658
4314
  extractScopeValuesFromClaims,
2659
4315
  extractTimestamp,
4316
+ foldPlanFingerprint,
4317
+ foldPlanFingerprints,
4318
+ foldRecord,
2660
4319
  formatSequenceValue,
2661
4320
  generateFullDDL,
2662
4321
  generateProtoDefinitions,
@@ -2664,35 +4323,61 @@ export {
2664
4323
  generateSQL,
2665
4324
  generateUUIDv7,
2666
4325
  getCollectionScopeBindings,
4326
+ getFoldFieldVersionStrings,
4327
+ getFoldFieldVersions,
2667
4328
  getKoraErrorFix,
4329
+ getScopeClaims,
2668
4330
  getTransitionMap,
2669
4331
  hasSchemaSyncRules,
2670
4332
  hashBlob,
2671
4333
  hashSecret,
2672
4334
  isApplyFailure,
2673
4335
  isAtomicOp,
4336
+ isAuthoritativeNodeId,
2674
4337
  isBlobRef,
2675
4338
  isCollectionSyncScoped,
4339
+ isFoldStateLive,
2676
4340
  isKoraBytesValue,
2677
4341
  isLegacyNumericByteObject,
4342
+ isReservedNodeId,
4343
+ isServerNodeId,
2678
4344
  isValidOperation,
2679
4345
  isValidUUIDv7,
4346
+ joinStates,
4347
+ materialize,
4348
+ measureOperationBytes,
4349
+ mergeArraySet,
4350
+ mergeOp,
2680
4351
  mergeVectors,
2681
4352
  migrate,
2682
4353
  migrationStepsToSQL,
4354
+ mismatchedFoldFields,
4355
+ missingTransformPaths,
4356
+ needsStoredTextEncoding,
2683
4357
  op,
4358
+ operationSchemaView,
4359
+ operationTransformsFingerprint,
4360
+ operationValueViolation,
4361
+ protoKeyViolation,
2684
4362
  quoteIdent,
4363
+ replayFieldVersionsForRecord,
2685
4364
  replayOperationsForRecord,
4365
+ reservedWireShapeViolation,
2686
4366
  resolveAtomicOp,
2687
4367
  revealSecret,
2688
4368
  rollbackStepsToSQL,
4369
+ serializeFoldState,
2689
4370
  serializeVector,
2690
4371
  t,
4372
+ timestampDomainViolation,
2691
4373
  toAtomicOp,
4374
+ toMergeTrace,
2692
4375
  transformSecretFieldsForWrite,
2693
4376
  validateRecord,
2694
4377
  validateTransition,
4378
+ valueDepthViolation,
2695
4379
  vectorsEqual,
4380
+ verifyOperationId,
2696
4381
  verifyOperationIntegrity,
2697
4382
  verifySecret,
2698
4383
  verifySecretValue