@korajs/core 0.4.0 → 0.5.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-CFP5YFXC.js → chunk-H4FXU5OP.js} +41 -5
- package/dist/chunk-H4FXU5OP.js.map +1 -0
- package/dist/{events-BMulupSB.d.cts → events-BeIEDJBW.d.cts} +46 -3
- package/dist/{events-BMulupSB.d.ts → events-BeIEDJBW.d.ts} +46 -3
- package/dist/index.cjs +368 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +185 -35
- package/dist/index.d.ts +185 -35
- package/dist/index.js +319 -12
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +37 -4
- 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 +2 -1
- package/dist/chunk-CFP5YFXC.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -20,12 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
APPLY_FAILURE_CODES: () => APPLY_FAILURE_CODES,
|
|
24
|
+
APPLY_RESULTS: () => APPLY_RESULTS,
|
|
23
25
|
ArrayFieldBuilder: () => ArrayFieldBuilder,
|
|
24
26
|
CONNECTION_QUALITIES: () => CONNECTION_QUALITIES,
|
|
27
|
+
CausalTracker: () => CausalTracker,
|
|
25
28
|
ClockDriftError: () => ClockDriftError,
|
|
26
29
|
EnumFieldBuilder: () => EnumFieldBuilder,
|
|
27
30
|
FieldBuilder: () => FieldBuilder,
|
|
28
31
|
HybridLogicalClock: () => HybridLogicalClock,
|
|
32
|
+
KORA_ERROR_FIX_SUGGESTIONS: () => KORA_ERROR_FIX_SUGGESTIONS,
|
|
29
33
|
KoraError: () => KoraError,
|
|
30
34
|
MERGE_STRATEGIES: () => MERGE_STRATEGIES,
|
|
31
35
|
MergeConflictError: () => MergeConflictError,
|
|
@@ -37,17 +41,22 @@ __export(index_exports, {
|
|
|
37
41
|
StorageError: () => StorageError,
|
|
38
42
|
SyncError: () => SyncError,
|
|
39
43
|
advanceVector: () => advanceVector,
|
|
44
|
+
applyOperationTransforms: () => applyOperationTransforms,
|
|
40
45
|
buildScopeMap: () => buildScopeMap,
|
|
41
46
|
buildStateMachineConstraints: () => buildStateMachineConstraints,
|
|
42
47
|
canAutoRollback: () => canAutoRollback,
|
|
48
|
+
collectSchemaScopeFields: () => collectSchemaScopeFields,
|
|
49
|
+
collectSchemaScopeValueKeys: () => collectSchemaScopeValueKeys,
|
|
43
50
|
computeDelta: () => computeDelta,
|
|
44
51
|
createOperation: () => createOperation,
|
|
45
52
|
createReversibleMigration: () => createReversibleMigration,
|
|
46
53
|
createVersionVector: () => createVersionVector,
|
|
54
|
+
defaultApplyFailureReason: () => defaultApplyFailureReason,
|
|
47
55
|
defaultSequenceFormat: () => defaultSequenceFormat,
|
|
48
56
|
defineSchema: () => defineSchema,
|
|
49
57
|
deserializeVector: () => deserializeVector,
|
|
50
58
|
dominates: () => dominates,
|
|
59
|
+
extractScopeValuesFromClaims: () => extractScopeValuesFromClaims,
|
|
51
60
|
extractTimestamp: () => extractTimestamp,
|
|
52
61
|
formatSequenceValue: () => formatSequenceValue,
|
|
53
62
|
generateFullDDL: () => generateFullDDL,
|
|
@@ -55,8 +64,13 @@ __export(index_exports, {
|
|
|
55
64
|
generateRollbackSteps: () => generateRollbackSteps,
|
|
56
65
|
generateSQL: () => generateSQL,
|
|
57
66
|
generateUUIDv7: () => generateUUIDv7,
|
|
67
|
+
getCollectionScopeBindings: () => getCollectionScopeBindings,
|
|
68
|
+
getKoraErrorFix: () => getKoraErrorFix,
|
|
58
69
|
getTransitionMap: () => getTransitionMap,
|
|
70
|
+
hasSchemaSyncRules: () => hasSchemaSyncRules,
|
|
71
|
+
isApplyFailure: () => isApplyFailure,
|
|
59
72
|
isAtomicOp: () => isAtomicOp,
|
|
73
|
+
isCollectionSyncScoped: () => isCollectionSyncScoped,
|
|
60
74
|
isValidOperation: () => isValidOperation,
|
|
61
75
|
isValidUUIDv7: () => isValidUUIDv7,
|
|
62
76
|
mergeVectors: () => mergeVectors,
|
|
@@ -85,6 +99,23 @@ var MERGE_STRATEGIES = [
|
|
|
85
99
|
];
|
|
86
100
|
var CONNECTION_QUALITIES = ["excellent", "good", "fair", "poor", "offline"];
|
|
87
101
|
|
|
102
|
+
// src/errors/error-fixes.ts
|
|
103
|
+
var KORA_ERROR_FIX_SUGGESTIONS = {
|
|
104
|
+
SCHEMA_VALIDATION: "Review your defineSchema() definition: every collection needs at least one field and a positive version.",
|
|
105
|
+
OPERATION_ERROR: "Check the operation payload matches your schema field types and required fields.",
|
|
106
|
+
MERGE_CONFLICT: "Add a custom resolver for the conflicting field in defineSchema(), or adjust constraint onConflict rules.",
|
|
107
|
+
SYNC_ERROR: "Verify the sync server URL, auth token, and that the server accepts your schema version.",
|
|
108
|
+
STORAGE_ERROR: "Confirm the storage adapter is supported in this environment and the database path is writable.",
|
|
109
|
+
CLOCK_DRIFT: "Sync device wall-clock time (NTP). Kora refuses new timestamps when drift exceeds five minutes.",
|
|
110
|
+
PERSISTENCE_ERROR: "IndexedDB persistence failed; check browser storage settings and available disk quota.",
|
|
111
|
+
MISSING_WORKER_URL: "Pass store.workerUrl pointing to sqlite-wasm-worker.js (see create-kora-app templates).",
|
|
112
|
+
INVALID_SYNC_URL: "Use ws:// or wss:// for WebSocket transport, or http(s):// for HTTP long-polling.",
|
|
113
|
+
APP_NOT_READY: "Await app.ready before querying or mutating collections, or wrap the tree in <KoraProvider app={app}>."
|
|
114
|
+
};
|
|
115
|
+
function getKoraErrorFix(code) {
|
|
116
|
+
return KORA_ERROR_FIX_SUGGESTIONS[code];
|
|
117
|
+
}
|
|
118
|
+
|
|
88
119
|
// src/errors/errors.ts
|
|
89
120
|
var KoraError = class extends Error {
|
|
90
121
|
constructor(message, code, context) {
|
|
@@ -95,6 +126,16 @@ var KoraError = class extends Error {
|
|
|
95
126
|
}
|
|
96
127
|
code;
|
|
97
128
|
context;
|
|
129
|
+
/**
|
|
130
|
+
* Actionable hint for resolving this error (from registry or error context).
|
|
131
|
+
*/
|
|
132
|
+
get fix() {
|
|
133
|
+
const fromContext = this.context?.fix;
|
|
134
|
+
if (typeof fromContext === "string" && fromContext.length > 0) {
|
|
135
|
+
return fromContext;
|
|
136
|
+
}
|
|
137
|
+
return getKoraErrorFix(this.code);
|
|
138
|
+
}
|
|
98
139
|
};
|
|
99
140
|
var SchemaValidationError = class extends KoraError {
|
|
100
141
|
constructor(message, context) {
|
|
@@ -192,6 +233,7 @@ var HybridLogicalClock = class {
|
|
|
192
233
|
*/
|
|
193
234
|
receive(remote) {
|
|
194
235
|
const physicalTime = this.timeSource.now();
|
|
236
|
+
const wasColdStart = this.wallTime === 0;
|
|
195
237
|
if (physicalTime > this.wallTime && physicalTime > remote.wallTime) {
|
|
196
238
|
this.wallTime = physicalTime;
|
|
197
239
|
this.logical = 0;
|
|
@@ -203,7 +245,9 @@ var HybridLogicalClock = class {
|
|
|
203
245
|
} else {
|
|
204
246
|
this.logical++;
|
|
205
247
|
}
|
|
206
|
-
|
|
248
|
+
if (!wasColdStart) {
|
|
249
|
+
this.checkDrift(physicalTime);
|
|
250
|
+
}
|
|
207
251
|
return { wallTime: this.wallTime, logical: this.logical, nodeId: this.nodeId };
|
|
208
252
|
}
|
|
209
253
|
/**
|
|
@@ -252,6 +296,50 @@ var HybridLogicalClock = class {
|
|
|
252
296
|
}
|
|
253
297
|
};
|
|
254
298
|
|
|
299
|
+
// src/causal/causal-tracker.ts
|
|
300
|
+
var CausalTracker = class {
|
|
301
|
+
lastOpIdByCollection = /* @__PURE__ */ new Map();
|
|
302
|
+
lastTransactionOpId = null;
|
|
303
|
+
/**
|
|
304
|
+
* Start a new transaction boundary. Clears in-transaction op ids.
|
|
305
|
+
*/
|
|
306
|
+
beginTransaction() {
|
|
307
|
+
this.lastTransactionOpId = null;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Clear the transaction boundary without recording ops (after rollback).
|
|
311
|
+
*/
|
|
312
|
+
clearTransaction() {
|
|
313
|
+
this.lastTransactionOpId = null;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Compute causal dependencies for the next operation in a collection.
|
|
317
|
+
* Uses direct parents only: collection head and the previous op in the open transaction.
|
|
318
|
+
*/
|
|
319
|
+
nextCausalDeps(collection, inTransaction) {
|
|
320
|
+
const deps = [];
|
|
321
|
+
const lastInCollection = this.lastOpIdByCollection.get(collection);
|
|
322
|
+
if (lastInCollection !== void 0) {
|
|
323
|
+
deps.push(lastInCollection);
|
|
324
|
+
}
|
|
325
|
+
if (inTransaction && this.lastTransactionOpId !== null) {
|
|
326
|
+
if (!deps.includes(this.lastTransactionOpId)) {
|
|
327
|
+
deps.push(this.lastTransactionOpId);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return deps;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Record an operation after it has been created and assigned an id.
|
|
334
|
+
*/
|
|
335
|
+
afterOperation(collection, operationId, inTransaction) {
|
|
336
|
+
this.lastOpIdByCollection.set(collection, operationId);
|
|
337
|
+
if (inTransaction) {
|
|
338
|
+
this.lastTransactionOpId = operationId;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
255
343
|
// src/identifiers/uuid-v7.ts
|
|
256
344
|
var defaultRandom = globalThis.crypto;
|
|
257
345
|
function generateUUIDv7(timestamp = Date.now(), randomSource = defaultRandom) {
|
|
@@ -288,6 +376,37 @@ function formatUUID(bytes) {
|
|
|
288
376
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
289
377
|
}
|
|
290
378
|
|
|
379
|
+
// src/operations/apply-result.ts
|
|
380
|
+
var APPLY_RESULTS = ["applied", "duplicate", "skipped", "rejected", "deferred"];
|
|
381
|
+
var APPLY_FAILURE_CODES = {
|
|
382
|
+
APPLY_FAILED: "APPLY_FAILED",
|
|
383
|
+
APPLY_SKIPPED: "APPLY_SKIPPED",
|
|
384
|
+
APPLY_REJECTED: "APPLY_REJECTED",
|
|
385
|
+
APPLY_DEFERRED: "APPLY_DEFERRED",
|
|
386
|
+
CLOCK_DRIFT: "CLOCK_DRIFT",
|
|
387
|
+
REFERENTIAL_INTEGRITY: "REFERENTIAL_INTEGRITY",
|
|
388
|
+
SCHEMA_MISMATCH: "SCHEMA_MISMATCH"
|
|
389
|
+
};
|
|
390
|
+
function isApplyFailure(result) {
|
|
391
|
+
return result === "skipped" || result === "rejected" || result === "deferred";
|
|
392
|
+
}
|
|
393
|
+
function defaultApplyFailureReason(result, overrides) {
|
|
394
|
+
const base = result === "deferred" ? {
|
|
395
|
+
code: APPLY_FAILURE_CODES.APPLY_DEFERRED,
|
|
396
|
+
message: "Operation apply was deferred.",
|
|
397
|
+
retriable: true
|
|
398
|
+
} : result === "rejected" ? {
|
|
399
|
+
code: APPLY_FAILURE_CODES.APPLY_REJECTED,
|
|
400
|
+
message: "Operation apply was rejected.",
|
|
401
|
+
retriable: false
|
|
402
|
+
} : {
|
|
403
|
+
code: APPLY_FAILURE_CODES.APPLY_SKIPPED,
|
|
404
|
+
message: "Operation apply was skipped.",
|
|
405
|
+
retriable: false
|
|
406
|
+
};
|
|
407
|
+
return { ...base, ...overrides };
|
|
408
|
+
}
|
|
409
|
+
|
|
291
410
|
// src/operations/content-hash.ts
|
|
292
411
|
async function computeOperationId(input, timestamp) {
|
|
293
412
|
const hashInput = {
|
|
@@ -307,8 +426,11 @@ async function computeOperationId(input, timestamp) {
|
|
|
307
426
|
return bufferToHex(hashBuffer);
|
|
308
427
|
}
|
|
309
428
|
function canonicalize(obj) {
|
|
310
|
-
if (obj === null
|
|
311
|
-
return
|
|
429
|
+
if (obj === null) {
|
|
430
|
+
return "null";
|
|
431
|
+
}
|
|
432
|
+
if (obj === void 0) {
|
|
433
|
+
return "null";
|
|
312
434
|
}
|
|
313
435
|
if (typeof obj !== "object") {
|
|
314
436
|
return JSON.stringify(obj);
|
|
@@ -320,7 +442,7 @@ function canonicalize(obj) {
|
|
|
320
442
|
const keys = Object.keys(obj).sort();
|
|
321
443
|
const pairs = keys.map((key) => {
|
|
322
444
|
const value = obj[key];
|
|
323
|
-
return `${JSON.stringify(key)}:${canonicalize(value)}`;
|
|
445
|
+
return `${JSON.stringify(key)}:${canonicalize(value === void 0 ? null : value)}`;
|
|
324
446
|
});
|
|
325
447
|
return `{${pairs.join(",")}}`;
|
|
326
448
|
}
|
|
@@ -441,6 +563,7 @@ function isValidOperation(value) {
|
|
|
441
563
|
}
|
|
442
564
|
function deepFreeze(obj) {
|
|
443
565
|
if (typeof obj !== "object" || obj === null) return obj;
|
|
566
|
+
if (ArrayBuffer.isView(obj)) return obj;
|
|
444
567
|
Object.freeze(obj);
|
|
445
568
|
for (const value of Object.values(obj)) {
|
|
446
569
|
if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
|
|
@@ -548,6 +671,69 @@ function toAtomicOp(sentinel) {
|
|
|
548
671
|
return { type: sentinel.type, value: sentinel.value };
|
|
549
672
|
}
|
|
550
673
|
|
|
674
|
+
// src/scopes/sync-scope-bindings.ts
|
|
675
|
+
function hasSchemaSyncRules(schema) {
|
|
676
|
+
return schema.sync !== void 0 && Object.keys(schema.sync).length > 0;
|
|
677
|
+
}
|
|
678
|
+
function getCollectionScopeBindings(schema, collectionName) {
|
|
679
|
+
const syncRule = schema.sync?.[collectionName];
|
|
680
|
+
if (syncRule && Object.keys(syncRule.where).length > 0) {
|
|
681
|
+
return { ...syncRule.where };
|
|
682
|
+
}
|
|
683
|
+
const scopeFields = schema.collections[collectionName]?.scope ?? [];
|
|
684
|
+
if (scopeFields.length === 0) {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
const bindings = {};
|
|
688
|
+
for (const field of scopeFields) {
|
|
689
|
+
bindings[field] = field;
|
|
690
|
+
}
|
|
691
|
+
return bindings;
|
|
692
|
+
}
|
|
693
|
+
function isCollectionSyncScoped(schema, collectionName) {
|
|
694
|
+
if (hasSchemaSyncRules(schema)) {
|
|
695
|
+
if (schema.sync?.[collectionName]) {
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
const legacyScope = schema.collections[collectionName]?.scope ?? [];
|
|
699
|
+
return legacyScope.length > 0;
|
|
700
|
+
}
|
|
701
|
+
return getCollectionScopeBindings(schema, collectionName) !== null;
|
|
702
|
+
}
|
|
703
|
+
function collectSchemaScopeValueKeys(schema) {
|
|
704
|
+
const keys = /* @__PURE__ */ new Set();
|
|
705
|
+
for (const collection of Object.values(schema.collections)) {
|
|
706
|
+
for (const field of collection.scope) {
|
|
707
|
+
keys.add(field);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (schema.sync) {
|
|
711
|
+
for (const rule of Object.values(schema.sync)) {
|
|
712
|
+
for (const scopeKey of Object.values(rule.where)) {
|
|
713
|
+
keys.add(scopeKey);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return [...keys];
|
|
718
|
+
}
|
|
719
|
+
function normalizeSyncRuleWhere(collectionName, where) {
|
|
720
|
+
const normalized = {};
|
|
721
|
+
for (const [field, scopeKey] of Object.entries(where)) {
|
|
722
|
+
if (scopeKey === true) {
|
|
723
|
+
normalized[field] = field;
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (typeof scopeKey === "string" && scopeKey.length > 0) {
|
|
727
|
+
normalized[field] = scopeKey;
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
throw new Error(
|
|
731
|
+
`Invalid sync rule for collection "${collectionName}": where.${field} must be true or a non-empty scope key string`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
return normalized;
|
|
735
|
+
}
|
|
736
|
+
|
|
551
737
|
// src/state-machine/state-machine.ts
|
|
552
738
|
function validateTransition(constraint, fromValue, toValue) {
|
|
553
739
|
const from = String(fromValue);
|
|
@@ -637,7 +823,7 @@ function validateStateMachineDefinition(collectionName, sm, fields) {
|
|
|
637
823
|
// src/schema/define.ts
|
|
638
824
|
var COLLECTION_NAME_RE = /^[a-z][a-z0-9_]*$/;
|
|
639
825
|
var FIELD_NAME_RE = /^[a-z][a-zA-Z0-9_]*$/;
|
|
640
|
-
var RESERVED_FIELDS = /* @__PURE__ */ new Set(["id", "_created_at", "_updated_at", "_deleted"]);
|
|
826
|
+
var RESERVED_FIELDS = /* @__PURE__ */ new Set(["id", "_created_at", "_updated_at", "_version", "_deleted"]);
|
|
641
827
|
function defineSchema(input) {
|
|
642
828
|
validateVersion(input.version);
|
|
643
829
|
const collections = {};
|
|
@@ -680,7 +866,15 @@ function defineSchema(input) {
|
|
|
680
866
|
migrations[version] = migration;
|
|
681
867
|
}
|
|
682
868
|
}
|
|
683
|
-
|
|
869
|
+
const sync = buildSyncRules(input.sync, collections);
|
|
870
|
+
applySyncRulesToCollectionScope(collections, sync);
|
|
871
|
+
return {
|
|
872
|
+
version: input.version,
|
|
873
|
+
collections,
|
|
874
|
+
relations,
|
|
875
|
+
migrations,
|
|
876
|
+
...sync ? { sync } : {}
|
|
877
|
+
};
|
|
684
878
|
}
|
|
685
879
|
function validateVersion(version) {
|
|
686
880
|
if (typeof version !== "number" || !Number.isInteger(version) || version < 1) {
|
|
@@ -761,6 +955,63 @@ function buildCollection(name, input) {
|
|
|
761
955
|
}
|
|
762
956
|
return { fields, indexes, constraints, resolvers, scope, stateMachine };
|
|
763
957
|
}
|
|
958
|
+
function buildSyncRules(syncInput, collections) {
|
|
959
|
+
if (!syncInput) {
|
|
960
|
+
return void 0;
|
|
961
|
+
}
|
|
962
|
+
const sync = {};
|
|
963
|
+
for (const [collectionName, ruleInput] of Object.entries(syncInput)) {
|
|
964
|
+
if (!(collectionName in collections)) {
|
|
965
|
+
throw new SchemaValidationError(
|
|
966
|
+
`Sync rule references collection "${collectionName}" which does not exist. Available collections: ${Object.keys(collections).join(", ")}`,
|
|
967
|
+
{ collection: collectionName }
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
if (!ruleInput.where || Object.keys(ruleInput.where).length === 0) {
|
|
971
|
+
throw new SchemaValidationError(
|
|
972
|
+
`Sync rule for collection "${collectionName}" must declare at least one where binding`,
|
|
973
|
+
{ collection: collectionName }
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
const collection = collections[collectionName];
|
|
977
|
+
if (!collection) {
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
for (const fieldName of Object.keys(ruleInput.where)) {
|
|
981
|
+
if (!(fieldName in collection.fields)) {
|
|
982
|
+
throw new SchemaValidationError(
|
|
983
|
+
`Sync rule where field "${fieldName}" does not exist in collection "${collectionName}". Available fields: ${Object.keys(collection.fields).join(", ")}`,
|
|
984
|
+
{ collection: collectionName, field: fieldName }
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
try {
|
|
989
|
+
sync[collectionName] = { where: normalizeSyncRuleWhere(collectionName, ruleInput.where) };
|
|
990
|
+
} catch (error) {
|
|
991
|
+
throw new SchemaValidationError(
|
|
992
|
+
error instanceof Error ? error.message : "Invalid sync rule",
|
|
993
|
+
{ collection: collectionName }
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
return sync;
|
|
998
|
+
}
|
|
999
|
+
function applySyncRulesToCollectionScope(collections, sync) {
|
|
1000
|
+
if (!sync) {
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
for (const [collectionName, rule] of Object.entries(sync)) {
|
|
1004
|
+
const collection = collections[collectionName];
|
|
1005
|
+
if (!collection) {
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
for (const fieldName of Object.keys(rule.where)) {
|
|
1009
|
+
if (!collection.scope.includes(fieldName)) {
|
|
1010
|
+
collection.scope.push(fieldName);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
764
1015
|
function validateFieldName(collection, fieldName) {
|
|
765
1016
|
if (RESERVED_FIELDS.has(fieldName)) {
|
|
766
1017
|
throw new SchemaValidationError(
|
|
@@ -848,6 +1099,7 @@ function generateSQL(collectionName, collection, relations) {
|
|
|
848
1099
|
}
|
|
849
1100
|
columns.push("_created_at INTEGER NOT NULL");
|
|
850
1101
|
columns.push("_updated_at INTEGER NOT NULL");
|
|
1102
|
+
columns.push("_version TEXT NOT NULL DEFAULT ''");
|
|
851
1103
|
columns.push("_deleted INTEGER NOT NULL DEFAULT 0");
|
|
852
1104
|
statements.push(`CREATE TABLE IF NOT EXISTS ${collectionName} (
|
|
853
1105
|
${columns.join(",\n ")}
|
|
@@ -857,6 +1109,10 @@ function generateSQL(collectionName, collection, relations) {
|
|
|
857
1109
|
statements.push(`--kora:safe-alter
|
|
858
1110
|
ALTER TABLE ${collectionName} ADD COLUMN ${colDef}`);
|
|
859
1111
|
}
|
|
1112
|
+
statements.push(
|
|
1113
|
+
`--kora:safe-alter
|
|
1114
|
+
ALTER TABLE ${collectionName} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`
|
|
1115
|
+
);
|
|
860
1116
|
for (const indexField of collection.indexes) {
|
|
861
1117
|
statements.push(
|
|
862
1118
|
`CREATE INDEX IF NOT EXISTS idx_${collectionName}_${indexField} ON ${collectionName} (${indexField})`
|
|
@@ -896,6 +1152,18 @@ function generateFullDDL(schema) {
|
|
|
896
1152
|
statements.push(
|
|
897
1153
|
"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
1154
|
);
|
|
1155
|
+
statements.push(
|
|
1156
|
+
"CREATE TABLE IF NOT EXISTS _kora_sync_queue (\n id TEXT PRIMARY KEY NOT NULL,\n payload TEXT NOT NULL\n)"
|
|
1157
|
+
);
|
|
1158
|
+
statements.push(
|
|
1159
|
+
"CREATE TABLE IF NOT EXISTS _kora_audit_traces (\n id TEXT PRIMARY KEY NOT NULL,\n recorded_at INTEGER NOT NULL,\n event_type TEXT NOT NULL,\n collection TEXT NOT NULL,\n record_id TEXT NOT NULL,\n field TEXT NOT NULL,\n strategy TEXT NOT NULL,\n tier INTEGER NOT NULL,\n constraint_name TEXT,\n trace_json TEXT NOT NULL\n)"
|
|
1160
|
+
);
|
|
1161
|
+
statements.push(
|
|
1162
|
+
"CREATE INDEX IF NOT EXISTS idx_kora_audit_traces_recorded_at ON _kora_audit_traces (recorded_at)"
|
|
1163
|
+
);
|
|
1164
|
+
statements.push(
|
|
1165
|
+
"CREATE INDEX IF NOT EXISTS idx_kora_audit_traces_collection ON _kora_audit_traces (collection)"
|
|
1166
|
+
);
|
|
899
1167
|
for (const [name, collection] of Object.entries(schema.collections)) {
|
|
900
1168
|
statements.push(...generateSQL(name, collection, schema.relations));
|
|
901
1169
|
}
|
|
@@ -1024,10 +1292,24 @@ var EnumFieldBuilder = class _EnumFieldBuilder extends FieldBuilder {
|
|
|
1024
1292
|
);
|
|
1025
1293
|
}
|
|
1026
1294
|
default(value) {
|
|
1027
|
-
return new _EnumFieldBuilder(
|
|
1295
|
+
return new _EnumFieldBuilder(
|
|
1296
|
+
this._enumValues,
|
|
1297
|
+
false,
|
|
1298
|
+
value,
|
|
1299
|
+
this._auto,
|
|
1300
|
+
this._mergeStrategy,
|
|
1301
|
+
this._transitions
|
|
1302
|
+
);
|
|
1028
1303
|
}
|
|
1029
1304
|
auto() {
|
|
1030
|
-
return new _EnumFieldBuilder(
|
|
1305
|
+
return new _EnumFieldBuilder(
|
|
1306
|
+
this._enumValues,
|
|
1307
|
+
false,
|
|
1308
|
+
void 0,
|
|
1309
|
+
true,
|
|
1310
|
+
this._mergeStrategy,
|
|
1311
|
+
this._transitions
|
|
1312
|
+
);
|
|
1031
1313
|
}
|
|
1032
1314
|
merge(strategy) {
|
|
1033
1315
|
return new _EnumFieldBuilder(
|
|
@@ -1513,12 +1795,13 @@ function vectorsEqual(a, b) {
|
|
|
1513
1795
|
}
|
|
1514
1796
|
return true;
|
|
1515
1797
|
}
|
|
1516
|
-
function computeDelta(localVector, remoteVector, operationLog) {
|
|
1798
|
+
async function computeDelta(localVector, remoteVector, operationLog) {
|
|
1517
1799
|
const missing = [];
|
|
1518
1800
|
for (const [nodeId, localSeq] of localVector) {
|
|
1519
1801
|
const remoteSeq = remoteVector.get(nodeId) ?? 0;
|
|
1520
1802
|
if (localSeq > remoteSeq) {
|
|
1521
|
-
|
|
1803
|
+
const ops = await operationLog.getRange(nodeId, remoteSeq + 1, localSeq);
|
|
1804
|
+
missing.push(...ops);
|
|
1522
1805
|
}
|
|
1523
1806
|
}
|
|
1524
1807
|
return topologicalSort(missing);
|
|
@@ -1535,12 +1818,17 @@ function deserializeVector(s) {
|
|
|
1535
1818
|
// src/scopes/build-scope-map.ts
|
|
1536
1819
|
function buildScopeMap(schema, scopeValues) {
|
|
1537
1820
|
const result = {};
|
|
1538
|
-
|
|
1539
|
-
|
|
1821
|
+
const partialSync = hasSchemaSyncRules(schema);
|
|
1822
|
+
for (const collName of Object.keys(schema.collections)) {
|
|
1823
|
+
if (partialSync && !isCollectionSyncScoped(schema, collName)) {
|
|
1824
|
+
continue;
|
|
1825
|
+
}
|
|
1826
|
+
const bindings = getCollectionScopeBindings(schema, collName);
|
|
1827
|
+
if (bindings) {
|
|
1540
1828
|
const collScope = {};
|
|
1541
|
-
for (const field of
|
|
1542
|
-
if (
|
|
1543
|
-
collScope[field] = scopeValues[
|
|
1829
|
+
for (const [field, scopeKey] of Object.entries(bindings)) {
|
|
1830
|
+
if (scopeKey in scopeValues) {
|
|
1831
|
+
collScope[field] = scopeValues[scopeKey];
|
|
1544
1832
|
}
|
|
1545
1833
|
}
|
|
1546
1834
|
result[collName] = collScope;
|
|
@@ -1551,6 +1839,34 @@ function buildScopeMap(schema, scopeValues) {
|
|
|
1551
1839
|
return result;
|
|
1552
1840
|
}
|
|
1553
1841
|
|
|
1842
|
+
// src/scopes/extract-scope-from-claims.ts
|
|
1843
|
+
function collectSchemaScopeFields(schema) {
|
|
1844
|
+
return collectSchemaScopeValueKeys(schema);
|
|
1845
|
+
}
|
|
1846
|
+
function extractScopeValuesFromClaims(schema, claims) {
|
|
1847
|
+
const scopeFields = collectSchemaScopeValueKeys(schema);
|
|
1848
|
+
if (scopeFields.length === 0) {
|
|
1849
|
+
return {};
|
|
1850
|
+
}
|
|
1851
|
+
const nestedScope = claims.scope;
|
|
1852
|
+
const scopeObject = typeof nestedScope === "object" && nestedScope !== null && !Array.isArray(nestedScope) ? nestedScope : {};
|
|
1853
|
+
const result = {};
|
|
1854
|
+
for (const field of scopeFields) {
|
|
1855
|
+
if (field in claims) {
|
|
1856
|
+
result[field] = claims[field];
|
|
1857
|
+
continue;
|
|
1858
|
+
}
|
|
1859
|
+
if (field in scopeObject) {
|
|
1860
|
+
result[field] = scopeObject[field];
|
|
1861
|
+
continue;
|
|
1862
|
+
}
|
|
1863
|
+
if (field === "userId" && typeof claims.sub === "string") {
|
|
1864
|
+
result.userId = claims.sub;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
return result;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1554
1870
|
// src/sequences/sequence-format.ts
|
|
1555
1871
|
function formatSequenceValue(template, counter, nodeId, now) {
|
|
1556
1872
|
const date = now ?? /* @__PURE__ */ new Date();
|
|
@@ -1577,6 +1893,29 @@ function defaultSequenceFormat(name) {
|
|
|
1577
1893
|
return `${name}-{seq:4}`;
|
|
1578
1894
|
}
|
|
1579
1895
|
|
|
1896
|
+
// src/migration/apply-operation-transforms.ts
|
|
1897
|
+
var MAX_TRANSFORM_STEPS = 32;
|
|
1898
|
+
function applyOperationTransforms(operation, targetSchemaVersion, transforms) {
|
|
1899
|
+
let current = operation;
|
|
1900
|
+
for (let step = 0; step < MAX_TRANSFORM_STEPS; step++) {
|
|
1901
|
+
if (current.schemaVersion === targetSchemaVersion) {
|
|
1902
|
+
return current;
|
|
1903
|
+
}
|
|
1904
|
+
const transform = transforms.find(
|
|
1905
|
+
(candidate) => candidate.fromVersion === current.schemaVersion
|
|
1906
|
+
);
|
|
1907
|
+
if (!transform) {
|
|
1908
|
+
return null;
|
|
1909
|
+
}
|
|
1910
|
+
const next = transform.transform(current);
|
|
1911
|
+
if (next === null) {
|
|
1912
|
+
return null;
|
|
1913
|
+
}
|
|
1914
|
+
current = next;
|
|
1915
|
+
}
|
|
1916
|
+
return null;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1580
1919
|
// src/migrations/migration-builder.ts
|
|
1581
1920
|
var RollbackBuilder = class {
|
|
1582
1921
|
_steps = [];
|
|
@@ -2121,12 +2460,16 @@ function generateProtoDefinitions(schema) {
|
|
|
2121
2460
|
}
|
|
2122
2461
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2123
2462
|
0 && (module.exports = {
|
|
2463
|
+
APPLY_FAILURE_CODES,
|
|
2464
|
+
APPLY_RESULTS,
|
|
2124
2465
|
ArrayFieldBuilder,
|
|
2125
2466
|
CONNECTION_QUALITIES,
|
|
2467
|
+
CausalTracker,
|
|
2126
2468
|
ClockDriftError,
|
|
2127
2469
|
EnumFieldBuilder,
|
|
2128
2470
|
FieldBuilder,
|
|
2129
2471
|
HybridLogicalClock,
|
|
2472
|
+
KORA_ERROR_FIX_SUGGESTIONS,
|
|
2130
2473
|
KoraError,
|
|
2131
2474
|
MERGE_STRATEGIES,
|
|
2132
2475
|
MergeConflictError,
|
|
@@ -2138,17 +2481,22 @@ function generateProtoDefinitions(schema) {
|
|
|
2138
2481
|
StorageError,
|
|
2139
2482
|
SyncError,
|
|
2140
2483
|
advanceVector,
|
|
2484
|
+
applyOperationTransforms,
|
|
2141
2485
|
buildScopeMap,
|
|
2142
2486
|
buildStateMachineConstraints,
|
|
2143
2487
|
canAutoRollback,
|
|
2488
|
+
collectSchemaScopeFields,
|
|
2489
|
+
collectSchemaScopeValueKeys,
|
|
2144
2490
|
computeDelta,
|
|
2145
2491
|
createOperation,
|
|
2146
2492
|
createReversibleMigration,
|
|
2147
2493
|
createVersionVector,
|
|
2494
|
+
defaultApplyFailureReason,
|
|
2148
2495
|
defaultSequenceFormat,
|
|
2149
2496
|
defineSchema,
|
|
2150
2497
|
deserializeVector,
|
|
2151
2498
|
dominates,
|
|
2499
|
+
extractScopeValuesFromClaims,
|
|
2152
2500
|
extractTimestamp,
|
|
2153
2501
|
formatSequenceValue,
|
|
2154
2502
|
generateFullDDL,
|
|
@@ -2156,8 +2504,13 @@ function generateProtoDefinitions(schema) {
|
|
|
2156
2504
|
generateRollbackSteps,
|
|
2157
2505
|
generateSQL,
|
|
2158
2506
|
generateUUIDv7,
|
|
2507
|
+
getCollectionScopeBindings,
|
|
2508
|
+
getKoraErrorFix,
|
|
2159
2509
|
getTransitionMap,
|
|
2510
|
+
hasSchemaSyncRules,
|
|
2511
|
+
isApplyFailure,
|
|
2160
2512
|
isAtomicOp,
|
|
2513
|
+
isCollectionSyncScoped,
|
|
2161
2514
|
isValidOperation,
|
|
2162
2515
|
isValidUUIDv7,
|
|
2163
2516
|
mergeVectors,
|