@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/{chunk-KQYXWUFV.js → chunk-CFP5YFXC.js} +26 -16
- package/dist/chunk-CFP5YFXC.js.map +1 -0
- package/dist/events-BMulupSB.d.cts +844 -0
- package/dist/events-BMulupSB.d.ts +844 -0
- package/dist/index.cjs +1050 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +385 -69
- package/dist/index.d.ts +385 -69
- package/dist/index.js +977 -22
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +19 -13
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +2 -2
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-KQYXWUFV.js.map +0 -1
- package/dist/events-7Fhdjxd2.d.cts +0 -327
- package/dist/events-7Fhdjxd2.d.ts +0 -327
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
|
|
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(
|
|
420
|
+
async function verifyOperationIntegrity(op2) {
|
|
394
421
|
const input = {
|
|
395
|
-
nodeId:
|
|
396
|
-
type:
|
|
397
|
-
collection:
|
|
398
|
-
recordId:
|
|
399
|
-
data:
|
|
400
|
-
previousData:
|
|
401
|
-
sequenceNumber:
|
|
402
|
-
causalDeps:
|
|
403
|
-
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(
|
|
433
|
+
const serializedTs = HybridLogicalClock.serialize(op2.timestamp);
|
|
406
434
|
const expectedId = await computeOperationId(input, serializedTs);
|
|
407
|
-
return
|
|
435
|
+
return op2.id === expectedId;
|
|
408
436
|
}
|
|
409
437
|
function isValidOperation(value) {
|
|
410
438
|
if (typeof value !== "object" || value === null) return false;
|
|
411
|
-
const
|
|
412
|
-
return typeof
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
728
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
999
|
-
opMap.set(
|
|
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
|
|
1004
|
-
if (!inDegree.has(
|
|
1005
|
-
inDegree.set(
|
|
1378
|
+
for (const op2 of operations) {
|
|
1379
|
+
if (!inDegree.has(op2.id)) {
|
|
1380
|
+
inDegree.set(op2.id, 0);
|
|
1006
1381
|
}
|
|
1007
|
-
if (!dependents.has(
|
|
1008
|
-
dependents.set(
|
|
1382
|
+
if (!dependents.has(op2.id)) {
|
|
1383
|
+
dependents.set(op2.id, []);
|
|
1009
1384
|
}
|
|
1010
|
-
for (const depId of
|
|
1385
|
+
for (const depId of op2.causalDeps) {
|
|
1011
1386
|
if (opMap.has(depId)) {
|
|
1012
|
-
inDegree.set(
|
|
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(
|
|
1390
|
+
deps.push(op2.id);
|
|
1016
1391
|
} else {
|
|
1017
|
-
dependents.set(depId, [
|
|
1392
|
+
dependents.set(depId, [op2.id]);
|
|
1018
1393
|
}
|
|
1019
1394
|
}
|
|
1020
1395
|
}
|
|
1021
1396
|
}
|
|
1022
1397
|
const heap = new MinHeap(compareByTimestamp);
|
|
1023
|
-
for (const
|
|
1024
|
-
if ((inDegree.get(
|
|
1025
|
-
heap.push(
|
|
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
|
|
1038
|
-
if (
|
|
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
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
this.
|
|
1083
|
-
|
|
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 =
|
|
1090
|
-
const left = 2 *
|
|
1091
|
-
const right = 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 ===
|
|
1099
|
-
this.swap(
|
|
1100
|
-
|
|
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
|
});
|