@korajs/core 1.0.0-beta.12 → 1.0.0-beta.14

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.cjs CHANGED
@@ -27,50 +27,91 @@ __export(index_exports, {
27
27
  CONNECTION_QUALITIES: () => CONNECTION_QUALITIES,
28
28
  CausalTracker: () => CausalTracker,
29
29
  ClockDriftError: () => ClockDriftError,
30
+ DEFAULT_MAX_OPERATION_BYTES: () => DEFAULT_MAX_OPERATION_BYTES,
31
+ DEFAULT_OPERATION_HASH_VERSION: () => DEFAULT_OPERATION_HASH_VERSION,
30
32
  EnumFieldBuilder: () => EnumFieldBuilder,
33
+ FOLD_RECORD_TRACE_FIELD: () => FOLD_RECORD_TRACE_FIELD,
34
+ FOLD_STATE_VERSION: () => FOLD_STATE_VERSION,
31
35
  FieldBuilder: () => FieldBuilder,
36
+ FoldConfigurationError: () => FoldConfigurationError,
37
+ FoldStateError: () => FoldStateError,
32
38
  HybridLogicalClock: () => HybridLogicalClock,
33
39
  InvalidTimestampError: () => InvalidTimestampError,
40
+ JsonFieldBuilder: () => JsonFieldBuilder,
34
41
  KORA_ERROR_FIX_SUGGESTIONS: () => KORA_ERROR_FIX_SUGGESTIONS,
35
42
  KoraError: () => KoraError,
36
43
  MAX_LOGICAL: () => MAX_LOGICAL,
44
+ MAX_VALUE_DEPTH: () => MAX_VALUE_DEPTH,
37
45
  MERGE_STRATEGIES: () => MERGE_STRATEGIES,
38
46
  MergeConflictError: () => MergeConflictError,
39
47
  MigrationBuilder: () => MigrationBuilder,
40
48
  MigrationRollbackError: () => MigrationRollbackError,
41
49
  MissingSecretKeyError: () => MissingSecretKeyError,
50
+ NonCanonicalValueError: () => NonCanonicalValueError,
51
+ ObjectFieldBuilder: () => ObjectFieldBuilder,
42
52
  OperationError: () => OperationError,
53
+ OperationTooLargeError: () => OperationTooLargeError,
54
+ OperationTransformCoverageError: () => OperationTransformCoverageError,
55
+ OperationTransformError: () => OperationTransformError,
56
+ RESERVED_NODE_ID_PREFIX: () => RESERVED_NODE_ID_PREFIX,
43
57
  RemoteClockDriftError: () => RemoteClockDriftError,
44
58
  RollbackBuilder: () => RollbackBuilder,
59
+ SCOPE_CLAIMS_KEY: () => SCOPE_CLAIMS_KEY,
60
+ SERVER_NODE_ID_PREFIX: () => SERVER_NODE_ID_PREFIX,
61
+ STORED_TEXT_ESCAPE: () => STORED_TEXT_ESCAPE,
45
62
  SchemaValidationError: () => SchemaValidationError,
63
+ SecretFieldBuilder: () => SecretFieldBuilder,
46
64
  StorageError: () => StorageError,
47
65
  SyncError: () => SyncError,
66
+ TIMESTAMP_MAX_MS: () => TIMESTAMP_MAX_MS,
67
+ TIMESTAMP_MIN_MS: () => TIMESTAMP_MIN_MS,
68
+ adaptFoldState: () => adaptFoldState,
48
69
  advanceVector: () => advanceVector,
49
70
  applyAtomicOp: () => applyAtomicOp,
50
71
  applyOperationTransforms: () => applyOperationTransforms,
72
+ assertOperationTransformCoverage: () => assertOperationTransformCoverage,
73
+ assertResolvedFieldValue: () => assertResolvedFieldValue,
51
74
  base64ToBytes: () => base64ToBytes,
52
75
  buildScopeMap: () => buildScopeMap,
53
76
  buildStateMachineConstraints: () => buildStateMachineConstraints,
54
77
  bytesToBase64: () => bytesToBase64,
55
78
  canAutoRollback: () => canAutoRollback,
79
+ canonicalValue: () => canonicalValue,
80
+ canonicalizeLegacyOperation: () => canonicalizeLegacyOperation,
81
+ canonicalizeOperationBody: () => canonicalizeOperationBody,
82
+ canonicalizeProvenLegacyClear: () => canonicalizeProvenLegacyClear,
83
+ claimScopes: () => claimScopes,
56
84
  collectSchemaScopeFields: () => collectSchemaScopeFields,
57
85
  collectSchemaScopeValueKeys: () => collectSchemaScopeValueKeys,
58
86
  computeDelta: () => computeDelta,
87
+ computeOperationId: () => computeOperationId,
59
88
  createBlobRef: () => createBlobRef,
89
+ createFoldState: () => createFoldState,
60
90
  createOperation: () => createOperation,
61
91
  createReversibleMigration: () => createReversibleMigration,
92
+ createSnapshotState: () => createSnapshotState,
62
93
  createVersionVector: () => createVersionVector,
63
94
  decodeBytesFromOpData: () => decodeBytesFromOpData,
95
+ decodeStoredJsonValue: () => decodeStoredJsonValue,
96
+ decodeStoredText: () => decodeStoredText,
64
97
  decryptSecret: () => decryptSecret,
65
98
  defaultApplyFailureReason: () => defaultApplyFailureReason,
66
99
  defaultSequenceFormat: () => defaultSequenceFormat,
67
100
  defineSchema: () => defineSchema,
101
+ deriveSideEffectOpId: () => deriveSideEffectOpId,
102
+ deserializeFoldState: () => deserializeFoldState,
68
103
  deserializeVector: () => deserializeVector,
69
104
  dominates: () => dominates,
70
105
  encodeBytesForOpData: () => encodeBytesForOpData,
106
+ encodeStoredJsonValue: () => encodeStoredJsonValue,
107
+ encodeStoredText: () => encodeStoredText,
71
108
  encryptSecret: () => encryptSecret,
109
+ expandFieldVersionedOperations: () => expandFieldVersionedOperations,
72
110
  extractScopeValuesFromClaims: () => extractScopeValuesFromClaims,
73
111
  extractTimestamp: () => extractTimestamp,
112
+ foldPlanFingerprint: () => foldPlanFingerprint,
113
+ foldPlanFingerprints: () => foldPlanFingerprints,
114
+ foldRecord: () => foldRecord,
74
115
  formatSequenceValue: () => formatSequenceValue,
75
116
  generateFullDDL: () => generateFullDDL,
76
117
  generateProtoDefinitions: () => generateProtoDefinitions,
@@ -78,35 +119,61 @@ __export(index_exports, {
78
119
  generateSQL: () => generateSQL,
79
120
  generateUUIDv7: () => generateUUIDv7,
80
121
  getCollectionScopeBindings: () => getCollectionScopeBindings,
122
+ getFoldFieldVersionStrings: () => getFoldFieldVersionStrings,
123
+ getFoldFieldVersions: () => getFoldFieldVersions,
81
124
  getKoraErrorFix: () => getKoraErrorFix,
125
+ getScopeClaims: () => getScopeClaims,
82
126
  getTransitionMap: () => getTransitionMap,
83
127
  hasSchemaSyncRules: () => hasSchemaSyncRules,
84
128
  hashBlob: () => hashBlob,
85
129
  hashSecret: () => hashSecret,
86
130
  isApplyFailure: () => isApplyFailure,
87
131
  isAtomicOp: () => isAtomicOp,
132
+ isAuthoritativeNodeId: () => isAuthoritativeNodeId,
88
133
  isBlobRef: () => isBlobRef,
89
134
  isCollectionSyncScoped: () => isCollectionSyncScoped,
135
+ isFoldStateLive: () => isFoldStateLive,
90
136
  isKoraBytesValue: () => isKoraBytesValue,
91
137
  isLegacyNumericByteObject: () => isLegacyNumericByteObject,
138
+ isReservedNodeId: () => isReservedNodeId,
139
+ isServerNodeId: () => isServerNodeId,
92
140
  isValidOperation: () => isValidOperation,
93
141
  isValidUUIDv7: () => isValidUUIDv7,
142
+ joinStates: () => joinStates,
143
+ materialize: () => materialize,
144
+ measureOperationBytes: () => measureOperationBytes,
145
+ mergeArraySet: () => mergeArraySet,
146
+ mergeOp: () => mergeOp,
94
147
  mergeVectors: () => mergeVectors,
95
148
  migrate: () => migrate,
96
149
  migrationStepsToSQL: () => migrationStepsToSQL,
150
+ mismatchedFoldFields: () => mismatchedFoldFields,
151
+ missingTransformPaths: () => missingTransformPaths,
152
+ needsStoredTextEncoding: () => needsStoredTextEncoding,
97
153
  op: () => op,
154
+ operationSchemaView: () => operationSchemaView,
155
+ operationTransformsFingerprint: () => operationTransformsFingerprint,
156
+ operationValueViolation: () => operationValueViolation,
157
+ protoKeyViolation: () => protoKeyViolation,
98
158
  quoteIdent: () => quoteIdent,
159
+ replayFieldVersionsForRecord: () => replayFieldVersionsForRecord,
99
160
  replayOperationsForRecord: () => replayOperationsForRecord,
161
+ reservedWireShapeViolation: () => reservedWireShapeViolation,
100
162
  resolveAtomicOp: () => resolveAtomicOp,
101
163
  revealSecret: () => revealSecret,
102
164
  rollbackStepsToSQL: () => rollbackStepsToSQL,
165
+ serializeFoldState: () => serializeFoldState,
103
166
  serializeVector: () => serializeVector,
104
167
  t: () => t,
168
+ timestampDomainViolation: () => timestampDomainViolation,
105
169
  toAtomicOp: () => toAtomicOp,
170
+ toMergeTrace: () => toMergeTrace,
106
171
  transformSecretFieldsForWrite: () => transformSecretFieldsForWrite,
107
172
  validateRecord: () => validateRecord,
108
173
  validateTransition: () => validateTransition,
174
+ valueDepthViolation: () => valueDepthViolation,
109
175
  vectorsEqual: () => vectorsEqual,
176
+ verifyOperationId: () => verifyOperationId,
110
177
  verifyOperationIntegrity: () => verifyOperationIntegrity,
111
178
  verifySecret: () => verifySecret,
112
179
  verifySecretValue: () => verifySecretValue
@@ -174,6 +241,24 @@ var OperationError = class extends KoraError {
174
241
  this.name = "OperationError";
175
242
  }
176
243
  };
244
+ var OperationTooLargeError = class extends KoraError {
245
+ constructor(collection, recordId, bytes, maxBytes) {
246
+ super(
247
+ `A write to ${collection}/${recordId} would produce an operation of ${bytes} bytes, more than the ${maxBytes}-byte limit (maxOperationBytes). Nothing was written. Store large content as a blob (t.blob()) or split it across records.`,
248
+ "OPERATION_TOO_LARGE",
249
+ { collection, recordId, bytes, maxBytes }
250
+ );
251
+ this.collection = collection;
252
+ this.recordId = recordId;
253
+ this.bytes = bytes;
254
+ this.maxBytes = maxBytes;
255
+ this.name = "OperationTooLargeError";
256
+ }
257
+ collection;
258
+ recordId;
259
+ bytes;
260
+ maxBytes;
261
+ };
177
262
  var MergeConflictError = class extends KoraError {
178
263
  constructor(operationA, operationB, field) {
179
264
  super(
@@ -799,8 +884,305 @@ function defaultApplyFailureReason(result, overrides) {
799
884
  return { ...base, ...overrides };
800
885
  }
801
886
 
887
+ // src/operations/op-data-binary.ts
888
+ function isKoraBytesValue(value) {
889
+ if (typeof value !== "object" || value === null) {
890
+ return false;
891
+ }
892
+ const record = value;
893
+ return Object.keys(record).length === 1 && typeof record.$koraBytes === "string";
894
+ }
895
+ function isLegacyNumericByteObject(value) {
896
+ if (typeof value !== "object" || value === null || ArrayBuffer.isView(value)) {
897
+ return false;
898
+ }
899
+ const record = value;
900
+ const keys = Object.keys(record);
901
+ if (keys.length === 0) {
902
+ return false;
903
+ }
904
+ for (let i = 0; i < keys.length; i++) {
905
+ const byte = record[String(i)];
906
+ if (typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255) {
907
+ return false;
908
+ }
909
+ }
910
+ return true;
911
+ }
912
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
913
+ var BASE64_REVERSE = new Map(
914
+ Array.from(BASE64_ALPHABET, (char, index) => [char, index])
915
+ );
916
+ function bytesToBase64(bytes) {
917
+ let out = "";
918
+ for (let i = 0; i < bytes.length; i += 3) {
919
+ const b0 = bytes[i] ?? 0;
920
+ const b1 = bytes[i + 1] ?? 0;
921
+ const b2 = bytes[i + 2] ?? 0;
922
+ const triple = b0 << 16 | b1 << 8 | b2;
923
+ out += BASE64_ALPHABET[triple >> 18 & 63] ?? "";
924
+ out += BASE64_ALPHABET[triple >> 12 & 63] ?? "";
925
+ out += i + 1 < bytes.length ? BASE64_ALPHABET[triple >> 6 & 63] ?? "" : "=";
926
+ out += i + 2 < bytes.length ? BASE64_ALPHABET[triple & 63] ?? "" : "=";
927
+ }
928
+ return out;
929
+ }
930
+ function base64ToBytes(base64) {
931
+ const cleaned = base64.replace(/=+$/, "");
932
+ const out = new Uint8Array(Math.floor(cleaned.length * 6 / 8));
933
+ let buffer = 0;
934
+ let bits = 0;
935
+ let index = 0;
936
+ for (const char of cleaned) {
937
+ const value = BASE64_REVERSE.get(char);
938
+ if (value === void 0) {
939
+ throw new OperationError(`Invalid base64 character "${char}" in tagged binary value.`, {
940
+ char
941
+ });
942
+ }
943
+ buffer = buffer << 6 | value;
944
+ bits += 6;
945
+ if (bits >= 8) {
946
+ bits -= 8;
947
+ out[index] = buffer >> bits & 255;
948
+ index += 1;
949
+ }
950
+ }
951
+ return out;
952
+ }
953
+ function encodeBytesForOpData(value) {
954
+ if (typeof value === "string") {
955
+ return value;
956
+ }
957
+ const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
958
+ return { $koraBytes: bytesToBase64(bytes) };
959
+ }
960
+ function decodeBytesFromOpData(value) {
961
+ if (typeof value === "string") {
962
+ return value;
963
+ }
964
+ if (value instanceof Uint8Array) {
965
+ return value;
966
+ }
967
+ if (value instanceof ArrayBuffer) {
968
+ return new Uint8Array(value);
969
+ }
970
+ if (isKoraBytesValue(value)) {
971
+ return base64ToBytes(value.$koraBytes);
972
+ }
973
+ if (isLegacyNumericByteObject(value)) {
974
+ const keys = Object.keys(value);
975
+ const bytes = new Uint8Array(keys.length);
976
+ for (let i = 0; i < keys.length; i++) {
977
+ bytes[i] = value[String(i)] ?? 0;
978
+ }
979
+ return bytes;
980
+ }
981
+ throw new OperationError(
982
+ "Binary op-data value must be a string, Uint8Array, ArrayBuffer, tagged { $koraBytes } object, or legacy numeric-key byte object.",
983
+ { receivedType: typeof value }
984
+ );
985
+ }
986
+
987
+ // src/operations/canonical-body.ts
988
+ var NonCanonicalValueError = class extends KoraError {
989
+ constructor(message, path, receivedType) {
990
+ super(message, "NON_CANONICAL_VALUE", { path, receivedType });
991
+ this.path = path;
992
+ this.receivedType = receivedType;
993
+ this.name = "NonCanonicalValueError";
994
+ }
995
+ path;
996
+ receivedType;
997
+ };
998
+ function canonicalizeOperationBody(input) {
999
+ const isUpdate = input.type === "update";
1000
+ const where = `${input.collection}/${input.recordId}`;
1001
+ const data = canonicalTopLevel(input.data, isUpdate, "data", where);
1002
+ const previousData = canonicalTopLevel(input.previousData, isUpdate, "previousData", where);
1003
+ let atomicOps = input.atomicOps;
1004
+ if (atomicOps !== void 0) {
1005
+ const canonical2 = canonicalTopLevel(
1006
+ atomicOps,
1007
+ false,
1008
+ "atomicOps",
1009
+ where
1010
+ );
1011
+ atomicOps = canonical2 ?? void 0;
1012
+ }
1013
+ if (data === input.data && previousData === input.previousData && atomicOps === input.atomicOps) {
1014
+ return input;
1015
+ }
1016
+ return {
1017
+ ...input,
1018
+ data,
1019
+ previousData,
1020
+ ...atomicOps !== void 0 ? { atomicOps } : {}
1021
+ };
1022
+ }
1023
+ function canonicalValue(value, path) {
1024
+ return canonical(value, path, /* @__PURE__ */ new Set());
1025
+ }
1026
+ function canonicalTopLevel(record, undefinedIsNull, label, where) {
1027
+ if (record === null || record === void 0) return null;
1028
+ if (!isPlainObject(record)) {
1029
+ throw new NonCanonicalValueError(
1030
+ `Operation ${label} of ${where} must be a plain object, got ${describe(record)}.`,
1031
+ label,
1032
+ describe(record)
1033
+ );
1034
+ }
1035
+ let changed = false;
1036
+ const out = {};
1037
+ for (const [key, member] of Object.entries(record)) {
1038
+ if (member === void 0) {
1039
+ changed = true;
1040
+ if (undefinedIsNull) out[key] = null;
1041
+ continue;
1042
+ }
1043
+ const next = canonical(member, `${where}.${key}`, /* @__PURE__ */ new Set());
1044
+ if (!Object.is(next, member)) changed = true;
1045
+ out[key] = next;
1046
+ }
1047
+ if (Object.getPrototypeOf(record) !== Object.prototype) changed = true;
1048
+ return changed ? out : record;
1049
+ }
1050
+ function canonical(value, path, ancestors) {
1051
+ switch (typeof value) {
1052
+ case "string":
1053
+ case "boolean":
1054
+ return value;
1055
+ case "number":
1056
+ if (!Number.isFinite(value)) {
1057
+ throw refusal(path, String(value), 'Use a finite number, or null for "no value".');
1058
+ }
1059
+ return Object.is(value, -0) ? 0 : value;
1060
+ case "undefined":
1061
+ return null;
1062
+ case "bigint":
1063
+ throw refusal(path, "bigint", "Convert it to a number or a string.");
1064
+ case "function":
1065
+ throw refusal(path, "function", "Store data only, not functions.");
1066
+ case "symbol":
1067
+ throw refusal(path, "symbol", "Use a string instead.");
1068
+ }
1069
+ if (value === null) return null;
1070
+ const object = value;
1071
+ if (object instanceof Uint8Array) return { $koraBytes: bytesToBase64(object) };
1072
+ if (object instanceof ArrayBuffer) return { $koraBytes: bytesToBase64(new Uint8Array(object)) };
1073
+ if (object instanceof Date) {
1074
+ const time = object.getTime();
1075
+ if (Number.isNaN(time)) {
1076
+ throw refusal(path, "invalid Date", "Pass a valid Date, or null.");
1077
+ }
1078
+ return object.toISOString();
1079
+ }
1080
+ if (ancestors.has(object)) {
1081
+ throw refusal(path, "circular reference", "Remove the cycle: JSON cannot hold it.");
1082
+ }
1083
+ if (Array.isArray(object)) {
1084
+ ancestors.add(object);
1085
+ let changed2 = false;
1086
+ const out2 = [];
1087
+ for (let index = 0; index < object.length; index++) {
1088
+ if (!(index in object)) {
1089
+ ancestors.delete(object);
1090
+ throw refusal(
1091
+ `${path}[${String(index)}]`,
1092
+ "array hole",
1093
+ "Use a dense array (fill the gap with null)."
1094
+ );
1095
+ }
1096
+ const item = object[index];
1097
+ const next = canonical(item, `${path}[${String(index)}]`, ancestors);
1098
+ if (!Object.is(next, item)) changed2 = true;
1099
+ out2.push(next);
1100
+ }
1101
+ ancestors.delete(object);
1102
+ return changed2 ? out2 : object;
1103
+ }
1104
+ if (!isPlainObject(object)) {
1105
+ const type = describe(object);
1106
+ throw refusal(
1107
+ path,
1108
+ type,
1109
+ type === "Map" ? "Convert it with Object.fromEntries(map)." : type === "Set" ? "Convert it with [...set]." : "Convert it to a plain object (JSON data only)."
1110
+ );
1111
+ }
1112
+ if (typeof object.toJSON === "function") {
1113
+ throw refusal(path, "object with toJSON", "Store the value toJSON() returns instead.");
1114
+ }
1115
+ if (Object.prototype.hasOwnProperty.call(object, "__proto__")) {
1116
+ throw refusal(`${path}.__proto__`, '"__proto__" key', "Rename the key.");
1117
+ }
1118
+ ancestors.add(object);
1119
+ let changed = Object.getPrototypeOf(object) !== Object.prototype;
1120
+ const out = {};
1121
+ for (const [key, member] of Object.entries(object)) {
1122
+ if (member === void 0) {
1123
+ changed = true;
1124
+ continue;
1125
+ }
1126
+ const next = canonical(member, `${path}.${key}`, ancestors);
1127
+ if (!Object.is(next, member)) changed = true;
1128
+ out[key] = next;
1129
+ }
1130
+ ancestors.delete(object);
1131
+ return changed ? out : object;
1132
+ }
1133
+ function refusal(path, receivedType, fix) {
1134
+ return new NonCanonicalValueError(
1135
+ `Value at "${path}" (${receivedType}) has no JSON form, so it cannot be stored or synced unchanged. ${fix}`,
1136
+ path,
1137
+ receivedType
1138
+ );
1139
+ }
1140
+ function isPlainObject(value) {
1141
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1142
+ const proto = Object.getPrototypeOf(value);
1143
+ return proto === Object.prototype || proto === null;
1144
+ }
1145
+ function describe(value) {
1146
+ if (value === null) return "null";
1147
+ if (Array.isArray(value)) return "array";
1148
+ if (typeof value !== "object") return typeof value;
1149
+ const ctor = value.constructor;
1150
+ return typeof ctor?.name === "string" && ctor.name.length > 0 ? ctor.name : "object";
1151
+ }
1152
+ async function canonicalizeProvenLegacyClear(op2) {
1153
+ const restored = canonicalizeLegacyOperation(op2);
1154
+ if (restored === op2) return op2;
1155
+ if (await verifyOperationId(op2)) return op2;
1156
+ if (await verifyOperationId(restored)) return restored;
1157
+ if (op2.hashVersion === void 0 && await verifyOperationId({ ...restored, hashVersion: 2 })) {
1158
+ return restored;
1159
+ }
1160
+ return op2;
1161
+ }
1162
+ function canonicalizeLegacyOperation(op2) {
1163
+ if (op2.type !== "update" || op2.hashVersion === 2 || op2.encrypted !== void 0) return op2;
1164
+ const previous = op2.previousData;
1165
+ if (previous === null || previous === void 0) return op2;
1166
+ const data = op2.data ?? {};
1167
+ const missing = Object.keys(previous).filter((key) => !(key in data));
1168
+ if (missing.length === 0) return op2;
1169
+ const out = { ...data };
1170
+ for (const key of missing) out[key] = null;
1171
+ return { ...op2, data: out };
1172
+ }
1173
+
802
1174
  // src/operations/content-hash.ts
803
- async function computeOperationId(input, timestamp) {
1175
+ var DEFAULT_OPERATION_HASH_VERSION = 2;
1176
+ async function computeOperationId(input, timestampOrVersion) {
1177
+ if (typeof timestampOrVersion === "string") {
1178
+ return sha256Hex(canonicalize(v1HashInput(input, timestampOrVersion)));
1179
+ }
1180
+ const op2 = input;
1181
+ const serializedTs = HybridLogicalClock.serialize(op2.timestamp);
1182
+ if (timestampOrVersion === 1) return sha256Hex(canonicalize(v1HashInput(op2, serializedTs)));
1183
+ return sha256Hex(canonicalize(v2HashInput(op2, serializedTs)));
1184
+ }
1185
+ function v1HashInput(input, timestamp) {
804
1186
  const hashInput = {
805
1187
  type: input.type,
806
1188
  collection: input.collection,
@@ -812,8 +1194,30 @@ async function computeOperationId(input, timestamp) {
812
1194
  if (input.atomicOps !== void 0 && Object.keys(input.atomicOps).length > 0) {
813
1195
  hashInput.atomicOps = input.atomicOps;
814
1196
  }
815
- const canonical = canonicalize(hashInput);
816
- const encoded = new TextEncoder().encode(canonical);
1197
+ return hashInput;
1198
+ }
1199
+ function v2HashInput(op2, timestamp) {
1200
+ const body = canonicalizeOperationBody(op2);
1201
+ const atomicOps = body.atomicOps !== void 0 && Object.keys(body.atomicOps).length > 0 ? body.atomicOps : null;
1202
+ return {
1203
+ hashVersion: 2,
1204
+ type: op2.type,
1205
+ collection: op2.collection,
1206
+ recordId: op2.recordId,
1207
+ data: body.data,
1208
+ previousData: body.previousData,
1209
+ timestamp,
1210
+ nodeId: op2.nodeId,
1211
+ sequenceNumber: op2.sequenceNumber,
1212
+ // A set of parents: their order carries no meaning, so a relay that reorders
1213
+ // them neither breaks verification nor creates a second id for one op.
1214
+ causalDeps: [...op2.causalDeps].sort(),
1215
+ schemaVersion: op2.schemaVersion,
1216
+ atomicOps
1217
+ };
1218
+ }
1219
+ async function sha256Hex(canonical2) {
1220
+ const encoded = new TextEncoder().encode(canonical2);
817
1221
  const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded);
818
1222
  return bufferToHex(hashBuffer);
819
1223
  }
@@ -844,11 +1248,12 @@ function bufferToHex(buffer) {
844
1248
  }
845
1249
 
846
1250
  // src/operations/operation.ts
847
- async function createOperation(input, clock) {
848
- validateOperationParams(input);
1251
+ async function createOperation(rawInput, clock, options = {}) {
1252
+ validateOperationParams(rawInput);
1253
+ const input = canonicalizeOperationBody(rawInput);
1254
+ const hashVersion = options.hashVersion ?? DEFAULT_OPERATION_HASH_VERSION;
849
1255
  const timestamp = clock.now();
850
- const serializedTs = HybridLogicalClock.serialize(timestamp);
851
- const id = await computeOperationId(input, serializedTs);
1256
+ const id = hashVersion === 1 ? await computeOperationId(input, HybridLogicalClock.serialize(timestamp)) : await computeOperationId({ ...input, timestamp }, hashVersion);
852
1257
  const operation = {
853
1258
  id,
854
1259
  nodeId: input.nodeId,
@@ -863,7 +1268,8 @@ async function createOperation(input, clock) {
863
1268
  schemaVersion: input.schemaVersion,
864
1269
  ...input.atomicOps !== void 0 && Object.keys(input.atomicOps).length > 0 ? { atomicOps: { ...input.atomicOps } } : {},
865
1270
  ...input.transactionId !== void 0 ? { transactionId: input.transactionId } : {},
866
- ...input.mutationName !== void 0 ? { mutationName: input.mutationName } : {}
1271
+ ...input.mutationName !== void 0 ? { mutationName: input.mutationName } : {},
1272
+ ...hashVersion !== 1 ? { hashVersion } : {}
867
1273
  };
868
1274
  return deepFreeze(operation);
869
1275
  }
@@ -931,23 +1337,35 @@ function validateOperationParams(input) {
931
1337
  });
932
1338
  }
933
1339
  }
934
- async function verifyOperationIntegrity(op2) {
935
- const input = {
936
- nodeId: op2.nodeId,
937
- type: op2.type,
938
- collection: op2.collection,
939
- recordId: op2.recordId,
940
- data: op2.data,
941
- previousData: op2.previousData,
942
- sequenceNumber: op2.sequenceNumber,
943
- causalDeps: op2.causalDeps,
944
- schemaVersion: op2.schemaVersion,
945
- ...op2.atomicOps !== void 0 ? { atomicOps: op2.atomicOps } : {}
946
- };
947
- const serializedTs = HybridLogicalClock.serialize(op2.timestamp);
948
- const expectedId = await computeOperationId(input, serializedTs);
1340
+ async function verifyOperationId(op2) {
1341
+ const version = op2.hashVersion ?? 1;
1342
+ if (version !== 1 && version !== 2) return false;
1343
+ let expectedId;
1344
+ try {
1345
+ expectedId = await computeOperationId(
1346
+ {
1347
+ nodeId: op2.nodeId,
1348
+ type: op2.type,
1349
+ collection: op2.collection,
1350
+ recordId: op2.recordId,
1351
+ data: op2.data,
1352
+ previousData: op2.previousData,
1353
+ timestamp: op2.timestamp,
1354
+ sequenceNumber: op2.sequenceNumber,
1355
+ causalDeps: op2.causalDeps,
1356
+ schemaVersion: op2.schemaVersion,
1357
+ ...op2.atomicOps !== void 0 ? { atomicOps: op2.atomicOps } : {}
1358
+ },
1359
+ version
1360
+ );
1361
+ } catch {
1362
+ return false;
1363
+ }
949
1364
  return op2.id === expectedId;
950
1365
  }
1366
+ async function verifyOperationIntegrity(op2) {
1367
+ return verifyOperationId(op2);
1368
+ }
951
1369
  function isValidOperation(value) {
952
1370
  if (typeof value !== "object" || value === null) return false;
953
1371
  const op2 = value;
@@ -1068,6 +1486,44 @@ function toAtomicOp(sentinel) {
1068
1486
  }
1069
1487
 
1070
1488
  // src/operations/replay-record.ts
1489
+ function mergeArraySet(local, remote, base) {
1490
+ const serialize = (v) => JSON.stringify(v);
1491
+ const baseSet = new Set(base.map(serialize));
1492
+ const localSet = new Set(local.map(serialize));
1493
+ const remoteSet = new Set(remote.map(serialize));
1494
+ const keep = (s) => localSet.has(s) && remoteSet.has(s) || !baseSet.has(s) && (localSet.has(s) || remoteSet.has(s));
1495
+ const seen = /* @__PURE__ */ new Set();
1496
+ const result = [];
1497
+ const addIfKept = (serialized, value) => {
1498
+ if (!seen.has(serialized) && keep(serialized)) {
1499
+ seen.add(serialized);
1500
+ result.push(value);
1501
+ }
1502
+ };
1503
+ for (const item of base) {
1504
+ addIfKept(serialize(item), item);
1505
+ }
1506
+ const others = /* @__PURE__ */ new Map();
1507
+ for (const item of [...local, ...remote]) {
1508
+ const s = serialize(item);
1509
+ if (!baseSet.has(s) && !others.has(s)) {
1510
+ others.set(s, item);
1511
+ }
1512
+ }
1513
+ for (const s of [...others.keys()].sort()) {
1514
+ addIfKept(s, others.get(s));
1515
+ }
1516
+ return result;
1517
+ }
1518
+ function resolveArrayWrite(running, value, base) {
1519
+ if (!Array.isArray(running) || !Array.isArray(value) || !Array.isArray(base)) {
1520
+ return value;
1521
+ }
1522
+ if (canonicalize(running) === canonicalize(base)) {
1523
+ return value;
1524
+ }
1525
+ return mergeArraySet(running, value, base);
1526
+ }
1071
1527
  function replayOperationsForRecord(ops) {
1072
1528
  let record = null;
1073
1529
  let deleted = false;
@@ -1090,6 +1546,8 @@ function replayOperationsForRecord(ops) {
1090
1546
  const atomicOp = op2.atomicOps?.[field];
1091
1547
  if (atomicOp && lastWriterKind[field] === `atomic:${atomicOp.type}`) {
1092
1548
  next[field] = applyAtomicOp(next[field], atomicOp);
1549
+ } else if (!atomicOp && op2.previousData && field in op2.previousData) {
1550
+ next[field] = resolveArrayWrite(next[field], value, op2.previousData[field]);
1093
1551
  } else {
1094
1552
  next[field] = value;
1095
1553
  }
@@ -1106,105 +1564,1655 @@ function replayOperationsForRecord(ops) {
1106
1564
  }
1107
1565
  return deleted ? null : record;
1108
1566
  }
1567
+ function replayFieldVersionsForRecord(ops) {
1568
+ let fields = null;
1569
+ let deleted = false;
1570
+ const first = ops[0];
1571
+ const last = ops[ops.length - 1];
1572
+ if (!first || !last) return null;
1573
+ for (const op2 of ops) {
1574
+ switch (op2.type) {
1575
+ case "insert":
1576
+ if (op2.data) {
1577
+ fields = {};
1578
+ for (const field of Object.keys(op2.data)) fields[field] = { ...op2.timestamp };
1579
+ deleted = false;
1580
+ }
1581
+ break;
1582
+ case "update":
1583
+ if (op2.data) {
1584
+ const next = { ...fields ?? {} };
1585
+ for (const field of Object.keys(op2.data)) next[field] = { ...op2.timestamp };
1586
+ fields = next;
1587
+ deleted = false;
1588
+ }
1589
+ break;
1590
+ case "delete":
1591
+ deleted = true;
1592
+ break;
1593
+ }
1594
+ }
1595
+ if (deleted || fields === null) return null;
1596
+ return { fields, created: { ...first.timestamp }, latest: { ...last.timestamp } };
1597
+ }
1598
+ function expandFieldVersionedOperations(ops) {
1599
+ const expanded = [];
1600
+ for (const op2 of ops) {
1601
+ if (op2.type !== "insert" || !op2.fieldVersions || !op2.data) {
1602
+ expanded.push(op2);
1603
+ continue;
1604
+ }
1605
+ const { fieldVersions, ...base } = op2;
1606
+ expanded.push(base);
1607
+ const groups = /* @__PURE__ */ new Map();
1608
+ let newest = op2.timestamp;
1609
+ for (const v of Object.values(fieldVersions)) {
1610
+ if (HybridLogicalClock.compare(v, newest) > 0) newest = v;
1611
+ }
1612
+ for (const [field, value] of Object.entries(op2.data)) {
1613
+ const version = fieldVersions[field] ?? newest;
1614
+ if (HybridLogicalClock.compare(version, op2.timestamp) <= 0) continue;
1615
+ const key = `${version.wallTime}:${version.logical}:${version.nodeId}`;
1616
+ const group = groups.get(key) ?? { timestamp: version, data: {} };
1617
+ group.data[field] = value;
1618
+ groups.set(key, group);
1619
+ }
1620
+ let index = 0;
1621
+ for (const group of groups.values()) {
1622
+ expanded.push({
1623
+ ...base,
1624
+ id: `${op2.id}#field-version-${index++}`,
1625
+ type: "update",
1626
+ data: group.data,
1627
+ previousData: null,
1628
+ timestamp: { ...group.timestamp }
1629
+ });
1630
+ }
1631
+ }
1632
+ return expanded.sort((a, b) => HybridLogicalClock.compare(a.timestamp, b.timestamp));
1633
+ }
1109
1634
 
1110
- // src/operations/op-data-binary.ts
1111
- function isKoraBytesValue(value) {
1112
- if (typeof value !== "object" || value === null) {
1113
- return false;
1635
+ // src/migration/apply-operation-transforms.ts
1636
+ var MAX_TRANSFORM_STEPS = 32;
1637
+ function applyOperationTransforms(operation, targetSchemaVersion, transforms) {
1638
+ let current = operation;
1639
+ for (let step = 0; step < MAX_TRANSFORM_STEPS; step++) {
1640
+ if (current.schemaVersion === targetSchemaVersion) {
1641
+ return current;
1642
+ }
1643
+ const transform = transforms.find(
1644
+ (candidate) => candidate.fromVersion === current.schemaVersion
1645
+ );
1646
+ if (!transform) {
1647
+ return null;
1648
+ }
1649
+ const next = transform.transform(current);
1650
+ if (next === null) {
1651
+ return null;
1652
+ }
1653
+ current = next;
1114
1654
  }
1115
- const record = value;
1116
- return Object.keys(record).length === 1 && typeof record.$koraBytes === "string";
1655
+ return null;
1117
1656
  }
1118
- function isLegacyNumericByteObject(value) {
1119
- if (typeof value !== "object" || value === null || ArrayBuffer.isView(value)) {
1120
- return false;
1657
+
1658
+ // src/migration/operation-view.ts
1659
+ var OperationTransformError = class extends KoraError {
1660
+ constructor(message, context) {
1661
+ super(message, "INVALID_OPERATION_TRANSFORM", {
1662
+ ...context,
1663
+ fix: "An OperationTransform may rewrite data, previousData, atomicOps and schemaVersion only, deterministically, with JSON values."
1664
+ });
1665
+ this.name = "OperationTransformError";
1121
1666
  }
1122
- const record = value;
1123
- const keys = Object.keys(record);
1124
- if (keys.length === 0) {
1125
- return false;
1667
+ };
1668
+ function operationSchemaView(op2, targetSchemaVersion, transforms) {
1669
+ if (op2.schemaVersion === targetSchemaVersion) return op2;
1670
+ if (transforms === void 0 || transforms.length === 0) return op2;
1671
+ if (op2.encrypted !== void 0) return op2;
1672
+ let view;
1673
+ try {
1674
+ view = applyOperationTransforms(op2, targetSchemaVersion, transforms);
1675
+ } catch (error) {
1676
+ throw new OperationTransformError(
1677
+ `The schema transform of operation ${op2.id} (schema v${op2.schemaVersion} -> v${targetSchemaVersion}) threw: ${error instanceof Error ? error.message : String(error)}`,
1678
+ { operationId: op2.id, fromVersion: op2.schemaVersion, toVersion: targetSchemaVersion }
1679
+ );
1126
1680
  }
1127
- for (let i = 0; i < keys.length; i++) {
1128
- const byte = record[String(i)];
1129
- if (typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255) {
1130
- return false;
1681
+ if (view === null || view === op2) return view;
1682
+ for (const key of ["id", "nodeId", "type", "collection", "recordId"]) {
1683
+ if (view[key] !== op2[key]) {
1684
+ throw new OperationTransformError(
1685
+ `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.`,
1686
+ { operationId: op2.id, field: key }
1687
+ );
1688
+ }
1689
+ }
1690
+ const a = op2.timestamp;
1691
+ const b = view.timestamp;
1692
+ if (a.wallTime !== b.wallTime || a.logical !== b.logical || a.nodeId !== b.nodeId) {
1693
+ throw new OperationTransformError(
1694
+ `The schema transform of operation ${op2.id} changed its timestamp. A transform may rewrite data, previousData, atomicOps and schemaVersion only.`,
1695
+ { operationId: op2.id, field: "timestamp" }
1696
+ );
1697
+ }
1698
+ try {
1699
+ return canonicalizeOperationBody(view);
1700
+ } catch (error) {
1701
+ throw new OperationTransformError(
1702
+ `The schema transform of operation ${op2.id} produced a value with no JSON form: ${error instanceof Error ? error.message : String(error)}`,
1703
+ { operationId: op2.id }
1704
+ );
1705
+ }
1706
+ }
1707
+ function hashText(text) {
1708
+ let hash = 2166136261;
1709
+ for (let i = 0; i < text.length; i++) {
1710
+ hash ^= text.charCodeAt(i);
1711
+ hash = Math.imul(hash, 16777619) >>> 0;
1712
+ }
1713
+ return hash.toString(16).padStart(8, "0");
1714
+ }
1715
+ function operationTransformsFingerprint(targetSchemaVersion, transforms) {
1716
+ if (transforms === void 0 || transforms.length === 0) return "";
1717
+ const parts = transforms.map((t2) => `${t2.fromVersion}>${t2.toVersion}:${hashText(String(t2.transform))}`).sort();
1718
+ return `xf@v${targetSchemaVersion}(${parts.join(",")})`;
1719
+ }
1720
+ var OperationTransformCoverageError = class extends KoraError {
1721
+ constructor(message, versions, targetSchemaVersion, context = {}) {
1722
+ super(message, "OPERATION_TRANSFORM_MISSING", {
1723
+ ...context,
1724
+ versions: [...versions],
1725
+ targetSchemaVersion,
1726
+ 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).`
1727
+ });
1728
+ this.versions = versions;
1729
+ this.targetSchemaVersion = targetSchemaVersion;
1730
+ this.name = "OperationTransformCoverageError";
1731
+ }
1732
+ versions;
1733
+ targetSchemaVersion;
1734
+ };
1735
+ function missingTransformPaths(storedVersions, targetSchemaVersion, transforms) {
1736
+ if (transforms === void 0 || transforms.length === 0) return [];
1737
+ const missing = [];
1738
+ for (const version of new Set(storedVersions)) {
1739
+ if (version === targetSchemaVersion) continue;
1740
+ let current = version;
1741
+ let reached = false;
1742
+ for (let step = 0; step < 32; step++) {
1743
+ if (current === targetSchemaVersion) {
1744
+ reached = true;
1745
+ break;
1746
+ }
1747
+ const next = transforms.find((candidate) => candidate.fromVersion === current);
1748
+ if (!next) break;
1749
+ current = next.toVersion;
1750
+ }
1751
+ if (!reached) missing.push({ version, missingFrom: current });
1752
+ }
1753
+ return missing.sort((a, b) => a.version - b.version);
1754
+ }
1755
+ function assertOperationTransformCoverage(storedVersions, targetSchemaVersion, transforms, replica) {
1756
+ const missing = missingTransformPaths(storedVersions, targetSchemaVersion, transforms);
1757
+ if (missing.length === 0) return;
1758
+ const versions = missing.map((entry) => entry.version);
1759
+ const steps = [...new Set(missing.map((entry) => entry.missingFrom))];
1760
+ const plural = versions.length > 1;
1761
+ throw new OperationTransformCoverageError(
1762
+ `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.`,
1763
+ versions,
1764
+ targetSchemaVersion,
1765
+ { replica, missingFrom: steps }
1766
+ );
1767
+ }
1768
+
1769
+ // src/fold/authority.ts
1770
+ var RESERVED_NODE_ID_PREFIX = "kora:";
1771
+ var SERVER_NODE_ID_PREFIX = "kora:server:";
1772
+ function isReservedNodeId(nodeId) {
1773
+ return nodeId.startsWith(RESERVED_NODE_ID_PREFIX);
1774
+ }
1775
+ function isServerNodeId(nodeId) {
1776
+ return nodeId.startsWith(SERVER_NODE_ID_PREFIX) && nodeId.length > SERVER_NODE_ID_PREFIX.length;
1777
+ }
1778
+ function isAuthoritativeNodeId(nodeId, explicit) {
1779
+ return isServerNodeId(nodeId) || (explicit?.has(nodeId) ?? false);
1780
+ }
1781
+
1782
+ // src/fold/errors.ts
1783
+ var FoldStateError = class extends KoraError {
1784
+ constructor(message, context) {
1785
+ super(message, "FOLD_STATE_INVALID", {
1786
+ fix: "Rebuild the record state from its operation log with foldRecord(ops, schema).",
1787
+ ...context
1788
+ });
1789
+ this.name = "FoldStateError";
1790
+ }
1791
+ };
1792
+ var FoldConfigurationError = class extends KoraError {
1793
+ constructor(message, context) {
1794
+ super(message, "FOLD_CONFIGURATION", context);
1795
+ this.name = "FoldConfigurationError";
1796
+ }
1797
+ };
1798
+
1799
+ // src/fold/field-kind.ts
1800
+ var LWW_PLAN = {
1801
+ kind: "reg",
1802
+ appendOnly: false,
1803
+ strategy: "lww",
1804
+ tier: 1,
1805
+ secret: false
1806
+ };
1807
+ function planField(collection, field) {
1808
+ const descriptor = collection?.fields[field];
1809
+ const secret = descriptor?.kind === "secret";
1810
+ const resolver = collection?.resolvers?.[field];
1811
+ if (resolver) {
1812
+ return { kind: "res", appendOnly: false, resolver, strategy: "custom", tier: 3, secret };
1813
+ }
1814
+ if (!descriptor) return LWW_PLAN;
1815
+ switch (descriptor.mergeStrategy) {
1816
+ case "counter":
1817
+ return { kind: "ctr", appendOnly: false, strategy: "schema-counter", tier: 1, secret };
1818
+ case "max":
1819
+ return { kind: "max", appendOnly: false, strategy: "schema-max", tier: 1, secret };
1820
+ case "min":
1821
+ return { kind: "min", appendOnly: false, strategy: "schema-min", tier: 1, secret };
1822
+ case "append-only":
1823
+ if (descriptor.kind === "array") {
1824
+ return {
1825
+ kind: "set",
1826
+ appendOnly: true,
1827
+ strategy: "schema-append-only",
1828
+ tier: 1,
1829
+ secret
1830
+ };
1831
+ }
1832
+ return { ...LWW_PLAN, secret };
1833
+ case "lww":
1834
+ return { ...LWW_PLAN, secret };
1835
+ case "server-authoritative":
1836
+ return {
1837
+ ...LWW_PLAN,
1838
+ strategy: "schema-server-authoritative",
1839
+ secret,
1840
+ authoritative: true
1841
+ };
1842
+ default:
1843
+ break;
1844
+ }
1845
+ switch (descriptor.kind) {
1846
+ case "array":
1847
+ return { kind: "set", appendOnly: false, strategy: "lww-element-set", tier: 1, secret };
1848
+ case "object":
1849
+ case "json":
1850
+ return { kind: "map", appendOnly: false, strategy: "object-key-lww", tier: 1, secret };
1851
+ case "richtext":
1852
+ return { kind: "rt", appendOnly: false, strategy: "crdt-text", tier: 1, secret };
1853
+ default:
1854
+ return { ...LWW_PLAN, secret };
1855
+ }
1856
+ }
1857
+
1858
+ // src/fold/stamp.ts
1859
+ function compareStamps(a, b) {
1860
+ const ca = a.c ?? 0;
1861
+ const cb = b.c ?? 0;
1862
+ if (ca !== cb) return ca - cb;
1863
+ if (a.t !== b.t) return a.t < b.t ? -1 : 1;
1864
+ if (a.o !== b.o) return a.o < b.o ? -1 : 1;
1865
+ return 0;
1866
+ }
1867
+ function maxStamp(a, b) {
1868
+ if (a === null) return b;
1869
+ if (b === null) return a;
1870
+ return compareStamps(a, b) >= 0 ? a : b;
1871
+ }
1872
+ function minStamp(a, b) {
1873
+ if (a === null) return b;
1874
+ if (b === null) return a;
1875
+ return compareStamps(a, b) <= 0 ? a : b;
1876
+ }
1877
+ function isAfter(a, b) {
1878
+ if (a === null) return false;
1879
+ if (b === null) return true;
1880
+ return compareStamps(a, b) > 0;
1881
+ }
1882
+ function stampOf(timestamp, opId) {
1883
+ return { t: HybridLogicalClock.serialize(timestamp), o: opId };
1884
+ }
1885
+ function stampTimestamp(stamp) {
1886
+ return HybridLogicalClock.deserialize(stamp.t);
1887
+ }
1888
+
1889
+ // src/fold/values.ts
1890
+ function isPlainObject2(value) {
1891
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1892
+ const proto = Object.getPrototypeOf(value);
1893
+ return proto === null || proto === Object.prototype;
1894
+ }
1895
+ function normalizeValue(value) {
1896
+ if (value instanceof Uint8Array) return { $koraBytes: bytesToBase64(value) };
1897
+ if (value instanceof ArrayBuffer) return { $koraBytes: bytesToBase64(new Uint8Array(value)) };
1898
+ if (Array.isArray(value)) return value.map(normalizeValue);
1899
+ if (isPlainObject2(value)) {
1900
+ const out = {};
1901
+ for (const [key, member] of Object.entries(value)) {
1902
+ if (member !== void 0) out[key] = normalizeValue(member);
1903
+ }
1904
+ return out;
1905
+ }
1906
+ return value;
1907
+ }
1908
+ function canonicalKey(value) {
1909
+ return canonicalize(normalizeValue(value));
1910
+ }
1911
+
1912
+ // src/fold/field-states.ts
1913
+ function emptyField(plan) {
1914
+ switch (plan.kind) {
1915
+ case "reg":
1916
+ return { k: "reg", e: [], val: void 0 };
1917
+ case "res":
1918
+ return { k: "res", e: [], val: void 0 };
1919
+ case "set":
1920
+ return { k: "set", ao: plan.appendOnly, sh: null, clr: null, el: {} };
1921
+ case "map":
1922
+ return { k: "map", sh: null, clr: null, keys: {} };
1923
+ case "ctr":
1924
+ return { k: "ctr", base: null, d: [], val: void 0 };
1925
+ case "max":
1926
+ case "min":
1927
+ return { k: plan.kind, best: null, reg: null };
1928
+ case "rt":
1929
+ return { k: "rt", reset: null, u: {} };
1930
+ }
1931
+ }
1932
+ function entryKind(entry) {
1933
+ return entry.a ? entry.a.type : "set";
1934
+ }
1935
+ function chainFold(entries) {
1936
+ let value;
1937
+ let last = null;
1938
+ for (const entry of entries) {
1939
+ value = entry.a && last === entry.a.type ? applyAtomicOp(value, entry.a) : entry.v;
1940
+ last = entryKind(entry);
1941
+ }
1942
+ return value;
1943
+ }
1944
+ function insertionIndex(entries, stamp) {
1945
+ let lo = 0;
1946
+ let hi = entries.length;
1947
+ while (lo < hi) {
1948
+ const mid = lo + hi >>> 1;
1949
+ const entry = entries[mid];
1950
+ const cmp = compareStamps(entry.s, stamp);
1951
+ if (cmp === 0) return -1;
1952
+ if (cmp < 0) lo = mid + 1;
1953
+ else hi = mid;
1954
+ }
1955
+ return lo;
1956
+ }
1957
+ function pruneChain(entries) {
1958
+ for (let i = entries.length - 1; i > 0; i--) {
1959
+ if (!entries[i].a) return entries.slice(i);
1960
+ }
1961
+ return entries;
1962
+ }
1963
+ function toEntry(write) {
1964
+ return write.a ? { s: write.s, v: write.v, a: write.a } : { s: write.s, v: write.v };
1965
+ }
1966
+ function applyRegister(state, write) {
1967
+ const entries = state.e;
1968
+ const index = insertionIndex(entries, write.s);
1969
+ if (index === -1) return { state, changed: false };
1970
+ const first = entries[0];
1971
+ if (index === 0 && first !== void 0 && !first.a) return { state, changed: false };
1972
+ const entry = toEntry(write);
1973
+ if (index === entries.length) {
1974
+ if (!entry.a) return { state: { k: "reg", e: [entry], val: entry.v }, changed: true };
1975
+ const last = entries[entries.length - 1];
1976
+ const val = last !== void 0 && entryKind(last) === entry.a.type ? applyAtomicOp(state.val, entry.a) : entry.v;
1977
+ return { state: { k: "reg", e: [...entries, entry], val }, changed: true };
1978
+ }
1979
+ const next = pruneChain([...entries.slice(0, index), entry, ...entries.slice(index)]);
1980
+ return { state: { k: "reg", e: next, val: chainFold(next) }, changed: true };
1981
+ }
1982
+ function mergeLogs(a, b) {
1983
+ const out = [];
1984
+ let i = 0;
1985
+ let j = 0;
1986
+ while (i < a.length || j < b.length) {
1987
+ const x = a[i];
1988
+ const y = b[j];
1989
+ if (y === void 0 || x !== void 0 && compareStamps(x.s, y.s) < 0) {
1990
+ out.push(x);
1991
+ i++;
1992
+ } else if (x === void 0 || compareStamps(x.s, y.s) > 0) {
1993
+ out.push(y);
1994
+ j++;
1995
+ } else {
1996
+ out.push(x);
1997
+ i++;
1998
+ j++;
1999
+ }
2000
+ }
2001
+ return out;
2002
+ }
2003
+ function resolveStep(plan, current, entry) {
2004
+ try {
2005
+ return { value: canonicalValue(plan.resolver?.(current, entry.v, entry.b), "resolver") };
2006
+ } catch (error) {
2007
+ return { value: entry.v, error: error instanceof Error ? error.message : String(error) };
2008
+ }
2009
+ }
2010
+ function resolverFold(plan, entries) {
2011
+ let value;
2012
+ let error;
2013
+ entries.forEach((entry, index) => {
2014
+ if (index === 0) {
2015
+ value = entry.v;
2016
+ return;
2017
+ }
2018
+ const step = resolveStep(plan, value, entry);
2019
+ value = step.value;
2020
+ if (step.error !== void 0) error = step.error;
2021
+ });
2022
+ return error === void 0 ? { value } : { value, error };
2023
+ }
2024
+ function pruneResolverLog(entries) {
2025
+ for (let i = entries.length - 1; i > 0; i--) {
2026
+ if (entries[i].z === 1) return entries.slice(i);
2027
+ }
2028
+ return entries;
2029
+ }
2030
+ function resolverState(entries, result) {
2031
+ return result.error === void 0 ? { k: "res", e: entries, val: result.value } : { k: "res", e: entries, val: result.value, err: result.error };
2032
+ }
2033
+ function applyResolver(state, write, plan) {
2034
+ const index = insertionIndex(state.e, write.s);
2035
+ if (index === -1) return { state, changed: false };
2036
+ if (index === 0 && state.e[0]?.z === 1) return { state, changed: false };
2037
+ const entry = { s: write.s, v: write.v };
2038
+ if (write.insert) entry.b = null;
2039
+ else if (write.hasPrev) entry.b = write.prev;
2040
+ if (index === state.e.length) {
2041
+ const entries2 = [...state.e, entry];
2042
+ if (state.e.length === 0)
2043
+ return { state: resolverState(entries2, { value: entry.v }), changed: true };
2044
+ const step = resolveStep(plan, state.val, entry);
2045
+ 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 };
2046
+ return {
2047
+ state: next,
2048
+ changed: true,
2049
+ ...step.error !== void 0 ? { error: step.error } : {}
2050
+ };
2051
+ }
2052
+ const entries = [...state.e.slice(0, index), entry, ...state.e.slice(index)];
2053
+ const result = resolverFold(plan, entries);
2054
+ return { state: resolverState(entries, result), changed: true };
2055
+ }
2056
+ function laterShape(current, next) {
2057
+ return current === null || compareStamps(next.s, current.s) > 0 ? next : null;
2058
+ }
2059
+ function firstAddBefore(a, b) {
2060
+ if (a === null) return true;
2061
+ const cmp = compareStamps(b.s, a.s);
2062
+ return cmp < 0 || cmp === 0 && b.i < a.i;
2063
+ }
2064
+ function elementKey(canonicalValue2, occurrence) {
2065
+ return `${canonicalValue2}#${occurrence}`;
2066
+ }
2067
+ function elementWriter(base) {
2068
+ let el = base;
2069
+ let changed = false;
2070
+ const read = (value, occurrence) => {
2071
+ const canonical2 = canonicalKey(value);
2072
+ const key = elementKey(canonical2, occurrence);
2073
+ const existing = el[key];
2074
+ return [
2075
+ key,
2076
+ existing ? { ...existing } : { v: JSON.parse(canonical2), n: occurrence, a: null, f: null, r: null }
2077
+ ];
2078
+ };
2079
+ const store = (key, element) => {
2080
+ if (el === base) el = { ...base };
2081
+ el[key] = element;
2082
+ changed = true;
2083
+ };
2084
+ return {
2085
+ add(value, occurrence, stamp, index) {
2086
+ const [key, element] = read(value, occurrence);
2087
+ let touched = false;
2088
+ if (isAfter(stamp, element.a)) {
2089
+ element.a = stamp;
2090
+ touched = true;
2091
+ }
2092
+ const first = { s: stamp, i: index };
2093
+ if (firstAddBefore(element.f, first)) {
2094
+ element.f = first;
2095
+ touched = true;
2096
+ }
2097
+ if (touched) store(key, element);
2098
+ },
2099
+ remove(value, occurrence, stamp) {
2100
+ const [key, element] = read(value, occurrence);
2101
+ if (isAfter(stamp, element.r)) {
2102
+ element.r = stamp;
2103
+ store(key, element);
2104
+ }
2105
+ },
2106
+ result: () => ({ el, changed })
2107
+ };
2108
+ }
2109
+ function countOccurrences(items) {
2110
+ const counts = /* @__PURE__ */ new Map();
2111
+ const occurrences = items.map((item) => {
2112
+ const canonical2 = canonicalKey(item);
2113
+ const k = counts.get(canonical2) ?? 0;
2114
+ counts.set(canonical2, k + 1);
2115
+ return { item, canonical: canonical2, k };
2116
+ });
2117
+ return { counts, occurrences };
2118
+ }
2119
+ function applyElementSet(state, write) {
2120
+ const writer = elementWriter(state.el);
2121
+ let sh = state.sh;
2122
+ let clr = state.clr;
2123
+ let changed = false;
2124
+ const setShape = (arr, value) => {
2125
+ const next = arr ? { s: write.s, arr } : { s: write.s, arr, v: value };
2126
+ const later = laterShape(sh, next);
2127
+ if (later) {
2128
+ sh = later;
2129
+ changed = true;
2130
+ }
2131
+ };
2132
+ const atomic = write.a;
2133
+ const prev = !write.insert && Array.isArray(write.prev) ? write.prev : null;
2134
+ const atomicElement = atomic !== void 0 && (atomic.type === "append" || atomic.type === "remove");
2135
+ if (atomicElement && (prev === null || !Array.isArray(write.v))) {
2136
+ setShape(true);
2137
+ if (atomic.type === "append") writer.add(atomic.value, 0, write.s, 0);
2138
+ else if (!state.ao) writer.remove(atomic.value, 0, write.s);
2139
+ } else if (!Array.isArray(write.v)) {
2140
+ setShape(false, write.v);
2141
+ const nextClr = maxStamp(clr, write.s);
2142
+ if (nextClr !== clr) {
2143
+ clr = nextClr;
2144
+ changed = true;
2145
+ }
2146
+ } else {
2147
+ setShape(true);
2148
+ const next = countOccurrences(write.v);
2149
+ const before = prev ? countOccurrences(prev) : null;
2150
+ next.occurrences.forEach(({ item, canonical: canonical2, k }, index) => {
2151
+ if (k >= (before?.counts.get(canonical2) ?? 0)) writer.add(item, k, write.s, index);
2152
+ });
2153
+ if (before && !state.ao) {
2154
+ for (const { item, canonical: canonical2, k } of before.occurrences) {
2155
+ if (k >= (next.counts.get(canonical2) ?? 0)) writer.remove(item, k, write.s);
2156
+ }
2157
+ }
2158
+ }
2159
+ const elements = writer.result();
2160
+ if (!changed && !elements.changed) return { state, changed: false };
2161
+ return { state: { k: "set", ao: state.ao, sh, clr, el: elements.el }, changed: true };
2162
+ }
2163
+ function isElementPresent(element, state) {
2164
+ if (element.a === null || element.f === null) return false;
2165
+ if (state.ao) return true;
2166
+ return isAfter(element.a, element.r) && isAfter(element.a, state.clr);
2167
+ }
2168
+ function materializeElementSet(state) {
2169
+ if (state.sh === null) return void 0;
2170
+ if (!state.sh.arr) return state.sh.v;
2171
+ const present = Object.entries(state.el).filter(([, element]) => isElementPresent(element, state));
2172
+ present.sort(([ka, a], [kb, b]) => {
2173
+ const fa = a.f;
2174
+ const fb = b.f;
2175
+ return compareStamps(fa.s, fb.s) || fa.i - fb.i || (ka < kb ? -1 : ka > kb ? 1 : 0);
2176
+ });
2177
+ return present.map(([, element]) => element.v);
2178
+ }
2179
+ function applyKeyMap(state, write) {
2180
+ let sh = state.sh;
2181
+ let clr = state.clr;
2182
+ let keys = state.keys;
2183
+ let changed = false;
2184
+ const setKey = (key, next) => {
2185
+ const current = keys[key];
2186
+ if (current === void 0 || compareStamps(next.s, current.s) > 0) {
2187
+ if (keys === state.keys) keys = { ...state.keys };
2188
+ keys[key] = next;
2189
+ changed = true;
2190
+ }
2191
+ };
2192
+ if (!isPlainObject2(write.v)) {
2193
+ const later = laterShape(sh, { s: write.s, obj: false, v: write.v });
2194
+ if (later) {
2195
+ sh = later;
2196
+ changed = true;
2197
+ }
2198
+ const nextClr = maxStamp(clr, write.s);
2199
+ if (nextClr !== clr) {
2200
+ clr = nextClr;
2201
+ changed = true;
2202
+ }
2203
+ } else {
2204
+ const later = laterShape(sh, { s: write.s, obj: true });
2205
+ if (later) {
2206
+ sh = later;
2207
+ changed = true;
2208
+ }
2209
+ const prev = !write.insert && isPlainObject2(write.prev) ? write.prev : null;
2210
+ for (const [key, value] of Object.entries(write.v)) {
2211
+ if (prev && key in prev && canonicalKey(prev[key]) === canonicalKey(value)) continue;
2212
+ setKey(key, { s: write.s, del: false, v: value });
2213
+ }
2214
+ if (prev) {
2215
+ for (const key of Object.keys(prev)) {
2216
+ if (!(key in write.v)) setKey(key, { s: write.s, del: true });
2217
+ }
2218
+ }
2219
+ }
2220
+ if (!changed) return { state, changed: false };
2221
+ return { state: { k: "map", sh, clr, keys }, changed: true };
2222
+ }
2223
+ function materializeKeyMap(state) {
2224
+ if (state.sh === null) return void 0;
2225
+ if (!state.sh.obj) return state.sh.v;
2226
+ const out = {};
2227
+ for (const key of Object.keys(state.keys).sort()) {
2228
+ const entry = state.keys[key];
2229
+ if (entry.del || !isAfter(entry.s, state.clr)) continue;
2230
+ out[key] = entry.v;
2231
+ }
2232
+ return out;
2233
+ }
2234
+ function counterDelta(write) {
2235
+ if (write.a?.type === "increment" && typeof write.a.value === "number") return write.a.value;
2236
+ if (!write.insert && !write.a && typeof write.v === "number" && typeof write.prev === "number") {
2237
+ return write.v - write.prev;
2238
+ }
2239
+ return null;
2240
+ }
2241
+ function deltaIndex(deltas, stamp) {
2242
+ let lo = 0;
2243
+ let hi = deltas.length;
2244
+ while (lo < hi) {
2245
+ const mid = lo + hi >>> 1;
2246
+ const cmp = compareStamps(deltas[mid].s, stamp);
2247
+ if (cmp === 0) return -1;
2248
+ if (cmp < 0) lo = mid + 1;
2249
+ else hi = mid;
2250
+ }
2251
+ return lo;
2252
+ }
2253
+ function counterValue(base, deltas) {
2254
+ if (deltas.length === 0) return base?.v;
2255
+ let sum = typeof base?.v === "number" ? base.v : 0;
2256
+ for (const delta of deltas) sum += delta.n;
2257
+ return sum;
2258
+ }
2259
+ function counterState(base, d) {
2260
+ return { k: "ctr", base, d, val: counterValue(base, d) };
2261
+ }
2262
+ function pruneDeltas(d, base) {
2263
+ if (base === null) return [...d];
2264
+ return d.filter((delta) => compareStamps(delta.s, base.s) > 0);
2265
+ }
2266
+ function applyCounter(state, write) {
2267
+ const n = counterDelta(write);
2268
+ if (n !== null) {
2269
+ if (state.base !== null && compareStamps(write.s, state.base.s) < 0) {
2270
+ return { state, changed: false };
2271
+ }
2272
+ const index = deltaIndex(state.d, write.s);
2273
+ if (index === -1) return { state, changed: false };
2274
+ const delta = { s: write.s, n };
2275
+ if (index === state.d.length) {
2276
+ const val = state.d.length === 0 ? counterValue(state.base, [delta]) : state.val + n;
2277
+ return { state: { k: "ctr", base: state.base, d: [...state.d, delta], val }, changed: true };
2278
+ }
2279
+ const d = [...state.d.slice(0, index), delta, ...state.d.slice(index)];
2280
+ return { state: counterState(state.base, d), changed: true };
2281
+ }
2282
+ if (state.base !== null && compareStamps(write.s, state.base.s) <= 0) {
2283
+ return { state, changed: false };
2284
+ }
2285
+ const base = { s: write.s, v: write.v };
2286
+ return { state: counterState(base, pruneDeltas(state.d, base)), changed: true };
2287
+ }
2288
+ function mergeDeltas(a, b) {
2289
+ const out = [];
2290
+ let i = 0;
2291
+ let j = 0;
2292
+ while (i < a.length || j < b.length) {
2293
+ const x = a[i];
2294
+ const y = b[j];
2295
+ if (y === void 0 || x !== void 0 && compareStamps(x.s, y.s) < 0) {
2296
+ out.push(x);
2297
+ i++;
2298
+ } else if (x === void 0 || compareStamps(x.s, y.s) > 0) {
2299
+ out.push(y);
2300
+ j++;
2301
+ } else {
2302
+ out.push(x);
2303
+ i++;
2304
+ j++;
2305
+ }
2306
+ }
2307
+ return out;
2308
+ }
2309
+ function betterExtremum(kind, current, next) {
2310
+ if (current === null) return true;
2311
+ if (next.v !== current.v) return kind === "max" ? next.v > current.v : next.v < current.v;
2312
+ return compareStamps(next.s, current.s) > 0;
2313
+ }
2314
+ function applyExtremum(state, write) {
2315
+ if (typeof write.v === "number" && !Number.isNaN(write.v)) {
2316
+ const next = { s: write.s, v: write.v };
2317
+ if (!betterExtremum(state.k, state.best, next)) return { state, changed: false };
2318
+ return { state: { k: state.k, best: next, reg: state.reg }, changed: true };
2319
+ }
2320
+ if (state.reg !== null && compareStamps(write.s, state.reg.s) <= 0) {
2321
+ return { state, changed: false };
2322
+ }
2323
+ return { state: { k: state.k, best: state.best, reg: { s: write.s, v: write.v } }, changed: true };
2324
+ }
2325
+ function decodeRichtext(value) {
2326
+ if (value === null || value === void 0) return { reset: null };
2327
+ try {
2328
+ const decoded = decodeBytesFromOpData(value);
2329
+ if (typeof decoded === "string") return { reset: decoded };
2330
+ return { update: bytesToBase64(decoded) };
2331
+ } catch {
2332
+ return { reset: value };
2333
+ }
2334
+ }
2335
+ function applyRichtext(state, write, subsumes) {
2336
+ const decoded = decodeRichtext(write.v);
2337
+ if ("reset" in decoded) {
2338
+ if (state.reset !== null && compareStamps(write.s, state.reset.s) <= 0) {
2339
+ return { state, changed: false };
2340
+ }
2341
+ return {
2342
+ state: { k: "rt", reset: { s: write.s, v: decoded.reset }, u: state.u },
2343
+ changed: true
2344
+ };
2345
+ }
2346
+ const current = state.u[decoded.update];
2347
+ if (current !== void 0 && compareStamps(write.s, current) <= 0) {
2348
+ return { state, changed: false };
2349
+ }
2350
+ if (subsumes === void 0) {
2351
+ return {
2352
+ state: { k: "rt", reset: state.reset, u: { ...state.u, [decoded.update]: write.s } },
2353
+ changed: true
2354
+ };
2355
+ }
2356
+ const incoming = base64ToBytes(decoded.update);
2357
+ const u = {};
2358
+ for (const [update, stamp] of Object.entries(state.u)) {
2359
+ if (update === decoded.update) continue;
2360
+ const bytes = base64ToBytes(update);
2361
+ if (compareStamps(write.s, stamp) <= 0 && subsumes(incoming, bytes)) {
2362
+ return { state, changed: false };
2363
+ }
2364
+ if (compareStamps(stamp, write.s) <= 0 && subsumes(bytes, incoming)) continue;
2365
+ u[update] = stamp;
2366
+ }
2367
+ u[decoded.update] = write.s;
2368
+ return { state: { k: "rt", reset: state.reset, u }, changed: true };
2369
+ }
2370
+ function materializeRichtext(state, merger, field) {
2371
+ const live = Object.entries(state.u).filter(([, stamp]) => isAfter(stamp, state.reset?.s ?? null)).map(([update]) => update).sort();
2372
+ if (live.length === 0) return state.reset ? state.reset.v : void 0;
2373
+ if (live.length === 1) return { $koraBytes: live[0] };
2374
+ if (!merger) {
2375
+ throw new FoldConfigurationError(
2376
+ `Richtext field "${field}" has ${live.length} concurrent Yjs updates and no richtext merger was given.`,
2377
+ {
2378
+ field,
2379
+ updates: live.length,
2380
+ fix: "Pass { richtext: mergeYjsUpdates } (from @korajs/store) in the fold options."
2381
+ }
2382
+ );
2383
+ }
2384
+ return { $koraBytes: bytesToBase64(merger(live.map((update) => base64ToBytes(update)))) };
2385
+ }
2386
+ function applyFieldWrite(state, write, plan, subsumes) {
2387
+ switch (state.k) {
2388
+ case "reg":
2389
+ return applyRegister(state, write);
2390
+ case "res":
2391
+ return applyResolver(state, write, plan);
2392
+ case "set":
2393
+ return applyElementSet(state, write);
2394
+ case "map":
2395
+ return applyKeyMap(state, write);
2396
+ case "ctr":
2397
+ return applyCounter(state, write);
2398
+ case "max":
2399
+ case "min":
2400
+ return applyExtremum(state, write);
2401
+ case "rt":
2402
+ return applyRichtext(state, write, subsumes);
2403
+ }
2404
+ }
2405
+ function materializeField(state, field, merger) {
2406
+ switch (state.k) {
2407
+ case "reg":
2408
+ case "res":
2409
+ return state.e.length === 0 ? void 0 : state.val;
2410
+ case "set":
2411
+ return materializeElementSet(state);
2412
+ case "map":
2413
+ return materializeKeyMap(state);
2414
+ case "ctr":
2415
+ return state.val;
2416
+ case "max":
2417
+ case "min":
2418
+ return state.best !== null ? state.best.v : state.reg?.v;
2419
+ case "rt":
2420
+ return materializeRichtext(state, merger, field);
2421
+ }
2422
+ }
2423
+ function fieldStamp(state) {
2424
+ switch (state.k) {
2425
+ case "reg":
2426
+ case "res":
2427
+ return state.e[state.e.length - 1]?.s ?? null;
2428
+ case "set": {
2429
+ let newest = maxStamp(state.sh?.s ?? null, state.clr);
2430
+ for (const element of Object.values(state.el)) {
2431
+ newest = maxStamp(maxStamp(newest, element.a), element.r);
2432
+ }
2433
+ return newest;
2434
+ }
2435
+ case "map": {
2436
+ let newest = maxStamp(state.sh?.s ?? null, state.clr);
2437
+ for (const entry of Object.values(state.keys)) newest = maxStamp(newest, entry.s);
2438
+ return newest;
2439
+ }
2440
+ case "ctr": {
2441
+ return maxStamp(state.base?.s ?? null, state.d[state.d.length - 1]?.s ?? null);
2442
+ }
2443
+ case "max":
2444
+ case "min":
2445
+ return maxStamp(state.best?.s ?? null, state.reg?.s ?? null);
2446
+ case "rt": {
2447
+ let newest = state.reset?.s ?? null;
2448
+ for (const stamp of Object.values(state.u)) newest = maxStamp(newest, stamp);
2449
+ return newest;
2450
+ }
2451
+ }
2452
+ }
2453
+ function laterOf(a, b) {
2454
+ if (a === null) return b;
2455
+ if (b === null) return a;
2456
+ return compareStamps(a.s, b.s) >= 0 ? a : b;
2457
+ }
2458
+ function joinElements(a, b) {
2459
+ const out = { ...a };
2460
+ for (const [key, y] of Object.entries(b)) {
2461
+ const x = out[key];
2462
+ if (x === void 0) {
2463
+ out[key] = y;
2464
+ continue;
2465
+ }
2466
+ out[key] = {
2467
+ v: x.v,
2468
+ n: x.n,
2469
+ a: maxStamp(x.a, y.a),
2470
+ f: y.f !== null && firstAddBefore(x.f, y.f) ? y.f : x.f,
2471
+ r: maxStamp(x.r, y.r)
2472
+ };
2473
+ }
2474
+ return out;
2475
+ }
2476
+ function joinFieldStates(a, b, plan) {
2477
+ if (a.k !== b.k) return a;
2478
+ switch (a.k) {
2479
+ case "reg": {
2480
+ const entries = pruneChain(mergeLogs(a.e, b.e));
2481
+ return { k: "reg", e: entries, val: chainFold(entries) };
2482
+ }
2483
+ case "res": {
2484
+ const entries = pruneResolverLog(mergeLogs(a.e, b.e));
2485
+ return resolverState(entries, resolverFold(plan, entries));
2486
+ }
2487
+ case "set": {
2488
+ const other = b;
2489
+ return {
2490
+ k: "set",
2491
+ ao: a.ao,
2492
+ sh: laterOf(a.sh, other.sh),
2493
+ clr: maxStamp(a.clr, other.clr),
2494
+ el: joinElements(a.el, other.el)
2495
+ };
2496
+ }
2497
+ case "map": {
2498
+ const other = b;
2499
+ const keys = { ...a.keys };
2500
+ for (const [key, entry] of Object.entries(other.keys)) {
2501
+ const current = keys[key];
2502
+ if (current === void 0 || compareStamps(entry.s, current.s) > 0) keys[key] = entry;
2503
+ }
2504
+ return { k: "map", sh: laterOf(a.sh, other.sh), clr: maxStamp(a.clr, other.clr), keys };
2505
+ }
2506
+ case "ctr": {
2507
+ const other = b;
2508
+ const base = laterOf(a.base, other.base);
2509
+ return counterState(base, pruneDeltas(mergeDeltas(a.d, other.d), base));
2510
+ }
2511
+ case "max":
2512
+ case "min": {
2513
+ const other = b;
2514
+ let best = a.best;
2515
+ if (other.best !== null && betterExtremum(a.k, best, other.best)) best = other.best;
2516
+ return { k: a.k, best, reg: laterOf(a.reg, other.reg) };
2517
+ }
2518
+ case "rt": {
2519
+ const other = b;
2520
+ const u = { ...a.u };
2521
+ for (const [update, stamp] of Object.entries(other.u)) {
2522
+ u[update] = maxStamp(u[update] ?? null, stamp);
2523
+ }
2524
+ return { k: "rt", reset: laterOf(a.reset, other.reset), u };
2525
+ }
2526
+ }
2527
+ }
2528
+
2529
+ // src/fold/types.ts
2530
+ var FOLD_STATE_VERSION = 2;
2531
+
2532
+ // src/fold/serialize.ts
2533
+ var FIELD_KINDS = /* @__PURE__ */ new Set(["reg", "set", "map", "ctr", "max", "min", "res", "rt"]);
2534
+ function serializeFoldState(state) {
2535
+ return canonicalize(state);
2536
+ }
2537
+ function isRecord(value) {
2538
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2539
+ }
2540
+ function deserializeFoldState(json) {
2541
+ let parsed;
2542
+ try {
2543
+ parsed = JSON.parse(json);
2544
+ } catch (error) {
2545
+ throw new FoldStateError("Stored fold state is not valid JSON.", {
2546
+ reason: error instanceof Error ? error.message : String(error)
2547
+ });
2548
+ }
2549
+ if (!isRecord(parsed)) throw new FoldStateError("Stored fold state is not an object.");
2550
+ if (parsed.v !== FOLD_STATE_VERSION) {
2551
+ throw new FoldStateError(
2552
+ `Stored fold state has format version ${String(parsed.v)}; this build reads version ${FOLD_STATE_VERSION}.`,
2553
+ { found: parsed.v, expected: FOLD_STATE_VERSION }
2554
+ );
2555
+ }
2556
+ if (typeof parsed.c !== "string" || typeof parsed.r !== "string" || !isRecord(parsed.f)) {
2557
+ throw new FoldStateError("Stored fold state is missing its collection, record id or fields.");
2558
+ }
2559
+ for (const [field, state2] of Object.entries(parsed.f)) {
2560
+ if (!isRecord(state2) || typeof state2.k !== "string" || !FIELD_KINDS.has(state2.k)) {
2561
+ throw new FoldStateError(`Stored fold state has an unknown kind for field "${field}".`, {
2562
+ field
2563
+ });
2564
+ }
2565
+ }
2566
+ const state = parsed;
2567
+ for (const fieldState of Object.values(state.f)) {
2568
+ if ((fieldState.k === "reg" || fieldState.k === "res") && fieldState.e.length === 0) {
2569
+ fieldState.val = void 0;
2570
+ }
2571
+ if (fieldState.k === "ctr" && fieldState.d.length === 0 && fieldState.base === null) {
2572
+ fieldState.val = void 0;
2573
+ }
2574
+ }
2575
+ return state;
2576
+ }
2577
+
2578
+ // src/fold/fold.ts
2579
+ var FOLD_RECORD_TRACE_FIELD = "*";
2580
+ var SECRET_REDACTED = "[secret]";
2581
+ function createFoldState(collection, recordId) {
2582
+ return {
2583
+ v: FOLD_STATE_VERSION,
2584
+ c: collection,
2585
+ r: recordId,
2586
+ cr: null,
2587
+ w: null,
2588
+ d: null,
2589
+ u: null,
2590
+ f: {}
2591
+ };
2592
+ }
2593
+ function isExcluded(op2, exclude) {
2594
+ if (exclude === void 0) return false;
2595
+ return typeof exclude === "function" ? exclude(op2) : exclude.has(op2.id);
2596
+ }
2597
+ function samePlanKind(state, plan) {
2598
+ if (state.k !== plan.kind) return false;
2599
+ return state.k !== "set" || state.ao === plan.appendOnly;
2600
+ }
2601
+ function fieldPlanFor(collection, field, existing, state) {
2602
+ const plan = planField(collection, field);
2603
+ if (existing !== void 0 && !samePlanKind(existing, plan)) {
2604
+ throw new FoldStateError(
2605
+ `Field "${field}" of ${state.c}/${state.r} is stored as fold kind "${existing.k}" but the schema now folds it as "${plan.kind}".`,
2606
+ { collection: state.c, recordId: state.r, field, stored: existing.k, expected: plan.kind }
2607
+ );
2608
+ }
2609
+ return plan;
2610
+ }
2611
+ function fieldWriteStamp(op2, field, opStamp) {
2612
+ if (op2.type !== "insert" || op2.fieldVersions === void 0) return opStamp;
2613
+ const version = op2.fieldVersions[field];
2614
+ if (version !== void 0) return stampOf(version, op2.id);
2615
+ let oldest = opStamp;
2616
+ for (const candidate of Object.values(op2.fieldVersions)) {
2617
+ const stamp = stampOf(candidate, op2.id);
2618
+ if (compareStamps(stamp, oldest) < 0) oldest = stamp;
2619
+ }
2620
+ return oldest;
2621
+ }
2622
+ function classify(stamp, op2, field, plan, options) {
2623
+ if (!plan.authoritative) return stamp;
2624
+ const author = op2.type === "insert" ? op2.fieldVersions?.[field]?.nodeId ?? op2.nodeId : op2.nodeId;
2625
+ if (isAuthoritativeNodeId(author, options.authoritativeNodeIds)) {
2626
+ return { ...stamp, c: 1 };
2627
+ }
2628
+ return stamp;
2629
+ }
2630
+ function mergeCarriedState(state, op2, schema, options) {
2631
+ let carried;
2632
+ try {
2633
+ carried = deserializeFoldState(op2.foldState);
2634
+ } catch (error) {
2635
+ if (error instanceof FoldStateError) return null;
2636
+ throw error;
2637
+ }
2638
+ if (carried.c !== state.c || carried.r !== state.r) {
2639
+ throw new FoldStateError(
2640
+ `Operation ${op2.id} carries the fold state of ${carried.c}/${carried.r} but targets ${state.c}/${state.r}.`,
2641
+ { operationId: op2.id, collection: op2.collection, recordId: op2.recordId }
2642
+ );
2643
+ }
2644
+ let joined;
2645
+ try {
2646
+ joined = joinStates(state, carried, schema);
2647
+ } catch (error) {
2648
+ if (error instanceof FoldStateError) return null;
2649
+ throw error;
2650
+ }
2651
+ const changed = serializeFoldState(joined) !== serializeFoldState(state);
2652
+ const traces = [];
2653
+ if ((options.traces ?? "conflicts") !== "none") {
2654
+ const collection = schema.collections[state.c];
2655
+ for (const [field, incomingState] of Object.entries(carried.f)) {
2656
+ const mine = state.f[field];
2657
+ if (mine === void 0) continue;
2658
+ const prior = safeMaterializeField(mine, field, options);
2659
+ const incoming = safeMaterializeField(incomingState, field, options);
2660
+ if (canonicalKey(prior) === canonicalKey(incoming)) continue;
2661
+ const plan = planField(collection, field);
2662
+ const output = safeMaterializeField(joined.f[field], field, options);
2663
+ const redact = (value) => plan.secret ? SECRET_REDACTED : value;
2664
+ traces.push({
2665
+ field,
2666
+ strategy: `scope-entry-${plan.strategy}`,
2667
+ inputA: redact(prior),
2668
+ inputB: redact(incoming),
2669
+ base: null,
2670
+ output: redact(output),
2671
+ tier: plan.tier,
2672
+ constraintViolated: null,
2673
+ duration: 0,
2674
+ operation: plan.secret ? redactOperation(op2, field) : op2,
2675
+ priorOperationId: fieldStamp(mine)?.o ?? null,
2676
+ conflict: true
2677
+ });
2678
+ }
2679
+ }
2680
+ return { state: changed ? joined : state, traces, changed };
2681
+ }
2682
+ function buildWrite(op2, field, raw, stamp) {
2683
+ if (raw === void 0) return null;
2684
+ const insert = op2.type === "insert";
2685
+ const v = normalizeValue(raw);
2686
+ const atomic = op2.atomicOps?.[field];
2687
+ const a = atomic ? { type: atomic.type, value: normalizeValue(atomic.value) } : void 0;
2688
+ const prevRaw = insert ? void 0 : op2.previousData?.[field];
2689
+ const hasPrev = !insert && op2.previousData !== null && field in op2.previousData && prevRaw !== void 0;
2690
+ const prev = hasPrev ? normalizeValue(prevRaw) : void 0;
2691
+ if (!insert && a === void 0 && hasPrev && canonicalKey(prev) === canonicalKey(v)) return null;
2692
+ return a ? { s: stamp, insert, v, prev, hasPrev, a } : { s: stamp, insert, v, prev, hasPrev };
2693
+ }
2694
+ function safeMaterializeField(state, field, options) {
2695
+ if (state === void 0) return void 0;
2696
+ try {
2697
+ return materializeField(state, field, options.richtext);
2698
+ } catch (error) {
2699
+ if (error instanceof FoldConfigurationError) return void 0;
2700
+ throw error;
2701
+ }
2702
+ }
2703
+ function redactOperation(op2, field) {
2704
+ const redact = (data) => data !== null && field in data ? { ...data, [field]: SECRET_REDACTED } : data;
2705
+ return { ...op2, data: redact(op2.data), previousData: redact(op2.previousData) };
2706
+ }
2707
+ function fieldTrace(op2, field, plan, write, prior, priorStamp, output, newest, error) {
2708
+ const lost = newest !== null && compareStamps(newest, write.s) > 0;
2709
+ const baseMismatch = prior !== void 0 && !write.insert && canonicalKey(prior) !== canonicalKey(write.prev);
2710
+ const insertOnto = write.insert && prior !== void 0;
2711
+ const strategy = plan.kind === "reg" && write.a ? `atomic-${write.a.type}` : plan.strategy;
2712
+ const trace = {
2713
+ field,
2714
+ strategy,
2715
+ inputA: plan.secret ? SECRET_REDACTED : prior,
2716
+ inputB: plan.secret ? SECRET_REDACTED : write.v,
2717
+ base: write.hasPrev ? plan.secret ? SECRET_REDACTED : write.prev : null,
2718
+ output: plan.secret ? SECRET_REDACTED : output,
2719
+ tier: plan.tier,
2720
+ constraintViolated: null,
2721
+ // The fold is pure: callers time the whole mergeOp if they need a duration.
2722
+ duration: 0,
2723
+ operation: plan.secret ? redactOperation(op2, field) : op2,
2724
+ priorOperationId: priorStamp?.o ?? null,
2725
+ conflict: lost || baseMismatch || insertOnto
2726
+ };
2727
+ if (error !== void 0) trace.error = error;
2728
+ return trace;
2729
+ }
2730
+ function isAlive(state) {
2731
+ return state.cr !== null && isAfter(state.w, state.d);
2732
+ }
2733
+ function mergeOp(state, input, schema, options = {}) {
2734
+ if (isExcluded(input, options.exclude)) return { state, traces: [], changed: false };
2735
+ const op2 = operationSchemaView(input, schema.version, options.transforms);
2736
+ if (op2 === null) return { state, traces: [], changed: false };
2737
+ if (op2.collection !== state.c || op2.recordId !== state.r) {
2738
+ throw new FoldStateError(
2739
+ `Operation ${op2.id} targets ${op2.collection}/${op2.recordId} but the fold state is for ${state.c}/${state.r}.`,
2740
+ { operationId: op2.id, collection: op2.collection, recordId: op2.recordId }
2741
+ );
2742
+ }
2743
+ if (op2.foldState !== void 0 && op2.type === "insert") {
2744
+ const carried = mergeCarriedState(state, op2, schema, options);
2745
+ if (carried !== null) return carried;
2746
+ }
2747
+ const mode = options.traces ?? "conflicts";
2748
+ const traces = [];
2749
+ const stamp = stampOf(op2.timestamp, op2.id);
2750
+ const next = { ...state };
2751
+ let changed = false;
2752
+ const setRecordStamp = (key, value) => {
2753
+ if (value !== next[key]) {
2754
+ next[key] = value;
2755
+ changed = true;
2756
+ }
2757
+ };
2758
+ setRecordStamp("u", maxStamp(state.u, stamp));
2759
+ if (op2.type === "delete") {
2760
+ setRecordStamp("d", maxStamp(state.d, stamp));
2761
+ if (mode !== "none" && state.cr !== null && isAfter(state.w, stamp)) {
2762
+ traces.push(recordTrace(op2, true, true));
2763
+ }
2764
+ return { state: changed ? next : state, traces, changed };
2765
+ }
2766
+ if (op2.data === null) {
2767
+ if (op2.encrypted !== void 0) {
2768
+ if (op2.type === "insert") setRecordStamp("cr", minStamp(state.cr, stamp));
2769
+ setRecordStamp("w", maxStamp(state.w, stamp));
2770
+ }
2771
+ return { state: changed ? next : state, traces, changed };
2772
+ }
2773
+ if (op2.type === "insert") setRecordStamp("cr", minStamp(state.cr, stamp));
2774
+ setRecordStamp("w", maxStamp(state.w, stamp));
2775
+ if (mode !== "none" && isAfter(state.d, stamp)) traces.push(recordTrace(op2, false, false));
2776
+ const collection = schema.collections[op2.collection];
2777
+ let fields = state.f;
2778
+ for (const [field, raw] of Object.entries(op2.data)) {
2779
+ if (raw === void 0) continue;
2780
+ const existing = fields[field];
2781
+ const plan = fieldPlanFor(collection, field, existing, state);
2782
+ const write = buildWrite(
2783
+ op2,
2784
+ field,
2785
+ raw,
2786
+ classify(fieldWriteStamp(op2, field, stamp), op2, field, plan, options)
2787
+ );
2788
+ if (write === null) continue;
2789
+ const tracing = mode !== "none";
2790
+ const prior = tracing ? safeMaterializeField(existing, field, options) : void 0;
2791
+ const priorStamp = existing ? fieldStamp(existing) : null;
2792
+ const result = applyFieldWrite(
2793
+ existing ?? emptyField(plan),
2794
+ write,
2795
+ plan,
2796
+ options.richtextSubsumes
2797
+ );
2798
+ if (result.changed) {
2799
+ if (fields === state.f) fields = { ...state.f };
2800
+ fields[field] = result.state;
2801
+ changed = true;
2802
+ }
2803
+ if (tracing) {
2804
+ const trace = fieldTrace(
2805
+ op2,
2806
+ field,
2807
+ plan,
2808
+ write,
2809
+ prior,
2810
+ priorStamp,
2811
+ safeMaterializeField(result.state, field, options),
2812
+ fieldStamp(result.state),
2813
+ result.error
2814
+ );
2815
+ if (mode === "all" || trace.conflict || trace.error !== void 0) traces.push(trace);
2816
+ }
2817
+ }
2818
+ next.f = fields;
2819
+ return { state: changed ? next : state, traces, changed };
2820
+ }
2821
+ function recordTrace(op2, isDelete, lost) {
2822
+ return {
2823
+ field: FOLD_RECORD_TRACE_FIELD,
2824
+ strategy: "delete-tombstone-lww",
2825
+ inputA: isDelete ? "live" : "deleted",
2826
+ inputB: isDelete ? "deleted" : "live",
2827
+ base: null,
2828
+ // A delete that lost keeps the record live; a write older than the newest
2829
+ // delete stays hidden.
2830
+ output: isDelete ? lost ? "live" : "deleted" : "deleted",
2831
+ tier: 1,
2832
+ constraintViolated: null,
2833
+ duration: 0,
2834
+ operation: op2,
2835
+ priorOperationId: null,
2836
+ conflict: true
2837
+ };
2838
+ }
2839
+ function foldRecord(ops, schema, options = {}) {
2840
+ const kept = ops.filter((op2) => !isExcluded(op2, options.exclude));
2841
+ const first = kept[0];
2842
+ if (first === void 0) return { state: null, traces: [] };
2843
+ const ordered = kept.map((op2) => ({ op: op2, stamp: stampOf(op2.timestamp, op2.id) })).sort((a, b) => compareStamps(a.stamp, b.stamp));
2844
+ let state = createFoldState(first.collection, first.recordId);
2845
+ const traces = [];
2846
+ const mergeOptions = {
2847
+ ...options,
2848
+ exclude: void 0,
2849
+ traces: options.traces ?? "none"
2850
+ };
2851
+ for (const { op: op2 } of ordered) {
2852
+ const result = mergeOp(state, op2, schema, mergeOptions);
2853
+ state = result.state;
2854
+ traces.push(...result.traces);
2855
+ }
2856
+ return { state, traces };
2857
+ }
2858
+ function materialize(state, options = {}) {
2859
+ if (!isAlive(state)) return null;
2860
+ const record = {};
2861
+ for (const field of Object.keys(state.f).sort()) {
2862
+ const value = materializeField(state.f[field], field, options.richtext);
2863
+ if (value !== void 0) record[field] = value;
2864
+ }
2865
+ return record;
2866
+ }
2867
+ function isFoldStateLive(state) {
2868
+ return isAlive(state);
2869
+ }
2870
+ function joinStates(a, b, schema) {
2871
+ if (a.c !== b.c || a.r !== b.r) {
2872
+ throw new FoldStateError(`Cannot join fold states of ${a.c}/${a.r} and ${b.c}/${b.r}.`, {
2873
+ left: `${a.c}/${a.r}`,
2874
+ right: `${b.c}/${b.r}`
2875
+ });
2876
+ }
2877
+ const collection = schema.collections[a.c];
2878
+ const fields = { ...a.f };
2879
+ for (const [field, other] of Object.entries(b.f)) {
2880
+ const mine = fields[field];
2881
+ if (mine === void 0) {
2882
+ fieldPlanFor(collection, field, other, b);
2883
+ fields[field] = other;
2884
+ continue;
2885
+ }
2886
+ const plan = fieldPlanFor(collection, field, mine, a);
2887
+ fieldPlanFor(collection, field, other, b);
2888
+ fields[field] = joinFieldStates(mine, other, plan);
2889
+ }
2890
+ return {
2891
+ v: FOLD_STATE_VERSION,
2892
+ c: a.c,
2893
+ r: a.r,
2894
+ cr: minStamp(a.cr, b.cr),
2895
+ w: maxStamp(a.w, b.w),
2896
+ d: maxStamp(a.d, b.d),
2897
+ u: maxStamp(a.u, b.u),
2898
+ f: fields
2899
+ };
2900
+ }
2901
+ function getFoldFieldVersionStrings(state) {
2902
+ const out = {};
2903
+ for (const field of Object.keys(state.f).sort()) {
2904
+ const stamp = fieldStamp(state.f[field]);
2905
+ if (stamp !== null) out[field] = stamp.t;
2906
+ }
2907
+ return out;
2908
+ }
2909
+ function getFoldFieldVersions(state) {
2910
+ if (!isAlive(state) || state.cr === null || state.u === null) return null;
2911
+ const fields = {};
2912
+ for (const [field, fieldState] of Object.entries(state.f)) {
2913
+ const stamp = fieldStamp(fieldState);
2914
+ if (stamp !== null) fields[field] = stampTimestamp(stamp);
2915
+ }
2916
+ return { fields, created: stampTimestamp(state.cr), latest: stampTimestamp(state.u) };
2917
+ }
2918
+
2919
+ // src/fold/plan.ts
2920
+ function hashText2(text) {
2921
+ let hash = 2166136261;
2922
+ for (let i = 0; i < text.length; i++) {
2923
+ hash ^= text.charCodeAt(i);
2924
+ hash = Math.imul(hash, 16777619) >>> 0;
2925
+ }
2926
+ return hash.toString(16).padStart(8, "0");
2927
+ }
2928
+ function collectionFoldPlanFingerprint(name, collection) {
2929
+ const fields = Object.keys(collection.fields).sort().map((field) => {
2930
+ const descriptor = collection.fields[field];
2931
+ const resolver = collection.resolvers?.[field];
2932
+ return `${field}:${descriptor?.kind ?? ""}:${descriptor?.mergeStrategy ?? ""}:${resolver ? hashText2(String(resolver)) : ""}`;
2933
+ });
2934
+ const resolverOnly = Object.keys(collection.resolvers ?? {}).filter((field) => !(field in collection.fields)).sort().map((field) => `${field}:resolver:${hashText2(String(collection.resolvers?.[field]))}`);
2935
+ return `${name}(${[...fields, ...resolverOnly].join(",")})`;
2936
+ }
2937
+ function foldPlanFingerprint(schema, transforms) {
2938
+ const parts = [`fold-v${FOLD_STATE_VERSION}`];
2939
+ for (const name of Object.keys(schema.collections).sort()) {
2940
+ const collection = schema.collections[name];
2941
+ if (!collection) continue;
2942
+ parts.push(collectionFoldPlanFingerprint(name, collection));
2943
+ }
2944
+ const xf = operationTransformsFingerprint(schema.version, transforms);
2945
+ if (xf !== "") parts.push(xf);
2946
+ return parts.join("|");
2947
+ }
2948
+ function foldPlanFingerprints(schema, transforms) {
2949
+ const out = {};
2950
+ const xf = operationTransformsFingerprint(schema.version, transforms);
2951
+ for (const name of Object.keys(schema.collections).sort()) {
2952
+ const collection = schema.collections[name];
2953
+ if (!collection) continue;
2954
+ out[name] = `fold-v${FOLD_STATE_VERSION}|${collectionFoldPlanFingerprint(name, collection)}${xf !== "" ? `|${xf}` : ""}`;
2955
+ }
2956
+ return out;
2957
+ }
2958
+ function fieldStateMatchesPlan(collection, field, state) {
2959
+ const plan = planField(collection, field);
2960
+ if (state.k !== plan.kind) return false;
2961
+ return state.k !== "set" || state.ao === plan.appendOnly;
2962
+ }
2963
+ function mismatchedFoldFields(state, schema) {
2964
+ const collection = schema.collections[state.c];
2965
+ return Object.keys(state.f).filter((field) => !fieldStateMatchesPlan(collection, field, state.f[field])).sort();
2966
+ }
2967
+
2968
+ // src/fold/snapshot.ts
2969
+ var SNAPSHOT_CLEAR_ID = "\uFFFFkora-snapshot:0";
2970
+ var SNAPSHOT_WRITE_ID = "\uFFFFkora-snapshot:1";
2971
+ function snapshotField(plan, raw, clear, write) {
2972
+ const v = normalizeValue(raw);
2973
+ switch (plan.kind) {
2974
+ case "reg":
2975
+ return { k: "reg", e: [{ s: write, v }], val: v };
2976
+ case "res":
2977
+ return { k: "res", e: [{ s: write, v, z: 1 }], val: v };
2978
+ case "ctr":
2979
+ return { k: "ctr", base: { s: write, v }, d: [], val: v };
2980
+ case "max":
2981
+ case "min":
2982
+ 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 } };
2983
+ case "set": {
2984
+ if (!Array.isArray(v)) {
2985
+ return {
2986
+ k: "set",
2987
+ ao: plan.appendOnly,
2988
+ sh: { s: write, arr: false, v },
2989
+ clr: write,
2990
+ el: {}
2991
+ };
2992
+ }
2993
+ const el = {};
2994
+ const counts = /* @__PURE__ */ new Map();
2995
+ v.forEach((item, index) => {
2996
+ const canonical2 = canonicalKey(item);
2997
+ const n = counts.get(canonical2) ?? 0;
2998
+ counts.set(canonical2, n + 1);
2999
+ el[elementKey(canonical2, n)] = {
3000
+ v: JSON.parse(canonical2),
3001
+ n,
3002
+ a: write,
3003
+ f: { s: write, i: index },
3004
+ r: null
3005
+ };
3006
+ });
3007
+ return { k: "set", ao: plan.appendOnly, sh: { s: write, arr: true }, clr: clear, el };
3008
+ }
3009
+ case "map": {
3010
+ if (!isPlainObject2(v)) {
3011
+ return { k: "map", sh: { s: write, obj: false, v }, clr: write, keys: {} };
3012
+ }
3013
+ const keys = {};
3014
+ for (const [key, member] of Object.entries(v)) keys[key] = { s: write, del: false, v: member };
3015
+ return { k: "map", sh: { s: write, obj: true }, clr: clear, keys };
3016
+ }
3017
+ case "rt": {
3018
+ if (v === null || typeof v === "string" || !isPlainObject2(v) || !("$koraBytes" in v)) {
3019
+ return { k: "rt", reset: { s: write, v }, u: {} };
3020
+ }
3021
+ const update = String(v.$koraBytes);
3022
+ return { k: "rt", reset: { s: clear, v: null }, u: { [update]: write } };
3023
+ }
3024
+ }
3025
+ }
3026
+ function createSnapshotState(input, schema, options = {}) {
3027
+ const state = createFoldState(input.collection, input.recordId);
3028
+ const collection = schema.collections[input.collection];
3029
+ const seed = options.seed && options.seed.c === input.collection && options.seed.r === input.recordId ? options.seed : null;
3030
+ const fields = {};
3031
+ for (const [field, raw] of Object.entries(input.values)) {
3032
+ if (raw === void 0) continue;
3033
+ const seeded = seed?.f[field];
3034
+ if (seeded !== void 0 && fieldStateMatchesPlan(collection, field, seeded)) {
3035
+ fields[field] = seeded;
3036
+ continue;
3037
+ }
3038
+ const version = input.fieldVersions[field] ?? input.latest;
3039
+ const plan = planField(collection, field);
3040
+ const authoritative = plan.authoritative === true && isAuthoritativeNodeId(version.nodeId, options.authoritativeNodeIds);
3041
+ fields[field] = snapshotField(
3042
+ plan,
3043
+ raw,
3044
+ classed(stampOf(version, SNAPSHOT_CLEAR_ID), authoritative),
3045
+ classed(stampOf(version, SNAPSHOT_WRITE_ID), authoritative)
3046
+ );
3047
+ }
3048
+ if (seed !== null && seed.cr !== null && seed.u !== null) {
3049
+ return { ...state, cr: seed.cr, w: seed.w, d: seed.d, u: seed.u, f: fields };
3050
+ }
3051
+ const latestClear = stampOf(input.latest, SNAPSHOT_CLEAR_ID);
3052
+ const latestWrite = stampOf(input.latest, SNAPSHOT_WRITE_ID);
3053
+ return {
3054
+ ...state,
3055
+ cr: stampOf(input.created, SNAPSHOT_WRITE_ID),
3056
+ w: input.deleted ? latestClear : latestWrite,
3057
+ d: input.deleted ? latestWrite : null,
3058
+ u: latestWrite,
3059
+ f: fields
3060
+ };
3061
+ }
3062
+ function classed(stamp, authoritative) {
3063
+ return authoritative ? { ...stamp, c: 1 } : stamp;
3064
+ }
3065
+ function adaptFoldState(state, schema, options = {}) {
3066
+ const mismatched = mismatchedFoldFields(state, schema);
3067
+ if (mismatched.length === 0) return { state, adapted: [] };
3068
+ const collection = schema.collections[state.c];
3069
+ const fields = { ...state.f };
3070
+ for (const field of mismatched) {
3071
+ const old = fields[field];
3072
+ delete fields[field];
3073
+ const stamp = fieldStamp(old);
3074
+ if (stamp === null) continue;
3075
+ let value;
3076
+ try {
3077
+ value = materializeField(old, field, options.richtext);
3078
+ } catch (error) {
3079
+ if (error instanceof FoldConfigurationError) continue;
3080
+ throw error;
1131
3081
  }
3082
+ if (value === void 0) continue;
3083
+ const plan = planField(collection, field);
3084
+ const authoritative = stamp.c === 1 && plan.authoritative === true;
3085
+ fields[field] = snapshotField(
3086
+ plan,
3087
+ value,
3088
+ classed({ t: stamp.t, o: SNAPSHOT_CLEAR_ID }, authoritative),
3089
+ classed({ t: stamp.t, o: SNAPSHOT_WRITE_ID }, authoritative)
3090
+ );
1132
3091
  }
1133
- return true;
3092
+ return { state: { ...state, f: fields }, adapted: mismatched };
1134
3093
  }
1135
- var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1136
- var BASE64_REVERSE = new Map(
1137
- Array.from(BASE64_ALPHABET, (char, index) => [char, index])
1138
- );
1139
- function bytesToBase64(bytes) {
3094
+
3095
+ // src/fold/trace.ts
3096
+ function toMergeTrace(trace, operationA) {
3097
+ return {
3098
+ operationA: operationA ?? trace.operation,
3099
+ operationB: trace.operation,
3100
+ field: trace.field,
3101
+ strategy: trace.strategy,
3102
+ inputA: trace.inputA,
3103
+ inputB: trace.inputB,
3104
+ base: trace.base,
3105
+ output: trace.output,
3106
+ tier: trace.tier,
3107
+ constraintViolated: trace.constraintViolated,
3108
+ duration: trace.duration
3109
+ };
3110
+ }
3111
+
3112
+ // src/text/stored-text.ts
3113
+ var STORED_TEXT_ESCAPE = "\uFFFF";
3114
+ var NEEDS_ENCODING = /[\u0000￿\ud800-\udfff]/;
3115
+ function needsStoredTextEncoding(s) {
3116
+ return NEEDS_ENCODING.test(s);
3117
+ }
3118
+ function encodeStoredText(s) {
3119
+ if (!NEEDS_ENCODING.test(s)) return s;
1140
3120
  let out = "";
1141
- for (let i = 0; i < bytes.length; i += 3) {
1142
- const b0 = bytes[i] ?? 0;
1143
- const b1 = bytes[i + 1] ?? 0;
1144
- const b2 = bytes[i + 2] ?? 0;
1145
- const triple = b0 << 16 | b1 << 8 | b2;
1146
- out += BASE64_ALPHABET[triple >> 18 & 63] ?? "";
1147
- out += BASE64_ALPHABET[triple >> 12 & 63] ?? "";
1148
- out += i + 1 < bytes.length ? BASE64_ALPHABET[triple >> 6 & 63] ?? "" : "=";
1149
- out += i + 2 < bytes.length ? BASE64_ALPHABET[triple & 63] ?? "" : "=";
3121
+ for (let i = 0; i < s.length; i++) {
3122
+ const code = s.charCodeAt(i);
3123
+ if (code === 0) {
3124
+ out += `${STORED_TEXT_ESCAPE}0`;
3125
+ } else if (code === 65535) {
3126
+ out += `${STORED_TEXT_ESCAPE}F`;
3127
+ } else if (code >= 55296 && code <= 56319) {
3128
+ const next = s.charCodeAt(i + 1);
3129
+ if (next >= 56320 && next <= 57343) {
3130
+ out += s.slice(i, i + 2);
3131
+ i++;
3132
+ } else {
3133
+ out += `${STORED_TEXT_ESCAPE}s${code.toString(16)}`;
3134
+ }
3135
+ } else if (code >= 56320 && code <= 57343) {
3136
+ out += `${STORED_TEXT_ESCAPE}s${code.toString(16)}`;
3137
+ } else {
3138
+ out += s.charAt(i);
3139
+ }
1150
3140
  }
1151
3141
  return out;
1152
3142
  }
1153
- function base64ToBytes(base64) {
1154
- const cleaned = base64.replace(/=+$/, "");
1155
- const out = new Uint8Array(Math.floor(cleaned.length * 6 / 8));
1156
- let buffer = 0;
1157
- let bits = 0;
1158
- let index = 0;
1159
- for (const char of cleaned) {
1160
- const value = BASE64_REVERSE.get(char);
1161
- if (value === void 0) {
1162
- throw new OperationError(`Invalid base64 character "${char}" in tagged binary value.`, {
1163
- char
1164
- });
3143
+ function decodeStoredText(s) {
3144
+ if (!s.includes(STORED_TEXT_ESCAPE)) return s;
3145
+ let out = "";
3146
+ for (let i = 0; i < s.length; i++) {
3147
+ const char = s[i];
3148
+ if (char !== STORED_TEXT_ESCAPE) {
3149
+ out += char;
3150
+ continue;
1165
3151
  }
1166
- buffer = buffer << 6 | value;
1167
- bits += 6;
1168
- if (bits >= 8) {
1169
- bits -= 8;
1170
- out[index] = buffer >> bits & 255;
1171
- index += 1;
3152
+ const tag = s[i + 1];
3153
+ if (tag === "0") {
3154
+ out += "\0";
3155
+ i += 1;
3156
+ } else if (tag === "F") {
3157
+ out += STORED_TEXT_ESCAPE;
3158
+ i += 1;
3159
+ } else if (tag === "s") {
3160
+ const hex = s.slice(i + 2, i + 6);
3161
+ const code = Number.parseInt(hex, 16);
3162
+ if (hex.length === 4 && Number.isInteger(code) && code >= 55296 && code <= 57343) {
3163
+ out += String.fromCharCode(code);
3164
+ i += 5;
3165
+ } else {
3166
+ out += char;
3167
+ }
3168
+ } else {
3169
+ out += char;
1172
3170
  }
1173
3171
  }
1174
3172
  return out;
1175
3173
  }
1176
- function encodeBytesForOpData(value) {
1177
- if (typeof value === "string") {
1178
- return value;
1179
- }
1180
- const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
1181
- return { $koraBytes: bytesToBase64(bytes) };
3174
+ function encodeStoredJsonValue(value) {
3175
+ return mapStrings(value, encodeStoredText);
1182
3176
  }
1183
- function decodeBytesFromOpData(value) {
1184
- if (typeof value === "string") {
1185
- return value;
1186
- }
1187
- if (value instanceof Uint8Array) {
1188
- return value;
1189
- }
1190
- if (value instanceof ArrayBuffer) {
1191
- return new Uint8Array(value);
1192
- }
1193
- if (isKoraBytesValue(value)) {
1194
- return base64ToBytes(value.$koraBytes);
1195
- }
1196
- if (isLegacyNumericByteObject(value)) {
1197
- const keys = Object.keys(value);
1198
- const bytes = new Uint8Array(keys.length);
1199
- for (let i = 0; i < keys.length; i++) {
1200
- bytes[i] = value[String(i)] ?? 0;
3177
+ function decodeStoredJsonValue(value) {
3178
+ return mapStrings(value, decodeStoredText);
3179
+ }
3180
+ function mapStrings(value, map) {
3181
+ if (typeof value === "string") return map(value);
3182
+ if (Array.isArray(value)) return value.map((member) => mapStrings(member, map));
3183
+ if (value !== null && typeof value === "object" && !ArrayBuffer.isView(value)) {
3184
+ const out = {};
3185
+ for (const [key, member] of Object.entries(value)) out[map(key)] = mapStrings(member, map);
3186
+ return out;
3187
+ }
3188
+ return value;
3189
+ }
3190
+
3191
+ // src/fold/side-effect-id.ts
3192
+ var SIDE_EFFECT_DOMAIN = "kora/side-effect/v1";
3193
+ async function deriveSideEffectOpId(parentOpId, ruleId, targetRecordId) {
3194
+ for (const [name, value] of [
3195
+ ["parentOpId", parentOpId],
3196
+ ["ruleId", ruleId],
3197
+ ["targetRecordId", targetRecordId]
3198
+ ]) {
3199
+ if (typeof value !== "string" || value.length === 0) {
3200
+ throw new OperationError(`deriveSideEffectOpId: ${name} must be a non-empty string`, {
3201
+ [name]: value
3202
+ });
1201
3203
  }
1202
- return bytes;
1203
3204
  }
1204
- throw new OperationError(
1205
- "Binary op-data value must be a string, Uint8Array, ArrayBuffer, tagged { $koraBytes } object, or legacy numeric-key byte object.",
1206
- { receivedType: typeof value }
3205
+ const canonical2 = canonicalize({
3206
+ domain: SIDE_EFFECT_DOMAIN,
3207
+ parent: parentOpId,
3208
+ rule: ruleId,
3209
+ target: targetRecordId
3210
+ });
3211
+ const digest = await globalThis.crypto.subtle.digest(
3212
+ "SHA-256",
3213
+ new TextEncoder().encode(canonical2)
1207
3214
  );
3215
+ return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
1208
3216
  }
1209
3217
 
1210
3218
  // src/scopes/sync-scope-bindings.ts
@@ -1489,6 +3497,21 @@ function buildCollection(name, input) {
1489
3497
  validateStateMachineDefinition(name, input.stateMachine, fields);
1490
3498
  stateMachine = { ...input.stateMachine };
1491
3499
  }
3500
+ if (!stateMachine) {
3501
+ const transitionFields = Object.entries(fields).filter(
3502
+ ([, descriptor]) => descriptor.kind === "enum" && descriptor.transitions
3503
+ );
3504
+ const only = transitionFields.length === 1 ? transitionFields[0] : void 0;
3505
+ if (only?.[1].transitions) {
3506
+ const derived = {
3507
+ field: only[0],
3508
+ transitions: { ...only[1].transitions },
3509
+ onInvalidTransition: "reject"
3510
+ };
3511
+ validateStateMachineDefinition(name, derived, fields);
3512
+ stateMachine = derived;
3513
+ }
3514
+ }
1492
3515
  return { fields, indexes, constraints, resolvers, scope, stateMachine };
1493
3516
  }
1494
3517
  function buildSyncRules(syncInput, collections) {
@@ -1571,6 +3594,22 @@ function validateConstraint(collection, constraint, fields) {
1571
3594
  );
1572
3595
  }
1573
3596
  }
3597
+ if (constraint.where !== void 0 && constraint.type !== "referential") {
3598
+ for (const [key, value] of Object.entries(constraint.where)) {
3599
+ if (key !== "id" && !(key in fields)) {
3600
+ throw new SchemaValidationError(
3601
+ `Constraint where references field "${key}" which does not exist in collection "${collection}". Available fields: ${Object.keys(fields).join(", ")}`,
3602
+ { collection, field: key }
3603
+ );
3604
+ }
3605
+ if (value !== null && typeof value === "object") {
3606
+ throw new SchemaValidationError(
3607
+ `Constraint where on "${collection}.${key}" uses ${Array.isArray(value) ? "an array" : `the operator object ${JSON.stringify(value)}`}, but constraint where matches by plain equality only, so the constraint would never apply. Use an equality value (for example { ${key}: 'published' }), or enforce the rule in validateOperation or a server route.`,
3608
+ { collection, field: key }
3609
+ );
3610
+ }
3611
+ }
3612
+ }
1574
3613
  if (constraint.onConflict === "priority-field" && !constraint.priorityField) {
1575
3614
  throw new SchemaValidationError(
1576
3615
  `Constraint with "priority-field" onConflict strategy in collection "${collection}" requires a priorityField`,
@@ -1665,13 +3704,13 @@ ALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN _field_versions TEXT NOT NU
1665
3704
  );
1666
3705
  for (const indexField of collection.indexes) {
1667
3706
  statements.push(
1668
- `CREATE INDEX IF NOT EXISTS idx_${collectionName}_${indexField} ON ${quoteIdent(collectionName)} (${quoteIdent(indexField)})`
3707
+ `CREATE INDEX IF NOT EXISTS ${quoteIdent(collectionIndexName(collectionName, indexField))} ON ${quoteIdent(collectionName)} (${quoteIdent(indexField)})`
1669
3708
  );
1670
3709
  }
1671
3710
  for (const fkField of fkFields) {
1672
3711
  if (!indexedFields.has(fkField)) {
1673
3712
  statements.push(
1674
- `CREATE INDEX IF NOT EXISTS idx_${collectionName}_${fkField} ON ${quoteIdent(collectionName)} (${quoteIdent(fkField)})`
3713
+ `CREATE INDEX IF NOT EXISTS ${quoteIdent(collectionIndexName(collectionName, fkField))} ON ${quoteIdent(collectionName)} (${quoteIdent(fkField)})`
1675
3714
  );
1676
3715
  }
1677
3716
  }
@@ -1694,11 +3733,16 @@ ALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN _field_versions TEXT NOT NU
1694
3733
  );
1695
3734
  return statements;
1696
3735
  }
3736
+ function collectionIndexName(collection, field) {
3737
+ return `idx_${collection.length}_${collection}_${field}`;
3738
+ }
3739
+ var SCHEMA_CEILING_DIRECTIVE = "--kora:schema-ceiling";
1697
3740
  function generateFullDDL(schema) {
1698
3741
  const statements = [];
1699
3742
  statements.push(
1700
3743
  "CREATE TABLE IF NOT EXISTS _kora_meta (\n key TEXT PRIMARY KEY NOT NULL,\n value TEXT NOT NULL\n)"
1701
3744
  );
3745
+ statements.push(`${SCHEMA_CEILING_DIRECTIVE} ${schema.version}`);
1702
3746
  statements.push(
1703
3747
  "CREATE TABLE IF NOT EXISTS _kora_version_vector (\n node_id TEXT PRIMARY KEY NOT NULL,\n sequence_number INTEGER NOT NULL\n)"
1704
3748
  );
@@ -1728,18 +3772,23 @@ function generateFullDDL(schema) {
1728
3772
  function columnDefinition(fieldName, descriptor) {
1729
3773
  const sqlType = mapFieldType(descriptor);
1730
3774
  const parts = [quoteIdent(fieldName), sqlType];
1731
- if (descriptor.required && descriptor.defaultValue === void 0 && !descriptor.auto) {
1732
- parts.push("NOT NULL");
1733
- }
1734
3775
  if (descriptor.defaultValue !== void 0) {
1735
- parts.push(`DEFAULT ${sqlDefault(descriptor.defaultValue)}`);
1736
- }
1737
- if (descriptor.kind === "enum" && descriptor.enumValues) {
1738
- const values = descriptor.enumValues.map((v) => `'${v}'`).join(", ");
1739
- parts.push(`CHECK (${quoteIdent(fieldName)} IN (${values}))`);
3776
+ parts.push(`DEFAULT ${sqlDefaultLiteral(descriptor.defaultValue)}`);
1740
3777
  }
1741
3778
  return parts.join(" ");
1742
3779
  }
3780
+ function sqlStringLiteral(value) {
3781
+ return `'${value.replaceAll("'", "''")}'`;
3782
+ }
3783
+ function sqlDefaultLiteral(value) {
3784
+ if (value === null) return "NULL";
3785
+ if (typeof value === "string") return sqlStringLiteral(value);
3786
+ if (typeof value === "number") {
3787
+ return Number.isFinite(value) ? String(value) : "NULL";
3788
+ }
3789
+ if (typeof value === "boolean") return value ? "1" : "0";
3790
+ return sqlStringLiteral(JSON.stringify(value) ?? "null");
3791
+ }
1743
3792
  function mapFieldType(descriptor) {
1744
3793
  switch (descriptor.kind) {
1745
3794
  case "string":
@@ -1770,13 +3819,6 @@ function mapFieldType(descriptor) {
1770
3819
  return "BLOB";
1771
3820
  }
1772
3821
  }
1773
- function sqlDefault(value) {
1774
- if (value === null) return "NULL";
1775
- if (typeof value === "string") return `'${value}'`;
1776
- if (typeof value === "number") return String(value);
1777
- if (typeof value === "boolean") return value ? "1" : "0";
1778
- return `'${JSON.stringify(value)}'`;
1779
- }
1780
3822
 
1781
3823
  // src/schema/types.ts
1782
3824
  var FieldBuilder = class _FieldBuilder {
@@ -1792,15 +3834,21 @@ var FieldBuilder = class _FieldBuilder {
1792
3834
  this._auto = auto;
1793
3835
  this._mergeStrategy = mergeStrategy;
1794
3836
  }
1795
- /** Mark this field as optional (not required on insert) */
3837
+ /** Mark this field as optional (not required on insert; reads may return `null`). */
1796
3838
  optional() {
1797
3839
  return new _FieldBuilder(this._kind, false, this._defaultValue, this._auto, this._mergeStrategy);
1798
3840
  }
1799
- /** Set a default value for this field. Implicitly makes the field optional. */
3841
+ /**
3842
+ * Set a default value for this field. Implicitly makes the field optional on insert.
3843
+ * The value must be of the field's type (`t.number().default('x')` is a type error).
3844
+ */
1800
3845
  default(value) {
1801
3846
  return new _FieldBuilder(this._kind, false, value, this._auto, this._mergeStrategy);
1802
3847
  }
1803
- /** Mark this field as auto-populated (e.g., createdAt timestamps). Developers cannot set auto fields. */
3848
+ /**
3849
+ * Mark this field as auto-populated (e.g., createdAt timestamps). Developers cannot
3850
+ * set auto fields; the framework fills `t.timestamp().auto()` with the insert time.
3851
+ */
1804
3852
  auto() {
1805
3853
  return new _FieldBuilder(this._kind, false, void 0, true, this._mergeStrategy);
1806
3854
  }
@@ -1818,13 +3866,7 @@ var FieldBuilder = class _FieldBuilder {
1818
3866
  * - `'server-authoritative'`: Always prefer the remote/server value
1819
3867
  */
1820
3868
  merge(strategy) {
1821
- return new _FieldBuilder(
1822
- this._kind,
1823
- this._required,
1824
- this._defaultValue,
1825
- this._auto,
1826
- strategy
1827
- );
3869
+ return new _FieldBuilder(this._kind, this._required, this._defaultValue, this._auto, strategy);
1828
3870
  }
1829
3871
  /** @internal Build the final FieldDescriptor. Used by defineSchema(). */
1830
3872
  _build() {
@@ -1840,6 +3882,16 @@ var FieldBuilder = class _FieldBuilder {
1840
3882
  };
1841
3883
  }
1842
3884
  };
3885
+ function assertStorableEnumValues(values) {
3886
+ for (const value of values) {
3887
+ if (typeof value !== "string" || encodeStoredText(value) !== value) {
3888
+ throw new SchemaValidationError(
3889
+ `Enum value ${JSON.stringify(value)} cannot be stored: enum values must be well-formed strings without U+0000 or U+FFFF.`,
3890
+ { value: String(value) }
3891
+ );
3892
+ }
3893
+ }
3894
+ }
1843
3895
  var EnumFieldBuilder = class _EnumFieldBuilder extends FieldBuilder {
1844
3896
  _enumValues;
1845
3897
  _transitions;
@@ -1945,14 +3997,14 @@ var EnumFieldBuilder = class _EnumFieldBuilder extends FieldBuilder {
1945
3997
  }
1946
3998
  };
1947
3999
  var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
1948
- _itemKind;
4000
+ _item;
1949
4001
  constructor(itemBuilder, required = true, defaultValue = void 0, auto = false, mergeStrategy = null) {
1950
4002
  super("array", required, defaultValue, auto, mergeStrategy);
1951
- this._itemKind = itemBuilder._build().kind;
4003
+ this._item = itemBuilder;
1952
4004
  }
1953
4005
  optional() {
1954
4006
  return new _ArrayFieldBuilder(
1955
- new FieldBuilder(this._itemKind),
4007
+ this._item,
1956
4008
  false,
1957
4009
  this._defaultValue,
1958
4010
  this._auto,
@@ -1960,26 +4012,14 @@ var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
1960
4012
  );
1961
4013
  }
1962
4014
  default(value) {
1963
- return new _ArrayFieldBuilder(
1964
- new FieldBuilder(this._itemKind),
1965
- false,
1966
- value,
1967
- this._auto,
1968
- this._mergeStrategy
1969
- );
4015
+ return new _ArrayFieldBuilder(this._item, false, value, this._auto, this._mergeStrategy);
1970
4016
  }
1971
4017
  auto() {
1972
- return new _ArrayFieldBuilder(
1973
- new FieldBuilder(this._itemKind),
1974
- false,
1975
- void 0,
1976
- true,
1977
- this._mergeStrategy
1978
- );
4018
+ return new _ArrayFieldBuilder(this._item, false, void 0, true, this._mergeStrategy);
1979
4019
  }
1980
4020
  merge(strategy) {
1981
4021
  return new _ArrayFieldBuilder(
1982
- new FieldBuilder(this._itemKind),
4022
+ this._item,
1983
4023
  this._required,
1984
4024
  this._defaultValue,
1985
4025
  this._auto,
@@ -1993,7 +4033,7 @@ var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
1993
4033
  defaultValue: this._defaultValue,
1994
4034
  auto: this._auto,
1995
4035
  enumValues: null,
1996
- itemKind: this._itemKind,
4036
+ itemKind: this._item._build().kind,
1997
4037
  mergeStrategy: this._mergeStrategy,
1998
4038
  transitions: null
1999
4039
  };
@@ -2127,6 +4167,9 @@ var SecretFieldBuilder = class _SecretFieldBuilder extends FieldBuilder {
2127
4167
  this._mergeStrategy
2128
4168
  );
2129
4169
  }
4170
+ default(value) {
4171
+ return new _SecretFieldBuilder(this._secretMode, false, value, this._auto, this._mergeStrategy);
4172
+ }
2130
4173
  auto() {
2131
4174
  return new _SecretFieldBuilder(this._secretMode, false, void 0, true, this._mergeStrategy);
2132
4175
  }
@@ -2153,42 +4196,220 @@ var SecretFieldBuilder = class _SecretFieldBuilder extends FieldBuilder {
2153
4196
  };
2154
4197
  }
2155
4198
  };
4199
+ function scalar(kind) {
4200
+ return new FieldBuilder(kind, true, void 0, false);
4201
+ }
2156
4202
  var t = {
4203
+ /** A string field. */
2157
4204
  string() {
2158
- return new FieldBuilder("string", true, void 0, false);
4205
+ return scalar("string");
2159
4206
  },
4207
+ /** A finite-number field. */
2160
4208
  number() {
2161
- return new FieldBuilder("number", true, void 0, false);
4209
+ return scalar("number");
2162
4210
  },
4211
+ /** A boolean field. */
2163
4212
  boolean() {
2164
- return new FieldBuilder("boolean", true, void 0, false);
4213
+ return scalar("boolean");
2165
4214
  },
4215
+ /** A timestamp field: integer milliseconds since the epoch (not a `Date`). */
2166
4216
  timestamp() {
2167
- return new FieldBuilder("timestamp", true, void 0, false);
4217
+ return scalar("timestamp");
2168
4218
  },
4219
+ /** A collaborative rich-text field (Yjs). Reads return the Yjs update bytes. */
2169
4220
  richtext() {
2170
- return new FieldBuilder("richtext", true, void 0, false);
4221
+ return scalar("richtext");
2171
4222
  },
4223
+ /** An enum field whose value is one of `values`. */
2172
4224
  enum(values) {
4225
+ assertStorableEnumValues(values);
2173
4226
  return new EnumFieldBuilder(values, true, void 0, false);
2174
4227
  },
4228
+ /** An array field whose items have the type of `itemBuilder`. */
2175
4229
  array(itemBuilder) {
2176
4230
  return new ArrayFieldBuilder(itemBuilder, true, void 0, false);
2177
4231
  },
4232
+ /** A structured object field whose keys are declared by nested builders. */
2178
4233
  object(fields) {
2179
4234
  return new ObjectFieldBuilder(fields, true, void 0, false);
2180
4235
  },
4236
+ /** A dynamic JSON field typed as `T` (compile-time only; not validated against `T`). */
2181
4237
  json() {
2182
4238
  return new JsonFieldBuilder("json", true, void 0, false);
2183
4239
  },
4240
+ /** A blob reference field (`BlobRef`); the bytes live in the blob store. */
2184
4241
  blob() {
2185
- return new FieldBuilder("blob", true, void 0, false);
4242
+ return scalar("blob");
2186
4243
  },
4244
+ /** A secret field (password, token): redacted from traces, hashed or encrypted at rest. */
2187
4245
  secret() {
2188
4246
  return new SecretFieldBuilder("encrypted", true, void 0, false);
2189
4247
  }
2190
4248
  };
2191
4249
 
4250
+ // src/schema/value-domain.ts
4251
+ var TIMESTAMP_MIN_MS = -864e13;
4252
+ var TIMESTAMP_MAX_MS = 864e13;
4253
+ var MAX_VALUE_DEPTH = 64;
4254
+ var DEFAULT_MAX_OPERATION_BYTES = 256 * 1024;
4255
+ function timestampDomainViolation(value) {
4256
+ if (typeof value !== "number" || !Number.isFinite(value)) {
4257
+ return `must be a timestamp (integer milliseconds), got ${describe2(value)}`;
4258
+ }
4259
+ if (!Number.isInteger(value)) {
4260
+ return `must be a timestamp in whole milliseconds, got ${String(value)}. Round it (Math.round) or use Date.getTime()`;
4261
+ }
4262
+ if (value < TIMESTAMP_MIN_MS || value > TIMESTAMP_MAX_MS) {
4263
+ return `must be a timestamp between ${TIMESTAMP_MIN_MS} and ${TIMESTAMP_MAX_MS} (the range of a JavaScript Date), got ${String(value)}`;
4264
+ }
4265
+ return null;
4266
+ }
4267
+ function valueDepthViolation(value, maxDepth = MAX_VALUE_DEPTH) {
4268
+ const walk = (current, depth, path) => {
4269
+ if (current === null || typeof current !== "object") return null;
4270
+ if (depth > maxDepth) return path;
4271
+ if (current instanceof Uint8Array || current instanceof ArrayBuffer) return null;
4272
+ const entries = Array.isArray(current) ? current.map((item, index) => [`[${index}]`, item]) : Object.entries(current).map(
4273
+ ([key, item]) => [`.${key}`, item]
4274
+ );
4275
+ for (const [suffix, item] of entries) {
4276
+ const found = walk(item, depth + 1, `${path}${suffix}`);
4277
+ if (found !== null) return found;
4278
+ }
4279
+ return null;
4280
+ };
4281
+ return walk(value, 1, "");
4282
+ }
4283
+ function protoKeyViolation(value) {
4284
+ const walk = (current, path) => {
4285
+ if (current === null || typeof current !== "object") return null;
4286
+ if (current instanceof Uint8Array || current instanceof ArrayBuffer) return null;
4287
+ if (Array.isArray(current)) {
4288
+ for (let index = 0; index < current.length; index++) {
4289
+ const found = walk(current[index], `${path}[${index}]`);
4290
+ if (found !== null) return found;
4291
+ }
4292
+ return null;
4293
+ }
4294
+ if (Object.prototype.hasOwnProperty.call(current, "__proto__")) return `${path}.__proto__`;
4295
+ for (const [key, item] of Object.entries(current)) {
4296
+ const found = walk(item, `${path}.${key}`);
4297
+ if (found !== null) return found;
4298
+ }
4299
+ return null;
4300
+ };
4301
+ return walk(value, "");
4302
+ }
4303
+ function operationValueViolation(descriptor, value) {
4304
+ if (value === null || value === void 0) return null;
4305
+ switch (descriptor.kind) {
4306
+ case "string":
4307
+ case "secret":
4308
+ return typeof value === "string" ? null : `must be a string, got ${describe2(value)}`;
4309
+ case "enum":
4310
+ if (typeof value !== "string") return `must be a string (enum), got ${describe2(value)}`;
4311
+ if (descriptor.enumValues && !descriptor.enumValues.includes(value)) {
4312
+ return `must be one of: ${descriptor.enumValues.join(", ")}. Got "${value}"`;
4313
+ }
4314
+ return null;
4315
+ case "number":
4316
+ return typeof value === "number" && Number.isFinite(value) ? null : `must be a finite number, got ${describe2(value)}`;
4317
+ case "boolean":
4318
+ return typeof value === "boolean" ? null : `must be a boolean, got ${describe2(value)}`;
4319
+ case "timestamp":
4320
+ return timestampDomainViolation(value);
4321
+ case "richtext":
4322
+ return typeof value === "string" || isKoraBytesValue(value) || value instanceof Uint8Array ? null : `must be richtext (Yjs bytes or a string), got ${describe2(value)}`;
4323
+ case "blob":
4324
+ return isBlobRef(value) ? null : `must be a BlobRef, got ${describe2(value)}`;
4325
+ case "array": {
4326
+ if (!Array.isArray(value)) return `must be an array, got ${describe2(value)}`;
4327
+ const item = descriptor.itemKind;
4328
+ for (let index = 0; index < value.length; index++) {
4329
+ const element = value[index];
4330
+ if (item === "timestamp") {
4331
+ const violation = timestampDomainViolation(element);
4332
+ if (violation) return `[${index}] ${violation}`;
4333
+ } else if (item === "number" && (typeof element !== "number" || !Number.isFinite(element))) {
4334
+ return `[${index}] must be a finite number, got ${describe2(element)}`;
4335
+ } else if ((item === "string" || item === "enum") && typeof element !== "string") {
4336
+ return `[${index}] must be a string, got ${describe2(element)}`;
4337
+ } else if (item === "boolean" && typeof element !== "boolean") {
4338
+ return `[${index}] must be a boolean, got ${describe2(element)}`;
4339
+ }
4340
+ }
4341
+ return structureViolation(value);
4342
+ }
4343
+ case "object": {
4344
+ if (typeof value !== "object" || Array.isArray(value)) {
4345
+ return `must be a plain object, got ${describe2(value)}`;
4346
+ }
4347
+ for (const [name, nested] of Object.entries(descriptor.nestedFields ?? {})) {
4348
+ const violation = operationValueViolation(nested, value[name]);
4349
+ if (violation) return `.${name} ${violation}`;
4350
+ }
4351
+ return structureViolation(value);
4352
+ }
4353
+ case "json":
4354
+ return structureViolation(value);
4355
+ }
4356
+ }
4357
+ var WIRE_BYTES_KEY = "__kora_bytes__";
4358
+ function reservedWireShapeViolation(value) {
4359
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
4360
+ const keys = Object.keys(value);
4361
+ 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;
4362
+ }
4363
+ function structureViolation(value) {
4364
+ const reserved = reservedWireShapeViolation(value);
4365
+ if (reserved !== null) return reserved;
4366
+ const deep = valueDepthViolation(value);
4367
+ if (deep !== null) return `nests deeper than ${MAX_VALUE_DEPTH} levels at "${deep}"`;
4368
+ const proto = protoKeyViolation(value);
4369
+ if (proto !== null) return `holds a "__proto__" key at "${proto}", which is not supported`;
4370
+ const walk = (current, path) => {
4371
+ if (typeof current === "number") {
4372
+ return Number.isFinite(current) ? null : `holds ${String(current)} at "${path}"`;
4373
+ }
4374
+ if (current === null || typeof current !== "object") return null;
4375
+ const entries = Array.isArray(current) ? current.map((item, index) => [`${path}[${index}]`, item]) : Object.entries(current).map(
4376
+ ([key, item]) => [`${path}.${key}`, item]
4377
+ );
4378
+ for (const [next, item] of entries) {
4379
+ const found = walk(item, next);
4380
+ if (found !== null) return found;
4381
+ }
4382
+ return null;
4383
+ };
4384
+ return walk(value, "");
4385
+ }
4386
+ function measureOperationBytes(op2) {
4387
+ return utf8Length(JSON.stringify(op2));
4388
+ }
4389
+ function utf8Length(text) {
4390
+ let bytes = 0;
4391
+ for (let i = 0; i < text.length; i++) {
4392
+ const code = text.charCodeAt(i);
4393
+ if (code < 128) bytes += 1;
4394
+ else if (code < 2048) bytes += 2;
4395
+ else if (code >= 55296 && code <= 56319 && i + 1 < text.length) {
4396
+ const next = text.charCodeAt(i + 1);
4397
+ if (next >= 56320 && next <= 57343) {
4398
+ bytes += 4;
4399
+ i++;
4400
+ } else bytes += 3;
4401
+ } else bytes += 3;
4402
+ }
4403
+ return bytes;
4404
+ }
4405
+ function describe2(value) {
4406
+ if (value === null) return "null";
4407
+ if (Array.isArray(value)) return "array";
4408
+ if (typeof value === "number") return Number.isFinite(value) ? "number" : String(value);
4409
+ if (value instanceof Date) return "Date";
4410
+ return typeof value;
4411
+ }
4412
+
2192
4413
  // src/schema/validation.ts
2193
4414
  function validateRecord(collection, collectionDef, data, operationType) {
2194
4415
  if (operationType === "delete") {
@@ -2219,7 +4440,7 @@ function validateRecord(collection, collectionDef, data, operationType) {
2219
4440
  result[fieldName] = value;
2220
4441
  } else if (value !== void 0 && value !== null) {
2221
4442
  validateFieldValue(collection, fieldName, descriptor, value);
2222
- result[fieldName] = value;
4443
+ result[fieldName] = canonicalFieldValue(collection, fieldName, descriptor, value);
2223
4444
  } else {
2224
4445
  result[fieldName] = value;
2225
4446
  }
@@ -2243,10 +4464,29 @@ function validateRecord(collection, collectionDef, data, operationType) {
2243
4464
  continue;
2244
4465
  }
2245
4466
  validateFieldValue(collection, fieldName, descriptor, value);
2246
- result[fieldName] = value;
4467
+ result[fieldName] = canonicalFieldValue(collection, fieldName, descriptor, value);
2247
4468
  }
2248
4469
  return result;
2249
4470
  }
4471
+ function canonicalFieldValue(collection, fieldName, descriptor, value) {
4472
+ if (descriptor.kind === "richtext") return value;
4473
+ try {
4474
+ return canonicalValue(value, `${collection}.${fieldName}`);
4475
+ } catch (error) {
4476
+ if (error instanceof NonCanonicalValueError) {
4477
+ throw new SchemaValidationError(
4478
+ `Field "${fieldName}" in collection "${collection}": ${error.message}`,
4479
+ {
4480
+ collection,
4481
+ field: fieldName,
4482
+ path: error.path,
4483
+ receivedType: error.receivedType
4484
+ }
4485
+ );
4486
+ }
4487
+ throw error;
4488
+ }
4489
+ }
2250
4490
  function validateFieldValue(collection, fieldName, descriptor, value) {
2251
4491
  switch (descriptor.kind) {
2252
4492
  case "string": {
@@ -2259,9 +4499,9 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
2259
4499
  break;
2260
4500
  }
2261
4501
  case "number": {
2262
- if (typeof value !== "number" || Number.isNaN(value)) {
4502
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2263
4503
  throw new SchemaValidationError(
2264
- `Field "${fieldName}" in collection "${collection}" must be a number, got ${typeof value}`,
4504
+ `Field "${fieldName}" in collection "${collection}" must be a finite number, got ${typeof value === "number" ? String(value) : typeof value}`,
2265
4505
  { collection, field: fieldName, expectedType: "number", receivedType: typeof value }
2266
4506
  );
2267
4507
  }
@@ -2277,14 +4517,16 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
2277
4517
  break;
2278
4518
  }
2279
4519
  case "timestamp": {
2280
- if (typeof value !== "number" || !Number.isFinite(value)) {
4520
+ const violation = timestampDomainViolation(value);
4521
+ if (violation !== null) {
2281
4522
  throw new SchemaValidationError(
2282
- `Field "${fieldName}" in collection "${collection}" must be a timestamp (number), got ${typeof value}`,
4523
+ `Field "${fieldName}" in collection "${collection}" ${violation}`,
2283
4524
  {
2284
4525
  collection,
2285
4526
  field: fieldName,
2286
4527
  expectedType: "timestamp",
2287
- receivedType: typeof value
4528
+ receivedType: typeof value,
4529
+ received: value
2288
4530
  }
2289
4531
  );
2290
4532
  }
@@ -2321,6 +4563,14 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
2321
4563
  const expectedType = jsTypeForKind(descriptor.itemKind);
2322
4564
  for (let i = 0; i < value.length; i++) {
2323
4565
  const item = value[i];
4566
+ if ((descriptor.itemKind === "timestamp" || descriptor.itemKind === "number") && item !== null && item !== void 0) {
4567
+ validateFieldValue(
4568
+ collection,
4569
+ `${fieldName}[${i}]`,
4570
+ { ...descriptor, kind: descriptor.itemKind, itemKind: null },
4571
+ item
4572
+ );
4573
+ }
2324
4574
  if (!matchesJsType(item, expectedType)) {
2325
4575
  throw new SchemaValidationError(
2326
4576
  `Field "${fieldName}[${i}]" in collection "${collection}" must be a ${descriptor.itemKind}, got ${typeof item}`,
@@ -2334,6 +4584,7 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
2334
4584
  }
2335
4585
  }
2336
4586
  }
4587
+ assertStructureInDomain(collection, fieldName, value);
2337
4588
  break;
2338
4589
  }
2339
4590
  case "richtext": {
@@ -2351,7 +4602,7 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
2351
4602
  break;
2352
4603
  }
2353
4604
  case "object": {
2354
- if (!isPlainObject(value)) {
4605
+ if (!isPlainObject3(value)) {
2355
4606
  throw new SchemaValidationError(
2356
4607
  `Field "${fieldName}" in collection "${collection}" must be a plain object, got ${describeType(value)}`,
2357
4608
  {
@@ -2375,6 +4626,7 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
2375
4626
  }
2376
4627
  }
2377
4628
  }
4629
+ assertStructureInDomain(collection, fieldName, value);
2378
4630
  break;
2379
4631
  }
2380
4632
  case "json": {
@@ -2391,6 +4643,7 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
2391
4643
  }
2392
4644
  );
2393
4645
  }
4646
+ assertStructureInDomain(collection, fieldName, value);
2394
4647
  break;
2395
4648
  }
2396
4649
  case "blob": {
@@ -2418,17 +4671,47 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
2418
4671
  }
2419
4672
  }
2420
4673
  }
2421
- function isPlainObject(value) {
4674
+ function assertStructureInDomain(collection, fieldName, value) {
4675
+ const reserved = reservedWireShapeViolation(value);
4676
+ if (reserved !== null) {
4677
+ throw new SchemaValidationError(
4678
+ `Field "${fieldName}" in collection "${collection}" ${reserved}.`,
4679
+ { collection, field: fieldName }
4680
+ );
4681
+ }
4682
+ const deep = valueDepthViolation(value);
4683
+ if (deep !== null) {
4684
+ throw new SchemaValidationError(
4685
+ `Field "${fieldName}" in collection "${collection}" nests deeper than ${MAX_VALUE_DEPTH} levels (at "${fieldName}${deep}"). Flatten the value.`,
4686
+ { collection, field: fieldName, path: `${fieldName}${deep}`, maxDepth: MAX_VALUE_DEPTH }
4687
+ );
4688
+ }
4689
+ const proto = protoKeyViolation(value);
4690
+ if (proto !== null) {
4691
+ throw new SchemaValidationError(
4692
+ `Field "${fieldName}" in collection "${collection}" holds a "__proto__" key (at "${fieldName}${proto}"), which cannot be stored and synced unchanged. Rename the key.`,
4693
+ { collection, field: fieldName, path: `${fieldName}${proto}` }
4694
+ );
4695
+ }
4696
+ }
4697
+ function assertResolvedFieldValue(collection, fieldName, descriptor, value) {
4698
+ if (value === null || value === void 0 || descriptor.kind === "richtext") return;
4699
+ validateFieldValue(collection, fieldName, descriptor, value);
4700
+ }
4701
+ function isPlainObject3(value) {
2422
4702
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
2423
4703
  return false;
2424
4704
  }
2425
4705
  const proto = Object.getPrototypeOf(value);
2426
4706
  return proto === null || proto === Object.prototype;
2427
4707
  }
2428
- function findNonJsonSerializable(value, path, seen = /* @__PURE__ */ new WeakSet()) {
4708
+ function findNonJsonSerializable(value, path, seen = /* @__PURE__ */ new WeakSet(), nested = false) {
2429
4709
  if (value === null) {
2430
4710
  return null;
2431
4711
  }
4712
+ if (value === void 0 && nested) {
4713
+ return null;
4714
+ }
2432
4715
  const type = typeof value;
2433
4716
  if (type === "string" || type === "number" || type === "boolean") {
2434
4717
  if (type === "number" && !Number.isFinite(value)) {
@@ -2441,7 +4724,7 @@ function findNonJsonSerializable(value, path, seen = /* @__PURE__ */ new WeakSet
2441
4724
  }
2442
4725
  if (Array.isArray(value)) {
2443
4726
  for (let index = 0; index < value.length; index++) {
2444
- const invalid = findNonJsonSerializable(value[index], `${path}[${index}]`, seen);
4727
+ const invalid = findNonJsonSerializable(value[index], `${path}[${index}]`, seen, true);
2445
4728
  if (invalid) return invalid;
2446
4729
  }
2447
4730
  return null;
@@ -2453,7 +4736,7 @@ function findNonJsonSerializable(value, path, seen = /* @__PURE__ */ new WeakSet
2453
4736
  }
2454
4737
  seen.add(objectValue);
2455
4738
  for (const [key, nestedValue] of Object.entries(objectValue)) {
2456
- const invalid = findNonJsonSerializable(nestedValue, `${path}.${key}`, seen);
4739
+ const invalid = findNonJsonSerializable(nestedValue, `${path}.${key}`, seen, true);
2457
4740
  if (invalid) return invalid;
2458
4741
  }
2459
4742
  seen.delete(objectValue);
@@ -2690,6 +4973,17 @@ function buildScopeMap(schema, scopeValues) {
2690
4973
  return result;
2691
4974
  }
2692
4975
 
4976
+ // src/scopes/scope-grant.ts
4977
+ var SCOPE_CLAIMS_KEY = "$claims";
4978
+ function claimScopes(claims, explicit) {
4979
+ return { ...explicit ?? {}, [SCOPE_CLAIMS_KEY]: { ...claims } };
4980
+ }
4981
+ function getScopeClaims(grant) {
4982
+ if (!grant) return null;
4983
+ const claims = grant[SCOPE_CLAIMS_KEY];
4984
+ return claims && typeof claims === "object" ? claims : null;
4985
+ }
4986
+
2693
4987
  // src/scopes/extract-scope-from-claims.ts
2694
4988
  function collectSchemaScopeFields(schema) {
2695
4989
  return collectSchemaScopeValueKeys(schema);
@@ -2744,29 +5038,6 @@ function defaultSequenceFormat(name) {
2744
5038
  return `${name}-{seq:4}`;
2745
5039
  }
2746
5040
 
2747
- // src/migration/apply-operation-transforms.ts
2748
- var MAX_TRANSFORM_STEPS = 32;
2749
- function applyOperationTransforms(operation, targetSchemaVersion, transforms) {
2750
- let current = operation;
2751
- for (let step = 0; step < MAX_TRANSFORM_STEPS; step++) {
2752
- if (current.schemaVersion === targetSchemaVersion) {
2753
- return current;
2754
- }
2755
- const transform = transforms.find(
2756
- (candidate) => candidate.fromVersion === current.schemaVersion
2757
- );
2758
- if (!transform) {
2759
- return null;
2760
- }
2761
- const next = transform.transform(current);
2762
- if (next === null) {
2763
- return null;
2764
- }
2765
- current = next;
2766
- }
2767
- return null;
2768
- }
2769
-
2770
5041
  // src/migrations/migration-builder.ts
2771
5042
  var RollbackBuilder = class {
2772
5043
  _steps = [];
@@ -2872,17 +5143,29 @@ var MigrationBuilder = class _MigrationBuilder {
2872
5143
  }
2873
5144
  /**
2874
5145
  * Backfill records in a collection using a transform function.
2875
- * The transform receives each record and returns the fields to update.
2876
- * Runs after structural changes (addField, renameField, etc.).
5146
+ * The transform receives each live record (typed values, as the app reads them) and
5147
+ * returns the fields to change. Runs after structural changes (addField, renameField,
5148
+ * etc.), in the same transaction as them and the schema version write.
5149
+ *
5150
+ * Changed records are written as update operations (mutation name `migration:v<N>`)
5151
+ * through the normal local write path, so backfilled values sync. Pass
5152
+ * `{ localOnly: true }` for device-local data that must not sync.
2877
5153
  *
2878
5154
  * @param collection - The collection name
2879
5155
  * @param transform - Forward transform function
2880
- * @param reverseTransform - Optional reverse transform for rollback support
5156
+ * @param reverseOrOptions - Reverse transform for rollback support, or options
2881
5157
  */
2882
- backfill(collection, transform, reverseTransform) {
5158
+ backfill(collection, transform, reverseOrOptions) {
5159
+ const options = typeof reverseOrOptions === "function" ? { reverseTransform: reverseOrOptions } : reverseOrOptions ?? {};
2883
5160
  return new _MigrationBuilder([
2884
5161
  ...this.steps,
2885
- { type: "backfill", collection, transform, reverseTransform }
5162
+ {
5163
+ type: "backfill",
5164
+ collection,
5165
+ transform,
5166
+ ...options.reverseTransform ? { reverseTransform: options.reverseTransform } : {},
5167
+ ...options.localOnly ? { localOnly: true } : {}
5168
+ }
2886
5169
  ]);
2887
5170
  }
2888
5171
  /**
@@ -3029,11 +5312,13 @@ function migrationStepsToSQL(steps) {
3029
5312
  break;
3030
5313
  case "addIndex":
3031
5314
  statements.push(
3032
- `CREATE INDEX IF NOT EXISTS idx_${step.collection}_${step.field} ON ${quoteIdent(step.collection)} (${quoteIdent(step.field)})`
5315
+ `CREATE INDEX IF NOT EXISTS ${quoteIdent(collectionIndexName(step.collection, step.field))} ON ${quoteIdent(step.collection)} (${quoteIdent(step.field)})`
3033
5316
  );
3034
5317
  break;
3035
5318
  case "removeIndex":
3036
- statements.push(`DROP INDEX IF EXISTS idx_${step.collection}_${step.field}`);
5319
+ statements.push(
5320
+ `DROP INDEX IF EXISTS ${quoteIdent(collectionIndexName(step.collection, step.field))}`
5321
+ );
3037
5322
  break;
3038
5323
  case "backfill":
3039
5324
  break;
@@ -3049,11 +5334,7 @@ function addFieldSQL(collection, field, descriptor) {
3049
5334
  const sqlType = mapFieldType2(descriptor);
3050
5335
  const parts = [`ALTER TABLE ${quoteIdent(collection)} ADD COLUMN ${quoteIdent(field)}`, sqlType];
3051
5336
  if (descriptor.defaultValue !== void 0) {
3052
- parts.push(`DEFAULT ${sqlDefault2(descriptor.defaultValue)}`);
3053
- }
3054
- if (descriptor.kind === "enum" && descriptor.enumValues) {
3055
- const values = descriptor.enumValues.map((v) => `'${v}'`).join(", ");
3056
- parts.push(`CHECK (${quoteIdent(field)} IN (${values}))`);
5337
+ parts.push(`DEFAULT ${sqlDefaultLiteral(descriptor.defaultValue)}`);
3057
5338
  }
3058
5339
  return parts.join(" ");
3059
5340
  }
@@ -3083,13 +5364,6 @@ function mapFieldType2(descriptor) {
3083
5364
  return "BLOB";
3084
5365
  }
3085
5366
  }
3086
- function sqlDefault2(value) {
3087
- if (value === null) return "NULL";
3088
- if (typeof value === "string") return `'${value}'`;
3089
- if (typeof value === "number") return String(value);
3090
- if (typeof value === "boolean") return value ? "1" : "0";
3091
- return `'${JSON.stringify(value)}'`;
3092
- }
3093
5367
 
3094
5368
  // src/codegen/proto-generator.ts
3095
5369
  var SCALAR_TYPE_MAP = {
@@ -3347,50 +5621,91 @@ function generateProtoDefinitions(schema) {
3347
5621
  CONNECTION_QUALITIES,
3348
5622
  CausalTracker,
3349
5623
  ClockDriftError,
5624
+ DEFAULT_MAX_OPERATION_BYTES,
5625
+ DEFAULT_OPERATION_HASH_VERSION,
3350
5626
  EnumFieldBuilder,
5627
+ FOLD_RECORD_TRACE_FIELD,
5628
+ FOLD_STATE_VERSION,
3351
5629
  FieldBuilder,
5630
+ FoldConfigurationError,
5631
+ FoldStateError,
3352
5632
  HybridLogicalClock,
3353
5633
  InvalidTimestampError,
5634
+ JsonFieldBuilder,
3354
5635
  KORA_ERROR_FIX_SUGGESTIONS,
3355
5636
  KoraError,
3356
5637
  MAX_LOGICAL,
5638
+ MAX_VALUE_DEPTH,
3357
5639
  MERGE_STRATEGIES,
3358
5640
  MergeConflictError,
3359
5641
  MigrationBuilder,
3360
5642
  MigrationRollbackError,
3361
5643
  MissingSecretKeyError,
5644
+ NonCanonicalValueError,
5645
+ ObjectFieldBuilder,
3362
5646
  OperationError,
5647
+ OperationTooLargeError,
5648
+ OperationTransformCoverageError,
5649
+ OperationTransformError,
5650
+ RESERVED_NODE_ID_PREFIX,
3363
5651
  RemoteClockDriftError,
3364
5652
  RollbackBuilder,
5653
+ SCOPE_CLAIMS_KEY,
5654
+ SERVER_NODE_ID_PREFIX,
5655
+ STORED_TEXT_ESCAPE,
3365
5656
  SchemaValidationError,
5657
+ SecretFieldBuilder,
3366
5658
  StorageError,
3367
5659
  SyncError,
5660
+ TIMESTAMP_MAX_MS,
5661
+ TIMESTAMP_MIN_MS,
5662
+ adaptFoldState,
3368
5663
  advanceVector,
3369
5664
  applyAtomicOp,
3370
5665
  applyOperationTransforms,
5666
+ assertOperationTransformCoverage,
5667
+ assertResolvedFieldValue,
3371
5668
  base64ToBytes,
3372
5669
  buildScopeMap,
3373
5670
  buildStateMachineConstraints,
3374
5671
  bytesToBase64,
3375
5672
  canAutoRollback,
5673
+ canonicalValue,
5674
+ canonicalizeLegacyOperation,
5675
+ canonicalizeOperationBody,
5676
+ canonicalizeProvenLegacyClear,
5677
+ claimScopes,
3376
5678
  collectSchemaScopeFields,
3377
5679
  collectSchemaScopeValueKeys,
3378
5680
  computeDelta,
5681
+ computeOperationId,
3379
5682
  createBlobRef,
5683
+ createFoldState,
3380
5684
  createOperation,
3381
5685
  createReversibleMigration,
5686
+ createSnapshotState,
3382
5687
  createVersionVector,
3383
5688
  decodeBytesFromOpData,
5689
+ decodeStoredJsonValue,
5690
+ decodeStoredText,
3384
5691
  decryptSecret,
3385
5692
  defaultApplyFailureReason,
3386
5693
  defaultSequenceFormat,
3387
5694
  defineSchema,
5695
+ deriveSideEffectOpId,
5696
+ deserializeFoldState,
3388
5697
  deserializeVector,
3389
5698
  dominates,
3390
5699
  encodeBytesForOpData,
5700
+ encodeStoredJsonValue,
5701
+ encodeStoredText,
3391
5702
  encryptSecret,
5703
+ expandFieldVersionedOperations,
3392
5704
  extractScopeValuesFromClaims,
3393
5705
  extractTimestamp,
5706
+ foldPlanFingerprint,
5707
+ foldPlanFingerprints,
5708
+ foldRecord,
3394
5709
  formatSequenceValue,
3395
5710
  generateFullDDL,
3396
5711
  generateProtoDefinitions,
@@ -3398,35 +5713,61 @@ function generateProtoDefinitions(schema) {
3398
5713
  generateSQL,
3399
5714
  generateUUIDv7,
3400
5715
  getCollectionScopeBindings,
5716
+ getFoldFieldVersionStrings,
5717
+ getFoldFieldVersions,
3401
5718
  getKoraErrorFix,
5719
+ getScopeClaims,
3402
5720
  getTransitionMap,
3403
5721
  hasSchemaSyncRules,
3404
5722
  hashBlob,
3405
5723
  hashSecret,
3406
5724
  isApplyFailure,
3407
5725
  isAtomicOp,
5726
+ isAuthoritativeNodeId,
3408
5727
  isBlobRef,
3409
5728
  isCollectionSyncScoped,
5729
+ isFoldStateLive,
3410
5730
  isKoraBytesValue,
3411
5731
  isLegacyNumericByteObject,
5732
+ isReservedNodeId,
5733
+ isServerNodeId,
3412
5734
  isValidOperation,
3413
5735
  isValidUUIDv7,
5736
+ joinStates,
5737
+ materialize,
5738
+ measureOperationBytes,
5739
+ mergeArraySet,
5740
+ mergeOp,
3414
5741
  mergeVectors,
3415
5742
  migrate,
3416
5743
  migrationStepsToSQL,
5744
+ mismatchedFoldFields,
5745
+ missingTransformPaths,
5746
+ needsStoredTextEncoding,
3417
5747
  op,
5748
+ operationSchemaView,
5749
+ operationTransformsFingerprint,
5750
+ operationValueViolation,
5751
+ protoKeyViolation,
3418
5752
  quoteIdent,
5753
+ replayFieldVersionsForRecord,
3419
5754
  replayOperationsForRecord,
5755
+ reservedWireShapeViolation,
3420
5756
  resolveAtomicOp,
3421
5757
  revealSecret,
3422
5758
  rollbackStepsToSQL,
5759
+ serializeFoldState,
3423
5760
  serializeVector,
3424
5761
  t,
5762
+ timestampDomainViolation,
3425
5763
  toAtomicOp,
5764
+ toMergeTrace,
3426
5765
  transformSecretFieldsForWrite,
3427
5766
  validateRecord,
3428
5767
  validateTransition,
5768
+ valueDepthViolation,
3429
5769
  vectorsEqual,
5770
+ verifyOperationId,
3430
5771
  verifyOperationIntegrity,
3431
5772
  verifySecret,
3432
5773
  verifySecretValue