@korajs/core 1.0.0-beta.11 → 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/README.md +21 -13
- package/dist/bindings/index.d.cts +26 -6
- package/dist/bindings/index.d.ts +26 -6
- package/dist/{build-scope-map-B3R_emUU.d.ts → build-scope-map-CIFa58oA.d.ts} +1 -1
- package/dist/{build-scope-map--0H8l6Tw.d.cts → build-scope-map-ClIq_doj.d.cts} +1 -1
- package/dist/{chunk-5IICSH6H.js → chunk-QUD3DMSD.js} +668 -24
- package/dist/chunk-QUD3DMSD.js.map +1 -0
- package/dist/events-BSU-0z5O.d.cts +1737 -0
- package/dist/events-BSU-0z5O.d.ts +1737 -0
- package/dist/index.cjs +2539 -207
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +810 -220
- package/dist/index.d.ts +810 -220
- package/dist/index.js +2025 -340
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +857 -85
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +251 -21
- package/dist/internal.d.ts +251 -21
- package/dist/internal.js +447 -1
- package/dist/internal.js.map +1 -1
- package/dist/sql-gen-ByOZIQU-.d.ts +622 -0
- package/dist/sql-gen-DdOkMojr.d.cts +622 -0
- package/package.json +2 -1
- package/dist/chunk-5IICSH6H.js.map +0 -1
- package/dist/events-BPbYUbrE.d.cts +0 -999
- package/dist/events-BPbYUbrE.d.ts +0 -999
- package/dist/operation-DbynlVcp.d.cts +0 -164
- package/dist/operation-Dt0AtT8u.d.ts +0 -164
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
|
});
|