@korajs/server 1.0.0-beta.1 → 1.0.0-beta.2
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/index.cjs +313 -198
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +456 -278
- package/dist/index.d.ts +456 -278
- package/dist/index.js +307 -193
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -54,11 +54,17 @@ function createDefaultLogger() {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// src/apply/rejection-taxonomy.ts
|
|
58
|
+
var RETRIABLE_REJECTION_CODES = /* @__PURE__ */ new Set(["RATE_LIMIT"]);
|
|
59
|
+
function isRetriableRejection(code) {
|
|
60
|
+
return RETRIABLE_REJECTION_CODES.has(code);
|
|
61
|
+
}
|
|
62
|
+
|
|
57
63
|
// src/store/memory-server-store.ts
|
|
58
64
|
import { generateUUIDv7 } from "@korajs/core";
|
|
59
65
|
|
|
60
66
|
// src/store/materialization.ts
|
|
61
|
-
import { StorageError } from "@korajs/core";
|
|
67
|
+
import { StorageError, quoteIdent } from "@korajs/core";
|
|
62
68
|
function fieldTypeToSql(descriptor, dialect) {
|
|
63
69
|
switch (descriptor.kind) {
|
|
64
70
|
case "string":
|
|
@@ -73,6 +79,14 @@ function fieldTypeToSql(descriptor, dialect) {
|
|
|
73
79
|
return dialect === "postgres" ? "BIGINT" : "INTEGER";
|
|
74
80
|
case "array":
|
|
75
81
|
return dialect === "postgres" ? "JSONB" : "TEXT";
|
|
82
|
+
case "object":
|
|
83
|
+
return dialect === "postgres" ? "JSONB" : "TEXT";
|
|
84
|
+
case "json":
|
|
85
|
+
return dialect === "postgres" ? "JSONB" : "TEXT";
|
|
86
|
+
case "blob":
|
|
87
|
+
return "TEXT";
|
|
88
|
+
case "secret":
|
|
89
|
+
return "TEXT";
|
|
76
90
|
case "richtext":
|
|
77
91
|
return dialect === "postgres" ? "BYTEA" : "BLOB";
|
|
78
92
|
}
|
|
@@ -89,13 +103,13 @@ function generateCollectionDDL(name, collection, dialect) {
|
|
|
89
103
|
const columns = ["id TEXT PRIMARY KEY NOT NULL"];
|
|
90
104
|
for (const [fieldName, descriptor] of Object.entries(collection.fields)) {
|
|
91
105
|
const sqlType = fieldTypeToSql(descriptor, dialect);
|
|
92
|
-
let colDef = `${fieldName} ${sqlType}`;
|
|
106
|
+
let colDef = `${quoteIdent(fieldName)} ${sqlType}`;
|
|
93
107
|
if (descriptor.defaultValue !== void 0) {
|
|
94
108
|
colDef += ` DEFAULT ${sqlDefaultLiteral(descriptor.defaultValue)}`;
|
|
95
109
|
}
|
|
96
110
|
if (descriptor.kind === "enum" && descriptor.enumValues) {
|
|
97
111
|
const values = descriptor.enumValues.map((v) => `'${v}'`).join(", ");
|
|
98
|
-
colDef += ` CHECK (${fieldName} IN (${values}))`;
|
|
112
|
+
colDef += ` CHECK (${quoteIdent(fieldName)} IN (${values}))`;
|
|
99
113
|
}
|
|
100
114
|
columns.push(colDef);
|
|
101
115
|
}
|
|
@@ -103,24 +117,26 @@ function generateCollectionDDL(name, collection, dialect) {
|
|
|
103
117
|
columns.push(`_created_at ${tsType} NOT NULL DEFAULT 0`);
|
|
104
118
|
columns.push(`_updated_at ${tsType} NOT NULL DEFAULT 0`);
|
|
105
119
|
columns.push("_deleted INTEGER NOT NULL DEFAULT 0");
|
|
106
|
-
statements.push(`CREATE TABLE IF NOT EXISTS ${name} (
|
|
120
|
+
statements.push(`CREATE TABLE IF NOT EXISTS ${quoteIdent(name)} (
|
|
107
121
|
${columns.join(",\n ")}
|
|
108
122
|
)`);
|
|
109
123
|
for (const [fieldName, descriptor] of Object.entries(collection.fields)) {
|
|
110
124
|
const sqlType = fieldTypeToSql(descriptor, dialect);
|
|
111
|
-
let colDef = `${fieldName} ${sqlType}`;
|
|
125
|
+
let colDef = `${quoteIdent(fieldName)} ${sqlType}`;
|
|
112
126
|
if (descriptor.defaultValue !== void 0) {
|
|
113
127
|
colDef += ` DEFAULT ${sqlDefaultLiteral(descriptor.defaultValue)}`;
|
|
114
128
|
}
|
|
115
129
|
statements.push(`--kora:safe-alter
|
|
116
|
-
ALTER TABLE ${name} ADD COLUMN ${colDef}`);
|
|
130
|
+
ALTER TABLE ${quoteIdent(name)} ADD COLUMN ${colDef}`);
|
|
117
131
|
}
|
|
118
132
|
for (const indexField of collection.indexes) {
|
|
119
133
|
statements.push(
|
|
120
|
-
`CREATE INDEX IF NOT EXISTS idx_${name}_${indexField} ON ${name} (${indexField})`
|
|
134
|
+
`CREATE INDEX IF NOT EXISTS idx_${name}_${indexField} ON ${quoteIdent(name)} (${quoteIdent(indexField)})`
|
|
121
135
|
);
|
|
122
136
|
}
|
|
123
|
-
statements.push(
|
|
137
|
+
statements.push(
|
|
138
|
+
`CREATE INDEX IF NOT EXISTS idx_${name}__deleted ON ${quoteIdent(name)} (_deleted)`
|
|
139
|
+
);
|
|
124
140
|
return statements;
|
|
125
141
|
}
|
|
126
142
|
function generateAllCollectionDDL(schema, dialect) {
|
|
@@ -590,7 +606,7 @@ var MemoryServerStore = class {
|
|
|
590
606
|
};
|
|
591
607
|
|
|
592
608
|
// src/store/postgres-server-store.ts
|
|
593
|
-
import { generateUUIDv7 as generateUUIDv72 } from "@korajs/core";
|
|
609
|
+
import { generateUUIDv7 as generateUUIDv72, quoteIdent as quoteIdent2 } from "@korajs/core";
|
|
594
610
|
import { and, asc, between, count, eq, sql } from "drizzle-orm";
|
|
595
611
|
|
|
596
612
|
// src/store/drizzle-pg-schema.ts
|
|
@@ -754,7 +770,7 @@ var PostgresServerStore = class {
|
|
|
754
770
|
this.assertCollection(collection);
|
|
755
771
|
const schema = this.schema;
|
|
756
772
|
const collectionDef = schema.collections[collection];
|
|
757
|
-
const query = sql`SELECT * FROM ${sql.raw(collection)} WHERE id = ${id} AND _deleted = 0`;
|
|
773
|
+
const query = sql`SELECT * FROM ${sql.raw(quoteIdent2(collection))} WHERE id = ${id} AND _deleted = 0`;
|
|
758
774
|
const rows = await this.db.execute(query);
|
|
759
775
|
if (rows.length === 0) return null;
|
|
760
776
|
return this.deserializeRow(rows[0], collectionDef);
|
|
@@ -771,7 +787,7 @@ var PostgresServerStore = class {
|
|
|
771
787
|
}
|
|
772
788
|
}
|
|
773
789
|
const whereClause = this.buildWhereClause(where ?? {}, false);
|
|
774
|
-
const query = sql`SELECT COUNT(*) as cnt FROM ${sql.raw(collection)} WHERE ${whereClause}`;
|
|
790
|
+
const query = sql`SELECT COUNT(*) as cnt FROM ${sql.raw(quoteIdent2(collection))} WHERE ${whereClause}`;
|
|
775
791
|
const rows = await this.db.execute(query);
|
|
776
792
|
const cnt = rows[0]?.cnt;
|
|
777
793
|
return typeof cnt === "string" ? Number.parseInt(cnt, 10) : cnt ?? 0;
|
|
@@ -858,7 +874,7 @@ var PostgresServerStore = class {
|
|
|
858
874
|
);
|
|
859
875
|
} else {
|
|
860
876
|
await txOrDb.execute(
|
|
861
|
-
sql`UPDATE ${sql.raw(collection)} SET _deleted = 1, _updated_at = ${Date.now()} WHERE id = ${recordId}`
|
|
877
|
+
sql`UPDATE ${sql.raw(quoteIdent2(collection))} SET _deleted = 1, _updated_at = ${Date.now()} WHERE id = ${recordId}`
|
|
862
878
|
);
|
|
863
879
|
}
|
|
864
880
|
}
|
|
@@ -877,16 +893,16 @@ var PostgresServerStore = class {
|
|
|
877
893
|
updatedAt,
|
|
878
894
|
0
|
|
879
895
|
];
|
|
880
|
-
const columnsSql = sql.raw(allColumns.join(", "));
|
|
896
|
+
const columnsSql = sql.raw(allColumns.map((c) => quoteIdent2(c)).join(", "));
|
|
881
897
|
const valuesSql = sql.join(
|
|
882
898
|
values.map((v) => sql`${v}`),
|
|
883
899
|
sql.raw(", ")
|
|
884
900
|
);
|
|
885
901
|
const updateSet = sql.raw(
|
|
886
|
-
allColumns.slice(1).map((c) => `${c} = excluded.${c}`).join(", ")
|
|
902
|
+
allColumns.slice(1).map((c) => `${quoteIdent2(c)} = excluded.${quoteIdent2(c)}`).join(", ")
|
|
887
903
|
);
|
|
888
904
|
await txOrDb.execute(
|
|
889
|
-
sql`INSERT INTO ${sql.raw(tableName)} (${columnsSql}) VALUES (${valuesSql}) ON CONFLICT (id) DO UPDATE SET ${updateSet}`
|
|
905
|
+
sql`INSERT INTO ${sql.raw(quoteIdent2(tableName))} (${columnsSql}) VALUES (${valuesSql}) ON CONFLICT (id) DO UPDATE SET ${updateSet}`
|
|
890
906
|
);
|
|
891
907
|
}
|
|
892
908
|
/**
|
|
@@ -946,7 +962,7 @@ var PostgresServerStore = class {
|
|
|
946
962
|
);
|
|
947
963
|
} else {
|
|
948
964
|
await this.db.execute(
|
|
949
|
-
sql`INSERT INTO ${sql.raw(collectionName)} (id, _deleted, _created_at, _updated_at) VALUES (${recordId}, 1, ${Date.now()}, ${Date.now()}) ON CONFLICT (id) DO UPDATE SET _deleted = 1, _updated_at = ${Date.now()}`
|
|
965
|
+
sql`INSERT INTO ${sql.raw(quoteIdent2(collectionName))} (id, _deleted, _created_at, _updated_at) VALUES (${recordId}, 1, ${Date.now()}, ${Date.now()}) ON CONFLICT (id) DO UPDATE SET _deleted = 1, _updated_at = ${Date.now()}`
|
|
950
966
|
);
|
|
951
967
|
}
|
|
952
968
|
}
|
|
@@ -959,10 +975,12 @@ var PostgresServerStore = class {
|
|
|
959
975
|
options?.where ?? {},
|
|
960
976
|
options?.includeDeleted ?? false
|
|
961
977
|
);
|
|
962
|
-
const parts = [
|
|
978
|
+
const parts = [
|
|
979
|
+
sql`SELECT * FROM ${sql.raw(quoteIdent2(collection))} WHERE ${whereClause}`
|
|
980
|
+
];
|
|
963
981
|
if (options?.orderBy) {
|
|
964
982
|
const dir = options.orderDirection === "desc" ? "DESC" : "ASC";
|
|
965
|
-
parts.push(sql.raw(` ORDER BY ${options.orderBy} ${dir}`));
|
|
983
|
+
parts.push(sql.raw(` ORDER BY ${quoteIdent2(options.orderBy)} ${dir}`));
|
|
966
984
|
}
|
|
967
985
|
if (options?.limit !== void 0) {
|
|
968
986
|
parts.push(sql` LIMIT ${options.limit}`);
|
|
@@ -978,7 +996,7 @@ var PostgresServerStore = class {
|
|
|
978
996
|
conditions.push(sql.raw("_deleted = 0"));
|
|
979
997
|
}
|
|
980
998
|
for (const [key, value] of Object.entries(where)) {
|
|
981
|
-
conditions.push(sql`${sql.raw(key)} = ${value}`);
|
|
999
|
+
conditions.push(sql`${sql.raw(quoteIdent2(key))} = ${value}`);
|
|
982
1000
|
}
|
|
983
1001
|
if (conditions.length === 0) {
|
|
984
1002
|
return sql.raw("1 = 1");
|
|
@@ -1173,7 +1191,7 @@ async function loadPostgresDeps() {
|
|
|
1173
1191
|
|
|
1174
1192
|
// src/store/sqlite-server-store.ts
|
|
1175
1193
|
import { createRequire } from "module";
|
|
1176
|
-
import { generateUUIDv7 as generateUUIDv73 } from "@korajs/core";
|
|
1194
|
+
import { generateUUIDv7 as generateUUIDv73, quoteIdent as quoteIdent3 } from "@korajs/core";
|
|
1177
1195
|
import { and as and2, asc as asc2, between as between2, count as count2, eq as eq2, sql as sql2 } from "drizzle-orm";
|
|
1178
1196
|
|
|
1179
1197
|
// src/store/drizzle-schema.ts
|
|
@@ -1328,7 +1346,7 @@ var SqliteServerStore = class {
|
|
|
1328
1346
|
this.assertCollection(collection);
|
|
1329
1347
|
const schema = this.schema;
|
|
1330
1348
|
const collectionDef = schema.collections[collection];
|
|
1331
|
-
const query = sql2`SELECT * FROM ${sql2.raw(collection)} WHERE id = ${id} AND _deleted = 0`;
|
|
1349
|
+
const query = sql2`SELECT * FROM ${sql2.raw(quoteIdent3(collection))} WHERE id = ${id} AND _deleted = 0`;
|
|
1332
1350
|
const rows = this.db.all(query);
|
|
1333
1351
|
if (rows.length === 0) return null;
|
|
1334
1352
|
return this.deserializeRow(rows[0], collectionDef);
|
|
@@ -1344,7 +1362,7 @@ var SqliteServerStore = class {
|
|
|
1344
1362
|
}
|
|
1345
1363
|
}
|
|
1346
1364
|
const whereClause = this.buildWhereClause(where ?? {}, false);
|
|
1347
|
-
const query = sql2`SELECT COUNT(*) as cnt FROM ${sql2.raw(collection)} WHERE ${whereClause}`;
|
|
1365
|
+
const query = sql2`SELECT COUNT(*) as cnt FROM ${sql2.raw(quoteIdent3(collection))} WHERE ${whereClause}`;
|
|
1348
1366
|
const rows = this.db.all(query);
|
|
1349
1367
|
return rows[0]?.cnt ?? 0;
|
|
1350
1368
|
}
|
|
@@ -1421,7 +1439,7 @@ var SqliteServerStore = class {
|
|
|
1421
1439
|
);
|
|
1422
1440
|
} else {
|
|
1423
1441
|
txOrDb.run(
|
|
1424
|
-
sql2`UPDATE ${sql2.raw(collection)} SET _deleted = 1, _updated_at = ${Date.now()} WHERE id = ${recordId}`
|
|
1442
|
+
sql2`UPDATE ${sql2.raw(quoteIdent3(collection))} SET _deleted = 1, _updated_at = ${Date.now()} WHERE id = ${recordId}`
|
|
1425
1443
|
);
|
|
1426
1444
|
}
|
|
1427
1445
|
}
|
|
@@ -1442,16 +1460,16 @@ var SqliteServerStore = class {
|
|
|
1442
1460
|
0
|
|
1443
1461
|
// _deleted = false
|
|
1444
1462
|
];
|
|
1445
|
-
const columnsSql = sql2.raw(allColumns.join(", "));
|
|
1463
|
+
const columnsSql = sql2.raw(allColumns.map((c) => quoteIdent3(c)).join(", "));
|
|
1446
1464
|
const valuesSql = sql2.join(
|
|
1447
1465
|
values.map((v) => sql2`${v}`),
|
|
1448
1466
|
sql2.raw(", ")
|
|
1449
1467
|
);
|
|
1450
1468
|
const updateSet = sql2.raw(
|
|
1451
|
-
allColumns.slice(1).map((c) => `${c} = excluded.${c}`).join(", ")
|
|
1469
|
+
allColumns.slice(1).map((c) => `${quoteIdent3(c)} = excluded.${quoteIdent3(c)}`).join(", ")
|
|
1452
1470
|
);
|
|
1453
1471
|
txOrDb.run(
|
|
1454
|
-
sql2`INSERT INTO ${sql2.raw(tableName)} (${columnsSql}) VALUES (${valuesSql}) ON CONFLICT (id) DO UPDATE SET ${updateSet}`
|
|
1472
|
+
sql2`INSERT INTO ${sql2.raw(quoteIdent3(tableName))} (${columnsSql}) VALUES (${valuesSql}) ON CONFLICT (id) DO UPDATE SET ${updateSet}`
|
|
1455
1473
|
);
|
|
1456
1474
|
}
|
|
1457
1475
|
/**
|
|
@@ -1509,7 +1527,7 @@ var SqliteServerStore = class {
|
|
|
1509
1527
|
);
|
|
1510
1528
|
} else {
|
|
1511
1529
|
tx.run(
|
|
1512
|
-
sql2`INSERT INTO ${sql2.raw(collectionName)} (id, _deleted, _created_at, _updated_at) VALUES (${recordId}, 1, ${Date.now()}, ${Date.now()}) ON CONFLICT (id) DO UPDATE SET _deleted = 1, _updated_at = ${Date.now()}`
|
|
1530
|
+
sql2`INSERT INTO ${sql2.raw(quoteIdent3(collectionName))} (id, _deleted, _created_at, _updated_at) VALUES (${recordId}, 1, ${Date.now()}, ${Date.now()}) ON CONFLICT (id) DO UPDATE SET _deleted = 1, _updated_at = ${Date.now()}`
|
|
1513
1531
|
);
|
|
1514
1532
|
}
|
|
1515
1533
|
}
|
|
@@ -1523,10 +1541,12 @@ var SqliteServerStore = class {
|
|
|
1523
1541
|
options?.where ?? {},
|
|
1524
1542
|
options?.includeDeleted ?? false
|
|
1525
1543
|
);
|
|
1526
|
-
const parts = [
|
|
1544
|
+
const parts = [
|
|
1545
|
+
sql2`SELECT * FROM ${sql2.raw(quoteIdent3(collection))} WHERE ${whereClause}`
|
|
1546
|
+
];
|
|
1527
1547
|
if (options?.orderBy) {
|
|
1528
1548
|
const dir = options.orderDirection === "desc" ? "DESC" : "ASC";
|
|
1529
|
-
parts.push(sql2.raw(` ORDER BY ${options.orderBy} ${dir}`));
|
|
1549
|
+
parts.push(sql2.raw(` ORDER BY ${quoteIdent3(options.orderBy)} ${dir}`));
|
|
1530
1550
|
}
|
|
1531
1551
|
if (options?.limit !== void 0) {
|
|
1532
1552
|
parts.push(sql2` LIMIT ${options.limit}`);
|
|
@@ -1542,7 +1562,7 @@ var SqliteServerStore = class {
|
|
|
1542
1562
|
conditions.push(sql2.raw("_deleted = 0"));
|
|
1543
1563
|
}
|
|
1544
1564
|
for (const [key, value] of Object.entries(where)) {
|
|
1545
|
-
conditions.push(sql2`${sql2.raw(key)} = ${value}`);
|
|
1565
|
+
conditions.push(sql2`${sql2.raw(quoteIdent3(key))} = ${value}`);
|
|
1546
1566
|
}
|
|
1547
1567
|
if (conditions.length === 0) {
|
|
1548
1568
|
return sql2.raw("1 = 1");
|
|
@@ -1993,12 +2013,14 @@ async function applyServerOperation(store, op, relationLookup) {
|
|
|
1993
2013
|
const lookup = relationLookup ?? (schema ? buildMergeRelationLookup(schema) : /* @__PURE__ */ new Map());
|
|
1994
2014
|
const constraintCheck = await validateIncomingOperationConstraints(store, op, schema);
|
|
1995
2015
|
if (!constraintCheck.valid) {
|
|
2016
|
+
const code = constraintCheck.code ?? "CONSTRAINT_VIOLATION";
|
|
1996
2017
|
return {
|
|
1997
2018
|
result: "skipped",
|
|
1998
2019
|
appliedOperations: [],
|
|
1999
2020
|
rejection: {
|
|
2000
|
-
code
|
|
2001
|
-
message: constraintCheck.message ?? `Operation "${op.id}" violates a schema constraint
|
|
2021
|
+
code,
|
|
2022
|
+
message: constraintCheck.message ?? `Operation "${op.id}" violates a schema constraint`,
|
|
2023
|
+
retriable: isRetriableRejection(code)
|
|
2002
2024
|
}
|
|
2003
2025
|
};
|
|
2004
2026
|
}
|
|
@@ -2011,7 +2033,8 @@ async function applyServerOperation(store, op, relationLookup) {
|
|
|
2011
2033
|
appliedOperations: [],
|
|
2012
2034
|
rejection: {
|
|
2013
2035
|
code: "REFERENTIAL_INTEGRITY",
|
|
2014
|
-
message: `Operation "${op.id}" violates referential integrity on "${op.collection}"
|
|
2036
|
+
message: `Operation "${op.id}" violates referential integrity on "${op.collection}"`,
|
|
2037
|
+
retriable: isRetriableRejection("REFERENTIAL_INTEGRITY")
|
|
2015
2038
|
}
|
|
2016
2039
|
};
|
|
2017
2040
|
}
|
|
@@ -2220,6 +2243,8 @@ var ClientSession = class {
|
|
|
2220
2243
|
maxOperationBytes;
|
|
2221
2244
|
maxOpsPerMinute;
|
|
2222
2245
|
rateLimiter;
|
|
2246
|
+
validateOperation;
|
|
2247
|
+
koraContext;
|
|
2223
2248
|
constructor(options) {
|
|
2224
2249
|
this.sessionId = options.sessionId;
|
|
2225
2250
|
this.transport = options.transport;
|
|
@@ -2244,6 +2269,8 @@ var ClientSession = class {
|
|
|
2244
2269
|
this.maxOperationBytes = options.maxOperationBytes ?? DEFAULT_MAX_OPERATION_BYTES;
|
|
2245
2270
|
this.maxOpsPerMinute = options.maxOpsPerMinute ?? DEFAULT_MAX_OPS_PER_MINUTE;
|
|
2246
2271
|
this.rateLimiter = new SessionRateLimiter(this.maxOpsPerMinute);
|
|
2272
|
+
this.validateOperation = options.validateOperation ?? null;
|
|
2273
|
+
this.koraContext = options.koraContext ?? null;
|
|
2247
2274
|
}
|
|
2248
2275
|
/**
|
|
2249
2276
|
* Start handling messages from the client transport.
|
|
@@ -2490,10 +2517,39 @@ var ClientSession = class {
|
|
|
2490
2517
|
);
|
|
2491
2518
|
continue;
|
|
2492
2519
|
}
|
|
2520
|
+
if (this.validateOperation && this.koraContext) {
|
|
2521
|
+
let decision;
|
|
2522
|
+
try {
|
|
2523
|
+
decision = await this.validateOperation(op, {
|
|
2524
|
+
auth: this.authContext,
|
|
2525
|
+
kora: this.koraContext
|
|
2526
|
+
});
|
|
2527
|
+
} catch (error) {
|
|
2528
|
+
const message = error instanceof Error ? error.message : "validator error";
|
|
2529
|
+
this.sendOperationRejected(op, "VALIDATION_ERROR", `Validator threw: ${message}`, true);
|
|
2530
|
+
continue;
|
|
2531
|
+
}
|
|
2532
|
+
if (decision.action === "reject") {
|
|
2533
|
+
this.sendOperationRejected(
|
|
2534
|
+
op,
|
|
2535
|
+
decision.code,
|
|
2536
|
+
decision.message,
|
|
2537
|
+
decision.retriable ?? isRetriableRejection(decision.code)
|
|
2538
|
+
);
|
|
2539
|
+
continue;
|
|
2540
|
+
}
|
|
2541
|
+
if (decision.action === "ignore") {
|
|
2542
|
+
continue;
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2493
2545
|
try {
|
|
2494
2546
|
const applyResult = await applyServerOperation(this.store, op);
|
|
2495
2547
|
if (applyResult.rejection) {
|
|
2496
|
-
this.sendError(
|
|
2548
|
+
this.sendError(
|
|
2549
|
+
applyResult.rejection.code,
|
|
2550
|
+
applyResult.rejection.message,
|
|
2551
|
+
applyResult.rejection.retriable
|
|
2552
|
+
);
|
|
2497
2553
|
continue;
|
|
2498
2554
|
}
|
|
2499
2555
|
if (applyResult.result === "applied") {
|
|
@@ -2612,6 +2668,25 @@ var ClientSession = class {
|
|
|
2612
2668
|
};
|
|
2613
2669
|
this.sendToClient(errorMsg);
|
|
2614
2670
|
}
|
|
2671
|
+
/**
|
|
2672
|
+
* Reject one specific client operation, tied to its id, so the submitter can
|
|
2673
|
+
* divert it out of its pending queue into a durable rejected store rather than
|
|
2674
|
+
* losing it or retrying forever. The op is NOT materialized, so no other
|
|
2675
|
+
* replica ever sees it.
|
|
2676
|
+
*/
|
|
2677
|
+
sendOperationRejected(op, code, message, retriable) {
|
|
2678
|
+
const rejectedMsg = {
|
|
2679
|
+
type: "operation-rejected",
|
|
2680
|
+
messageId: generateUUIDv74(),
|
|
2681
|
+
operationId: op.id,
|
|
2682
|
+
collection: op.collection,
|
|
2683
|
+
recordId: op.recordId,
|
|
2684
|
+
code,
|
|
2685
|
+
message,
|
|
2686
|
+
retriable
|
|
2687
|
+
};
|
|
2688
|
+
this.sendToClient(rejectedMsg);
|
|
2689
|
+
}
|
|
2615
2690
|
setSerializerWireFormat(format) {
|
|
2616
2691
|
if (typeof this.serializer.setWireFormat === "function") {
|
|
2617
2692
|
this.serializer.setWireFormat(format);
|
|
@@ -2632,7 +2707,7 @@ function selectWireFormat(supportedWireFormats) {
|
|
|
2632
2707
|
}
|
|
2633
2708
|
|
|
2634
2709
|
// src/server/kora-sync-server.ts
|
|
2635
|
-
import { SyncError as SyncError4, generateUUIDv7 as
|
|
2710
|
+
import { SyncError as SyncError4, generateUUIDv7 as generateUUIDv77, isBlobRef } from "@korajs/core";
|
|
2636
2711
|
import { SimpleEventEmitter } from "@korajs/core/internal";
|
|
2637
2712
|
import { JsonMessageSerializer as JsonMessageSerializer2 } from "@korajs/sync";
|
|
2638
2713
|
|
|
@@ -2992,6 +3067,169 @@ var YjsDocRelay = class {
|
|
|
2992
3067
|
}
|
|
2993
3068
|
};
|
|
2994
3069
|
|
|
3070
|
+
// src/server/route-context.ts
|
|
3071
|
+
import { HybridLogicalClock as HybridLogicalClock2, createOperation as createOperation2, generateUUIDv7 as generateUUIDv76 } from "@korajs/core";
|
|
3072
|
+
function recordMatchesScope(collection, record, scope) {
|
|
3073
|
+
const collectionScope = scope[collection];
|
|
3074
|
+
if (!collectionScope) {
|
|
3075
|
+
return false;
|
|
3076
|
+
}
|
|
3077
|
+
for (const [field, expected] of Object.entries(collectionScope)) {
|
|
3078
|
+
if (record[field] !== expected) {
|
|
3079
|
+
return false;
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
return true;
|
|
3083
|
+
}
|
|
3084
|
+
function pickFields(record, keys) {
|
|
3085
|
+
const picked = {};
|
|
3086
|
+
for (const key of keys) {
|
|
3087
|
+
if (key in record) {
|
|
3088
|
+
picked[key] = record[key];
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
return picked;
|
|
3092
|
+
}
|
|
3093
|
+
async function buildRouteOperation(store, mutation) {
|
|
3094
|
+
const nodeId = store.getNodeId();
|
|
3095
|
+
const clock = new HybridLogicalClock2(nodeId);
|
|
3096
|
+
const schemaVersion = store.getSchema()?.version ?? 1;
|
|
3097
|
+
const sequenceNumber = nextServerSequenceNumber(store);
|
|
3098
|
+
if (mutation.type === "insert") {
|
|
3099
|
+
return createOperation2(
|
|
3100
|
+
{
|
|
3101
|
+
nodeId,
|
|
3102
|
+
type: "insert",
|
|
3103
|
+
collection: mutation.collection,
|
|
3104
|
+
recordId: mutation.recordId ?? generateUUIDv76(),
|
|
3105
|
+
data: mutation.data ?? {},
|
|
3106
|
+
previousData: null,
|
|
3107
|
+
sequenceNumber,
|
|
3108
|
+
causalDeps: [],
|
|
3109
|
+
schemaVersion
|
|
3110
|
+
},
|
|
3111
|
+
clock
|
|
3112
|
+
);
|
|
3113
|
+
}
|
|
3114
|
+
if (!mutation.recordId) {
|
|
3115
|
+
throw new RouteMutationError(
|
|
3116
|
+
"MISSING_RECORD_ID",
|
|
3117
|
+
`A ${mutation.type} mutation on "${mutation.collection}" requires a recordId.`
|
|
3118
|
+
);
|
|
3119
|
+
}
|
|
3120
|
+
const current = await store.findRecord(mutation.collection, mutation.recordId);
|
|
3121
|
+
if (mutation.type === "update") {
|
|
3122
|
+
const data = mutation.data ?? {};
|
|
3123
|
+
return createOperation2(
|
|
3124
|
+
{
|
|
3125
|
+
nodeId,
|
|
3126
|
+
type: "update",
|
|
3127
|
+
collection: mutation.collection,
|
|
3128
|
+
recordId: mutation.recordId,
|
|
3129
|
+
data,
|
|
3130
|
+
previousData: current ? pickFields(current, Object.keys(data)) : {},
|
|
3131
|
+
sequenceNumber,
|
|
3132
|
+
causalDeps: [],
|
|
3133
|
+
schemaVersion
|
|
3134
|
+
},
|
|
3135
|
+
clock
|
|
3136
|
+
);
|
|
3137
|
+
}
|
|
3138
|
+
return createOperation2(
|
|
3139
|
+
{
|
|
3140
|
+
nodeId,
|
|
3141
|
+
type: "delete",
|
|
3142
|
+
collection: mutation.collection,
|
|
3143
|
+
recordId: mutation.recordId,
|
|
3144
|
+
data: null,
|
|
3145
|
+
previousData: current ? { ...current } : null,
|
|
3146
|
+
sequenceNumber,
|
|
3147
|
+
causalDeps: [],
|
|
3148
|
+
schemaVersion
|
|
3149
|
+
},
|
|
3150
|
+
clock
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
var RouteMutationError = class extends Error {
|
|
3154
|
+
constructor(code, message) {
|
|
3155
|
+
super(message);
|
|
3156
|
+
this.code = code;
|
|
3157
|
+
this.name = "RouteMutationError";
|
|
3158
|
+
}
|
|
3159
|
+
code;
|
|
3160
|
+
};
|
|
3161
|
+
function createRouteContext(server, store) {
|
|
3162
|
+
let mutationTail = Promise.resolve();
|
|
3163
|
+
function serialize(work) {
|
|
3164
|
+
const run = mutationTail.then(work, work);
|
|
3165
|
+
mutationTail = run.then(
|
|
3166
|
+
() => void 0,
|
|
3167
|
+
() => void 0
|
|
3168
|
+
);
|
|
3169
|
+
return run;
|
|
3170
|
+
}
|
|
3171
|
+
return {
|
|
3172
|
+
apply(mutation, options) {
|
|
3173
|
+
return serialize(async () => {
|
|
3174
|
+
let op;
|
|
3175
|
+
try {
|
|
3176
|
+
op = await buildRouteOperation(store, mutation);
|
|
3177
|
+
} catch (error) {
|
|
3178
|
+
if (error instanceof RouteMutationError) {
|
|
3179
|
+
return {
|
|
3180
|
+
ok: false,
|
|
3181
|
+
code: error.code,
|
|
3182
|
+
message: error.message,
|
|
3183
|
+
retriable: isRetriableRejection(error.code)
|
|
3184
|
+
};
|
|
3185
|
+
}
|
|
3186
|
+
throw error;
|
|
3187
|
+
}
|
|
3188
|
+
if (options?.scope && !operationMatchesScopes(op, options.scope)) {
|
|
3189
|
+
return {
|
|
3190
|
+
ok: false,
|
|
3191
|
+
code: "SCOPE_VIOLATION",
|
|
3192
|
+
message: `Mutation on "${mutation.collection}" is outside the provided scope.`,
|
|
3193
|
+
retriable: isRetriableRejection("SCOPE_VIOLATION")
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
const result = await server.applyLocalOperation(op);
|
|
3197
|
+
if (result.result !== "applied") {
|
|
3198
|
+
const code = result.rejection?.code ?? "NOT_APPLIED";
|
|
3199
|
+
return {
|
|
3200
|
+
ok: false,
|
|
3201
|
+
code,
|
|
3202
|
+
message: result.rejection?.message ?? `Operation on "${mutation.collection}" was not applied (${result.result}).`,
|
|
3203
|
+
// Prefer the pipeline's own classification when present; fall back
|
|
3204
|
+
// to classifying the code so this stays correct if new codes appear.
|
|
3205
|
+
retriable: result.rejection?.retriable ?? isRetriableRejection(code)
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
const record = await store.findRecord(mutation.collection, op.recordId);
|
|
3209
|
+
return { ok: true, operation: op, record };
|
|
3210
|
+
});
|
|
3211
|
+
},
|
|
3212
|
+
async query(collection, options) {
|
|
3213
|
+
const { scope, ...queryOptions } = options ?? {};
|
|
3214
|
+
const records = await store.queryCollection(collection, queryOptions);
|
|
3215
|
+
if (!scope) {
|
|
3216
|
+
return records;
|
|
3217
|
+
}
|
|
3218
|
+
return records.filter((record) => recordMatchesScope(collection, record, scope));
|
|
3219
|
+
},
|
|
3220
|
+
async findById(collection, id, options) {
|
|
3221
|
+
const record = await store.findRecord(collection, id);
|
|
3222
|
+
if (!record) {
|
|
3223
|
+
return null;
|
|
3224
|
+
}
|
|
3225
|
+
if (options?.scope && !recordMatchesScope(collection, record, options.scope)) {
|
|
3226
|
+
return null;
|
|
3227
|
+
}
|
|
3228
|
+
return record;
|
|
3229
|
+
}
|
|
3230
|
+
};
|
|
3231
|
+
}
|
|
3232
|
+
|
|
2995
3233
|
// src/server/kora-sync-server.ts
|
|
2996
3234
|
var DEFAULT_MAX_CONNECTIONS = 0;
|
|
2997
3235
|
var DEFAULT_BATCH_SIZE2 = 100;
|
|
@@ -3016,10 +3254,16 @@ var KoraSyncServer = class {
|
|
|
3016
3254
|
yjsDocRelay = new YjsDocRelay();
|
|
3017
3255
|
blobChunkRelay;
|
|
3018
3256
|
persistBlobChunk;
|
|
3257
|
+
maxOperationBytes;
|
|
3258
|
+
maxOpsPerMinute;
|
|
3259
|
+
validateOperation;
|
|
3260
|
+
koraContext;
|
|
3019
3261
|
sessions = /* @__PURE__ */ new Map();
|
|
3020
3262
|
httpClients = /* @__PURE__ */ new Map();
|
|
3021
3263
|
httpSessionToClient = /* @__PURE__ */ new Map();
|
|
3022
|
-
|
|
3264
|
+
// Informational value reported by getStatus(). Keep in sync with the
|
|
3265
|
+
// @korajs/server package version on release; it is not used for protocol negotiation.
|
|
3266
|
+
serverVersion = "1.0.0-beta.0";
|
|
3023
3267
|
wsServer = null;
|
|
3024
3268
|
running = false;
|
|
3025
3269
|
constructor(config) {
|
|
@@ -3042,10 +3286,22 @@ var KoraSyncServer = class {
|
|
|
3042
3286
|
this.metrics.setSchemaVersion(this.schemaVersion);
|
|
3043
3287
|
this.blobChunkRelay = new BlobChunkRelay(config.resolveBlobChunk);
|
|
3044
3288
|
this.persistBlobChunk = config.persistBlobChunk ?? null;
|
|
3289
|
+
this.maxOperationBytes = config.maxOperationBytes;
|
|
3290
|
+
this.maxOpsPerMinute = config.maxOpsPerMinute;
|
|
3291
|
+
this.validateOperation = config.validateOperation;
|
|
3292
|
+
this.koraContext = createRouteContext(this, this.store);
|
|
3045
3293
|
if (!this.emitter) {
|
|
3046
3294
|
this.emitter = new SimpleEventEmitter();
|
|
3047
3295
|
}
|
|
3048
3296
|
}
|
|
3297
|
+
/**
|
|
3298
|
+
* The trusted, scoped data-plane context (`apply` / `query` / `findById`).
|
|
3299
|
+
* Shared with the production server so HTTP routes, the operation validator,
|
|
3300
|
+
* and `server.kora` are all the same object over the same pipeline.
|
|
3301
|
+
*/
|
|
3302
|
+
getKoraContext() {
|
|
3303
|
+
return this.koraContext;
|
|
3304
|
+
}
|
|
3049
3305
|
/**
|
|
3050
3306
|
* Subscribe to session-level events for metrics collection and logging.
|
|
3051
3307
|
* Called when a new session is created.
|
|
@@ -3227,7 +3483,7 @@ var KoraSyncServer = class {
|
|
|
3227
3483
|
if (this.maxConnections > 0 && this.sessions.size >= this.maxConnections) {
|
|
3228
3484
|
transport.send({
|
|
3229
3485
|
type: "error",
|
|
3230
|
-
messageId:
|
|
3486
|
+
messageId: generateUUIDv77(),
|
|
3231
3487
|
code: "MAX_CONNECTIONS",
|
|
3232
3488
|
message: `Server has reached maximum connections (${this.maxConnections})`,
|
|
3233
3489
|
retriable: true
|
|
@@ -3245,7 +3501,7 @@ var KoraSyncServer = class {
|
|
|
3245
3501
|
max: this.maxConnections
|
|
3246
3502
|
});
|
|
3247
3503
|
}
|
|
3248
|
-
const sessionId =
|
|
3504
|
+
const sessionId = generateUUIDv77();
|
|
3249
3505
|
this.metrics.recordConnection(sessionId);
|
|
3250
3506
|
const sessionEmitter = new SimpleEventEmitter();
|
|
3251
3507
|
sessionEmitter.on("sync:connected", (event) => {
|
|
@@ -3317,6 +3573,11 @@ var KoraSyncServer = class {
|
|
|
3317
3573
|
this.blobChunkRelay.handleResponse(sourceSessionId, message);
|
|
3318
3574
|
},
|
|
3319
3575
|
...this.persistBlobChunk ? { persistBlobChunk: this.persistBlobChunk } : {},
|
|
3576
|
+
// Only forward when configured, so an unset server value leaves the
|
|
3577
|
+
// session on its own documented default rather than `undefined`.
|
|
3578
|
+
...this.maxOperationBytes !== void 0 ? { maxOperationBytes: this.maxOperationBytes } : {},
|
|
3579
|
+
...this.maxOpsPerMinute !== void 0 ? { maxOpsPerMinute: this.maxOpsPerMinute } : {},
|
|
3580
|
+
...this.validateOperation ? { validateOperation: this.validateOperation, koraContext: this.koraContext } : {},
|
|
3320
3581
|
onClose: (sid) => {
|
|
3321
3582
|
this.handleSessionClose(sid);
|
|
3322
3583
|
}
|
|
@@ -3577,159 +3838,6 @@ function createKoraServer(config) {
|
|
|
3577
3838
|
return new KoraSyncServer(config);
|
|
3578
3839
|
}
|
|
3579
3840
|
|
|
3580
|
-
// src/server/route-context.ts
|
|
3581
|
-
import { HybridLogicalClock as HybridLogicalClock2, createOperation as createOperation2, generateUUIDv7 as generateUUIDv77 } from "@korajs/core";
|
|
3582
|
-
function recordMatchesScope(collection, record, scope) {
|
|
3583
|
-
const collectionScope = scope[collection];
|
|
3584
|
-
if (!collectionScope) {
|
|
3585
|
-
return false;
|
|
3586
|
-
}
|
|
3587
|
-
for (const [field, expected] of Object.entries(collectionScope)) {
|
|
3588
|
-
if (record[field] !== expected) {
|
|
3589
|
-
return false;
|
|
3590
|
-
}
|
|
3591
|
-
}
|
|
3592
|
-
return true;
|
|
3593
|
-
}
|
|
3594
|
-
function pickFields(record, keys) {
|
|
3595
|
-
const picked = {};
|
|
3596
|
-
for (const key of keys) {
|
|
3597
|
-
if (key in record) {
|
|
3598
|
-
picked[key] = record[key];
|
|
3599
|
-
}
|
|
3600
|
-
}
|
|
3601
|
-
return picked;
|
|
3602
|
-
}
|
|
3603
|
-
async function buildRouteOperation(store, mutation) {
|
|
3604
|
-
const nodeId = store.getNodeId();
|
|
3605
|
-
const clock = new HybridLogicalClock2(nodeId);
|
|
3606
|
-
const schemaVersion = store.getSchema()?.version ?? 1;
|
|
3607
|
-
const sequenceNumber = nextServerSequenceNumber(store);
|
|
3608
|
-
if (mutation.type === "insert") {
|
|
3609
|
-
return createOperation2(
|
|
3610
|
-
{
|
|
3611
|
-
nodeId,
|
|
3612
|
-
type: "insert",
|
|
3613
|
-
collection: mutation.collection,
|
|
3614
|
-
recordId: mutation.recordId ?? generateUUIDv77(),
|
|
3615
|
-
data: mutation.data ?? {},
|
|
3616
|
-
previousData: null,
|
|
3617
|
-
sequenceNumber,
|
|
3618
|
-
causalDeps: [],
|
|
3619
|
-
schemaVersion
|
|
3620
|
-
},
|
|
3621
|
-
clock
|
|
3622
|
-
);
|
|
3623
|
-
}
|
|
3624
|
-
if (!mutation.recordId) {
|
|
3625
|
-
throw new RouteMutationError(
|
|
3626
|
-
"MISSING_RECORD_ID",
|
|
3627
|
-
`A ${mutation.type} mutation on "${mutation.collection}" requires a recordId.`
|
|
3628
|
-
);
|
|
3629
|
-
}
|
|
3630
|
-
const current = await store.findRecord(mutation.collection, mutation.recordId);
|
|
3631
|
-
if (mutation.type === "update") {
|
|
3632
|
-
const data = mutation.data ?? {};
|
|
3633
|
-
return createOperation2(
|
|
3634
|
-
{
|
|
3635
|
-
nodeId,
|
|
3636
|
-
type: "update",
|
|
3637
|
-
collection: mutation.collection,
|
|
3638
|
-
recordId: mutation.recordId,
|
|
3639
|
-
data,
|
|
3640
|
-
previousData: current ? pickFields(current, Object.keys(data)) : {},
|
|
3641
|
-
sequenceNumber,
|
|
3642
|
-
causalDeps: [],
|
|
3643
|
-
schemaVersion
|
|
3644
|
-
},
|
|
3645
|
-
clock
|
|
3646
|
-
);
|
|
3647
|
-
}
|
|
3648
|
-
return createOperation2(
|
|
3649
|
-
{
|
|
3650
|
-
nodeId,
|
|
3651
|
-
type: "delete",
|
|
3652
|
-
collection: mutation.collection,
|
|
3653
|
-
recordId: mutation.recordId,
|
|
3654
|
-
data: null,
|
|
3655
|
-
previousData: current ? { ...current } : null,
|
|
3656
|
-
sequenceNumber,
|
|
3657
|
-
causalDeps: [],
|
|
3658
|
-
schemaVersion
|
|
3659
|
-
},
|
|
3660
|
-
clock
|
|
3661
|
-
);
|
|
3662
|
-
}
|
|
3663
|
-
var RouteMutationError = class extends Error {
|
|
3664
|
-
constructor(code, message) {
|
|
3665
|
-
super(message);
|
|
3666
|
-
this.code = code;
|
|
3667
|
-
this.name = "RouteMutationError";
|
|
3668
|
-
}
|
|
3669
|
-
code;
|
|
3670
|
-
};
|
|
3671
|
-
function createRouteContext(server, store) {
|
|
3672
|
-
let mutationTail = Promise.resolve();
|
|
3673
|
-
function serialize(work) {
|
|
3674
|
-
const run = mutationTail.then(work, work);
|
|
3675
|
-
mutationTail = run.then(
|
|
3676
|
-
() => void 0,
|
|
3677
|
-
() => void 0
|
|
3678
|
-
);
|
|
3679
|
-
return run;
|
|
3680
|
-
}
|
|
3681
|
-
return {
|
|
3682
|
-
apply(mutation, options) {
|
|
3683
|
-
return serialize(async () => {
|
|
3684
|
-
let op;
|
|
3685
|
-
try {
|
|
3686
|
-
op = await buildRouteOperation(store, mutation);
|
|
3687
|
-
} catch (error) {
|
|
3688
|
-
if (error instanceof RouteMutationError) {
|
|
3689
|
-
return { ok: false, code: error.code, message: error.message };
|
|
3690
|
-
}
|
|
3691
|
-
throw error;
|
|
3692
|
-
}
|
|
3693
|
-
if (options?.scope && !operationMatchesScopes(op, options.scope)) {
|
|
3694
|
-
return {
|
|
3695
|
-
ok: false,
|
|
3696
|
-
code: "SCOPE_VIOLATION",
|
|
3697
|
-
message: `Mutation on "${mutation.collection}" is outside the provided scope.`
|
|
3698
|
-
};
|
|
3699
|
-
}
|
|
3700
|
-
const result = await server.applyLocalOperation(op);
|
|
3701
|
-
if (result.result !== "applied") {
|
|
3702
|
-
return {
|
|
3703
|
-
ok: false,
|
|
3704
|
-
code: result.rejection?.code ?? "NOT_APPLIED",
|
|
3705
|
-
message: result.rejection?.message ?? `Operation on "${mutation.collection}" was not applied (${result.result}).`
|
|
3706
|
-
};
|
|
3707
|
-
}
|
|
3708
|
-
const record = await store.findRecord(mutation.collection, op.recordId);
|
|
3709
|
-
return { ok: true, operation: op, record };
|
|
3710
|
-
});
|
|
3711
|
-
},
|
|
3712
|
-
async query(collection, options) {
|
|
3713
|
-
const { scope, ...queryOptions } = options ?? {};
|
|
3714
|
-
const records = await store.queryCollection(collection, queryOptions);
|
|
3715
|
-
if (!scope) {
|
|
3716
|
-
return records;
|
|
3717
|
-
}
|
|
3718
|
-
return records.filter((record) => recordMatchesScope(collection, record, scope));
|
|
3719
|
-
},
|
|
3720
|
-
async findById(collection, id, options) {
|
|
3721
|
-
const record = await store.findRecord(collection, id);
|
|
3722
|
-
if (!record) {
|
|
3723
|
-
return null;
|
|
3724
|
-
}
|
|
3725
|
-
if (options?.scope && !recordMatchesScope(collection, record, options.scope)) {
|
|
3726
|
-
return null;
|
|
3727
|
-
}
|
|
3728
|
-
return record;
|
|
3729
|
-
}
|
|
3730
|
-
};
|
|
3731
|
-
}
|
|
3732
|
-
|
|
3733
3841
|
// src/server/production-server.ts
|
|
3734
3842
|
var MIME_TYPES = {
|
|
3735
3843
|
".html": "text/html",
|
|
@@ -3754,7 +3862,7 @@ function createProductionServer(config) {
|
|
|
3754
3862
|
enableDashboard: true,
|
|
3755
3863
|
...config.syncOptions
|
|
3756
3864
|
});
|
|
3757
|
-
const routeContext =
|
|
3865
|
+
const routeContext = syncServer.getKoraContext();
|
|
3758
3866
|
let httpServer = null;
|
|
3759
3867
|
function getOperationalToken(kind) {
|
|
3760
3868
|
const auth = config.operationalAuth;
|
|
@@ -3894,6 +4002,10 @@ function createProductionServer(config) {
|
|
|
3894
4002
|
res.end(JSON.stringify(result.body ?? null));
|
|
3895
4003
|
}
|
|
3896
4004
|
return {
|
|
4005
|
+
kora: routeContext,
|
|
4006
|
+
getLiveBlobRefs() {
|
|
4007
|
+
return syncServer.getLiveBlobRefs();
|
|
4008
|
+
},
|
|
3897
4009
|
async start() {
|
|
3898
4010
|
const { createServer } = await import("http");
|
|
3899
4011
|
const { createReadStream, existsSync, statSync } = await import("fs");
|
|
@@ -4192,6 +4304,7 @@ export {
|
|
|
4192
4304
|
MixedAuthProvider,
|
|
4193
4305
|
NoAuthProvider,
|
|
4194
4306
|
PostgresServerStore,
|
|
4307
|
+
RETRIABLE_REJECTION_CODES,
|
|
4195
4308
|
SqliteServerStore,
|
|
4196
4309
|
TokenAuthProvider,
|
|
4197
4310
|
WsServerTransport,
|
|
@@ -4202,6 +4315,7 @@ export {
|
|
|
4202
4315
|
createPrettyLogger,
|
|
4203
4316
|
createProductionServer,
|
|
4204
4317
|
createSilentLogger,
|
|
4205
|
-
createSqliteServerStore
|
|
4318
|
+
createSqliteServerStore,
|
|
4319
|
+
isRetriableRejection
|
|
4206
4320
|
};
|
|
4207
4321
|
//# sourceMappingURL=index.js.map
|