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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/internal.cjs CHANGED
@@ -20,99 +20,35 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/internal.ts
21
21
  var internal_exports = {};
22
22
  __export(internal_exports, {
23
+ SCHEMA_CEILING_DIRECTIVE: () => SCHEMA_CEILING_DIRECTIVE,
23
24
  SimpleEventEmitter: () => SimpleEventEmitter,
24
25
  canonicalize: () => canonicalize,
26
+ collectionIndexName: () => collectionIndexName,
27
+ compareStamps: () => compareStamps,
25
28
  computeOperationId: () => computeOperationId,
29
+ enumCheckConstraint: () => enumCheckConstraint,
30
+ isKoraInternalColumn: () => isKoraInternalColumn,
31
+ isKoraSqliteEnumCheck: () => isKoraSqliteEnumCheck,
32
+ isPostgresEnumCheckDefinition: () => isPostgresEnumCheckDefinition,
33
+ legacyCollectionIndexName: () => legacyCollectionIndexName,
34
+ parseEnumCheckDefinition: () => parseEnumCheckDefinition,
35
+ parseSqliteCheckConstraints: () => parseSqliteCheckConstraints,
36
+ planField: () => planField,
37
+ planPostgresConstraintRelaxation: () => planPostgresConstraintRelaxation,
38
+ planSqliteConstraintRelaxation: () => planSqliteConstraintRelaxation,
39
+ readSqliteTableCatalog: () => readSqliteTableCatalog,
40
+ sqlDefaultLiteral: () => sqlDefaultLiteral,
41
+ sqlStringLiteral: () => sqlStringLiteral,
42
+ sqliteCheckConstraintDefinition: () => sqliteCheckConstraintDefinition,
43
+ sqliteCheckReferencesColumn: () => sqliteCheckReferencesColumn,
44
+ sqliteConstraintRelaxationStatements: () => sqliteConstraintRelaxationStatements,
45
+ sqliteTableNeedsRelaxation: () => sqliteTableNeedsRelaxation,
46
+ stampOf: () => stampOf,
26
47
  topologicalSort: () => topologicalSort,
27
48
  validateOperationParams: () => validateOperationParams
28
49
  });
29
50
  module.exports = __toCommonJS(internal_exports);
30
51
 
31
- // src/operations/content-hash.ts
32
- async function computeOperationId(input, timestamp) {
33
- const hashInput = {
34
- type: input.type,
35
- collection: input.collection,
36
- recordId: input.recordId,
37
- data: input.data,
38
- timestamp,
39
- nodeId: input.nodeId
40
- };
41
- if (input.atomicOps !== void 0 && Object.keys(input.atomicOps).length > 0) {
42
- hashInput.atomicOps = input.atomicOps;
43
- }
44
- const canonical = canonicalize(hashInput);
45
- const encoded = new TextEncoder().encode(canonical);
46
- const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded);
47
- return bufferToHex(hashBuffer);
48
- }
49
- function canonicalize(obj) {
50
- if (obj === null) {
51
- return "null";
52
- }
53
- if (obj === void 0) {
54
- return "null";
55
- }
56
- if (typeof obj !== "object") {
57
- return JSON.stringify(obj);
58
- }
59
- if (Array.isArray(obj)) {
60
- const items = obj.map((item) => canonicalize(item));
61
- return `[${items.join(",")}]`;
62
- }
63
- const keys = Object.keys(obj).sort();
64
- const pairs = keys.map((key) => {
65
- const value = obj[key];
66
- return `${JSON.stringify(key)}:${canonicalize(value === void 0 ? null : value)}`;
67
- });
68
- return `{${pairs.join(",")}}`;
69
- }
70
- function bufferToHex(buffer) {
71
- const bytes = new Uint8Array(buffer);
72
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
73
- }
74
-
75
- // src/events/event-emitter.ts
76
- var SimpleEventEmitter = class {
77
- listeners = /* @__PURE__ */ new Map();
78
- on(type, listener) {
79
- let set = this.listeners.get(type);
80
- if (!set) {
81
- set = /* @__PURE__ */ new Set();
82
- this.listeners.set(type, set);
83
- }
84
- set.add(listener);
85
- return () => {
86
- this.off(type, listener);
87
- };
88
- }
89
- off(type, listener) {
90
- const set = this.listeners.get(type);
91
- if (set) {
92
- set.delete(listener);
93
- if (set.size === 0) {
94
- this.listeners.delete(type);
95
- }
96
- }
97
- }
98
- emit(event) {
99
- const set = this.listeners.get(event.type);
100
- if (!set) return;
101
- for (const listener of set) {
102
- ;
103
- listener(event);
104
- }
105
- }
106
- /** Remove all listeners for all event types. */
107
- clear() {
108
- this.listeners.clear();
109
- }
110
- /** Get the number of listeners for a specific event type. */
111
- listenerCount(type) {
112
- return this.listeners.get(type)?.size ?? 0;
113
- }
114
- };
115
-
116
52
  // src/errors/error-fixes.ts
117
53
  var KORA_ERROR_FIX_SUGGESTIONS = {
118
54
  SCHEMA_VALIDATION: "Review your defineSchema() definition: every collection needs at least one field and a positive version.",
@@ -401,6 +337,26 @@ var HybridLogicalClock = class _HybridLogicalClock {
401
337
  }
402
338
  };
403
339
 
340
+ // src/operations/op-data-binary.ts
341
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
342
+ var BASE64_REVERSE = new Map(
343
+ Array.from(BASE64_ALPHABET, (char, index) => [char, index])
344
+ );
345
+ function bytesToBase64(bytes) {
346
+ let out = "";
347
+ for (let i = 0; i < bytes.length; i += 3) {
348
+ const b0 = bytes[i] ?? 0;
349
+ const b1 = bytes[i + 1] ?? 0;
350
+ const b2 = bytes[i + 2] ?? 0;
351
+ const triple = b0 << 16 | b1 << 8 | b2;
352
+ out += BASE64_ALPHABET[triple >> 18 & 63] ?? "";
353
+ out += BASE64_ALPHABET[triple >> 12 & 63] ?? "";
354
+ out += i + 1 < bytes.length ? BASE64_ALPHABET[triple >> 6 & 63] ?? "" : "=";
355
+ out += i + 2 < bytes.length ? BASE64_ALPHABET[triple & 63] ?? "" : "=";
356
+ }
357
+ return out;
358
+ }
359
+
404
360
  // src/operations/operation.ts
405
361
  function validateOperationParams(input) {
406
362
  if (!input.nodeId || typeof input.nodeId !== "string") {
@@ -467,6 +423,285 @@ function validateOperationParams(input) {
467
423
  }
468
424
  }
469
425
 
426
+ // src/operations/canonical-body.ts
427
+ var NonCanonicalValueError = class extends KoraError {
428
+ constructor(message, path, receivedType) {
429
+ super(message, "NON_CANONICAL_VALUE", { path, receivedType });
430
+ this.path = path;
431
+ this.receivedType = receivedType;
432
+ this.name = "NonCanonicalValueError";
433
+ }
434
+ path;
435
+ receivedType;
436
+ };
437
+ function canonicalizeOperationBody(input) {
438
+ const isUpdate = input.type === "update";
439
+ const where = `${input.collection}/${input.recordId}`;
440
+ const data = canonicalTopLevel(input.data, isUpdate, "data", where);
441
+ const previousData = canonicalTopLevel(input.previousData, isUpdate, "previousData", where);
442
+ let atomicOps = input.atomicOps;
443
+ if (atomicOps !== void 0) {
444
+ const canonical2 = canonicalTopLevel(
445
+ atomicOps,
446
+ false,
447
+ "atomicOps",
448
+ where
449
+ );
450
+ atomicOps = canonical2 ?? void 0;
451
+ }
452
+ if (data === input.data && previousData === input.previousData && atomicOps === input.atomicOps) {
453
+ return input;
454
+ }
455
+ return {
456
+ ...input,
457
+ data,
458
+ previousData,
459
+ ...atomicOps !== void 0 ? { atomicOps } : {}
460
+ };
461
+ }
462
+ function canonicalTopLevel(record, undefinedIsNull, label, where) {
463
+ if (record === null || record === void 0) return null;
464
+ if (!isPlainObject(record)) {
465
+ throw new NonCanonicalValueError(
466
+ `Operation ${label} of ${where} must be a plain object, got ${describe(record)}.`,
467
+ label,
468
+ describe(record)
469
+ );
470
+ }
471
+ let changed = false;
472
+ const out = {};
473
+ for (const [key, member] of Object.entries(record)) {
474
+ if (member === void 0) {
475
+ changed = true;
476
+ if (undefinedIsNull) out[key] = null;
477
+ continue;
478
+ }
479
+ const next = canonical(member, `${where}.${key}`, /* @__PURE__ */ new Set());
480
+ if (!Object.is(next, member)) changed = true;
481
+ out[key] = next;
482
+ }
483
+ if (Object.getPrototypeOf(record) !== Object.prototype) changed = true;
484
+ return changed ? out : record;
485
+ }
486
+ function canonical(value, path, ancestors) {
487
+ switch (typeof value) {
488
+ case "string":
489
+ case "boolean":
490
+ return value;
491
+ case "number":
492
+ if (!Number.isFinite(value)) {
493
+ throw refusal(path, String(value), 'Use a finite number, or null for "no value".');
494
+ }
495
+ return Object.is(value, -0) ? 0 : value;
496
+ case "undefined":
497
+ return null;
498
+ case "bigint":
499
+ throw refusal(path, "bigint", "Convert it to a number or a string.");
500
+ case "function":
501
+ throw refusal(path, "function", "Store data only, not functions.");
502
+ case "symbol":
503
+ throw refusal(path, "symbol", "Use a string instead.");
504
+ }
505
+ if (value === null) return null;
506
+ const object = value;
507
+ if (object instanceof Uint8Array) return { $koraBytes: bytesToBase64(object) };
508
+ if (object instanceof ArrayBuffer) return { $koraBytes: bytesToBase64(new Uint8Array(object)) };
509
+ if (object instanceof Date) {
510
+ const time = object.getTime();
511
+ if (Number.isNaN(time)) {
512
+ throw refusal(path, "invalid Date", "Pass a valid Date, or null.");
513
+ }
514
+ return object.toISOString();
515
+ }
516
+ if (ancestors.has(object)) {
517
+ throw refusal(path, "circular reference", "Remove the cycle: JSON cannot hold it.");
518
+ }
519
+ if (Array.isArray(object)) {
520
+ ancestors.add(object);
521
+ let changed2 = false;
522
+ const out2 = [];
523
+ for (let index = 0; index < object.length; index++) {
524
+ if (!(index in object)) {
525
+ ancestors.delete(object);
526
+ throw refusal(
527
+ `${path}[${String(index)}]`,
528
+ "array hole",
529
+ "Use a dense array (fill the gap with null)."
530
+ );
531
+ }
532
+ const item = object[index];
533
+ const next = canonical(item, `${path}[${String(index)}]`, ancestors);
534
+ if (!Object.is(next, item)) changed2 = true;
535
+ out2.push(next);
536
+ }
537
+ ancestors.delete(object);
538
+ return changed2 ? out2 : object;
539
+ }
540
+ if (!isPlainObject(object)) {
541
+ const type = describe(object);
542
+ throw refusal(
543
+ path,
544
+ type,
545
+ type === "Map" ? "Convert it with Object.fromEntries(map)." : type === "Set" ? "Convert it with [...set]." : "Convert it to a plain object (JSON data only)."
546
+ );
547
+ }
548
+ if (typeof object.toJSON === "function") {
549
+ throw refusal(path, "object with toJSON", "Store the value toJSON() returns instead.");
550
+ }
551
+ if (Object.prototype.hasOwnProperty.call(object, "__proto__")) {
552
+ throw refusal(`${path}.__proto__`, '"__proto__" key', "Rename the key.");
553
+ }
554
+ ancestors.add(object);
555
+ let changed = Object.getPrototypeOf(object) !== Object.prototype;
556
+ const out = {};
557
+ for (const [key, member] of Object.entries(object)) {
558
+ if (member === void 0) {
559
+ changed = true;
560
+ continue;
561
+ }
562
+ const next = canonical(member, `${path}.${key}`, ancestors);
563
+ if (!Object.is(next, member)) changed = true;
564
+ out[key] = next;
565
+ }
566
+ ancestors.delete(object);
567
+ return changed ? out : object;
568
+ }
569
+ function refusal(path, receivedType, fix) {
570
+ return new NonCanonicalValueError(
571
+ `Value at "${path}" (${receivedType}) has no JSON form, so it cannot be stored or synced unchanged. ${fix}`,
572
+ path,
573
+ receivedType
574
+ );
575
+ }
576
+ function isPlainObject(value) {
577
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
578
+ const proto = Object.getPrototypeOf(value);
579
+ return proto === Object.prototype || proto === null;
580
+ }
581
+ function describe(value) {
582
+ if (value === null) return "null";
583
+ if (Array.isArray(value)) return "array";
584
+ if (typeof value !== "object") return typeof value;
585
+ const ctor = value.constructor;
586
+ return typeof ctor?.name === "string" && ctor.name.length > 0 ? ctor.name : "object";
587
+ }
588
+
589
+ // src/operations/content-hash.ts
590
+ async function computeOperationId(input, timestampOrVersion) {
591
+ if (typeof timestampOrVersion === "string") {
592
+ return sha256Hex(canonicalize(v1HashInput(input, timestampOrVersion)));
593
+ }
594
+ const op = input;
595
+ const serializedTs = HybridLogicalClock.serialize(op.timestamp);
596
+ if (timestampOrVersion === 1) return sha256Hex(canonicalize(v1HashInput(op, serializedTs)));
597
+ return sha256Hex(canonicalize(v2HashInput(op, serializedTs)));
598
+ }
599
+ function v1HashInput(input, timestamp) {
600
+ const hashInput = {
601
+ type: input.type,
602
+ collection: input.collection,
603
+ recordId: input.recordId,
604
+ data: input.data,
605
+ timestamp,
606
+ nodeId: input.nodeId
607
+ };
608
+ if (input.atomicOps !== void 0 && Object.keys(input.atomicOps).length > 0) {
609
+ hashInput.atomicOps = input.atomicOps;
610
+ }
611
+ return hashInput;
612
+ }
613
+ function v2HashInput(op, timestamp) {
614
+ const body = canonicalizeOperationBody(op);
615
+ const atomicOps = body.atomicOps !== void 0 && Object.keys(body.atomicOps).length > 0 ? body.atomicOps : null;
616
+ return {
617
+ hashVersion: 2,
618
+ type: op.type,
619
+ collection: op.collection,
620
+ recordId: op.recordId,
621
+ data: body.data,
622
+ previousData: body.previousData,
623
+ timestamp,
624
+ nodeId: op.nodeId,
625
+ sequenceNumber: op.sequenceNumber,
626
+ // A set of parents: their order carries no meaning, so a relay that reorders
627
+ // them neither breaks verification nor creates a second id for one op.
628
+ causalDeps: [...op.causalDeps].sort(),
629
+ schemaVersion: op.schemaVersion,
630
+ atomicOps
631
+ };
632
+ }
633
+ async function sha256Hex(canonical2) {
634
+ const encoded = new TextEncoder().encode(canonical2);
635
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded);
636
+ return bufferToHex(hashBuffer);
637
+ }
638
+ function canonicalize(obj) {
639
+ if (obj === null) {
640
+ return "null";
641
+ }
642
+ if (obj === void 0) {
643
+ return "null";
644
+ }
645
+ if (typeof obj !== "object") {
646
+ return JSON.stringify(obj);
647
+ }
648
+ if (Array.isArray(obj)) {
649
+ const items = obj.map((item) => canonicalize(item));
650
+ return `[${items.join(",")}]`;
651
+ }
652
+ const keys = Object.keys(obj).sort();
653
+ const pairs = keys.map((key) => {
654
+ const value = obj[key];
655
+ return `${JSON.stringify(key)}:${canonicalize(value === void 0 ? null : value)}`;
656
+ });
657
+ return `{${pairs.join(",")}}`;
658
+ }
659
+ function bufferToHex(buffer) {
660
+ const bytes = new Uint8Array(buffer);
661
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
662
+ }
663
+
664
+ // src/events/event-emitter.ts
665
+ var SimpleEventEmitter = class {
666
+ listeners = /* @__PURE__ */ new Map();
667
+ on(type, listener) {
668
+ let set = this.listeners.get(type);
669
+ if (!set) {
670
+ set = /* @__PURE__ */ new Set();
671
+ this.listeners.set(type, set);
672
+ }
673
+ set.add(listener);
674
+ return () => {
675
+ this.off(type, listener);
676
+ };
677
+ }
678
+ off(type, listener) {
679
+ const set = this.listeners.get(type);
680
+ if (set) {
681
+ set.delete(listener);
682
+ if (set.size === 0) {
683
+ this.listeners.delete(type);
684
+ }
685
+ }
686
+ }
687
+ emit(event) {
688
+ const set = this.listeners.get(event.type);
689
+ if (!set) return;
690
+ for (const listener of set) {
691
+ ;
692
+ listener(event);
693
+ }
694
+ }
695
+ /** Remove all listeners for all event types. */
696
+ clear() {
697
+ this.listeners.clear();
698
+ }
699
+ /** Get the number of listeners for a specific event type. */
700
+ listenerCount(type) {
701
+ return this.listeners.get(type)?.size ?? 0;
702
+ }
703
+ };
704
+
470
705
  // src/version-vector/topological-sort.ts
471
706
  function topologicalSort(operations) {
472
707
  if (operations.length <= 1) return [...operations];
@@ -584,11 +819,548 @@ var MinHeap = class {
584
819
  this.data[j] = tmp;
585
820
  }
586
821
  };
822
+
823
+ // src/schema/quote-ident.ts
824
+ function quoteIdent(name) {
825
+ return `"${name.replace(/"/g, '""')}"`;
826
+ }
827
+
828
+ // src/schema/sql-gen.ts
829
+ function collectionIndexName(collection, field) {
830
+ return `idx_${collection.length}_${collection}_${field}`;
831
+ }
832
+ function legacyCollectionIndexName(collection, field) {
833
+ return `idx_${collection}_${field}`;
834
+ }
835
+ var SCHEMA_CEILING_DIRECTIVE = "--kora:schema-ceiling";
836
+ function sqlStringLiteral(value) {
837
+ return `'${value.replaceAll("'", "''")}'`;
838
+ }
839
+ function sqlDefaultLiteral(value) {
840
+ if (value === null) return "NULL";
841
+ if (typeof value === "string") return sqlStringLiteral(value);
842
+ if (typeof value === "number") {
843
+ return Number.isFinite(value) ? String(value) : "NULL";
844
+ }
845
+ if (typeof value === "boolean") return value ? "1" : "0";
846
+ return sqlStringLiteral(JSON.stringify(value) ?? "null");
847
+ }
848
+ function enumCheckConstraint(fieldName, values) {
849
+ return `CHECK (${quoteIdent(fieldName)} IN (${values.map(sqlStringLiteral).join(", ")}))`;
850
+ }
851
+
852
+ // src/schema/constraint-relaxation.ts
853
+ function isKoraInternalColumn(name) {
854
+ return name === "id" || name.startsWith("_");
855
+ }
856
+ function scanSqlTokens(sql) {
857
+ const tokens = [];
858
+ const pattern = /\s+|--[^\n]*|\/\*[\s\S]*?(?:\*\/|$)|'(?:[^']|'')*'?|"(?:[^"]|"")*"?|`(?:[^`]|``)*`?|\[[^\]]*\]?|[A-Za-z_][A-Za-z0-9_$]*|[0-9]+(?:\.[0-9]+)?|[(),]|[^\s(),'"`[A-Za-z0-9_]+/g;
859
+ for (let match = pattern.exec(sql); match !== null; match = pattern.exec(sql)) {
860
+ const text = match[0];
861
+ if (/^\s/.test(text) || text.startsWith("--") || text.startsWith("/*")) continue;
862
+ tokens.push({ text, start: match.index, end: match.index + text.length });
863
+ }
864
+ return tokens;
865
+ }
866
+ function unquoteSqliteName(token) {
867
+ if (token === void 0) return null;
868
+ if (/^"(?:[^"]|"")*"$/.test(token)) return token.slice(1, -1).replaceAll('""', '"');
869
+ if (/^`(?:[^`]|``)*`$/.test(token)) return token.slice(1, -1).replaceAll("``", "`");
870
+ if (/^\[[^\]]*\]$/.test(token)) return token.slice(1, -1);
871
+ if (/^'(?:[^']|'')*'$/.test(token)) return token.slice(1, -1).replaceAll("''", "'");
872
+ if (/^[A-Za-z_][A-Za-z0-9_$]*$/.test(token)) return token;
873
+ return null;
874
+ }
875
+ var TABLE_CONSTRAINT_KEYWORDS = /* @__PURE__ */ new Set([
876
+ "CONSTRAINT",
877
+ "PRIMARY",
878
+ "UNIQUE",
879
+ "CHECK",
880
+ "FOREIGN"
881
+ ]);
882
+ function parseSqliteCheckConstraints(createTableSql) {
883
+ const tokens = scanSqlTokens(createTableSql);
884
+ const open = tokens.findIndex((token) => token.text === "(");
885
+ if (open < 0) return [];
886
+ const definitions = [];
887
+ let current = [];
888
+ let depth = 0;
889
+ for (let i = open + 1; i < tokens.length; i++) {
890
+ const token = tokens[i];
891
+ if (token.text === "(") depth++;
892
+ if (token.text === ")") {
893
+ if (depth === 0) break;
894
+ depth--;
895
+ }
896
+ if (token.text === "," && depth === 0) {
897
+ definitions.push(current);
898
+ current = [];
899
+ continue;
900
+ }
901
+ current.push(token);
902
+ }
903
+ if (current.length > 0) definitions.push(current);
904
+ const checks = [];
905
+ for (const definition of definitions) {
906
+ const first = definition[0];
907
+ if (first === void 0) continue;
908
+ const column = TABLE_CONSTRAINT_KEYWORDS.has(first.text.toUpperCase()) ? null : unquoteSqliteName(first.text);
909
+ let level = 0;
910
+ for (let i = 0; i < definition.length; i++) {
911
+ const token = definition[i];
912
+ if (token.text === "(") level++;
913
+ else if (token.text === ")") level--;
914
+ if (level !== 0 || token.text.toUpperCase() !== "CHECK") continue;
915
+ const opening = definition[i + 1];
916
+ if (opening?.text !== "(") continue;
917
+ let inner = 0;
918
+ let closing;
919
+ for (let j = i + 1; j < definition.length; j++) {
920
+ const candidate = definition[j];
921
+ if (candidate.text === "(") inner++;
922
+ if (candidate.text === ")" && --inner === 0) {
923
+ closing = candidate;
924
+ break;
925
+ }
926
+ }
927
+ if (closing === void 0) continue;
928
+ const named = i >= 2 && definition[i - 2].text.toUpperCase() === "CONSTRAINT" ? unquoteSqliteName(definition[i - 1].text) : null;
929
+ checks.push({
930
+ name: named,
931
+ expression: createTableSql.slice(opening.end, closing.start).trim(),
932
+ column
933
+ });
934
+ }
935
+ }
936
+ return checks;
937
+ }
938
+ function isKoraSqliteEnumCheck(check, enumColumns) {
939
+ const shape = parseEnumCheckDefinition(`CHECK (${check.expression})`);
940
+ if (shape === null || isKoraInternalColumn(shape.column)) return false;
941
+ if (check.column !== null && check.column !== shape.column) return false;
942
+ return enumColumns === void 0 || enumColumns.includes(shape.column);
943
+ }
944
+ function sqliteTableNeedsRelaxation(catalog, enumColumns) {
945
+ if (parseSqliteCheckConstraints(catalog.sql).some(
946
+ (check) => isKoraSqliteEnumCheck(check, enumColumns)
947
+ )) {
948
+ return true;
949
+ }
950
+ return catalog.columns.some(
951
+ (column) => column.notnull === 1 && !isKoraInternalColumn(column.name)
952
+ );
953
+ }
954
+ function sqliteCheckReferencesColumn(check, columns) {
955
+ if (check.column !== null && columns.has(check.column)) return true;
956
+ return scanSqlTokens(check.expression).some((token) => {
957
+ if (token.text.startsWith("'")) return false;
958
+ const name = unquoteSqliteName(token.text);
959
+ return name !== null && columns.has(name);
960
+ });
961
+ }
962
+ function sqliteCheckConstraintDefinition(check) {
963
+ const name = check.name === null ? "" : `CONSTRAINT ${quoteIdent(check.name)} `;
964
+ return `${name}CHECK (${check.expression})`;
965
+ }
966
+ function sqliteConstraintRelaxationStatements(catalog, enumColumns) {
967
+ if (!sqliteTableNeedsRelaxation(catalog, enumColumns)) return [];
968
+ const table = catalog.table;
969
+ const temp = `_kora_relax_${table}`;
970
+ const ordered = [...catalog.columns].sort((a, b) => a.cid - b.cid);
971
+ const pkColumns = ordered.filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk);
972
+ const fkGroups = /* @__PURE__ */ new Map();
973
+ for (const fk of catalog.foreignKeys) {
974
+ const group = fkGroups.get(fk.id) ?? [];
975
+ group.push(fk);
976
+ fkGroups.set(fk.id, group);
977
+ }
978
+ const columnReferences = /* @__PURE__ */ new Map();
979
+ const tableConstraints = [];
980
+ for (const group of fkGroups.values()) {
981
+ group.sort((a, b) => a.seq - b.seq);
982
+ const first = group[0];
983
+ if (!first) continue;
984
+ const actions = foreignKeyActions(first);
985
+ const target = group.every((fk) => fk.to !== null && fk.to !== "") ? `${quoteIdent(first.table)}(${group.map((fk) => quoteIdent(fk.to ?? "")).join(", ")})` : quoteIdent(first.table);
986
+ if (group.length === 1) {
987
+ columnReferences.set(first.from, `REFERENCES ${target}${actions}`);
988
+ } else {
989
+ tableConstraints.push(
990
+ `FOREIGN KEY (${group.map((fk) => quoteIdent(fk.from)).join(", ")}) REFERENCES ${target}${actions}`
991
+ );
992
+ }
993
+ }
994
+ if (pkColumns.length > 1) {
995
+ tableConstraints.unshift(`PRIMARY KEY (${pkColumns.map((c) => quoteIdent(c.name)).join(", ")})`);
996
+ }
997
+ for (const unique of catalog.uniqueConstraints) {
998
+ tableConstraints.push(`UNIQUE (${unique.map(quoteIdent).join(", ")})`);
999
+ }
1000
+ for (const check of parseSqliteCheckConstraints(catalog.sql)) {
1001
+ if (!isKoraSqliteEnumCheck(check, enumColumns)) {
1002
+ tableConstraints.push(sqliteCheckConstraintDefinition(check));
1003
+ }
1004
+ }
1005
+ const definitions = ordered.map((column) => {
1006
+ const parts = [quoteIdent(column.name)];
1007
+ if (column.type) parts.push(column.type);
1008
+ if (pkColumns.length === 1 && column.pk === 1) parts.push("PRIMARY KEY");
1009
+ if (column.notnull === 1 && isKoraInternalColumn(column.name)) parts.push("NOT NULL");
1010
+ if (column.dflt_value !== null) parts.push(`DEFAULT (${column.dflt_value})`);
1011
+ const reference = columnReferences.get(column.name);
1012
+ if (reference) parts.push(reference);
1013
+ return parts.join(" ");
1014
+ });
1015
+ const columnList = ordered.map((column) => quoteIdent(column.name)).join(", ");
1016
+ return [
1017
+ `DROP TABLE IF EXISTS ${quoteIdent(temp)}`,
1018
+ `CREATE TABLE ${quoteIdent(temp)} (
1019
+ ${[...definitions, ...tableConstraints].join(",\n ")}
1020
+ )`,
1021
+ `INSERT INTO ${quoteIdent(temp)} (${columnList}) SELECT ${columnList} FROM ${quoteIdent(table)}`,
1022
+ `DROP TABLE ${quoteIdent(table)}`,
1023
+ `ALTER TABLE ${quoteIdent(temp)} RENAME TO ${quoteIdent(table)}`,
1024
+ ...catalog.dependents
1025
+ ];
1026
+ }
1027
+ function foreignKeyActions(fk) {
1028
+ let out = "";
1029
+ const onDelete = fk.on_delete?.toUpperCase();
1030
+ const onUpdate = fk.on_update?.toUpperCase();
1031
+ if (onDelete && onDelete !== "NO ACTION") out += ` ON DELETE ${onDelete}`;
1032
+ if (onUpdate && onUpdate !== "NO ACTION") out += ` ON UPDATE ${onUpdate}`;
1033
+ return out;
1034
+ }
1035
+ var EXPRESSION_KEYWORDS = /* @__PURE__ */ new Set([
1036
+ "AND",
1037
+ "ANY",
1038
+ "ARRAY",
1039
+ "IN",
1040
+ "IS",
1041
+ "NOT",
1042
+ "NULL",
1043
+ "OR",
1044
+ "VALID"
1045
+ ]);
1046
+ function tokenizeCheck(definition) {
1047
+ const tokens = [];
1048
+ const pattern = /\s+|'(?:[^']|'')*'|"(?:[^"]|"")+"|::|[A-Za-z_][A-Za-z0-9_$]*|[0-9]+|<>|!=|<=|>=|[()[\],=<>]|./gy;
1049
+ for (let match = pattern.exec(definition); match !== null; match = pattern.exec(definition)) {
1050
+ if (match[0] === "") return null;
1051
+ if (!/^\s+$/.test(match[0])) tokens.push(match[0]);
1052
+ if (pattern.lastIndex >= definition.length) break;
1053
+ }
1054
+ return tokens;
1055
+ }
1056
+ function isWord(token) {
1057
+ return token !== void 0 && /^[A-Za-z_][A-Za-z0-9_$]*$/.test(token);
1058
+ }
1059
+ function dropCasts(tokens) {
1060
+ const out = [];
1061
+ let i = 0;
1062
+ while (i < tokens.length) {
1063
+ if (tokens[i] !== "::") {
1064
+ out.push(tokens[i]);
1065
+ i++;
1066
+ continue;
1067
+ }
1068
+ i++;
1069
+ let words = 0;
1070
+ while (isWord(tokens[i]) && !EXPRESSION_KEYWORDS.has(tokens[i].toUpperCase()) || /^"/.test(tokens[i] ?? "")) {
1071
+ i++;
1072
+ words++;
1073
+ }
1074
+ if (words === 0) return ["::invalid"];
1075
+ if (tokens[i] === "(" && /^[0-9]+$/.test(tokens[i + 1] ?? "")) {
1076
+ let j = i + 1;
1077
+ while (/^[0-9]+$/.test(tokens[j] ?? "") || tokens[j] === ",") j++;
1078
+ if (tokens[j] === ")") i = j + 1;
1079
+ }
1080
+ while (tokens[i] === "[" && tokens[i + 1] === "]") i += 2;
1081
+ }
1082
+ return out;
1083
+ }
1084
+ function unquoteIdentifier(token) {
1085
+ if (/^"(?:[^"]|"")+"$/.test(token)) return token.slice(1, -1).replaceAll('""', '"');
1086
+ if (isWord(token) && !EXPRESSION_KEYWORDS.has(token.toUpperCase())) return token;
1087
+ return null;
1088
+ }
1089
+ function unquoteLiteral(token) {
1090
+ if (token === void 0 || !/^'(?:[^']|'')*'$/.test(token)) return null;
1091
+ return token.slice(1, -1).replaceAll("''", "'");
1092
+ }
1093
+ function parseEnumCheckDefinition(definition) {
1094
+ const raw = tokenizeCheck(definition.trim());
1095
+ if (raw === null || raw[0]?.toUpperCase() !== "CHECK") return null;
1096
+ let tokens = dropCasts(raw.slice(1)).filter((token) => token !== "(" && token !== ")");
1097
+ const upper = (index) => (tokens[index] ?? "").toUpperCase();
1098
+ if (upper(tokens.length - 2) === "NOT" && upper(tokens.length - 1) === "VALID") {
1099
+ tokens = tokens.slice(0, -2);
1100
+ }
1101
+ let column = null;
1102
+ const values = [];
1103
+ let allowsNull = false;
1104
+ let i = 0;
1105
+ const readLiteral = () => {
1106
+ const value = unquoteLiteral(tokens[i]);
1107
+ if (value === null) return false;
1108
+ values.push(value);
1109
+ i++;
1110
+ return true;
1111
+ };
1112
+ const readList = () => {
1113
+ if (!readLiteral()) return false;
1114
+ while (tokens[i] === ",") {
1115
+ i++;
1116
+ if (!readLiteral()) return false;
1117
+ }
1118
+ return true;
1119
+ };
1120
+ for (; ; ) {
1121
+ const name = unquoteIdentifier(tokens[i] ?? "");
1122
+ if (name === null || column !== null && name !== column) return null;
1123
+ column = name;
1124
+ i++;
1125
+ if (tokens[i] === "=" && upper(i + 1) === "ANY" && upper(i + 2) === "ARRAY") {
1126
+ if (tokens[i + 3] !== "[") return null;
1127
+ i += 4;
1128
+ if (!readList() || tokens[i] !== "]") return null;
1129
+ i++;
1130
+ } else if (tokens[i] === "=") {
1131
+ i++;
1132
+ if (!readLiteral()) return null;
1133
+ } else if (upper(i) === "IN") {
1134
+ i++;
1135
+ if (!readList()) return null;
1136
+ } else if (upper(i) === "IS" && upper(i + 1) === "NULL") {
1137
+ allowsNull = true;
1138
+ i += 2;
1139
+ } else {
1140
+ return null;
1141
+ }
1142
+ if (i === tokens.length) break;
1143
+ if (upper(i) !== "OR") return null;
1144
+ i++;
1145
+ }
1146
+ if (column === null || values.length === 0) return null;
1147
+ return { column, values, allowsNull };
1148
+ }
1149
+ function isPostgresEnumCheckDefinition(definition) {
1150
+ return parseEnumCheckDefinition(definition) !== null;
1151
+ }
1152
+ function isAnyArrayCheck(definition) {
1153
+ return /^CHECK\s*\(\(*\s*(?:"(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)(?:\)?::[A-Za-z ]+)?\s*=\s*ANY\s*\(/i.test(
1154
+ definition.trim()
1155
+ );
1156
+ }
1157
+ function sqlText(value) {
1158
+ return `'${value.replaceAll("'", "''")}'`;
1159
+ }
1160
+ async function readSqliteTableCatalog(query, table) {
1161
+ const master = await query(
1162
+ `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ${sqlText(table)}`
1163
+ );
1164
+ const tableSql = master[0]?.sql;
1165
+ if (typeof tableSql !== "string") return null;
1166
+ const columns = await query(
1167
+ `SELECT cid, name, type, "notnull" AS "notnull", dflt_value, pk FROM pragma_table_info(${sqlText(table)})`
1168
+ );
1169
+ const foreignKeys = await query(
1170
+ `SELECT id, seq, "table" AS "table", "from" AS "from", "to" AS "to", on_update, on_delete FROM pragma_foreign_key_list(${sqlText(table)})`
1171
+ );
1172
+ const uniqueConstraints = [];
1173
+ const indexes = await query(
1174
+ `SELECT name FROM pragma_index_list(${sqlText(table)}) WHERE origin = 'u'`
1175
+ );
1176
+ for (const index of indexes) {
1177
+ const info = await query(
1178
+ `SELECT name FROM pragma_index_info(${sqlText(String(index.name))}) ORDER BY seqno`
1179
+ );
1180
+ uniqueConstraints.push(info.map((row) => String(row.name)));
1181
+ }
1182
+ const dependents = await query(
1183
+ `SELECT sql FROM sqlite_master WHERE type IN ('index', 'trigger') AND tbl_name = ${sqlText(table)} AND sql IS NOT NULL ORDER BY type, name`
1184
+ );
1185
+ return {
1186
+ table,
1187
+ sql: tableSql,
1188
+ columns: columns.map((c) => ({
1189
+ cid: Number(c.cid),
1190
+ name: String(c.name),
1191
+ type: typeof c.type === "string" ? c.type : "",
1192
+ notnull: Number(c.notnull),
1193
+ dflt_value: typeof c.dflt_value === "string" ? c.dflt_value : null,
1194
+ pk: Number(c.pk)
1195
+ })),
1196
+ foreignKeys: foreignKeys.map((fk) => ({
1197
+ id: Number(fk.id),
1198
+ seq: Number(fk.seq),
1199
+ table: String(fk.table),
1200
+ from: String(fk.from),
1201
+ to: typeof fk.to === "string" ? fk.to : null,
1202
+ on_update: String(fk.on_update ?? "NO ACTION"),
1203
+ on_delete: String(fk.on_delete ?? "NO ACTION")
1204
+ })),
1205
+ uniqueConstraints,
1206
+ dependents: dependents.map((row) => String(row.sql))
1207
+ };
1208
+ }
1209
+ async function planSqliteConstraintRelaxation(query, tables, enumColumnsByTable = {}) {
1210
+ const statements = [];
1211
+ for (const table of tables) {
1212
+ const catalog = await readSqliteTableCatalog(query, table);
1213
+ if (catalog) {
1214
+ statements.push(...sqliteConstraintRelaxationStatements(catalog, enumColumnsByTable[table]));
1215
+ }
1216
+ }
1217
+ return statements;
1218
+ }
1219
+ async function planPostgresConstraintRelaxation(query, fieldsByTable, enumFieldsByTable = {}) {
1220
+ const tables = Object.keys(fieldsByTable);
1221
+ if (tables.length === 0) return [];
1222
+ const tableList = tables.map(sqlText).join(", ");
1223
+ const statements = [];
1224
+ const checks = await query(
1225
+ `SELECT rel.relname::text AS table_name, con.conname::text AS name, pg_get_constraintdef(con.oid) AS definition,
1226
+ (SELECT array_agg(att.attname::text) FROM pg_attribute att WHERE att.attrelid = con.conrelid AND att.attnum = ANY (con.conkey)) AS columns
1227
+ FROM pg_constraint con
1228
+ JOIN pg_class rel ON rel.oid = con.conrelid
1229
+ JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
1230
+ WHERE con.contype = 'c' AND nsp.nspname = current_schema() AND rel.relname IN (${tableList})
1231
+ ORDER BY rel.relname, con.conname`
1232
+ );
1233
+ for (const check of checks) {
1234
+ const columns = Array.isArray(check.columns) ? check.columns.map(String) : [];
1235
+ const column = columns[0];
1236
+ if (columns.length !== 1 || column === void 0 || isKoraInternalColumn(column)) continue;
1237
+ const shape = parseEnumCheckDefinition(String(check.definition));
1238
+ if (shape === null || shape.column !== column) continue;
1239
+ const table = String(check.table_name);
1240
+ const enumColumns = enumFieldsByTable[table] ?? fieldsByTable[table] ?? [];
1241
+ if (!enumColumns.includes(column) && !isAnyArrayCheck(String(check.definition))) continue;
1242
+ statements.push(
1243
+ `ALTER TABLE ${quoteIdent(String(check.table_name))} DROP CONSTRAINT IF EXISTS ${quoteIdent(String(check.name))}`
1244
+ );
1245
+ }
1246
+ const notNull = await query(
1247
+ `SELECT rel.relname::text AS table_name, att.attname::text AS column_name
1248
+ FROM pg_attribute att
1249
+ JOIN pg_class rel ON rel.oid = att.attrelid
1250
+ JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
1251
+ WHERE att.attnotnull AND att.attnum > 0 AND NOT att.attisdropped
1252
+ AND rel.relkind = 'r' AND nsp.nspname = current_schema() AND rel.relname IN (${tableList})
1253
+ ORDER BY rel.relname, att.attnum`
1254
+ );
1255
+ for (const row of notNull) {
1256
+ const table = String(row.table_name);
1257
+ const column = String(row.column_name);
1258
+ if (isKoraInternalColumn(column)) continue;
1259
+ if (!(fieldsByTable[table] ?? []).includes(column)) continue;
1260
+ statements.push(
1261
+ `ALTER TABLE ${quoteIdent(table)} ALTER COLUMN ${quoteIdent(column)} DROP NOT NULL`
1262
+ );
1263
+ }
1264
+ return statements;
1265
+ }
1266
+
1267
+ // src/fold/stamp.ts
1268
+ function compareStamps(a, b) {
1269
+ const ca = a.c ?? 0;
1270
+ const cb = b.c ?? 0;
1271
+ if (ca !== cb) return ca - cb;
1272
+ if (a.t !== b.t) return a.t < b.t ? -1 : 1;
1273
+ if (a.o !== b.o) return a.o < b.o ? -1 : 1;
1274
+ return 0;
1275
+ }
1276
+ function stampOf(timestamp, opId) {
1277
+ return { t: HybridLogicalClock.serialize(timestamp), o: opId };
1278
+ }
1279
+
1280
+ // src/fold/field-kind.ts
1281
+ var LWW_PLAN = {
1282
+ kind: "reg",
1283
+ appendOnly: false,
1284
+ strategy: "lww",
1285
+ tier: 1,
1286
+ secret: false
1287
+ };
1288
+ function planField(collection, field) {
1289
+ const descriptor = collection?.fields[field];
1290
+ const secret = descriptor?.kind === "secret";
1291
+ const resolver = collection?.resolvers?.[field];
1292
+ if (resolver) {
1293
+ return { kind: "res", appendOnly: false, resolver, strategy: "custom", tier: 3, secret };
1294
+ }
1295
+ if (!descriptor) return LWW_PLAN;
1296
+ switch (descriptor.mergeStrategy) {
1297
+ case "counter":
1298
+ return { kind: "ctr", appendOnly: false, strategy: "schema-counter", tier: 1, secret };
1299
+ case "max":
1300
+ return { kind: "max", appendOnly: false, strategy: "schema-max", tier: 1, secret };
1301
+ case "min":
1302
+ return { kind: "min", appendOnly: false, strategy: "schema-min", tier: 1, secret };
1303
+ case "append-only":
1304
+ if (descriptor.kind === "array") {
1305
+ return {
1306
+ kind: "set",
1307
+ appendOnly: true,
1308
+ strategy: "schema-append-only",
1309
+ tier: 1,
1310
+ secret
1311
+ };
1312
+ }
1313
+ return { ...LWW_PLAN, secret };
1314
+ case "lww":
1315
+ return { ...LWW_PLAN, secret };
1316
+ case "server-authoritative":
1317
+ return {
1318
+ ...LWW_PLAN,
1319
+ strategy: "schema-server-authoritative",
1320
+ secret,
1321
+ authoritative: true
1322
+ };
1323
+ default:
1324
+ break;
1325
+ }
1326
+ switch (descriptor.kind) {
1327
+ case "array":
1328
+ return { kind: "set", appendOnly: false, strategy: "lww-element-set", tier: 1, secret };
1329
+ case "object":
1330
+ case "json":
1331
+ return { kind: "map", appendOnly: false, strategy: "object-key-lww", tier: 1, secret };
1332
+ case "richtext":
1333
+ return { kind: "rt", appendOnly: false, strategy: "crdt-text", tier: 1, secret };
1334
+ default:
1335
+ return { ...LWW_PLAN, secret };
1336
+ }
1337
+ }
587
1338
  // Annotate the CommonJS export names for ESM import in node:
588
1339
  0 && (module.exports = {
1340
+ SCHEMA_CEILING_DIRECTIVE,
589
1341
  SimpleEventEmitter,
590
1342
  canonicalize,
1343
+ collectionIndexName,
1344
+ compareStamps,
591
1345
  computeOperationId,
1346
+ enumCheckConstraint,
1347
+ isKoraInternalColumn,
1348
+ isKoraSqliteEnumCheck,
1349
+ isPostgresEnumCheckDefinition,
1350
+ legacyCollectionIndexName,
1351
+ parseEnumCheckDefinition,
1352
+ parseSqliteCheckConstraints,
1353
+ planField,
1354
+ planPostgresConstraintRelaxation,
1355
+ planSqliteConstraintRelaxation,
1356
+ readSqliteTableCatalog,
1357
+ sqlDefaultLiteral,
1358
+ sqlStringLiteral,
1359
+ sqliteCheckConstraintDefinition,
1360
+ sqliteCheckReferencesColumn,
1361
+ sqliteConstraintRelaxationStatements,
1362
+ sqliteTableNeedsRelaxation,
1363
+ stampOf,
592
1364
  topologicalSort,
593
1365
  validateOperationParams
594
1366
  });