@korajs/core 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -29,27 +29,47 @@ __export(index_exports, {
29
29
  KoraError: () => KoraError,
30
30
  MERGE_STRATEGIES: () => MERGE_STRATEGIES,
31
31
  MergeConflictError: () => MergeConflictError,
32
+ MigrationBuilder: () => MigrationBuilder,
33
+ MigrationRollbackError: () => MigrationRollbackError,
32
34
  OperationError: () => OperationError,
35
+ RollbackBuilder: () => RollbackBuilder,
33
36
  SchemaValidationError: () => SchemaValidationError,
34
37
  StorageError: () => StorageError,
35
38
  SyncError: () => SyncError,
36
39
  advanceVector: () => advanceVector,
40
+ buildScopeMap: () => buildScopeMap,
41
+ buildStateMachineConstraints: () => buildStateMachineConstraints,
42
+ canAutoRollback: () => canAutoRollback,
37
43
  computeDelta: () => computeDelta,
38
44
  createOperation: () => createOperation,
45
+ createReversibleMigration: () => createReversibleMigration,
39
46
  createVersionVector: () => createVersionVector,
47
+ defaultSequenceFormat: () => defaultSequenceFormat,
40
48
  defineSchema: () => defineSchema,
41
49
  deserializeVector: () => deserializeVector,
42
50
  dominates: () => dominates,
43
51
  extractTimestamp: () => extractTimestamp,
52
+ formatSequenceValue: () => formatSequenceValue,
44
53
  generateFullDDL: () => generateFullDDL,
54
+ generateProtoDefinitions: () => generateProtoDefinitions,
55
+ generateRollbackSteps: () => generateRollbackSteps,
45
56
  generateSQL: () => generateSQL,
46
57
  generateUUIDv7: () => generateUUIDv7,
58
+ getTransitionMap: () => getTransitionMap,
59
+ isAtomicOp: () => isAtomicOp,
47
60
  isValidOperation: () => isValidOperation,
48
61
  isValidUUIDv7: () => isValidUUIDv7,
49
62
  mergeVectors: () => mergeVectors,
63
+ migrate: () => migrate,
64
+ migrationStepsToSQL: () => migrationStepsToSQL,
65
+ op: () => op,
66
+ resolveAtomicOp: () => resolveAtomicOp,
67
+ rollbackStepsToSQL: () => rollbackStepsToSQL,
50
68
  serializeVector: () => serializeVector,
51
69
  t: () => t,
70
+ toAtomicOp: () => toAtomicOp,
52
71
  validateRecord: () => validateRecord,
72
+ validateTransition: () => validateTransition,
53
73
  vectorsEqual: () => vectorsEqual,
54
74
  verifyOperationIntegrity: () => verifyOperationIntegrity
55
75
  });
@@ -270,14 +290,18 @@ function formatUUID(bytes) {
270
290
 
271
291
  // src/operations/content-hash.ts
272
292
  async function computeOperationId(input, timestamp) {
273
- const canonical = canonicalize({
293
+ const hashInput = {
274
294
  type: input.type,
275
295
  collection: input.collection,
276
296
  recordId: input.recordId,
277
297
  data: input.data,
278
298
  timestamp,
279
299
  nodeId: input.nodeId
280
- });
300
+ };
301
+ if (input.atomicOps !== void 0 && Object.keys(input.atomicOps).length > 0) {
302
+ hashInput.atomicOps = input.atomicOps;
303
+ }
304
+ const canonical = canonicalize(hashInput);
281
305
  const encoded = new TextEncoder().encode(canonical);
282
306
  const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded);
283
307
  return bufferToHex(hashBuffer);
@@ -322,7 +346,10 @@ async function createOperation(input, clock) {
322
346
  timestamp,
323
347
  sequenceNumber: input.sequenceNumber,
324
348
  causalDeps: [...input.causalDeps],
325
- schemaVersion: input.schemaVersion
349
+ schemaVersion: input.schemaVersion,
350
+ ...input.atomicOps !== void 0 && Object.keys(input.atomicOps).length > 0 ? { atomicOps: { ...input.atomicOps } } : {},
351
+ ...input.transactionId !== void 0 ? { transactionId: input.transactionId } : {},
352
+ ...input.mutationName !== void 0 ? { mutationName: input.mutationName } : {}
326
353
  };
327
354
  return deepFreeze(operation);
328
355
  }
@@ -390,26 +417,27 @@ function validateOperationParams(input) {
390
417
  });
391
418
  }
392
419
  }
393
- async function verifyOperationIntegrity(op) {
420
+ async function verifyOperationIntegrity(op2) {
394
421
  const input = {
395
- nodeId: op.nodeId,
396
- type: op.type,
397
- collection: op.collection,
398
- recordId: op.recordId,
399
- data: op.data,
400
- previousData: op.previousData,
401
- sequenceNumber: op.sequenceNumber,
402
- causalDeps: op.causalDeps,
403
- schemaVersion: op.schemaVersion
422
+ nodeId: op2.nodeId,
423
+ type: op2.type,
424
+ collection: op2.collection,
425
+ recordId: op2.recordId,
426
+ data: op2.data,
427
+ previousData: op2.previousData,
428
+ sequenceNumber: op2.sequenceNumber,
429
+ causalDeps: op2.causalDeps,
430
+ schemaVersion: op2.schemaVersion,
431
+ ...op2.atomicOps !== void 0 ? { atomicOps: op2.atomicOps } : {}
404
432
  };
405
- const serializedTs = HybridLogicalClock.serialize(op.timestamp);
433
+ const serializedTs = HybridLogicalClock.serialize(op2.timestamp);
406
434
  const expectedId = await computeOperationId(input, serializedTs);
407
- return op.id === expectedId;
435
+ return op2.id === expectedId;
408
436
  }
409
437
  function isValidOperation(value) {
410
438
  if (typeof value !== "object" || value === null) return false;
411
- const op = value;
412
- return typeof op.id === "string" && typeof op.nodeId === "string" && (op.type === "insert" || op.type === "update" || op.type === "delete") && typeof op.collection === "string" && typeof op.recordId === "string" && typeof op.sequenceNumber === "number" && Array.isArray(op.causalDeps) && typeof op.schemaVersion === "number" && typeof op.timestamp === "object" && op.timestamp !== null;
439
+ const op2 = value;
440
+ return typeof op2.id === "string" && typeof op2.nodeId === "string" && (op2.type === "insert" || op2.type === "update" || op2.type === "delete") && typeof op2.collection === "string" && typeof op2.recordId === "string" && typeof op2.sequenceNumber === "number" && Array.isArray(op2.causalDeps) && typeof op2.schemaVersion === "number" && typeof op2.timestamp === "object" && op2.timestamp !== null;
413
441
  }
414
442
  function deepFreeze(obj) {
415
443
  if (typeof obj !== "object" || obj === null) return obj;
@@ -422,6 +450,190 @@ function deepFreeze(obj) {
422
450
  return obj;
423
451
  }
424
452
 
453
+ // src/operations/atomic-ops.ts
454
+ var KORA_ATOMIC_OP = /* @__PURE__ */ Symbol.for("kora.atomic_op");
455
+ function isAtomicOp(value) {
456
+ return typeof value === "object" && value !== null && KORA_ATOMIC_OP in value && value[KORA_ATOMIC_OP] === true;
457
+ }
458
+ function resolveAtomicOp(currentValue, sentinel) {
459
+ switch (sentinel.type) {
460
+ case "increment": {
461
+ const current = typeof currentValue === "number" ? currentValue : 0;
462
+ const operand = sentinel.value;
463
+ return current + operand;
464
+ }
465
+ case "max": {
466
+ const current = typeof currentValue === "number" ? currentValue : Number.NEGATIVE_INFINITY;
467
+ return Math.max(current, sentinel.value);
468
+ }
469
+ case "min": {
470
+ const current = typeof currentValue === "number" ? currentValue : Number.POSITIVE_INFINITY;
471
+ return Math.min(current, sentinel.value);
472
+ }
473
+ case "append": {
474
+ const current = Array.isArray(currentValue) ? [...currentValue] : [];
475
+ current.push(sentinel.value);
476
+ return current;
477
+ }
478
+ case "remove": {
479
+ if (!Array.isArray(currentValue)) return [];
480
+ return currentValue.filter((item) => item !== sentinel.value);
481
+ }
482
+ }
483
+ }
484
+ function createSentinel(type, value) {
485
+ return Object.freeze({
486
+ [KORA_ATOMIC_OP]: true,
487
+ type,
488
+ value
489
+ });
490
+ }
491
+ var op = {
492
+ /**
493
+ * Increment a number field by the given amount.
494
+ * Concurrent increments compose: the sum of all deltas is applied to the base.
495
+ *
496
+ * @param n - The amount to increment (use negative values to decrement)
497
+ */
498
+ increment(n) {
499
+ return createSentinel("increment", n);
500
+ },
501
+ /**
502
+ * Decrement a number field by the given amount.
503
+ * Syntactic sugar for `op.increment(-n)`.
504
+ *
505
+ * @param n - The amount to decrement
506
+ */
507
+ decrement(n) {
508
+ return createSentinel("increment", -n);
509
+ },
510
+ /**
511
+ * Set the field to the maximum of the current value and the given value.
512
+ * Concurrent max operations take the maximum of all values.
513
+ *
514
+ * @param n - The value to compare against the current value
515
+ */
516
+ max(n) {
517
+ return createSentinel("max", n);
518
+ },
519
+ /**
520
+ * Set the field to the minimum of the current value and the given value.
521
+ * Concurrent min operations take the minimum of all values.
522
+ *
523
+ * @param n - The value to compare against the current value
524
+ */
525
+ min(n) {
526
+ return createSentinel("min", n);
527
+ },
528
+ /**
529
+ * Append an item to an array field.
530
+ * Concurrent appends include all appended items.
531
+ *
532
+ * @param item - The item to append to the array
533
+ */
534
+ append(item) {
535
+ return createSentinel("append", item);
536
+ },
537
+ /**
538
+ * Remove an item from an array field (by value equality).
539
+ * Concurrent removes of the same item are idempotent.
540
+ *
541
+ * @param item - The item to remove from the array
542
+ */
543
+ remove(item) {
544
+ return createSentinel("remove", item);
545
+ }
546
+ };
547
+ function toAtomicOp(sentinel) {
548
+ return { type: sentinel.type, value: sentinel.value };
549
+ }
550
+
551
+ // src/state-machine/state-machine.ts
552
+ function validateTransition(constraint, fromValue, toValue) {
553
+ const from = String(fromValue);
554
+ const to = String(toValue);
555
+ const allowedTargets = constraint.transitions[from] ?? [];
556
+ return {
557
+ valid: allowedTargets.includes(to),
558
+ from,
559
+ to,
560
+ field: constraint.field,
561
+ collection: constraint.collection,
562
+ allowedTargets
563
+ };
564
+ }
565
+ function buildStateMachineConstraints(schema) {
566
+ const constraints = [];
567
+ for (const [collectionName, collection] of Object.entries(schema.collections)) {
568
+ for (const [fieldName, descriptor] of Object.entries(collection.fields)) {
569
+ if (descriptor.kind === "enum" && descriptor.transitions !== null) {
570
+ constraints.push({
571
+ field: fieldName,
572
+ collection: collectionName,
573
+ transitions: descriptor.transitions
574
+ });
575
+ }
576
+ }
577
+ }
578
+ return constraints;
579
+ }
580
+ function getTransitionMap(schema, collection, field) {
581
+ const collectionDef = schema.collections[collection];
582
+ if (!collectionDef) {
583
+ return null;
584
+ }
585
+ const fieldDef = collectionDef.fields[field];
586
+ if (!fieldDef || fieldDef.kind !== "enum") {
587
+ return null;
588
+ }
589
+ return fieldDef.transitions;
590
+ }
591
+ function validateStateMachineDefinition(collectionName, sm, fields) {
592
+ const fieldDef = fields[sm.field];
593
+ if (!fieldDef) {
594
+ throw new SchemaValidationError(
595
+ `State machine field "${sm.field}" does not exist in collection "${collectionName}". Available fields: ${Object.keys(fields).join(", ")}`,
596
+ { collection: collectionName, field: sm.field }
597
+ );
598
+ }
599
+ if (fieldDef.kind !== "enum") {
600
+ throw new SchemaValidationError(
601
+ `State machine field "${sm.field}" in collection "${collectionName}" must be an enum field, but got "${fieldDef.kind}"`,
602
+ { collection: collectionName, field: sm.field, kind: fieldDef.kind }
603
+ );
604
+ }
605
+ const enumValues = fieldDef.enumValues;
606
+ if (!enumValues || enumValues.length === 0) {
607
+ throw new SchemaValidationError(
608
+ `State machine field "${sm.field}" in collection "${collectionName}" has no enum values defined`,
609
+ { collection: collectionName, field: sm.field }
610
+ );
611
+ }
612
+ const validValues = new Set(enumValues);
613
+ for (const [state, targets] of Object.entries(sm.transitions)) {
614
+ if (!validValues.has(state)) {
615
+ throw new SchemaValidationError(
616
+ `State machine transition source "${state}" is not a valid enum value for field "${sm.field}" in collection "${collectionName}". Valid values: ${[...validValues].join(", ")}`,
617
+ { collection: collectionName, field: sm.field, state }
618
+ );
619
+ }
620
+ for (const target of targets) {
621
+ if (!validValues.has(target)) {
622
+ throw new SchemaValidationError(
623
+ `State machine transition target "${target}" is not a valid enum value for field "${sm.field}" in collection "${collectionName}". Valid values: ${[...validValues].join(", ")}`,
624
+ { collection: collectionName, field: sm.field, state, target }
625
+ );
626
+ }
627
+ }
628
+ }
629
+ if (sm.onInvalidTransition !== "reject" && sm.onInvalidTransition !== "last-valid-state") {
630
+ throw new SchemaValidationError(
631
+ `State machine onInvalidTransition must be "reject" or "last-valid-state", got "${sm.onInvalidTransition}" in collection "${collectionName}"`,
632
+ { collection: collectionName, onInvalidTransition: sm.onInvalidTransition }
633
+ );
634
+ }
635
+ }
636
+
425
637
  // src/schema/define.ts
426
638
  var COLLECTION_NAME_RE = /^[a-z][a-z0-9_]*$/;
427
639
  var FIELD_NAME_RE = /^[a-z][a-zA-Z0-9_]*$/;
@@ -443,7 +655,32 @@ function defineSchema(input) {
443
655
  relations[name] = { ...relationInput };
444
656
  }
445
657
  }
446
- return { version: input.version, collections, relations };
658
+ const migrations = {};
659
+ if (input.migrations) {
660
+ for (const [versionStr, migration] of Object.entries(input.migrations)) {
661
+ const version = Number(versionStr);
662
+ if (!Number.isInteger(version) || version < 2) {
663
+ throw new SchemaValidationError(
664
+ `Migration key "${versionStr}" is invalid. Must be an integer >= 2 (version 1 is the initial schema).`,
665
+ { version: versionStr }
666
+ );
667
+ }
668
+ if (version > input.version) {
669
+ throw new SchemaValidationError(
670
+ `Migration version ${version} exceeds schema version ${input.version}. Migrations must target versions <= schema version.`,
671
+ { migrationVersion: version, schemaVersion: input.version }
672
+ );
673
+ }
674
+ if (!migration.steps || migration.steps.length === 0) {
675
+ throw new SchemaValidationError(
676
+ `Migration for version ${version} has no steps. Use migrate() to define at least one step.`,
677
+ { version }
678
+ );
679
+ }
680
+ migrations[version] = migration;
681
+ }
682
+ }
683
+ return { version: input.version, collections, relations, migrations };
447
684
  }
448
685
  function validateVersion(version) {
449
686
  if (typeof version !== "number" || !Number.isInteger(version) || version < 1) {
@@ -505,7 +742,24 @@ function buildCollection(name, input) {
505
742
  resolvers[fieldName] = resolver;
506
743
  }
507
744
  }
508
- return { fields, indexes, constraints, resolvers };
745
+ const scope = [];
746
+ if (input.scope) {
747
+ for (const scopeField of input.scope) {
748
+ if (!(scopeField in fields)) {
749
+ throw new SchemaValidationError(
750
+ `Scope field "${scopeField}" does not exist in collection "${name}". Available fields: ${Object.keys(fields).join(", ")}`,
751
+ { collection: name, field: scopeField }
752
+ );
753
+ }
754
+ scope.push(scopeField);
755
+ }
756
+ }
757
+ let stateMachine;
758
+ if (input.stateMachine) {
759
+ validateStateMachineDefinition(name, input.stateMachine, fields);
760
+ stateMachine = { ...input.stateMachine };
761
+ }
762
+ return { fields, indexes, constraints, resolvers, scope, stateMachine };
509
763
  }
510
764
  function validateFieldName(collection, fieldName) {
511
765
  if (RESERVED_FIELDS.has(fieldName)) {
@@ -639,6 +893,9 @@ function generateFullDDL(schema) {
639
893
  statements.push(
640
894
  "CREATE TABLE IF NOT EXISTS _kora_version_vector (\n node_id TEXT PRIMARY KEY NOT NULL,\n sequence_number INTEGER NOT NULL\n)"
641
895
  );
896
+ statements.push(
897
+ "CREATE TABLE IF NOT EXISTS _kora_sequences (\n name TEXT NOT NULL,\n scope TEXT NOT NULL DEFAULT '',\n node_id TEXT NOT NULL,\n counter INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (name, scope, node_id)\n)"
898
+ );
642
899
  for (const [name, collection] of Object.entries(schema.collections)) {
643
900
  statements.push(...generateSQL(name, collection, schema.relations));
644
901
  }
@@ -692,23 +949,47 @@ var FieldBuilder = class _FieldBuilder {
692
949
  _required;
693
950
  _defaultValue;
694
951
  _auto;
695
- constructor(kind, required = true, defaultValue = void 0, auto = false) {
952
+ _mergeStrategy;
953
+ constructor(kind, required = true, defaultValue = void 0, auto = false, mergeStrategy = null) {
696
954
  this._kind = kind;
697
955
  this._required = required;
698
956
  this._defaultValue = defaultValue;
699
957
  this._auto = auto;
958
+ this._mergeStrategy = mergeStrategy;
700
959
  }
701
960
  /** Mark this field as optional (not required on insert) */
702
961
  optional() {
703
- return new _FieldBuilder(this._kind, false, this._defaultValue, this._auto);
962
+ return new _FieldBuilder(this._kind, false, this._defaultValue, this._auto, this._mergeStrategy);
704
963
  }
705
964
  /** Set a default value for this field. Implicitly makes the field optional. */
706
965
  default(value) {
707
- return new _FieldBuilder(this._kind, false, value, this._auto);
966
+ return new _FieldBuilder(this._kind, false, value, this._auto, this._mergeStrategy);
708
967
  }
709
968
  /** Mark this field as auto-populated (e.g., createdAt timestamps). Developers cannot set auto fields. */
710
969
  auto() {
711
- return new _FieldBuilder(this._kind, false, void 0, true);
970
+ return new _FieldBuilder(this._kind, false, void 0, true, this._mergeStrategy);
971
+ }
972
+ /**
973
+ * Declare a merge strategy for this field.
974
+ * Controls how concurrent modifications are resolved during sync.
975
+ *
976
+ * @param strategy - The merge strategy to use:
977
+ * - `'lww'`: Last-write-wins (default for scalar fields)
978
+ * - `'counter'`: Sum of deltas from base (for numbers)
979
+ * - `'max'`: Keep the maximum value (for numbers/timestamps)
980
+ * - `'min'`: Keep the minimum value (for numbers/timestamps)
981
+ * - `'union'`: Set-union merge (default for arrays)
982
+ * - `'append-only'`: Concatenate additions (for arrays)
983
+ * - `'server-authoritative'`: Always prefer the remote/server value
984
+ */
985
+ merge(strategy) {
986
+ return new _FieldBuilder(
987
+ this._kind,
988
+ this._required,
989
+ this._defaultValue,
990
+ this._auto,
991
+ strategy
992
+ );
712
993
  }
713
994
  /** @internal Build the final FieldDescriptor. Used by defineSchema(). */
714
995
  _build() {
@@ -718,24 +999,88 @@ var FieldBuilder = class _FieldBuilder {
718
999
  defaultValue: this._defaultValue,
719
1000
  auto: this._auto,
720
1001
  enumValues: null,
721
- itemKind: null
1002
+ itemKind: null,
1003
+ mergeStrategy: this._mergeStrategy,
1004
+ transitions: null
722
1005
  };
723
1006
  }
724
1007
  };
725
1008
  var EnumFieldBuilder = class _EnumFieldBuilder extends FieldBuilder {
726
1009
  _enumValues;
727
- constructor(values, required = true, defaultValue = void 0, auto = false) {
728
- super("enum", required, defaultValue, auto);
1010
+ _transitions;
1011
+ constructor(values, required = true, defaultValue = void 0, auto = false, mergeStrategy = null, transitions = null) {
1012
+ super("enum", required, defaultValue, auto, mergeStrategy);
729
1013
  this._enumValues = values;
1014
+ this._transitions = transitions;
730
1015
  }
731
1016
  optional() {
732
- return new _EnumFieldBuilder(this._enumValues, false, this._defaultValue, this._auto);
1017
+ return new _EnumFieldBuilder(
1018
+ this._enumValues,
1019
+ false,
1020
+ this._defaultValue,
1021
+ this._auto,
1022
+ this._mergeStrategy,
1023
+ this._transitions
1024
+ );
733
1025
  }
734
1026
  default(value) {
735
- return new _EnumFieldBuilder(this._enumValues, false, value, this._auto);
1027
+ return new _EnumFieldBuilder(this._enumValues, false, value, this._auto, this._mergeStrategy, this._transitions);
736
1028
  }
737
1029
  auto() {
738
- return new _EnumFieldBuilder(this._enumValues, false, void 0, true);
1030
+ return new _EnumFieldBuilder(this._enumValues, false, void 0, true, this._mergeStrategy, this._transitions);
1031
+ }
1032
+ merge(strategy) {
1033
+ return new _EnumFieldBuilder(
1034
+ this._enumValues,
1035
+ this._required,
1036
+ this._defaultValue,
1037
+ this._auto,
1038
+ strategy,
1039
+ this._transitions
1040
+ );
1041
+ }
1042
+ /**
1043
+ * Declare allowed state transitions for this enum field.
1044
+ * Enables state machine validation during mutations and merges.
1045
+ *
1046
+ * @param map - Map of state to allowed next states
1047
+ *
1048
+ * @example
1049
+ * ```typescript
1050
+ * t.enum(['draft', 'pending', 'confirmed', 'cancelled']).transitions({
1051
+ * draft: ['pending', 'cancelled'],
1052
+ * pending: ['confirmed', 'cancelled'],
1053
+ * confirmed: [],
1054
+ * cancelled: [],
1055
+ * })
1056
+ * ```
1057
+ */
1058
+ transitions(map) {
1059
+ const validValues = new Set(this._enumValues);
1060
+ for (const [state, targets] of Object.entries(map)) {
1061
+ if (!validValues.has(state)) {
1062
+ throw new SchemaValidationError(
1063
+ `Invalid source state "${state}" in transition map. Valid values: ${[...validValues].join(", ")}`,
1064
+ { state, validValues: [...validValues] }
1065
+ );
1066
+ }
1067
+ for (const target of targets) {
1068
+ if (!validValues.has(target)) {
1069
+ throw new SchemaValidationError(
1070
+ `Invalid target state "${target}" in transition from "${state}". Valid values: ${[...validValues].join(", ")}`,
1071
+ { state, target, validValues: [...validValues] }
1072
+ );
1073
+ }
1074
+ }
1075
+ }
1076
+ return new _EnumFieldBuilder(
1077
+ this._enumValues,
1078
+ this._required,
1079
+ this._defaultValue,
1080
+ this._auto,
1081
+ this._mergeStrategy,
1082
+ map
1083
+ );
739
1084
  }
740
1085
  _build() {
741
1086
  return {
@@ -744,14 +1089,16 @@ var EnumFieldBuilder = class _EnumFieldBuilder extends FieldBuilder {
744
1089
  defaultValue: this._defaultValue,
745
1090
  auto: this._auto,
746
1091
  enumValues: this._enumValues,
747
- itemKind: null
1092
+ itemKind: null,
1093
+ mergeStrategy: this._mergeStrategy,
1094
+ transitions: this._transitions
748
1095
  };
749
1096
  }
750
1097
  };
751
1098
  var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
752
1099
  _itemKind;
753
- constructor(itemBuilder, required = true, defaultValue = void 0, auto = false) {
754
- super("array", required, defaultValue, auto);
1100
+ constructor(itemBuilder, required = true, defaultValue = void 0, auto = false, mergeStrategy = null) {
1101
+ super("array", required, defaultValue, auto, mergeStrategy);
755
1102
  this._itemKind = itemBuilder._build().kind;
756
1103
  }
757
1104
  optional() {
@@ -759,14 +1106,36 @@ var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
759
1106
  new FieldBuilder(this._itemKind),
760
1107
  false,
761
1108
  this._defaultValue,
762
- this._auto
1109
+ this._auto,
1110
+ this._mergeStrategy
763
1111
  );
764
1112
  }
765
1113
  default(value) {
766
- return new _ArrayFieldBuilder(new FieldBuilder(this._itemKind), false, value, this._auto);
1114
+ return new _ArrayFieldBuilder(
1115
+ new FieldBuilder(this._itemKind),
1116
+ false,
1117
+ value,
1118
+ this._auto,
1119
+ this._mergeStrategy
1120
+ );
767
1121
  }
768
1122
  auto() {
769
- return new _ArrayFieldBuilder(new FieldBuilder(this._itemKind), false, void 0, true);
1123
+ return new _ArrayFieldBuilder(
1124
+ new FieldBuilder(this._itemKind),
1125
+ false,
1126
+ void 0,
1127
+ true,
1128
+ this._mergeStrategy
1129
+ );
1130
+ }
1131
+ merge(strategy) {
1132
+ return new _ArrayFieldBuilder(
1133
+ new FieldBuilder(this._itemKind),
1134
+ this._required,
1135
+ this._defaultValue,
1136
+ this._auto,
1137
+ strategy
1138
+ );
770
1139
  }
771
1140
  _build() {
772
1141
  return {
@@ -775,7 +1144,9 @@ var ArrayFieldBuilder = class _ArrayFieldBuilder extends FieldBuilder {
775
1144
  defaultValue: this._defaultValue,
776
1145
  auto: this._auto,
777
1146
  enumValues: null,
778
- itemKind: this._itemKind
1147
+ itemKind: this._itemKind,
1148
+ mergeStrategy: this._mergeStrategy,
1149
+ transitions: null
779
1150
  };
780
1151
  }
781
1152
  };
@@ -829,10 +1200,14 @@ function validateRecord(collection, collectionDef, data, operationType) {
829
1200
  }
830
1201
  if (operationType === "update") {
831
1202
  if (hasValue) {
832
- if (value !== void 0 && value !== null) {
1203
+ if (isAtomicOp(value)) {
1204
+ result[fieldName] = value;
1205
+ } else if (value !== void 0 && value !== null) {
833
1206
  validateFieldValue(collection, fieldName, descriptor, value);
1207
+ result[fieldName] = value;
1208
+ } else {
1209
+ result[fieldName] = value;
834
1210
  }
835
- result[fieldName] = value;
836
1211
  }
837
1212
  continue;
838
1213
  }
@@ -995,34 +1370,34 @@ function matchesJsType(value, expected) {
995
1370
  function topologicalSort(operations) {
996
1371
  if (operations.length <= 1) return [...operations];
997
1372
  const opMap = /* @__PURE__ */ new Map();
998
- for (const op of operations) {
999
- opMap.set(op.id, op);
1373
+ for (const op2 of operations) {
1374
+ opMap.set(op2.id, op2);
1000
1375
  }
1001
1376
  const inDegree = /* @__PURE__ */ new Map();
1002
1377
  const dependents = /* @__PURE__ */ new Map();
1003
- for (const op of operations) {
1004
- if (!inDegree.has(op.id)) {
1005
- inDegree.set(op.id, 0);
1378
+ for (const op2 of operations) {
1379
+ if (!inDegree.has(op2.id)) {
1380
+ inDegree.set(op2.id, 0);
1006
1381
  }
1007
- if (!dependents.has(op.id)) {
1008
- dependents.set(op.id, []);
1382
+ if (!dependents.has(op2.id)) {
1383
+ dependents.set(op2.id, []);
1009
1384
  }
1010
- for (const depId of op.causalDeps) {
1385
+ for (const depId of op2.causalDeps) {
1011
1386
  if (opMap.has(depId)) {
1012
- inDegree.set(op.id, (inDegree.get(op.id) ?? 0) + 1);
1387
+ inDegree.set(op2.id, (inDegree.get(op2.id) ?? 0) + 1);
1013
1388
  const deps = dependents.get(depId);
1014
1389
  if (deps) {
1015
- deps.push(op.id);
1390
+ deps.push(op2.id);
1016
1391
  } else {
1017
- dependents.set(depId, [op.id]);
1392
+ dependents.set(depId, [op2.id]);
1018
1393
  }
1019
1394
  }
1020
1395
  }
1021
1396
  }
1022
1397
  const heap = new MinHeap(compareByTimestamp);
1023
- for (const op of operations) {
1024
- if ((inDegree.get(op.id) ?? 0) === 0) {
1025
- heap.push(op);
1398
+ for (const op2 of operations) {
1399
+ if ((inDegree.get(op2.id) ?? 0) === 0) {
1400
+ heap.push(op2);
1026
1401
  }
1027
1402
  }
1028
1403
  const result = [];
@@ -1034,8 +1409,8 @@ function topologicalSort(operations) {
1034
1409
  const deg = (inDegree.get(depId) ?? 0) - 1;
1035
1410
  inDegree.set(depId, deg);
1036
1411
  if (deg === 0) {
1037
- const op = opMap.get(depId);
1038
- if (op) heap.push(op);
1412
+ const op2 = opMap.get(depId);
1413
+ if (op2) heap.push(op2);
1039
1414
  }
1040
1415
  }
1041
1416
  }
@@ -1076,28 +1451,30 @@ var MinHeap = class {
1076
1451
  return top;
1077
1452
  }
1078
1453
  bubbleUp(index) {
1079
- while (index > 0) {
1080
- const parentIndex = index - 1 >> 1;
1081
- if (this.cmp(this.data[index], this.data[parentIndex]) >= 0) break;
1082
- this.swap(index, parentIndex);
1083
- index = parentIndex;
1454
+ let current = index;
1455
+ while (current > 0) {
1456
+ const parentIndex = current - 1 >> 1;
1457
+ if (this.cmp(this.data[current], this.data[parentIndex]) >= 0) break;
1458
+ this.swap(current, parentIndex);
1459
+ current = parentIndex;
1084
1460
  }
1085
1461
  }
1086
1462
  sinkDown(index) {
1087
1463
  const length = this.data.length;
1464
+ let current = index;
1088
1465
  while (true) {
1089
- let smallest = index;
1090
- const left = 2 * index + 1;
1091
- const right = 2 * index + 2;
1466
+ let smallest = current;
1467
+ const left = 2 * current + 1;
1468
+ const right = 2 * current + 2;
1092
1469
  if (left < length && this.cmp(this.data[left], this.data[smallest]) < 0) {
1093
1470
  smallest = left;
1094
1471
  }
1095
1472
  if (right < length && this.cmp(this.data[right], this.data[smallest]) < 0) {
1096
1473
  smallest = right;
1097
1474
  }
1098
- if (smallest === index) break;
1099
- this.swap(index, smallest);
1100
- index = smallest;
1475
+ if (smallest === current) break;
1476
+ this.swap(current, smallest);
1477
+ current = smallest;
1101
1478
  }
1102
1479
  }
1103
1480
  swap(i, j) {
@@ -1154,6 +1531,594 @@ function deserializeVector(s) {
1154
1531
  const entries = JSON.parse(s);
1155
1532
  return new Map(entries);
1156
1533
  }
1534
+
1535
+ // src/scopes/build-scope-map.ts
1536
+ function buildScopeMap(schema, scopeValues) {
1537
+ const result = {};
1538
+ for (const [collName, collDef] of Object.entries(schema.collections)) {
1539
+ if (collDef.scope.length > 0) {
1540
+ const collScope = {};
1541
+ for (const field of collDef.scope) {
1542
+ if (field in scopeValues) {
1543
+ collScope[field] = scopeValues[field];
1544
+ }
1545
+ }
1546
+ result[collName] = collScope;
1547
+ } else {
1548
+ result[collName] = {};
1549
+ }
1550
+ }
1551
+ return result;
1552
+ }
1553
+
1554
+ // src/sequences/sequence-format.ts
1555
+ function formatSequenceValue(template, counter, nodeId, now) {
1556
+ const date = now ?? /* @__PURE__ */ new Date();
1557
+ const yyyy = String(date.getUTCFullYear());
1558
+ const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
1559
+ const dd = String(date.getUTCDate()).padStart(2, "0");
1560
+ const dateStr = `${yyyy}${mm}${dd}`;
1561
+ return template.replace(/\{([^}]+)\}/g, (_match, token) => {
1562
+ if (token === "date") return dateStr;
1563
+ if (token === "node4") return nodeId.slice(0, 4);
1564
+ if (token === "node8") return nodeId.slice(0, 8);
1565
+ if (token === "seq") return String(counter).padStart(4, "0");
1566
+ if (token.startsWith("seq:")) {
1567
+ const width = Number.parseInt(token.slice(4), 10);
1568
+ if (Number.isNaN(width) || width < 1) {
1569
+ return String(counter).padStart(4, "0");
1570
+ }
1571
+ return String(counter).padStart(width, "0");
1572
+ }
1573
+ return `{${token}}`;
1574
+ });
1575
+ }
1576
+ function defaultSequenceFormat(name) {
1577
+ return `${name}-{seq:4}`;
1578
+ }
1579
+
1580
+ // src/migrations/migration-builder.ts
1581
+ var RollbackBuilder = class {
1582
+ _steps = [];
1583
+ /**
1584
+ * Add a field during rollback (to reverse a removeField).
1585
+ */
1586
+ addField(collection, field, builder) {
1587
+ this._steps.push({ type: "addField", collection, field, descriptor: builder._build() });
1588
+ return this;
1589
+ }
1590
+ /**
1591
+ * Remove a field during rollback (to reverse an addField).
1592
+ */
1593
+ removeField(collection, field) {
1594
+ this._steps.push({ type: "removeField", collection, field });
1595
+ return this;
1596
+ }
1597
+ /**
1598
+ * Rename a field during rollback (to reverse a renameField).
1599
+ */
1600
+ renameField(collection, from, to) {
1601
+ this._steps.push({ type: "renameField", collection, from, to });
1602
+ return this;
1603
+ }
1604
+ /**
1605
+ * Add an index during rollback (to reverse a removeIndex).
1606
+ */
1607
+ addIndex(collection, field) {
1608
+ this._steps.push({ type: "addIndex", collection, field });
1609
+ return this;
1610
+ }
1611
+ /**
1612
+ * Remove an index during rollback (to reverse an addIndex).
1613
+ */
1614
+ removeIndex(collection, field) {
1615
+ this._steps.push({ type: "removeIndex", collection, field });
1616
+ return this;
1617
+ }
1618
+ /**
1619
+ * Run a backfill during rollback (to reverse a previous backfill).
1620
+ */
1621
+ backfill(collection, transform) {
1622
+ this._steps.push({ type: "backfill", collection, transform });
1623
+ return this;
1624
+ }
1625
+ /**
1626
+ * Get the accumulated rollback steps.
1627
+ */
1628
+ _getSteps() {
1629
+ return [...this._steps];
1630
+ }
1631
+ };
1632
+ var MigrationBuilder = class _MigrationBuilder {
1633
+ steps;
1634
+ rollbackSteps;
1635
+ safelyReversible;
1636
+ constructor(steps = [], rollbackSteps, safelyReversible) {
1637
+ this.steps = steps;
1638
+ this.rollbackSteps = rollbackSteps;
1639
+ this.safelyReversible = safelyReversible ?? this._computeSafelyReversible(steps, rollbackSteps);
1640
+ }
1641
+ /**
1642
+ * Add a new field to a collection.
1643
+ * The field builder provides the type and default value.
1644
+ */
1645
+ addField(collection, field, builder) {
1646
+ return new _MigrationBuilder([
1647
+ ...this.steps,
1648
+ { type: "addField", collection, field, descriptor: builder._build() }
1649
+ ]);
1650
+ }
1651
+ /**
1652
+ * Remove a field from a collection.
1653
+ * Optionally accepts a field builder to preserve the descriptor for rollback.
1654
+ * Without the descriptor, rollback of this step requires a custom `.down()`.
1655
+ *
1656
+ * @param collection - The collection name
1657
+ * @param field - The field name to remove
1658
+ * @param builder - Optional field builder preserving the type info for rollback
1659
+ */
1660
+ removeField(collection, field, builder) {
1661
+ const step = builder ? { type: "removeField", collection, field, descriptor: builder._build() } : { type: "removeField", collection, field };
1662
+ return new _MigrationBuilder([...this.steps, step]);
1663
+ }
1664
+ /**
1665
+ * Rename a field in a collection.
1666
+ * Implemented as ALTER TABLE RENAME COLUMN (SQLite 3.25+).
1667
+ */
1668
+ renameField(collection, from, to) {
1669
+ return new _MigrationBuilder([...this.steps, { type: "renameField", collection, from, to }]);
1670
+ }
1671
+ /**
1672
+ * Add an index on a field.
1673
+ */
1674
+ addIndex(collection, field) {
1675
+ return new _MigrationBuilder([...this.steps, { type: "addIndex", collection, field }]);
1676
+ }
1677
+ /**
1678
+ * Remove an index on a field.
1679
+ */
1680
+ removeIndex(collection, field) {
1681
+ return new _MigrationBuilder([...this.steps, { type: "removeIndex", collection, field }]);
1682
+ }
1683
+ /**
1684
+ * Backfill records in a collection using a transform function.
1685
+ * The transform receives each record and returns the fields to update.
1686
+ * Runs after structural changes (addField, renameField, etc.).
1687
+ *
1688
+ * @param collection - The collection name
1689
+ * @param transform - Forward transform function
1690
+ * @param reverseTransform - Optional reverse transform for rollback support
1691
+ */
1692
+ backfill(collection, transform, reverseTransform) {
1693
+ return new _MigrationBuilder([
1694
+ ...this.steps,
1695
+ { type: "backfill", collection, transform, reverseTransform }
1696
+ ]);
1697
+ }
1698
+ /**
1699
+ * Define explicit rollback steps for this migration.
1700
+ * If not called, inverse steps are auto-generated from forward steps.
1701
+ *
1702
+ * @param fn - Function that receives a RollbackBuilder to define rollback steps
1703
+ * @returns A new MigrationBuilder with the rollback steps attached
1704
+ *
1705
+ * @example
1706
+ * ```typescript
1707
+ * migrate()
1708
+ * .addField('todos', 'priority', t.enum(['low', 'medium', 'high']).default('medium'))
1709
+ * .addIndex('todos', 'priority')
1710
+ * .down((rollback) => {
1711
+ * rollback
1712
+ * .removeIndex('todos', 'priority')
1713
+ * .removeField('todos', 'priority')
1714
+ * })
1715
+ * ```
1716
+ */
1717
+ down(fn) {
1718
+ const rollbackBuilder = new RollbackBuilder();
1719
+ fn(rollbackBuilder);
1720
+ const rbSteps = rollbackBuilder._getSteps();
1721
+ return new _MigrationBuilder(this.steps, rbSteps, true);
1722
+ }
1723
+ /**
1724
+ * Determine if a migration is safely reversible based on its steps.
1725
+ * A migration is not safely reversible if:
1726
+ * - It has a backfill step without a reverseTransform and no explicit rollback
1727
+ * - It has a removeField without a stored descriptor and no explicit rollback
1728
+ */
1729
+ _computeSafelyReversible(steps, rollbackSteps) {
1730
+ if (rollbackSteps !== void 0) {
1731
+ return true;
1732
+ }
1733
+ for (const step of steps) {
1734
+ if (step.type === "backfill" && !step.reverseTransform) {
1735
+ return false;
1736
+ }
1737
+ if (step.type === "removeField" && !step.descriptor) {
1738
+ return false;
1739
+ }
1740
+ }
1741
+ return true;
1742
+ }
1743
+ };
1744
+ function migrate() {
1745
+ return new MigrationBuilder();
1746
+ }
1747
+
1748
+ // src/migrations/migration-rollback.ts
1749
+ var MigrationRollbackError = class extends KoraError {
1750
+ constructor(step) {
1751
+ super(
1752
+ `Cannot auto-generate rollback for "${step.type}" step on collection "${step.collection}". Provide an explicit .down() definition for this migration.`,
1753
+ "MIGRATION_ROLLBACK",
1754
+ { stepType: step.type, collection: step.collection }
1755
+ );
1756
+ this.name = "MigrationRollbackError";
1757
+ }
1758
+ };
1759
+ function canAutoRollback(step) {
1760
+ switch (step.type) {
1761
+ case "addField":
1762
+ case "addIndex":
1763
+ case "removeIndex":
1764
+ case "renameField":
1765
+ return true;
1766
+ case "removeField":
1767
+ case "backfill":
1768
+ return false;
1769
+ }
1770
+ }
1771
+ function generateRollbackSteps(forwardSteps) {
1772
+ const rollbackSteps = [];
1773
+ for (let i = forwardSteps.length - 1; i >= 0; i--) {
1774
+ const step = forwardSteps[i];
1775
+ if (step === void 0) continue;
1776
+ const rollback = generateSingleRollbackStep(step);
1777
+ rollbackSteps.push(rollback);
1778
+ }
1779
+ return rollbackSteps;
1780
+ }
1781
+ function generateSingleRollbackStep(step) {
1782
+ switch (step.type) {
1783
+ case "addField":
1784
+ return { type: "removeField", collection: step.collection, field: step.field };
1785
+ case "removeField":
1786
+ if (step.descriptor) {
1787
+ return {
1788
+ type: "addField",
1789
+ collection: step.collection,
1790
+ field: step.field,
1791
+ descriptor: step.descriptor
1792
+ };
1793
+ }
1794
+ throw new MigrationRollbackError(step);
1795
+ case "renameField":
1796
+ return { type: "renameField", collection: step.collection, from: step.to, to: step.from };
1797
+ case "addIndex":
1798
+ return { type: "removeIndex", collection: step.collection, field: step.field };
1799
+ case "removeIndex":
1800
+ return { type: "addIndex", collection: step.collection, field: step.field };
1801
+ case "backfill":
1802
+ if (step.reverseTransform) {
1803
+ return {
1804
+ type: "backfill",
1805
+ collection: step.collection,
1806
+ transform: step.reverseTransform
1807
+ };
1808
+ }
1809
+ throw new MigrationRollbackError(step);
1810
+ }
1811
+ }
1812
+ function createReversibleMigration(upSteps, downSteps, fromVersion, toVersion) {
1813
+ const down = downSteps !== null ? [...downSteps] : generateRollbackSteps(upSteps);
1814
+ return {
1815
+ up: [...upSteps],
1816
+ down,
1817
+ fromVersion,
1818
+ toVersion
1819
+ };
1820
+ }
1821
+
1822
+ // src/migrations/migration-sql.ts
1823
+ function migrationStepsToSQL(steps) {
1824
+ const statements = [];
1825
+ for (const step of steps) {
1826
+ switch (step.type) {
1827
+ case "addField":
1828
+ statements.push(addFieldSQL(step.collection, step.field, step.descriptor));
1829
+ break;
1830
+ case "removeField":
1831
+ statements.push(`ALTER TABLE ${step.collection} DROP COLUMN ${step.field}`);
1832
+ break;
1833
+ case "renameField":
1834
+ statements.push(`ALTER TABLE ${step.collection} RENAME COLUMN ${step.from} TO ${step.to}`);
1835
+ break;
1836
+ case "addIndex":
1837
+ statements.push(
1838
+ `CREATE INDEX IF NOT EXISTS idx_${step.collection}_${step.field} ON ${step.collection} (${step.field})`
1839
+ );
1840
+ break;
1841
+ case "removeIndex":
1842
+ statements.push(`DROP INDEX IF EXISTS idx_${step.collection}_${step.field}`);
1843
+ break;
1844
+ case "backfill":
1845
+ break;
1846
+ }
1847
+ }
1848
+ return statements;
1849
+ }
1850
+ function rollbackStepsToSQL(migration) {
1851
+ const rollbackSteps = migration.rollbackSteps ?? generateRollbackSteps(migration.steps);
1852
+ return migrationStepsToSQL(rollbackSteps);
1853
+ }
1854
+ function addFieldSQL(collection, field, descriptor) {
1855
+ const sqlType = mapFieldType2(descriptor);
1856
+ const parts = [`ALTER TABLE ${collection} ADD COLUMN ${field}`, sqlType];
1857
+ if (descriptor.defaultValue !== void 0) {
1858
+ parts.push(`DEFAULT ${sqlDefault2(descriptor.defaultValue)}`);
1859
+ }
1860
+ if (descriptor.kind === "enum" && descriptor.enumValues) {
1861
+ const values = descriptor.enumValues.map((v) => `'${v}'`).join(", ");
1862
+ parts.push(`CHECK (${field} IN (${values}))`);
1863
+ }
1864
+ return parts.join(" ");
1865
+ }
1866
+ function mapFieldType2(descriptor) {
1867
+ switch (descriptor.kind) {
1868
+ case "string":
1869
+ return "TEXT";
1870
+ case "number":
1871
+ return "REAL";
1872
+ case "boolean":
1873
+ return "INTEGER";
1874
+ case "enum":
1875
+ return "TEXT";
1876
+ case "timestamp":
1877
+ return "INTEGER";
1878
+ case "array":
1879
+ return "TEXT";
1880
+ case "richtext":
1881
+ return "BLOB";
1882
+ }
1883
+ }
1884
+ function sqlDefault2(value) {
1885
+ if (value === null) return "NULL";
1886
+ if (typeof value === "string") return `'${value}'`;
1887
+ if (typeof value === "number") return String(value);
1888
+ if (typeof value === "boolean") return value ? "1" : "0";
1889
+ return `'${JSON.stringify(value)}'`;
1890
+ }
1891
+
1892
+ // src/codegen/proto-generator.ts
1893
+ var SCALAR_TYPE_MAP = {
1894
+ string: "string",
1895
+ number: "double",
1896
+ boolean: "bool",
1897
+ timestamp: "int64",
1898
+ richtext: "bytes"
1899
+ };
1900
+ var ARRAY_ITEM_TYPE_MAP = {
1901
+ string: "string",
1902
+ number: "double",
1903
+ boolean: "bool",
1904
+ timestamp: "int64"
1905
+ };
1906
+ function toPascalCase(name) {
1907
+ return name.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1908
+ }
1909
+ function toSnakeCase(name) {
1910
+ return name.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
1911
+ }
1912
+ function resolveProtoType(fieldName, descriptor, messageName) {
1913
+ if (descriptor.kind === "enum") {
1914
+ return `${messageName}${toPascalCase(fieldName)}`;
1915
+ }
1916
+ if (descriptor.kind === "array") {
1917
+ const itemType = descriptor.itemKind ? ARRAY_ITEM_TYPE_MAP[descriptor.itemKind] ?? "string" : "string";
1918
+ return itemType;
1919
+ }
1920
+ return SCALAR_TYPE_MAP[descriptor.kind];
1921
+ }
1922
+ function generateEnumBlock(enumTypeName, enumValues, indent) {
1923
+ const lines = [];
1924
+ lines.push(`${indent}enum ${enumTypeName} {`);
1925
+ lines.push(`${indent} ${enumTypeName.toUpperCase()}_UNSPECIFIED = 0;`);
1926
+ for (let i = 0; i < enumValues.length; i++) {
1927
+ const value = enumValues[i];
1928
+ if (value === void 0) continue;
1929
+ lines.push(`${indent} ${enumTypeName.toUpperCase()}_${value.toUpperCase()} = ${i + 1};`);
1930
+ }
1931
+ lines.push(`${indent}}`);
1932
+ return lines.join("\n");
1933
+ }
1934
+ function generateCollectionMessage(collectionName, collection, typeMap) {
1935
+ const messageName = `${toPascalCase(collectionName)}Record`;
1936
+ const lines = [];
1937
+ const nestedEnums = [];
1938
+ lines.push(`message ${messageName} {`);
1939
+ lines.push(" string id = 1;");
1940
+ typeMap.set(`${collectionName}.id`, "string");
1941
+ let fieldNumber = 2;
1942
+ const entries = Object.entries(collection.fields);
1943
+ for (const [fieldName, descriptor] of entries) {
1944
+ const protoFieldName = toSnakeCase(fieldName);
1945
+ const protoType = resolveProtoType(fieldName, descriptor, messageName);
1946
+ const mapKey = `${collectionName}.${fieldName}`;
1947
+ if (descriptor.kind === "enum" && descriptor.enumValues) {
1948
+ const enumTypeName = `${messageName}${toPascalCase(fieldName)}`;
1949
+ nestedEnums.push(generateEnumBlock(enumTypeName, descriptor.enumValues, " "));
1950
+ typeMap.set(mapKey, enumTypeName);
1951
+ lines.push(` ${enumTypeName} ${protoFieldName} = ${fieldNumber};`);
1952
+ } else if (descriptor.kind === "array") {
1953
+ typeMap.set(mapKey, `repeated ${protoType}`);
1954
+ lines.push(` repeated ${protoType} ${protoFieldName} = ${fieldNumber};`);
1955
+ } else {
1956
+ typeMap.set(mapKey, protoType);
1957
+ lines.push(` ${protoType} ${protoFieldName} = ${fieldNumber};`);
1958
+ }
1959
+ fieldNumber++;
1960
+ }
1961
+ if (nestedEnums.length > 0) {
1962
+ lines.push("");
1963
+ for (const enumBlock of nestedEnums) {
1964
+ lines.push(enumBlock);
1965
+ }
1966
+ }
1967
+ lines.push("}");
1968
+ return lines.join("\n");
1969
+ }
1970
+ var KORA_OPERATION_MESSAGE = `message KoraOperation {
1971
+ string id = 1;
1972
+ string node_id = 2;
1973
+ string type = 3;
1974
+ string collection = 4;
1975
+ string record_id = 5;
1976
+ bytes data = 6;
1977
+ bytes previous_data = 7;
1978
+ int64 wall_time = 8;
1979
+ int32 logical = 9;
1980
+ string timestamp_node_id = 10;
1981
+ int64 sequence_number = 11;
1982
+ repeated string causal_deps = 12;
1983
+ int32 schema_version = 13;
1984
+ }`;
1985
+ var OPERATION_BATCH_MESSAGE = `message OperationBatch {
1986
+ repeated KoraOperation operations = 1;
1987
+ bool is_final = 2;
1988
+ }`;
1989
+ var HANDSHAKE_MESSAGE = `message HandshakeMessage {
1990
+ map<string, int64> version_vector = 1;
1991
+ int32 schema_version = 2;
1992
+ string node_id = 3;
1993
+ }`;
1994
+ var HANDSHAKE_RESPONSE_MESSAGE = `message HandshakeResponse {
1995
+ map<string, int64> version_vector = 1;
1996
+ int32 schema_version = 2;
1997
+ }`;
1998
+ var ACKNOWLEDGMENT_MESSAGE = `message Acknowledgment {
1999
+ int64 sequence_number = 1;
2000
+ string node_id = 2;
2001
+ }`;
2002
+ function buildJsonDescriptor(schema) {
2003
+ const nested = {};
2004
+ for (const [collectionName, collection] of Object.entries(schema.collections)) {
2005
+ const messageName = `${toPascalCase(collectionName)}Record`;
2006
+ const fields = {
2007
+ id: { type: "string", id: 1 }
2008
+ };
2009
+ const nestedTypes = {};
2010
+ let fieldNumber = 2;
2011
+ for (const [fieldName, descriptor] of Object.entries(collection.fields)) {
2012
+ const protoFieldName = toSnakeCase(fieldName);
2013
+ const fieldDef = { id: fieldNumber };
2014
+ if (descriptor.kind === "enum" && descriptor.enumValues) {
2015
+ const enumTypeName = `${messageName}${toPascalCase(fieldName)}`;
2016
+ fieldDef.type = enumTypeName;
2017
+ const enumValuesObj = {
2018
+ [`${enumTypeName.toUpperCase()}_UNSPECIFIED`]: 0
2019
+ };
2020
+ for (let i = 0; i < descriptor.enumValues.length; i++) {
2021
+ const val = descriptor.enumValues[i];
2022
+ if (val === void 0) continue;
2023
+ enumValuesObj[`${enumTypeName.toUpperCase()}_${val.toUpperCase()}`] = i + 1;
2024
+ }
2025
+ nestedTypes[enumTypeName] = { values: enumValuesObj };
2026
+ } else if (descriptor.kind === "array") {
2027
+ const itemType = descriptor.itemKind ? ARRAY_ITEM_TYPE_MAP[descriptor.itemKind] ?? "string" : "string";
2028
+ fieldDef.type = itemType;
2029
+ fieldDef.rule = "repeated";
2030
+ } else {
2031
+ fieldDef.type = SCALAR_TYPE_MAP[descriptor.kind];
2032
+ }
2033
+ fields[protoFieldName] = fieldDef;
2034
+ fieldNumber++;
2035
+ }
2036
+ const messageDescriptor = { fields };
2037
+ if (Object.keys(nestedTypes).length > 0) {
2038
+ messageDescriptor.nested = nestedTypes;
2039
+ }
2040
+ nested[messageName] = messageDescriptor;
2041
+ }
2042
+ nested.KoraOperation = {
2043
+ fields: {
2044
+ id: { type: "string", id: 1 },
2045
+ node_id: { type: "string", id: 2 },
2046
+ type: { type: "string", id: 3 },
2047
+ collection: { type: "string", id: 4 },
2048
+ record_id: { type: "string", id: 5 },
2049
+ data: { type: "bytes", id: 6 },
2050
+ previous_data: { type: "bytes", id: 7 },
2051
+ wall_time: { type: "int64", id: 8 },
2052
+ logical: { type: "int32", id: 9 },
2053
+ timestamp_node_id: { type: "string", id: 10 },
2054
+ sequence_number: { type: "int64", id: 11 },
2055
+ causal_deps: { type: "string", id: 12, rule: "repeated" },
2056
+ schema_version: { type: "int32", id: 13 }
2057
+ }
2058
+ };
2059
+ nested.OperationBatch = {
2060
+ fields: {
2061
+ operations: { type: "KoraOperation", id: 1, rule: "repeated" },
2062
+ is_final: { type: "bool", id: 2 }
2063
+ }
2064
+ };
2065
+ nested.HandshakeMessage = {
2066
+ fields: {
2067
+ version_vector: { keyType: "string", type: "int64", id: 1 },
2068
+ schema_version: { type: "int32", id: 2 },
2069
+ node_id: { type: "string", id: 3 }
2070
+ }
2071
+ };
2072
+ nested.HandshakeResponse = {
2073
+ fields: {
2074
+ version_vector: { keyType: "string", type: "int64", id: 1 },
2075
+ schema_version: { type: "int32", id: 2 }
2076
+ }
2077
+ };
2078
+ nested.Acknowledgment = {
2079
+ fields: {
2080
+ sequence_number: { type: "int64", id: 1 },
2081
+ node_id: { type: "string", id: 2 }
2082
+ }
2083
+ };
2084
+ return {
2085
+ nested: {
2086
+ kora: { nested }
2087
+ }
2088
+ };
2089
+ }
2090
+ function generateProtoDefinitions(schema) {
2091
+ const typeMap = /* @__PURE__ */ new Map();
2092
+ const sections = [];
2093
+ sections.push('syntax = "proto3";');
2094
+ sections.push("");
2095
+ sections.push("package kora;");
2096
+ const collectionEntries = Object.entries(schema.collections);
2097
+ if (collectionEntries.length > 0) {
2098
+ sections.push("");
2099
+ sections.push("// Collection record messages");
2100
+ for (const [collectionName, collection] of collectionEntries) {
2101
+ sections.push("");
2102
+ sections.push(generateCollectionMessage(collectionName, collection, typeMap));
2103
+ }
2104
+ }
2105
+ sections.push("");
2106
+ sections.push("// Sync protocol messages");
2107
+ sections.push("");
2108
+ sections.push(KORA_OPERATION_MESSAGE);
2109
+ sections.push("");
2110
+ sections.push(OPERATION_BATCH_MESSAGE);
2111
+ sections.push("");
2112
+ sections.push(HANDSHAKE_MESSAGE);
2113
+ sections.push("");
2114
+ sections.push(HANDSHAKE_RESPONSE_MESSAGE);
2115
+ sections.push("");
2116
+ sections.push(ACKNOWLEDGMENT_MESSAGE);
2117
+ const proto = `${sections.join("\n")}
2118
+ `;
2119
+ const jsonDescriptor = buildJsonDescriptor(schema);
2120
+ return { proto, typeMap, jsonDescriptor };
2121
+ }
1157
2122
  // Annotate the CommonJS export names for ESM import in node:
1158
2123
  0 && (module.exports = {
1159
2124
  ArrayFieldBuilder,
@@ -1165,27 +2130,47 @@ function deserializeVector(s) {
1165
2130
  KoraError,
1166
2131
  MERGE_STRATEGIES,
1167
2132
  MergeConflictError,
2133
+ MigrationBuilder,
2134
+ MigrationRollbackError,
1168
2135
  OperationError,
2136
+ RollbackBuilder,
1169
2137
  SchemaValidationError,
1170
2138
  StorageError,
1171
2139
  SyncError,
1172
2140
  advanceVector,
2141
+ buildScopeMap,
2142
+ buildStateMachineConstraints,
2143
+ canAutoRollback,
1173
2144
  computeDelta,
1174
2145
  createOperation,
2146
+ createReversibleMigration,
1175
2147
  createVersionVector,
2148
+ defaultSequenceFormat,
1176
2149
  defineSchema,
1177
2150
  deserializeVector,
1178
2151
  dominates,
1179
2152
  extractTimestamp,
2153
+ formatSequenceValue,
1180
2154
  generateFullDDL,
2155
+ generateProtoDefinitions,
2156
+ generateRollbackSteps,
1181
2157
  generateSQL,
1182
2158
  generateUUIDv7,
2159
+ getTransitionMap,
2160
+ isAtomicOp,
1183
2161
  isValidOperation,
1184
2162
  isValidUUIDv7,
1185
2163
  mergeVectors,
2164
+ migrate,
2165
+ migrationStepsToSQL,
2166
+ op,
2167
+ resolveAtomicOp,
2168
+ rollbackStepsToSQL,
1186
2169
  serializeVector,
1187
2170
  t,
2171
+ toAtomicOp,
1188
2172
  validateRecord,
2173
+ validateTransition,
1189
2174
  vectorsEqual,
1190
2175
  verifyOperationIntegrity
1191
2176
  });