@korajs/core 0.6.0 → 1.0.0-beta.1

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
@@ -30,33 +30,44 @@ __export(index_exports, {
30
30
  EnumFieldBuilder: () => EnumFieldBuilder,
31
31
  FieldBuilder: () => FieldBuilder,
32
32
  HybridLogicalClock: () => HybridLogicalClock,
33
+ InvalidTimestampError: () => InvalidTimestampError,
33
34
  KORA_ERROR_FIX_SUGGESTIONS: () => KORA_ERROR_FIX_SUGGESTIONS,
34
35
  KoraError: () => KoraError,
36
+ MAX_LOGICAL: () => MAX_LOGICAL,
35
37
  MERGE_STRATEGIES: () => MERGE_STRATEGIES,
36
38
  MergeConflictError: () => MergeConflictError,
37
39
  MigrationBuilder: () => MigrationBuilder,
38
40
  MigrationRollbackError: () => MigrationRollbackError,
41
+ MissingSecretKeyError: () => MissingSecretKeyError,
39
42
  OperationError: () => OperationError,
43
+ RemoteClockDriftError: () => RemoteClockDriftError,
40
44
  RollbackBuilder: () => RollbackBuilder,
41
45
  SchemaValidationError: () => SchemaValidationError,
42
46
  StorageError: () => StorageError,
43
47
  SyncError: () => SyncError,
44
48
  advanceVector: () => advanceVector,
45
49
  applyOperationTransforms: () => applyOperationTransforms,
50
+ base64ToBytes: () => base64ToBytes,
46
51
  buildScopeMap: () => buildScopeMap,
47
52
  buildStateMachineConstraints: () => buildStateMachineConstraints,
53
+ bytesToBase64: () => bytesToBase64,
48
54
  canAutoRollback: () => canAutoRollback,
49
55
  collectSchemaScopeFields: () => collectSchemaScopeFields,
50
56
  collectSchemaScopeValueKeys: () => collectSchemaScopeValueKeys,
51
57
  computeDelta: () => computeDelta,
58
+ createBlobRef: () => createBlobRef,
52
59
  createOperation: () => createOperation,
53
60
  createReversibleMigration: () => createReversibleMigration,
54
61
  createVersionVector: () => createVersionVector,
62
+ decodeBytesFromOpData: () => decodeBytesFromOpData,
63
+ decryptSecret: () => decryptSecret,
55
64
  defaultApplyFailureReason: () => defaultApplyFailureReason,
56
65
  defaultSequenceFormat: () => defaultSequenceFormat,
57
66
  defineSchema: () => defineSchema,
58
67
  deserializeVector: () => deserializeVector,
59
68
  dominates: () => dominates,
69
+ encodeBytesForOpData: () => encodeBytesForOpData,
70
+ encryptSecret: () => encryptSecret,
60
71
  extractScopeValuesFromClaims: () => extractScopeValuesFromClaims,
61
72
  extractTimestamp: () => extractTimestamp,
62
73
  formatSequenceValue: () => formatSequenceValue,
@@ -69,9 +80,14 @@ __export(index_exports, {
69
80
  getKoraErrorFix: () => getKoraErrorFix,
70
81
  getTransitionMap: () => getTransitionMap,
71
82
  hasSchemaSyncRules: () => hasSchemaSyncRules,
83
+ hashBlob: () => hashBlob,
84
+ hashSecret: () => hashSecret,
72
85
  isApplyFailure: () => isApplyFailure,
73
86
  isAtomicOp: () => isAtomicOp,
87
+ isBlobRef: () => isBlobRef,
74
88
  isCollectionSyncScoped: () => isCollectionSyncScoped,
89
+ isKoraBytesValue: () => isKoraBytesValue,
90
+ isLegacyNumericByteObject: () => isLegacyNumericByteObject,
75
91
  isValidOperation: () => isValidOperation,
76
92
  isValidUUIDv7: () => isValidUUIDv7,
77
93
  mergeVectors: () => mergeVectors,
@@ -79,14 +95,18 @@ __export(index_exports, {
79
95
  migrationStepsToSQL: () => migrationStepsToSQL,
80
96
  op: () => op,
81
97
  resolveAtomicOp: () => resolveAtomicOp,
98
+ revealSecret: () => revealSecret,
82
99
  rollbackStepsToSQL: () => rollbackStepsToSQL,
83
100
  serializeVector: () => serializeVector,
84
101
  t: () => t,
85
102
  toAtomicOp: () => toAtomicOp,
103
+ transformSecretFieldsForWrite: () => transformSecretFieldsForWrite,
86
104
  validateRecord: () => validateRecord,
87
105
  validateTransition: () => validateTransition,
88
106
  vectorsEqual: () => vectorsEqual,
89
- verifyOperationIntegrity: () => verifyOperationIntegrity
107
+ verifyOperationIntegrity: () => verifyOperationIntegrity,
108
+ verifySecret: () => verifySecret,
109
+ verifySecretValue: () => verifySecretValue
90
110
  });
91
111
  module.exports = __toCommonJS(index_exports);
92
112
 
@@ -108,6 +128,7 @@ var KORA_ERROR_FIX_SUGGESTIONS = {
108
128
  SYNC_ERROR: "Verify the sync server URL, auth token, and that the server accepts your schema version.",
109
129
  STORAGE_ERROR: "Confirm the storage adapter is supported in this environment and the database path is writable.",
110
130
  CLOCK_DRIFT: "Sync device wall-clock time (NTP). Kora refuses new timestamps when drift exceeds five minutes.",
131
+ INVALID_TIMESTAMP_FIELDS: "HLC timestamps need non-negative integer wallTime (< 10^15) and logical (<= 99999). Generate timestamps through HybridLogicalClock instead of building them by hand.",
111
132
  PERSISTENCE_ERROR: "IndexedDB persistence failed; check browser storage settings and available disk quota.",
112
133
  MISSING_WORKER_URL: "Pass store.workerUrl pointing to sqlite-wasm-worker.js (see create-kora-app templates).",
113
134
  INVALID_SYNC_URL: "Use ws:// or wss:// for WebSocket transport, or http(s):// for HTTP long-polling.",
@@ -186,6 +207,31 @@ var AppNotReadyError = class extends KoraError {
186
207
  this.name = "AppNotReadyError";
187
208
  }
188
209
  };
210
+ var RemoteClockDriftError = class extends KoraError {
211
+ constructor(remoteWallTime, localReferenceTime) {
212
+ const aheadSeconds = Math.round((remoteWallTime - localReferenceTime) / 1e3);
213
+ super(
214
+ `Rejected remote timestamp ${aheadSeconds}s ahead of local reference time. The sending device's clock is set too far in the future.`,
215
+ "REMOTE_CLOCK_DRIFT",
216
+ { remoteWallTime, localReferenceTime, aheadSeconds }
217
+ );
218
+ this.remoteWallTime = remoteWallTime;
219
+ this.localReferenceTime = localReferenceTime;
220
+ this.name = "RemoteClockDriftError";
221
+ }
222
+ remoteWallTime;
223
+ localReferenceTime;
224
+ };
225
+ var InvalidTimestampError = class extends KoraError {
226
+ constructor(message, wallTime, logical) {
227
+ super(message, "INVALID_TIMESTAMP_FIELDS", { wallTime, logical });
228
+ this.wallTime = wallTime;
229
+ this.logical = logical;
230
+ this.name = "InvalidTimestampError";
231
+ }
232
+ wallTime;
233
+ logical;
234
+ };
189
235
  var ClockDriftError = class extends KoraError {
190
236
  constructor(currentHlcTime, physicalTime) {
191
237
  const driftSeconds = Math.round((currentHlcTime - physicalTime) / 1e3);
@@ -204,24 +250,51 @@ var ClockDriftError = class extends KoraError {
204
250
 
205
251
  // src/clock/hlc.ts
206
252
  var systemTimeSource = { now: () => Date.now() };
253
+ var MAX_LOGICAL = 99999;
254
+ var LOGICAL_MODULUS = MAX_LOGICAL + 1;
255
+ var WALL_TIME_LIMIT = 10 ** 15;
207
256
  var DRIFT_WARN_MS = 6e4;
208
257
  var DRIFT_ERROR_MS = 5 * 6e4;
209
- var HybridLogicalClock = class {
210
- constructor(nodeId, timeSource = systemTimeSource, onDriftWarning) {
258
+ var MAX_REMOTE_FUTURE_MS = 5 * 6e4;
259
+ var HybridLogicalClock = class _HybridLogicalClock {
260
+ constructor(nodeId, timeSource = systemTimeSource, onDriftWarning, onDriftError) {
211
261
  this.nodeId = nodeId;
212
262
  this.timeSource = timeSource;
213
263
  this.onDriftWarning = onDriftWarning;
264
+ this.onDriftError = onDriftError;
214
265
  }
215
266
  nodeId;
216
267
  timeSource;
217
268
  onDriftWarning;
269
+ onDriftError;
218
270
  wallTime = 0;
219
271
  logical = 0;
272
+ referenceOffsetMs = null;
273
+ /**
274
+ * Records the known offset between an external time reference (usually the
275
+ * sync server, learned at handshake) and this device's physical clock:
276
+ * `referenceTime - localPhysicalTime`. Once set, drift evaluation and remote
277
+ * timestamp validation are performed against reference-corrected time, so a
278
+ * device with a wrong local clock still validates remote timestamps correctly.
279
+ */
280
+ setReferenceOffset(offsetMs) {
281
+ this.referenceOffsetMs = offsetMs;
282
+ }
283
+ getReferenceOffset() {
284
+ return this.referenceOffsetMs;
285
+ }
286
+ /** Physical time corrected by the known reference offset, when available. */
287
+ effectiveTime(physicalTime) {
288
+ return physicalTime + (this.referenceOffsetMs ?? 0);
289
+ }
220
290
  /**
221
291
  * Generate a new timestamp for a local event.
222
292
  * Guarantees monotonicity: each call returns a timestamp strictly greater than the previous.
223
293
  *
224
- * @throws {ClockDriftError} If physical time is more than 5 minutes behind the HLC wallTime
294
+ * Never throws and never blocks a local write: if the physical clock has
295
+ * fallen behind the HLC (e.g. the user corrected a fast clock), the HLC
296
+ * freezes wallTime and advances the logical counter, and drift is reported
297
+ * through the onDriftWarning / onDriftError callbacks instead.
225
298
  */
226
299
  now() {
227
300
  const physicalTime = this.timeSource.now();
@@ -232,17 +305,40 @@ var HybridLogicalClock = class {
232
305
  } else {
233
306
  this.logical++;
234
307
  }
308
+ this.carryLogicalOverflow();
235
309
  return { wallTime: this.wallTime, logical: this.logical, nodeId: this.nodeId };
236
310
  }
237
311
  /**
238
312
  * Update clock on receiving a remote timestamp.
239
313
  * Merges the remote clock state with the local state to maintain causal ordering.
240
314
  *
241
- * @throws {ClockDriftError} If physical time is more than 5 minutes behind the resulting wallTime
315
+ * @throws {InvalidTimestampError} If the remote timestamp has non-integer or
316
+ * negative wallTime/logical, or logical beyond {@link MAX_LOGICAL}. Checked
317
+ * BEFORE any state change, so malformed input cannot corrupt this clock.
318
+ * @throws {RemoteClockDriftError} If the remote timestamp is more than 5 minutes
319
+ * ahead of reference-corrected local time. Validation happens BEFORE any state
320
+ * is adopted, so a rejected timestamp cannot poison this clock. When no
321
+ * reference offset is known and this clock is uninitialized (cold start with
322
+ * a possibly-wrong local clock), validation is skipped, matching the previous
323
+ * cold-start behavior but now without a corrupting failure mode afterward.
242
324
  */
243
325
  receive(remote) {
244
326
  const physicalTime = this.timeSource.now();
245
327
  const wasColdStart = this.wallTime === 0;
328
+ if (!Number.isInteger(remote.wallTime) || remote.wallTime < 0 || !Number.isInteger(remote.logical) || remote.logical < 0 || remote.logical > MAX_LOGICAL) {
329
+ throw new InvalidTimestampError(
330
+ `Rejected remote HLC timestamp with invalid fields (wallTime=${remote.wallTime}, logical=${remote.logical}). wallTime and logical must be non-negative integers and logical must not exceed ${MAX_LOGICAL}.`,
331
+ remote.wallTime,
332
+ remote.logical
333
+ );
334
+ }
335
+ const canValidate = this.referenceOffsetMs !== null || !wasColdStart;
336
+ if (canValidate) {
337
+ const reference = this.effectiveTime(physicalTime);
338
+ if (remote.wallTime > reference + MAX_REMOTE_FUTURE_MS) {
339
+ throw new RemoteClockDriftError(remote.wallTime, reference);
340
+ }
341
+ }
246
342
  if (physicalTime > this.wallTime && physicalTime > remote.wallTime) {
247
343
  this.wallTime = physicalTime;
248
344
  this.logical = 0;
@@ -254,11 +350,54 @@ var HybridLogicalClock = class {
254
350
  } else {
255
351
  this.logical++;
256
352
  }
353
+ this.carryLogicalOverflow();
257
354
  if (!wasColdStart) {
258
355
  this.checkDrift(physicalTime);
259
356
  }
260
357
  return { wallTime: this.wallTime, logical: this.logical, nodeId: this.nodeId };
261
358
  }
359
+ /**
360
+ * Advance this clock to at least the given timestamp.
361
+ *
362
+ * Used after a timestamp rebase rewrites unsynced operations: future `now()`
363
+ * timestamps must sort after every rebased operation, otherwise a write issued
364
+ * immediately after the rebase could interleave with (or precede) rebased ops
365
+ * and break the log's total order. Never moves the clock backward — a
366
+ * timestamp at or before the current state is a no-op, preserving the
367
+ * monotonicity guarantee of `now()`.
368
+ *
369
+ * Inputs with logical > MAX_LOGICAL are normalized deterministically by
370
+ * carrying the excess into wallTime, so the adopted state always leaves room
371
+ * for the next increment inside the serializable range.
372
+ */
373
+ advanceTo(ts) {
374
+ const normalized = {
375
+ wallTime: ts.wallTime + Math.floor(ts.logical / LOGICAL_MODULUS),
376
+ logical: ts.logical % LOGICAL_MODULUS,
377
+ nodeId: ts.nodeId
378
+ };
379
+ const current = {
380
+ wallTime: this.wallTime,
381
+ logical: this.logical,
382
+ nodeId: this.nodeId
383
+ };
384
+ if (_HybridLogicalClock.compare(normalized, current) > 0) {
385
+ this.wallTime = normalized.wallTime;
386
+ this.logical = normalized.logical;
387
+ }
388
+ }
389
+ /**
390
+ * Carry the logical counter into wallTime when an increment pushed it past
391
+ * MAX_LOGICAL. Bumping wallTime by 1ms keeps the timestamp strictly greater
392
+ * than everything issued before (monotonicity) while keeping the logical
393
+ * counter inside the 5-digit slot the serialized form depends on.
394
+ */
395
+ carryLogicalOverflow() {
396
+ if (this.logical > MAX_LOGICAL) {
397
+ this.wallTime += 1;
398
+ this.logical = 0;
399
+ }
400
+ }
262
401
  /**
263
402
  * Compare two timestamps. Returns negative if a < b, positive if a > b, zero if equal.
264
403
  * Total order: wallTime first, then logical, then nodeId (lexicographic).
@@ -273,8 +412,23 @@ var HybridLogicalClock = class {
273
412
  /**
274
413
  * Serialize an HLC timestamp to a string that sorts lexicographically.
275
414
  * Format: zero-padded wallTime:logical:nodeId
415
+ *
416
+ * @throws {InvalidTimestampError} If wallTime does not fit 15 digits or
417
+ * logical does not fit 5 digits (or either is negative/non-integer). Such a
418
+ * value would overflow its zero-padded slot and the serialized string would
419
+ * no longer sort in the same order as {@link HybridLogicalClock.compare} —
420
+ * silently corrupting every LWW comparison on stored `_version` columns.
421
+ * Internal clocks can no longer produce such values (the logical counter
422
+ * carries into wallTime); this guards against hand-built timestamps.
276
423
  */
277
424
  static serialize(ts) {
425
+ if (!Number.isInteger(ts.wallTime) || ts.wallTime < 0 || ts.wallTime >= WALL_TIME_LIMIT || !Number.isInteger(ts.logical) || ts.logical < 0 || ts.logical > MAX_LOGICAL) {
426
+ throw new InvalidTimestampError(
427
+ `Cannot serialize HLC timestamp (wallTime=${ts.wallTime}, logical=${ts.logical}): wallTime must be an integer in [0, 10^15) and logical an integer in [0, ${MAX_LOGICAL}]. Values outside these ranges overflow the zero-padded serialized form, which must sort lexicographically identically to HybridLogicalClock.compare.`,
428
+ ts.wallTime,
429
+ ts.logical
430
+ );
431
+ }
278
432
  const wall = ts.wallTime.toString().padStart(15, "0");
279
433
  const log = ts.logical.toString().padStart(5, "0");
280
434
  return `${wall}:${log}:${ts.nodeId}`;
@@ -294,10 +448,16 @@ var HybridLogicalClock = class {
294
448
  nodeId: parts.slice(2).join(":")
295
449
  };
296
450
  }
451
+ /**
452
+ * Reports drift between the HLC and (reference-corrected) physical time.
453
+ * Reporting only: local timestamp generation is never blocked, because the
454
+ * user's data always outranks the quality of its timestamps.
455
+ */
297
456
  checkDrift(physicalTime) {
298
- const drift = this.wallTime - physicalTime;
457
+ const drift = this.wallTime - this.effectiveTime(physicalTime);
299
458
  if (drift > DRIFT_ERROR_MS) {
300
- throw new ClockDriftError(this.wallTime, physicalTime);
459
+ this.onDriftError?.(drift);
460
+ return;
301
461
  }
302
462
  if (drift > DRIFT_WARN_MS) {
303
463
  this.onDriftWarning?.(drift);
@@ -385,6 +545,226 @@ function formatUUID(bytes) {
385
545
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
386
546
  }
387
547
 
548
+ // src/blob/blob-ref.ts
549
+ async function hashBlob(bytes) {
550
+ const view = new Uint8Array(bytes.byteLength);
551
+ view.set(bytes);
552
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", view);
553
+ const out = new Uint8Array(digest);
554
+ return Array.from(out, (b) => b.toString(16).padStart(2, "0")).join("");
555
+ }
556
+ async function createBlobRef(bytes, metadata = {}) {
557
+ const hash = await hashBlob(bytes);
558
+ const ref = { hash, size: bytes.byteLength };
559
+ if (metadata.mimeType !== void 0) {
560
+ ref.mimeType = metadata.mimeType;
561
+ }
562
+ if (metadata.filename !== void 0) {
563
+ ref.filename = metadata.filename;
564
+ }
565
+ return ref;
566
+ }
567
+ function isBlobRef(value) {
568
+ if (typeof value !== "object" || value === null) {
569
+ return false;
570
+ }
571
+ const ref = value;
572
+ if (typeof ref.hash !== "string" || !/^[0-9a-f]{64}$/.test(ref.hash)) {
573
+ return false;
574
+ }
575
+ if (typeof ref.size !== "number" || !Number.isInteger(ref.size) || ref.size < 0) {
576
+ return false;
577
+ }
578
+ if (ref.mimeType !== void 0 && typeof ref.mimeType !== "string") {
579
+ return false;
580
+ }
581
+ if (ref.filename !== void 0 && typeof ref.filename !== "string") {
582
+ return false;
583
+ }
584
+ if (ref.manifestHash !== void 0 && (typeof ref.manifestHash !== "string" || !/^[0-9a-f]{64}$/.test(ref.manifestHash))) {
585
+ return false;
586
+ }
587
+ return true;
588
+ }
589
+
590
+ // src/secret/secret-crypto.ts
591
+ var PBKDF2_ITERATIONS = 21e4;
592
+ var SALT_BYTES = 16;
593
+ var IV_BYTES = 12;
594
+ var HASH_BYTES = 32;
595
+ function toHex(bytes) {
596
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
597
+ }
598
+ function fromHex(hex) {
599
+ if (hex.length % 2 !== 0 || /[^0-9a-f]/i.test(hex)) {
600
+ throw new Error("Invalid hex string");
601
+ }
602
+ const out = new Uint8Array(hex.length / 2);
603
+ for (let i = 0; i < out.length; i++) {
604
+ out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
605
+ }
606
+ return out;
607
+ }
608
+ function randomBytes(n) {
609
+ const out = new Uint8Array(n);
610
+ globalThis.crypto.getRandomValues(out);
611
+ return out;
612
+ }
613
+ function timingSafeEqual(a, b) {
614
+ if (a.length !== b.length) {
615
+ return false;
616
+ }
617
+ let diff = 0;
618
+ for (let i = 0; i < a.length; i++) {
619
+ diff |= a[i] ^ b[i];
620
+ }
621
+ return diff === 0;
622
+ }
623
+ async function deriveAesKey(secretKey, salt) {
624
+ const baseKey = await globalThis.crypto.subtle.importKey(
625
+ "raw",
626
+ new TextEncoder().encode(secretKey),
627
+ "PBKDF2",
628
+ false,
629
+ ["deriveKey"]
630
+ );
631
+ return globalThis.crypto.subtle.deriveKey(
632
+ { name: "PBKDF2", salt, iterations: PBKDF2_ITERATIONS, hash: "SHA-256" },
633
+ baseKey,
634
+ { name: "AES-GCM", length: 256 },
635
+ false,
636
+ ["encrypt", "decrypt"]
637
+ );
638
+ }
639
+ async function encryptSecret(plaintext, secretKey) {
640
+ const salt = randomBytes(SALT_BYTES);
641
+ const iv = randomBytes(IV_BYTES);
642
+ const key = await deriveAesKey(secretKey, salt);
643
+ const ciphertext = await globalThis.crypto.subtle.encrypt(
644
+ { name: "AES-GCM", iv },
645
+ key,
646
+ new TextEncoder().encode(plaintext)
647
+ );
648
+ return `v1.${toHex(salt)}.${toHex(iv)}.${toHex(new Uint8Array(ciphertext))}`;
649
+ }
650
+ async function decryptSecret(encrypted, secretKey) {
651
+ const parts = encrypted.split(".");
652
+ if (parts.length !== 4 || parts[0] !== "v1") {
653
+ throw new Error("Invalid encrypted secret format");
654
+ }
655
+ const salt = fromHex(parts[1]);
656
+ const iv = fromHex(parts[2]);
657
+ const ciphertext = fromHex(parts[3]);
658
+ const key = await deriveAesKey(secretKey, salt);
659
+ const plaintext = await globalThis.crypto.subtle.decrypt(
660
+ { name: "AES-GCM", iv },
661
+ key,
662
+ ciphertext
663
+ );
664
+ return new TextDecoder().decode(plaintext);
665
+ }
666
+ async function hashSecret(plaintext) {
667
+ const salt = randomBytes(SALT_BYTES);
668
+ const hash = await pbkdf2(plaintext, salt);
669
+ return `v1.${toHex(salt)}.${toHex(hash)}`;
670
+ }
671
+ async function verifySecret(plaintext, stored) {
672
+ const parts = stored.split(".");
673
+ if (parts.length !== 3 || parts[0] !== "v1") {
674
+ return false;
675
+ }
676
+ let salt;
677
+ let expected;
678
+ try {
679
+ salt = fromHex(parts[1]);
680
+ expected = fromHex(parts[2]);
681
+ } catch {
682
+ return false;
683
+ }
684
+ const actual = await pbkdf2(plaintext, salt);
685
+ return timingSafeEqual(actual, expected);
686
+ }
687
+ async function pbkdf2(plaintext, salt) {
688
+ const baseKey = await globalThis.crypto.subtle.importKey(
689
+ "raw",
690
+ new TextEncoder().encode(plaintext),
691
+ "PBKDF2",
692
+ false,
693
+ ["deriveBits"]
694
+ );
695
+ const bits = await globalThis.crypto.subtle.deriveBits(
696
+ { name: "PBKDF2", salt, iterations: PBKDF2_ITERATIONS, hash: "SHA-256" },
697
+ baseKey,
698
+ HASH_BYTES * 8
699
+ );
700
+ return new Uint8Array(bits);
701
+ }
702
+
703
+ // src/secret/secret-transform.ts
704
+ var MissingSecretKeyError = class extends Error {
705
+ constructor(field) {
706
+ super(
707
+ `Secret field "${field}" is encrypted but no encryption key is configured. Provide sync.encryption.key (or a key provider) to createApp.`
708
+ );
709
+ this.field = field;
710
+ this.name = "MissingSecretKeyError";
711
+ }
712
+ field;
713
+ };
714
+ async function resolveKey(provider) {
715
+ if (!provider) {
716
+ return void 0;
717
+ }
718
+ return provider();
719
+ }
720
+ async function transformSecretFieldsForWrite(data, collectionDef, keyProvider) {
721
+ const hasSecret = Object.values(collectionDef.fields).some((f) => f.kind === "secret");
722
+ if (!hasSecret) {
723
+ return data;
724
+ }
725
+ const result = { ...data };
726
+ let key;
727
+ let keyResolved = false;
728
+ for (const [field, value] of Object.entries(data)) {
729
+ const descriptor = collectionDef.fields[field];
730
+ if (descriptor?.kind !== "secret") {
731
+ continue;
732
+ }
733
+ if (typeof value !== "string") {
734
+ continue;
735
+ }
736
+ const mode = descriptor.secretMode ?? "encrypted";
737
+ if (mode === "hashed") {
738
+ result[field] = await hashSecret(value);
739
+ continue;
740
+ }
741
+ if (!keyResolved) {
742
+ key = await resolveKey(keyProvider);
743
+ keyResolved = true;
744
+ }
745
+ if (key === void 0) {
746
+ throw new MissingSecretKeyError(field);
747
+ }
748
+ result[field] = await encryptSecret(value, key);
749
+ }
750
+ return result;
751
+ }
752
+ async function revealSecret(storedValue, mode, keyProvider, field = "secret") {
753
+ if (mode === "hashed") {
754
+ throw new Error(
755
+ `Secret field "${field}" is hashed (one-way) and cannot be revealed. Use verifySecretValue to check a candidate.`
756
+ );
757
+ }
758
+ const key = await resolveKey(keyProvider);
759
+ if (key === void 0) {
760
+ throw new MissingSecretKeyError(field);
761
+ }
762
+ return decryptSecret(storedValue, key);
763
+ }
764
+ async function verifySecretValue(candidate, storedValue) {
765
+ return verifySecret(candidate, storedValue);
766
+ }
767
+
388
768
  // src/operations/apply-result.ts
389
769
  var APPLY_RESULTS = ["applied", "duplicate", "skipped", "rejected", "deferred"];
390
770
  var APPLY_FAILURE_CODES = {
@@ -680,6 +1060,106 @@ function toAtomicOp(sentinel) {
680
1060
  return { type: sentinel.type, value: sentinel.value };
681
1061
  }
682
1062
 
1063
+ // src/operations/op-data-binary.ts
1064
+ function isKoraBytesValue(value) {
1065
+ if (typeof value !== "object" || value === null) {
1066
+ return false;
1067
+ }
1068
+ const record = value;
1069
+ return Object.keys(record).length === 1 && typeof record.$koraBytes === "string";
1070
+ }
1071
+ function isLegacyNumericByteObject(value) {
1072
+ if (typeof value !== "object" || value === null || ArrayBuffer.isView(value)) {
1073
+ return false;
1074
+ }
1075
+ const record = value;
1076
+ const keys = Object.keys(record);
1077
+ if (keys.length === 0) {
1078
+ return false;
1079
+ }
1080
+ for (let i = 0; i < keys.length; i++) {
1081
+ const byte = record[String(i)];
1082
+ if (typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255) {
1083
+ return false;
1084
+ }
1085
+ }
1086
+ return true;
1087
+ }
1088
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1089
+ var BASE64_REVERSE = new Map(
1090
+ Array.from(BASE64_ALPHABET, (char, index) => [char, index])
1091
+ );
1092
+ function bytesToBase64(bytes) {
1093
+ let out = "";
1094
+ for (let i = 0; i < bytes.length; i += 3) {
1095
+ const b0 = bytes[i] ?? 0;
1096
+ const b1 = bytes[i + 1] ?? 0;
1097
+ const b2 = bytes[i + 2] ?? 0;
1098
+ const triple = b0 << 16 | b1 << 8 | b2;
1099
+ out += BASE64_ALPHABET[triple >> 18 & 63] ?? "";
1100
+ out += BASE64_ALPHABET[triple >> 12 & 63] ?? "";
1101
+ out += i + 1 < bytes.length ? BASE64_ALPHABET[triple >> 6 & 63] ?? "" : "=";
1102
+ out += i + 2 < bytes.length ? BASE64_ALPHABET[triple & 63] ?? "" : "=";
1103
+ }
1104
+ return out;
1105
+ }
1106
+ function base64ToBytes(base64) {
1107
+ const cleaned = base64.replace(/=+$/, "");
1108
+ const out = new Uint8Array(Math.floor(cleaned.length * 6 / 8));
1109
+ let buffer = 0;
1110
+ let bits = 0;
1111
+ let index = 0;
1112
+ for (const char of cleaned) {
1113
+ const value = BASE64_REVERSE.get(char);
1114
+ if (value === void 0) {
1115
+ throw new OperationError(`Invalid base64 character "${char}" in tagged binary value.`, {
1116
+ char
1117
+ });
1118
+ }
1119
+ buffer = buffer << 6 | value;
1120
+ bits += 6;
1121
+ if (bits >= 8) {
1122
+ bits -= 8;
1123
+ out[index] = buffer >> bits & 255;
1124
+ index += 1;
1125
+ }
1126
+ }
1127
+ return out;
1128
+ }
1129
+ function encodeBytesForOpData(value) {
1130
+ if (typeof value === "string") {
1131
+ return value;
1132
+ }
1133
+ const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
1134
+ return { $koraBytes: bytesToBase64(bytes) };
1135
+ }
1136
+ function decodeBytesFromOpData(value) {
1137
+ if (typeof value === "string") {
1138
+ return value;
1139
+ }
1140
+ if (value instanceof Uint8Array) {
1141
+ return value;
1142
+ }
1143
+ if (value instanceof ArrayBuffer) {
1144
+ return new Uint8Array(value);
1145
+ }
1146
+ if (isKoraBytesValue(value)) {
1147
+ return base64ToBytes(value.$koraBytes);
1148
+ }
1149
+ if (isLegacyNumericByteObject(value)) {
1150
+ const keys = Object.keys(value);
1151
+ const bytes = new Uint8Array(keys.length);
1152
+ for (let i = 0; i < keys.length; i++) {
1153
+ bytes[i] = value[String(i)] ?? 0;
1154
+ }
1155
+ return bytes;
1156
+ }
1157
+ throw new OperationError(
1158
+ "Binary op-data value must be a string, Uint8Array, ArrayBuffer, tagged { $koraBytes } object, or legacy numeric-key byte object.",
1159
+ { receivedType: typeof value }
1160
+ );
1161
+ }
1162
+
683
1163
  // src/scopes/sync-scope-bindings.ts
684
1164
  function hasSchemaSyncRules(schema) {
685
1165
  return schema.sync !== void 0 && Object.keys(schema.sync).length > 0;
@@ -1109,6 +1589,7 @@ function generateSQL(collectionName, collection, relations) {
1109
1589
  columns.push("_created_at INTEGER NOT NULL");
1110
1590
  columns.push("_updated_at INTEGER NOT NULL");
1111
1591
  columns.push("_version TEXT NOT NULL DEFAULT ''");
1592
+ columns.push("_field_versions TEXT NOT NULL DEFAULT '{}'");
1112
1593
  columns.push("_deleted INTEGER NOT NULL DEFAULT 0");
1113
1594
  statements.push(`CREATE TABLE IF NOT EXISTS ${collectionName} (
1114
1595
  ${columns.join(",\n ")}
@@ -1121,6 +1602,10 @@ ALTER TABLE ${collectionName} ADD COLUMN ${colDef}`);
1121
1602
  statements.push(
1122
1603
  `--kora:safe-alter
1123
1604
  ALTER TABLE ${collectionName} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`
1605
+ );
1606
+ statements.push(
1607
+ `--kora:safe-alter
1608
+ ALTER TABLE ${collectionName} ADD COLUMN _field_versions TEXT NOT NULL DEFAULT '{}'`
1124
1609
  );
1125
1610
  for (const indexField of collection.indexes) {
1126
1611
  statements.push(
@@ -1148,6 +1633,9 @@ ALTER TABLE ${collectionName} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`
1148
1633
  schema_version INTEGER NOT NULL
1149
1634
  )`
1150
1635
  );
1636
+ statements.push(
1637
+ `CREATE INDEX IF NOT EXISTS idx_kora_ops_${collectionName}_record_id ON _kora_ops_${collectionName} (record_id)`
1638
+ );
1151
1639
  return statements;
1152
1640
  }
1153
1641
  function generateFullDDL(schema) {
@@ -1208,6 +1696,17 @@ function mapFieldType(descriptor) {
1208
1696
  case "array":
1209
1697
  return "TEXT";
1210
1698
  // JSON-serialized
1699
+ case "object":
1700
+ return "TEXT";
1701
+ // JSON-serialized structured object
1702
+ case "json":
1703
+ return "TEXT";
1704
+ // JSON-serialized dynamic-key value
1705
+ case "blob":
1706
+ return "TEXT";
1707
+ // JSON-serialized content-addressed BlobRef (bytes stored out of band)
1708
+ case "secret":
1709
+ return "TEXT";
1211
1710
  case "richtext":
1212
1711
  return "BLOB";
1213
1712
  }
@@ -1441,6 +1940,160 @@ var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
1441
1940
  };
1442
1941
  }
1443
1942
  };
1943
+ var ObjectFieldBuilder = class _ObjectFieldBuilder extends FieldBuilder {
1944
+ _fields;
1945
+ constructor(fields, required = true, defaultValue = void 0, auto = false, mergeStrategy = null) {
1946
+ super("object", required, defaultValue, auto, mergeStrategy);
1947
+ this._fields = fields;
1948
+ }
1949
+ optional() {
1950
+ return new _ObjectFieldBuilder(
1951
+ this._fields,
1952
+ false,
1953
+ this._defaultValue,
1954
+ this._auto,
1955
+ this._mergeStrategy
1956
+ );
1957
+ }
1958
+ default(value) {
1959
+ return new _ObjectFieldBuilder(this._fields, false, value, this._auto, this._mergeStrategy);
1960
+ }
1961
+ auto() {
1962
+ return new _ObjectFieldBuilder(this._fields, false, void 0, true, this._mergeStrategy);
1963
+ }
1964
+ merge(strategy) {
1965
+ return new _ObjectFieldBuilder(
1966
+ this._fields,
1967
+ this._required,
1968
+ this._defaultValue,
1969
+ this._auto,
1970
+ strategy
1971
+ );
1972
+ }
1973
+ _build() {
1974
+ const nestedFields = {};
1975
+ for (const [key, builder] of Object.entries(this._fields)) {
1976
+ nestedFields[key] = builder._build();
1977
+ }
1978
+ return {
1979
+ kind: "object",
1980
+ required: this._required,
1981
+ defaultValue: this._defaultValue,
1982
+ auto: this._auto,
1983
+ enumValues: null,
1984
+ itemKind: null,
1985
+ mergeStrategy: this._mergeStrategy,
1986
+ transitions: null,
1987
+ nestedFields
1988
+ };
1989
+ }
1990
+ };
1991
+ var JsonFieldBuilder = class _JsonFieldBuilder extends FieldBuilder {
1992
+ optional() {
1993
+ return new _JsonFieldBuilder(
1994
+ "json",
1995
+ false,
1996
+ this._defaultValue,
1997
+ this._auto,
1998
+ this._mergeStrategy
1999
+ );
2000
+ }
2001
+ default(value) {
2002
+ return new _JsonFieldBuilder(
2003
+ "json",
2004
+ false,
2005
+ value,
2006
+ this._auto,
2007
+ this._mergeStrategy
2008
+ );
2009
+ }
2010
+ auto() {
2011
+ return new _JsonFieldBuilder("json", false, void 0, true, this._mergeStrategy);
2012
+ }
2013
+ merge(strategy) {
2014
+ return new _JsonFieldBuilder(
2015
+ "json",
2016
+ this._required,
2017
+ this._defaultValue,
2018
+ this._auto,
2019
+ strategy
2020
+ );
2021
+ }
2022
+ _build() {
2023
+ return {
2024
+ kind: "json",
2025
+ required: this._required,
2026
+ defaultValue: this._defaultValue,
2027
+ auto: this._auto,
2028
+ enumValues: null,
2029
+ itemKind: null,
2030
+ mergeStrategy: this._mergeStrategy,
2031
+ transitions: null,
2032
+ nestedFields: null
2033
+ };
2034
+ }
2035
+ };
2036
+ var SecretFieldBuilder = class _SecretFieldBuilder extends FieldBuilder {
2037
+ _secretMode;
2038
+ constructor(secretMode = "encrypted", required = true, defaultValue = void 0, auto = false, mergeStrategy = null) {
2039
+ super("secret", required, defaultValue, auto, mergeStrategy);
2040
+ this._secretMode = secretMode;
2041
+ }
2042
+ /** Store this secret as a one-way salted hash (passwords: verify, never read back). */
2043
+ hashed() {
2044
+ return new _SecretFieldBuilder(
2045
+ "hashed",
2046
+ this._required,
2047
+ this._defaultValue,
2048
+ this._auto,
2049
+ this._mergeStrategy
2050
+ );
2051
+ }
2052
+ /** Store this secret as reversible ciphertext (tokens/keys: decrypt to use). */
2053
+ encrypted() {
2054
+ return new _SecretFieldBuilder(
2055
+ "encrypted",
2056
+ this._required,
2057
+ this._defaultValue,
2058
+ this._auto,
2059
+ this._mergeStrategy
2060
+ );
2061
+ }
2062
+ optional() {
2063
+ return new _SecretFieldBuilder(
2064
+ this._secretMode,
2065
+ false,
2066
+ this._defaultValue,
2067
+ this._auto,
2068
+ this._mergeStrategy
2069
+ );
2070
+ }
2071
+ auto() {
2072
+ return new _SecretFieldBuilder(this._secretMode, false, void 0, true, this._mergeStrategy);
2073
+ }
2074
+ merge(strategy) {
2075
+ return new _SecretFieldBuilder(
2076
+ this._secretMode,
2077
+ this._required,
2078
+ this._defaultValue,
2079
+ this._auto,
2080
+ strategy
2081
+ );
2082
+ }
2083
+ _build() {
2084
+ return {
2085
+ kind: "secret",
2086
+ required: this._required,
2087
+ defaultValue: this._defaultValue,
2088
+ auto: this._auto,
2089
+ enumValues: null,
2090
+ itemKind: null,
2091
+ mergeStrategy: this._mergeStrategy,
2092
+ transitions: null,
2093
+ secretMode: this._secretMode
2094
+ };
2095
+ }
2096
+ };
1444
2097
  var t = {
1445
2098
  string() {
1446
2099
  return new FieldBuilder("string", true, void 0, false);
@@ -1462,6 +2115,18 @@ var t = {
1462
2115
  },
1463
2116
  array(itemBuilder) {
1464
2117
  return new ArrayFieldBuilder(itemBuilder, true, void 0, false);
2118
+ },
2119
+ object(fields) {
2120
+ return new ObjectFieldBuilder(fields, true, void 0, false);
2121
+ },
2122
+ json() {
2123
+ return new JsonFieldBuilder("json", true, void 0, false);
2124
+ },
2125
+ blob() {
2126
+ return new FieldBuilder("blob", true, void 0, false);
2127
+ },
2128
+ secret() {
2129
+ return new SecretFieldBuilder("encrypted", true, void 0, false);
1465
2130
  }
1466
2131
  };
1467
2132
 
@@ -1613,9 +2278,9 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
1613
2278
  break;
1614
2279
  }
1615
2280
  case "richtext": {
1616
- if (!(value instanceof Uint8Array) && typeof value !== "string") {
2281
+ if (!(value instanceof Uint8Array) && !(value instanceof ArrayBuffer) && typeof value !== "string") {
1617
2282
  throw new SchemaValidationError(
1618
- `Field "${fieldName}" in collection "${collection}" must be a Uint8Array or string for richtext, got ${typeof value}`,
2283
+ `Field "${fieldName}" in collection "${collection}" must be a Uint8Array, ArrayBuffer, or string for richtext, got ${typeof value}`,
1619
2284
  {
1620
2285
  collection,
1621
2286
  field: fieldName,
@@ -1626,8 +2291,99 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
1626
2291
  }
1627
2292
  break;
1628
2293
  }
2294
+ case "object": {
2295
+ if (!isPlainObject(value)) {
2296
+ throw new SchemaValidationError(
2297
+ `Field "${fieldName}" in collection "${collection}" must be a plain object, got ${describeType(value)}`,
2298
+ {
2299
+ collection,
2300
+ field: fieldName,
2301
+ expectedType: "object",
2302
+ receivedType: describeType(value)
2303
+ }
2304
+ );
2305
+ }
2306
+ if (descriptor.nestedFields) {
2307
+ for (const [nestedName, nestedDescriptor] of Object.entries(descriptor.nestedFields)) {
2308
+ const nestedValue = value[nestedName];
2309
+ if (nestedValue !== void 0 && nestedValue !== null) {
2310
+ validateFieldValue(
2311
+ collection,
2312
+ `${fieldName}.${nestedName}`,
2313
+ nestedDescriptor,
2314
+ nestedValue
2315
+ );
2316
+ }
2317
+ }
2318
+ }
2319
+ break;
2320
+ }
2321
+ case "json": {
2322
+ if (!isJsonSerializable(value)) {
2323
+ throw new SchemaValidationError(
2324
+ `Field "${fieldName}" in collection "${collection}" must be JSON-serializable, got ${describeType(value)}`,
2325
+ { collection, field: fieldName, expectedType: "json", receivedType: describeType(value) }
2326
+ );
2327
+ }
2328
+ break;
2329
+ }
2330
+ case "blob": {
2331
+ if (!isBlobRef(value)) {
2332
+ throw new SchemaValidationError(
2333
+ `Field "${fieldName}" in collection "${collection}" must be a BlobRef (from the blob store), got ${describeType(value)}`,
2334
+ { collection, field: fieldName, expectedType: "blob", receivedType: describeType(value) }
2335
+ );
2336
+ }
2337
+ break;
2338
+ }
2339
+ case "secret": {
2340
+ if (typeof value !== "string") {
2341
+ throw new SchemaValidationError(
2342
+ `Field "${fieldName}" in collection "${collection}" must be a string, got ${describeType(value)}`,
2343
+ {
2344
+ collection,
2345
+ field: fieldName,
2346
+ expectedType: "secret",
2347
+ receivedType: describeType(value)
2348
+ }
2349
+ );
2350
+ }
2351
+ break;
2352
+ }
1629
2353
  }
1630
2354
  }
2355
+ function isPlainObject(value) {
2356
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2357
+ return false;
2358
+ }
2359
+ const proto = Object.getPrototypeOf(value);
2360
+ return proto === null || proto === Object.prototype;
2361
+ }
2362
+ function isJsonSerializable(value) {
2363
+ if (value === null) {
2364
+ return true;
2365
+ }
2366
+ const type = typeof value;
2367
+ if (type === "string" || type === "number" || type === "boolean") {
2368
+ return Number.isFinite(value) || type !== "number";
2369
+ }
2370
+ if (Array.isArray(value)) {
2371
+ return value.every(isJsonSerializable);
2372
+ }
2373
+ if (type === "object") {
2374
+ return Object.values(value).every(isJsonSerializable);
2375
+ }
2376
+ return false;
2377
+ }
2378
+ function describeType(value) {
2379
+ if (value === null) {
2380
+ return "null";
2381
+ }
2382
+ if (Array.isArray(value)) {
2383
+ return "array";
2384
+ }
2385
+ return typeof value;
2386
+ }
1631
2387
  function jsTypeForKind(kind) {
1632
2388
  switch (kind) {
1633
2389
  case "string":
@@ -2225,6 +2981,14 @@ function mapFieldType2(descriptor) {
2225
2981
  return "INTEGER";
2226
2982
  case "array":
2227
2983
  return "TEXT";
2984
+ case "object":
2985
+ return "TEXT";
2986
+ case "json":
2987
+ return "TEXT";
2988
+ case "blob":
2989
+ return "TEXT";
2990
+ case "secret":
2991
+ return "TEXT";
2228
2992
  case "richtext":
2229
2993
  return "BLOB";
2230
2994
  }
@@ -2243,7 +3007,14 @@ var SCALAR_TYPE_MAP = {
2243
3007
  number: "double",
2244
3008
  boolean: "bool",
2245
3009
  timestamp: "int64",
2246
- richtext: "bytes"
3010
+ richtext: "bytes",
3011
+ // object/json values travel as JSON-serialized strings on the proto wire.
3012
+ object: "string",
3013
+ json: "string",
3014
+ // A blob field carries a JSON-serialized content-addressed reference, not bytes.
3015
+ blob: "string",
3016
+ // A secret field's stored value is a JSON string (ciphertext or hash).
3017
+ secret: "string"
2247
3018
  };
2248
3019
  var ARRAY_ITEM_TYPE_MAP = {
2249
3020
  string: "string",
@@ -2479,33 +3250,44 @@ function generateProtoDefinitions(schema) {
2479
3250
  EnumFieldBuilder,
2480
3251
  FieldBuilder,
2481
3252
  HybridLogicalClock,
3253
+ InvalidTimestampError,
2482
3254
  KORA_ERROR_FIX_SUGGESTIONS,
2483
3255
  KoraError,
3256
+ MAX_LOGICAL,
2484
3257
  MERGE_STRATEGIES,
2485
3258
  MergeConflictError,
2486
3259
  MigrationBuilder,
2487
3260
  MigrationRollbackError,
3261
+ MissingSecretKeyError,
2488
3262
  OperationError,
3263
+ RemoteClockDriftError,
2489
3264
  RollbackBuilder,
2490
3265
  SchemaValidationError,
2491
3266
  StorageError,
2492
3267
  SyncError,
2493
3268
  advanceVector,
2494
3269
  applyOperationTransforms,
3270
+ base64ToBytes,
2495
3271
  buildScopeMap,
2496
3272
  buildStateMachineConstraints,
3273
+ bytesToBase64,
2497
3274
  canAutoRollback,
2498
3275
  collectSchemaScopeFields,
2499
3276
  collectSchemaScopeValueKeys,
2500
3277
  computeDelta,
3278
+ createBlobRef,
2501
3279
  createOperation,
2502
3280
  createReversibleMigration,
2503
3281
  createVersionVector,
3282
+ decodeBytesFromOpData,
3283
+ decryptSecret,
2504
3284
  defaultApplyFailureReason,
2505
3285
  defaultSequenceFormat,
2506
3286
  defineSchema,
2507
3287
  deserializeVector,
2508
3288
  dominates,
3289
+ encodeBytesForOpData,
3290
+ encryptSecret,
2509
3291
  extractScopeValuesFromClaims,
2510
3292
  extractTimestamp,
2511
3293
  formatSequenceValue,
@@ -2518,9 +3300,14 @@ function generateProtoDefinitions(schema) {
2518
3300
  getKoraErrorFix,
2519
3301
  getTransitionMap,
2520
3302
  hasSchemaSyncRules,
3303
+ hashBlob,
3304
+ hashSecret,
2521
3305
  isApplyFailure,
2522
3306
  isAtomicOp,
3307
+ isBlobRef,
2523
3308
  isCollectionSyncScoped,
3309
+ isKoraBytesValue,
3310
+ isLegacyNumericByteObject,
2524
3311
  isValidOperation,
2525
3312
  isValidUUIDv7,
2526
3313
  mergeVectors,
@@ -2528,13 +3315,17 @@ function generateProtoDefinitions(schema) {
2528
3315
  migrationStepsToSQL,
2529
3316
  op,
2530
3317
  resolveAtomicOp,
3318
+ revealSecret,
2531
3319
  rollbackStepsToSQL,
2532
3320
  serializeVector,
2533
3321
  t,
2534
3322
  toAtomicOp,
3323
+ transformSecretFieldsForWrite,
2535
3324
  validateRecord,
2536
3325
  validateTransition,
2537
3326
  vectorsEqual,
2538
- verifyOperationIntegrity
3327
+ verifyOperationIntegrity,
3328
+ verifySecret,
3329
+ verifySecretValue
2539
3330
  });
2540
3331
  //# sourceMappingURL=index.cjs.map