@korajs/core 1.0.0-beta.1 → 1.0.0-beta.4
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/bindings/index.d.cts +2 -2
- package/dist/bindings/index.d.ts +2 -2
- package/dist/{build-scope-map-DvOHbYjn.d.ts → build-scope-map-BvdTzmm4.d.ts} +1 -1
- package/dist/{build-scope-map-DtTr9C7A.d.cts → build-scope-map-K25AyQXb.d.cts} +1 -1
- package/dist/{events-CqDkr1LN.d.cts → events-DhblJN4r.d.cts} +43 -0
- package/dist/{events-CqDkr1LN.d.ts → events-DhblJN4r.d.ts} +43 -0
- package/dist/index.cjs +38 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -6
- package/dist/index.d.ts +38 -6
- package/dist/index.js +37 -20
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +2 -2
- package/dist/internal.d.ts +2 -2
- package/dist/{operation-BJtu18Fi.d.ts → operation-CC7O3-wf.d.ts} +1 -1
- package/dist/{operation-CFoY-ZmB.d.cts → operation-D-pemjcf.d.cts} +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { p as KoraEventEmitter } from '../events-
|
|
2
|
-
import { S as ScopeMap } from '../build-scope-map-
|
|
1
|
+
import { p as KoraEventEmitter } from '../events-DhblJN4r.cjs';
|
|
2
|
+
import { S as ScopeMap } from '../build-scope-map-K25AyQXb.cjs';
|
|
3
3
|
|
|
4
4
|
/** Sync status shape consumed by framework binding hooks (matches {@link SyncStatusInfo} in `@korajs/sync`). */
|
|
5
5
|
interface KoraBindingSyncStatus {
|
package/dist/bindings/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { p as KoraEventEmitter } from '../events-
|
|
2
|
-
import { S as ScopeMap } from '../build-scope-map-
|
|
1
|
+
import { p as KoraEventEmitter } from '../events-DhblJN4r.js';
|
|
2
|
+
import { S as ScopeMap } from '../build-scope-map-BvdTzmm4.js';
|
|
3
3
|
|
|
4
4
|
/** Sync status shape consumed by framework binding hooks (matches {@link SyncStatusInfo} in `@korajs/sync`). */
|
|
5
5
|
interface KoraBindingSyncStatus {
|
|
@@ -826,6 +826,21 @@ type KoraEvent = {
|
|
|
826
826
|
code: string;
|
|
827
827
|
message: string;
|
|
828
828
|
retriable: boolean;
|
|
829
|
+
} | {
|
|
830
|
+
/**
|
|
831
|
+
* The server rejected one of THIS client's outbound operations before it
|
|
832
|
+
* became authoritative. The op has been diverted out of the pending sync
|
|
833
|
+
* queue into the durable rejected store (kept, not retried); the app can
|
|
834
|
+
* surface the reason and decide whether to roll back the optimistic local
|
|
835
|
+
* write or let the user edit and resubmit.
|
|
836
|
+
*/
|
|
837
|
+
type: 'sync:operation-rejected';
|
|
838
|
+
operationId: string;
|
|
839
|
+
collection: string;
|
|
840
|
+
recordId: string;
|
|
841
|
+
code: string;
|
|
842
|
+
message: string;
|
|
843
|
+
retriable: boolean;
|
|
829
844
|
} | {
|
|
830
845
|
type: 'query:subscribed';
|
|
831
846
|
queryId: string;
|
|
@@ -880,6 +895,34 @@ type KoraEvent = {
|
|
|
880
895
|
type: 'store:quota-exceeded';
|
|
881
896
|
dbName: string;
|
|
882
897
|
message: string;
|
|
898
|
+
} | {
|
|
899
|
+
/**
|
|
900
|
+
* OPFS persistence was unavailable, so the store fell back to a
|
|
901
|
+
* NON-PERSISTENT in-memory database. Anything written this session is lost
|
|
902
|
+
* on reload. This is emitted instead of failing silently so the condition
|
|
903
|
+
* is observable rather than a data-loss trap.
|
|
904
|
+
*/
|
|
905
|
+
type: 'store:opfs-unavailable';
|
|
906
|
+
dbName: string;
|
|
907
|
+
/**
|
|
908
|
+
* Why OPFS could not be used. `lock-conflict` means another runtime on this
|
|
909
|
+
* origin already holds the OPFS pool for this database; `timeout` means the
|
|
910
|
+
* VFS install did not complete in time (common in headless CI); `unsupported`
|
|
911
|
+
* means the runtime has no usable OPFS.
|
|
912
|
+
*/
|
|
913
|
+
reason: 'lock-conflict' | 'timeout' | 'unsupported';
|
|
914
|
+
message: string;
|
|
915
|
+
} | {
|
|
916
|
+
/**
|
|
917
|
+
* Another runtime on this origin was already using this database name, so
|
|
918
|
+
* this runtime attached to it as a follower and now SHARES that one
|
|
919
|
+
* database. That is intended for multiple tabs of the SAME app; it is a bug
|
|
920
|
+
* if these are logically separate apps, which should each use a distinct
|
|
921
|
+
* store name (`store: { name: '...' }`) to stay isolated.
|
|
922
|
+
*/
|
|
923
|
+
type: 'store:db-name-collision';
|
|
924
|
+
dbName: string;
|
|
925
|
+
message: string;
|
|
883
926
|
} | {
|
|
884
927
|
type: 'replay:completed';
|
|
885
928
|
targetOperationId: string;
|
|
@@ -826,6 +826,21 @@ type KoraEvent = {
|
|
|
826
826
|
code: string;
|
|
827
827
|
message: string;
|
|
828
828
|
retriable: boolean;
|
|
829
|
+
} | {
|
|
830
|
+
/**
|
|
831
|
+
* The server rejected one of THIS client's outbound operations before it
|
|
832
|
+
* became authoritative. The op has been diverted out of the pending sync
|
|
833
|
+
* queue into the durable rejected store (kept, not retried); the app can
|
|
834
|
+
* surface the reason and decide whether to roll back the optimistic local
|
|
835
|
+
* write or let the user edit and resubmit.
|
|
836
|
+
*/
|
|
837
|
+
type: 'sync:operation-rejected';
|
|
838
|
+
operationId: string;
|
|
839
|
+
collection: string;
|
|
840
|
+
recordId: string;
|
|
841
|
+
code: string;
|
|
842
|
+
message: string;
|
|
843
|
+
retriable: boolean;
|
|
829
844
|
} | {
|
|
830
845
|
type: 'query:subscribed';
|
|
831
846
|
queryId: string;
|
|
@@ -880,6 +895,34 @@ type KoraEvent = {
|
|
|
880
895
|
type: 'store:quota-exceeded';
|
|
881
896
|
dbName: string;
|
|
882
897
|
message: string;
|
|
898
|
+
} | {
|
|
899
|
+
/**
|
|
900
|
+
* OPFS persistence was unavailable, so the store fell back to a
|
|
901
|
+
* NON-PERSISTENT in-memory database. Anything written this session is lost
|
|
902
|
+
* on reload. This is emitted instead of failing silently so the condition
|
|
903
|
+
* is observable rather than a data-loss trap.
|
|
904
|
+
*/
|
|
905
|
+
type: 'store:opfs-unavailable';
|
|
906
|
+
dbName: string;
|
|
907
|
+
/**
|
|
908
|
+
* Why OPFS could not be used. `lock-conflict` means another runtime on this
|
|
909
|
+
* origin already holds the OPFS pool for this database; `timeout` means the
|
|
910
|
+
* VFS install did not complete in time (common in headless CI); `unsupported`
|
|
911
|
+
* means the runtime has no usable OPFS.
|
|
912
|
+
*/
|
|
913
|
+
reason: 'lock-conflict' | 'timeout' | 'unsupported';
|
|
914
|
+
message: string;
|
|
915
|
+
} | {
|
|
916
|
+
/**
|
|
917
|
+
* Another runtime on this origin was already using this database name, so
|
|
918
|
+
* this runtime attached to it as a follower and now SHARES that one
|
|
919
|
+
* database. That is intended for multiple tabs of the SAME app; it is a bug
|
|
920
|
+
* if these are logically separate apps, which should each use a distinct
|
|
921
|
+
* store name (`store: { name: '...' }`) to stay isolated.
|
|
922
|
+
*/
|
|
923
|
+
type: 'store:db-name-collision';
|
|
924
|
+
dbName: string;
|
|
925
|
+
message: string;
|
|
883
926
|
} | {
|
|
884
927
|
type: 'replay:completed';
|
|
885
928
|
targetOperationId: string;
|
package/dist/index.cjs
CHANGED
|
@@ -94,6 +94,7 @@ __export(index_exports, {
|
|
|
94
94
|
migrate: () => migrate,
|
|
95
95
|
migrationStepsToSQL: () => migrationStepsToSQL,
|
|
96
96
|
op: () => op,
|
|
97
|
+
quoteIdent: () => quoteIdent,
|
|
97
98
|
resolveAtomicOp: () => resolveAtomicOp,
|
|
98
99
|
revealSecret: () => revealSecret,
|
|
99
100
|
rollbackStepsToSQL: () => rollbackStepsToSQL,
|
|
@@ -1310,7 +1311,7 @@ function validateStateMachineDefinition(collectionName, sm, fields) {
|
|
|
1310
1311
|
}
|
|
1311
1312
|
|
|
1312
1313
|
// src/schema/define.ts
|
|
1313
|
-
var COLLECTION_NAME_RE = /^[
|
|
1314
|
+
var COLLECTION_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
1314
1315
|
var FIELD_NAME_RE = /^[a-z][a-zA-Z0-9_]*$/;
|
|
1315
1316
|
var RESERVED_FIELDS = /* @__PURE__ */ new Set(["id", "_created_at", "_updated_at", "_version", "_deleted"]);
|
|
1316
1317
|
function defineSchema(input) {
|
|
@@ -1375,7 +1376,7 @@ function validateVersion(version) {
|
|
|
1375
1376
|
function validateCollectionName(name) {
|
|
1376
1377
|
if (!COLLECTION_NAME_RE.test(name)) {
|
|
1377
1378
|
throw new SchemaValidationError(
|
|
1378
|
-
`Collection name "${name}" is invalid. Must
|
|
1379
|
+
`Collection name "${name}" is invalid. Must start with a letter and contain only letters, numbers, and underscores.`,
|
|
1379
1380
|
{ collection: name }
|
|
1380
1381
|
);
|
|
1381
1382
|
}
|
|
@@ -1567,6 +1568,11 @@ function validateRelation(name, relation, collections) {
|
|
|
1567
1568
|
}
|
|
1568
1569
|
}
|
|
1569
1570
|
|
|
1571
|
+
// src/schema/quote-ident.ts
|
|
1572
|
+
function quoteIdent(name) {
|
|
1573
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1570
1576
|
// src/schema/sql-gen.ts
|
|
1571
1577
|
function generateSQL(collectionName, collection, relations) {
|
|
1572
1578
|
const statements = [];
|
|
@@ -1578,7 +1584,7 @@ function generateSQL(collectionName, collection, relations) {
|
|
|
1578
1584
|
if (relations) {
|
|
1579
1585
|
for (const rel of Object.values(relations)) {
|
|
1580
1586
|
if (rel.from === collectionName && rel.field === fieldName) {
|
|
1581
|
-
colDef += ` REFERENCES ${rel.to}(id)`;
|
|
1587
|
+
colDef += ` REFERENCES ${quoteIdent(rel.to)}(id)`;
|
|
1582
1588
|
fkFields.push(fieldName);
|
|
1583
1589
|
break;
|
|
1584
1590
|
}
|
|
@@ -1591,36 +1597,40 @@ function generateSQL(collectionName, collection, relations) {
|
|
|
1591
1597
|
columns.push("_version TEXT NOT NULL DEFAULT ''");
|
|
1592
1598
|
columns.push("_field_versions TEXT NOT NULL DEFAULT '{}'");
|
|
1593
1599
|
columns.push("_deleted INTEGER NOT NULL DEFAULT 0");
|
|
1594
|
-
statements.push(
|
|
1600
|
+
statements.push(
|
|
1601
|
+
`CREATE TABLE IF NOT EXISTS ${quoteIdent(collectionName)} (
|
|
1595
1602
|
${columns.join(",\n ")}
|
|
1596
|
-
)`
|
|
1603
|
+
)`
|
|
1604
|
+
);
|
|
1597
1605
|
for (const [fieldName, descriptor] of Object.entries(collection.fields)) {
|
|
1598
1606
|
const colDef = columnDefinition(fieldName, descriptor);
|
|
1599
|
-
statements.push(
|
|
1600
|
-
|
|
1607
|
+
statements.push(
|
|
1608
|
+
`--kora:safe-alter
|
|
1609
|
+
ALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN ${colDef}`
|
|
1610
|
+
);
|
|
1601
1611
|
}
|
|
1602
1612
|
statements.push(
|
|
1603
1613
|
`--kora:safe-alter
|
|
1604
|
-
ALTER TABLE ${collectionName} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`
|
|
1614
|
+
ALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`
|
|
1605
1615
|
);
|
|
1606
1616
|
statements.push(
|
|
1607
1617
|
`--kora:safe-alter
|
|
1608
|
-
ALTER TABLE ${collectionName} ADD COLUMN _field_versions TEXT NOT NULL DEFAULT '{}'`
|
|
1618
|
+
ALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN _field_versions TEXT NOT NULL DEFAULT '{}'`
|
|
1609
1619
|
);
|
|
1610
1620
|
for (const indexField of collection.indexes) {
|
|
1611
1621
|
statements.push(
|
|
1612
|
-
`CREATE INDEX IF NOT EXISTS idx_${collectionName}_${indexField} ON ${collectionName} (${indexField})`
|
|
1622
|
+
`CREATE INDEX IF NOT EXISTS idx_${collectionName}_${indexField} ON ${quoteIdent(collectionName)} (${quoteIdent(indexField)})`
|
|
1613
1623
|
);
|
|
1614
1624
|
}
|
|
1615
1625
|
for (const fkField of fkFields) {
|
|
1616
1626
|
if (!indexedFields.has(fkField)) {
|
|
1617
1627
|
statements.push(
|
|
1618
|
-
`CREATE INDEX IF NOT EXISTS idx_${collectionName}_${fkField} ON ${collectionName} (${fkField})`
|
|
1628
|
+
`CREATE INDEX IF NOT EXISTS idx_${collectionName}_${fkField} ON ${quoteIdent(collectionName)} (${quoteIdent(fkField)})`
|
|
1619
1629
|
);
|
|
1620
1630
|
}
|
|
1621
1631
|
}
|
|
1622
1632
|
statements.push(
|
|
1623
|
-
`CREATE TABLE IF NOT EXISTS _kora_ops_${collectionName} (
|
|
1633
|
+
`CREATE TABLE IF NOT EXISTS ${quoteIdent(`_kora_ops_${collectionName}`)} (
|
|
1624
1634
|
id TEXT PRIMARY KEY NOT NULL,
|
|
1625
1635
|
node_id TEXT NOT NULL,
|
|
1626
1636
|
type TEXT NOT NULL,
|
|
@@ -1634,7 +1644,7 @@ ALTER TABLE ${collectionName} ADD COLUMN _field_versions TEXT NOT NULL DEFAULT '
|
|
|
1634
1644
|
)`
|
|
1635
1645
|
);
|
|
1636
1646
|
statements.push(
|
|
1637
|
-
`CREATE INDEX IF NOT EXISTS idx_kora_ops_${collectionName}_record_id ON _kora_ops_${collectionName} (record_id)`
|
|
1647
|
+
`CREATE INDEX IF NOT EXISTS idx_kora_ops_${collectionName}_record_id ON ${quoteIdent(`_kora_ops_${collectionName}`)} (record_id)`
|
|
1638
1648
|
);
|
|
1639
1649
|
return statements;
|
|
1640
1650
|
}
|
|
@@ -1652,6 +1662,9 @@ function generateFullDDL(schema) {
|
|
|
1652
1662
|
statements.push(
|
|
1653
1663
|
"CREATE TABLE IF NOT EXISTS _kora_sync_queue (\n id TEXT PRIMARY KEY NOT NULL,\n payload TEXT NOT NULL\n)"
|
|
1654
1664
|
);
|
|
1665
|
+
statements.push(
|
|
1666
|
+
"CREATE TABLE IF NOT EXISTS _kora_sync_rejected (\n operation_id TEXT PRIMARY KEY NOT NULL,\n collection TEXT NOT NULL,\n record_id TEXT NOT NULL,\n code TEXT NOT NULL,\n message TEXT NOT NULL,\n retriable INTEGER NOT NULL,\n rejected_at INTEGER NOT NULL\n)"
|
|
1667
|
+
);
|
|
1655
1668
|
statements.push(
|
|
1656
1669
|
"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)"
|
|
1657
1670
|
);
|
|
@@ -1668,7 +1681,7 @@ function generateFullDDL(schema) {
|
|
|
1668
1681
|
}
|
|
1669
1682
|
function columnDefinition(fieldName, descriptor) {
|
|
1670
1683
|
const sqlType = mapFieldType(descriptor);
|
|
1671
|
-
const parts = [fieldName, sqlType];
|
|
1684
|
+
const parts = [quoteIdent(fieldName), sqlType];
|
|
1672
1685
|
if (descriptor.required && descriptor.defaultValue === void 0 && !descriptor.auto) {
|
|
1673
1686
|
parts.push("NOT NULL");
|
|
1674
1687
|
}
|
|
@@ -1677,7 +1690,7 @@ function columnDefinition(fieldName, descriptor) {
|
|
|
1677
1690
|
}
|
|
1678
1691
|
if (descriptor.kind === "enum" && descriptor.enumValues) {
|
|
1679
1692
|
const values = descriptor.enumValues.map((v) => `'${v}'`).join(", ");
|
|
1680
|
-
parts.push(`CHECK (${fieldName} IN (${values}))`);
|
|
1693
|
+
parts.push(`CHECK (${quoteIdent(fieldName)} IN (${values}))`);
|
|
1681
1694
|
}
|
|
1682
1695
|
return parts.join(" ");
|
|
1683
1696
|
}
|
|
@@ -2932,14 +2945,18 @@ function migrationStepsToSQL(steps) {
|
|
|
2932
2945
|
statements.push(addFieldSQL(step.collection, step.field, step.descriptor));
|
|
2933
2946
|
break;
|
|
2934
2947
|
case "removeField":
|
|
2935
|
-
statements.push(
|
|
2948
|
+
statements.push(
|
|
2949
|
+
`ALTER TABLE ${quoteIdent(step.collection)} DROP COLUMN ${quoteIdent(step.field)}`
|
|
2950
|
+
);
|
|
2936
2951
|
break;
|
|
2937
2952
|
case "renameField":
|
|
2938
|
-
statements.push(
|
|
2953
|
+
statements.push(
|
|
2954
|
+
`ALTER TABLE ${quoteIdent(step.collection)} RENAME COLUMN ${quoteIdent(step.from)} TO ${quoteIdent(step.to)}`
|
|
2955
|
+
);
|
|
2939
2956
|
break;
|
|
2940
2957
|
case "addIndex":
|
|
2941
2958
|
statements.push(
|
|
2942
|
-
`CREATE INDEX IF NOT EXISTS idx_${step.collection}_${step.field} ON ${step.collection} (${step.field})`
|
|
2959
|
+
`CREATE INDEX IF NOT EXISTS idx_${step.collection}_${step.field} ON ${quoteIdent(step.collection)} (${quoteIdent(step.field)})`
|
|
2943
2960
|
);
|
|
2944
2961
|
break;
|
|
2945
2962
|
case "removeIndex":
|
|
@@ -2957,13 +2974,13 @@ function rollbackStepsToSQL(migration) {
|
|
|
2957
2974
|
}
|
|
2958
2975
|
function addFieldSQL(collection, field, descriptor) {
|
|
2959
2976
|
const sqlType = mapFieldType2(descriptor);
|
|
2960
|
-
const parts = [`ALTER TABLE ${collection} ADD COLUMN ${field}`, sqlType];
|
|
2977
|
+
const parts = [`ALTER TABLE ${quoteIdent(collection)} ADD COLUMN ${quoteIdent(field)}`, sqlType];
|
|
2961
2978
|
if (descriptor.defaultValue !== void 0) {
|
|
2962
2979
|
parts.push(`DEFAULT ${sqlDefault2(descriptor.defaultValue)}`);
|
|
2963
2980
|
}
|
|
2964
2981
|
if (descriptor.kind === "enum" && descriptor.enumValues) {
|
|
2965
2982
|
const values = descriptor.enumValues.map((v) => `'${v}'`).join(", ");
|
|
2966
|
-
parts.push(`CHECK (${field} IN (${values}))`);
|
|
2983
|
+
parts.push(`CHECK (${quoteIdent(field)} IN (${values}))`);
|
|
2967
2984
|
}
|
|
2968
2985
|
return parts.join(" ");
|
|
2969
2986
|
}
|
|
@@ -3314,6 +3331,7 @@ function generateProtoDefinitions(schema) {
|
|
|
3314
3331
|
migrate,
|
|
3315
3332
|
migrationStepsToSQL,
|
|
3316
3333
|
op,
|
|
3334
|
+
quoteIdent,
|
|
3317
3335
|
resolveAtomicOp,
|
|
3318
3336
|
revealSecret,
|
|
3319
3337
|
rollbackStepsToSQL,
|