@lunora/do 1.0.0-alpha.7 → 1.0.0-alpha.71
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/LICENSE.md +6 -0
- package/dist/index.d.mts +3394 -5259
- package/dist/index.d.ts +3394 -5259
- package/dist/index.mjs +1 -36
- package/dist/packem_shared/ROOT_DO_SIZE_WARN_BYTES-Bze_kj6L.mjs +13 -0
- package/dist/packem_shared/SESSION_DO_TTL_DEFAULT-Dan63qLN.mjs +1 -0
- package/dist/packem_shared/SHARD_REGISTRY_DO_NAME-tOQVzDCB.mjs +1 -0
- package/dist/packem_shared/constant-time-equal-BVG05Guz.mjs +1 -0
- package/dist/packem_shared/json-response-wrh9TBPw.mjs +1 -0
- package/dist/packem_shared/serveRelationFanout-Cbmlso1k.mjs +1 -0
- package/package.json +6 -2
- package/dist/packem_shared/ADMIN_FUNCTIONS-CHcC8fKV.mjs +0 -314
- package/dist/packem_shared/AGGREGATE_SQL_FUNCTION-CFk6adSu.mjs +0 -54
- package/dist/packem_shared/AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs +0 -84
- package/dist/packem_shared/CDC_LOG_TABLE-Ctdmxmrv.mjs +0 -103
- package/dist/packem_shared/ConflictError-C0STs6bU.mjs +0 -13
- package/dist/packem_shared/CountRlsUnsupportedError-28ZvvwKS.mjs +0 -133
- package/dist/packem_shared/DATA_MIGRATION_STATE_TABLE-PTtTiQ7U.mjs +0 -237
- package/dist/packem_shared/DEFAULT_MAX_RELATION_KEYS-Dou2PWdO.mjs +0 -160
- package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs +0 -248
- package/dist/packem_shared/LogBuffer-B_Ezju_N.mjs +0 -37
- package/dist/packem_shared/MAIL_RETENTION-CPpgl-dX.mjs +0 -104
- package/dist/packem_shared/MAX_SQL_ROWS-dDcFE1YZ.mjs +0 -29
- package/dist/packem_shared/MIN_ADMIN_TOKEN_LENGTH-CCAvoFlr.mjs +0 -1
- package/dist/packem_shared/NotFoundError-CMuMZt81.mjs +0 -10
- package/dist/packem_shared/NotUniqueError-h_thNFSZ.mjs +0 -1809
- package/dist/packem_shared/RANK_TIEBREAK-C6blLR5K.mjs +0 -1
- package/dist/packem_shared/RLS_UNWRAP_SYMBOL-EtGQdC9d.mjs +0 -132
- package/dist/packem_shared/ROOT_DO_SIZE_WARN_BYTES-2DxWrdla.mjs +0 -3958
- package/dist/packem_shared/ReactiveCache-ByVzgH3d.mjs +0 -259
- package/dist/packem_shared/SCAN_DEP-DLJF8dsj.mjs +0 -19
- package/dist/packem_shared/SESSION_DO_TTL_DEFAULT-ilPZsVwu.mjs +0 -180
- package/dist/packem_shared/SHARD_REGISTRY_DO_NAME-BsAbi5Mn.mjs +0 -146
- package/dist/packem_shared/aggregateTableName-CxNqY1Sl.mjs +0 -64
- package/dist/packem_shared/applyOnDelete-sA7o1CqD.mjs +0 -169
- package/dist/packem_shared/applySelect-BvZdFUBT.mjs +0 -101
- package/dist/packem_shared/armRestore-BJk53Ro8.mjs +0 -55
- package/dist/packem_shared/backfillAggregateIndexes-BbVPvciS.mjs +0 -80
- package/dist/packem_shared/buildFtsMatch-BLEMawrp.mjs +0 -38
- package/dist/packem_shared/compileWhereSql-CXrhFA3G.mjs +0 -127
- package/dist/packem_shared/createSystemReader-8CzSZP9V.mjs +0 -80
- package/dist/packem_shared/ctx-db-idempotency-DkC9rP91.mjs +0 -35
- package/dist/packem_shared/do-exec-5eQy5cEi.mjs +0 -12
- package/dist/packem_shared/do-sql-BCHCWtrD.mjs +0 -87
- package/dist/packem_shared/exportShardRows-DZEhUeyI.mjs +0 -156
- package/dist/packem_shared/hasTrigger-5N6_Fx0A.mjs +0 -20
- package/dist/packem_shared/rank-CrkEIpF4.mjs +0 -102
- package/dist/packem_shared/renderSql-D6eUcn2N.mjs +0 -16
- package/dist/packem_shared/runShardMigrations-PabobOjF.mjs +0 -103
- package/dist/packem_shared/security-audit-CucgBice.mjs +0 -158
- package/dist/packem_shared/serveRelationFanout-oxaM6_WL.mjs +0 -24
- package/dist/packem_shared/subscriptionListDeltas-ce84gpwL.mjs +0 -111
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { renderSql } from './renderSql-D6eUcn2N.mjs';
|
|
2
|
-
|
|
3
|
-
const runSql = (sql, query, ...params) => {
|
|
4
|
-
const runner = sql.exec;
|
|
5
|
-
return runner.call(sql, query, ...params);
|
|
6
|
-
};
|
|
7
|
-
const runDrizzle = (exec, query) => {
|
|
8
|
-
const { params, sql: text } = renderSql("sqlite", query);
|
|
9
|
-
return runSql(exec, text, ...params);
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export { runDrizzle as r };
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { sql } from 'drizzle-orm';
|
|
2
|
-
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
3
|
-
|
|
4
|
-
const DOC_COLUMN = "__doc__";
|
|
5
|
-
const quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
6
|
-
const jsonPath = (field) => {
|
|
7
|
-
if (field === "_id" || field === "id") {
|
|
8
|
-
return "id";
|
|
9
|
-
}
|
|
10
|
-
if (field === "_creationTime") {
|
|
11
|
-
return "_creationTime";
|
|
12
|
-
}
|
|
13
|
-
return `json_extract(${DOC_COLUMN}, '$.${field.replaceAll("'", "''")}')`;
|
|
14
|
-
};
|
|
15
|
-
const jsonPathSql = (field) => sql.raw(jsonPath(field));
|
|
16
|
-
const qualifiedJsonPath = (table, field) => {
|
|
17
|
-
const qualified = quoteIdentifier(table);
|
|
18
|
-
if (field === "_id" || field === "id") {
|
|
19
|
-
return `${qualified}.id`;
|
|
20
|
-
}
|
|
21
|
-
if (field === "_creationTime") {
|
|
22
|
-
return `${qualified}._creationTime`;
|
|
23
|
-
}
|
|
24
|
-
return `json_extract(${qualified}.${DOC_COLUMN}, '$.${field.replaceAll("'", "''")}')`;
|
|
25
|
-
};
|
|
26
|
-
const qualifiedJsonPathSql = (table, field) => sql.raw(qualifiedJsonPath(table, field));
|
|
27
|
-
const createIndexSql = (name, table, columns, unique) => sql`CREATE ${unique ? sql`UNIQUE ` : sql``}INDEX IF NOT EXISTS ${sql.identifier(name)} ON ${sql.identifier(table)} (${columns})`;
|
|
28
|
-
const AGG_KEY = sql.identifier("__key__");
|
|
29
|
-
const AGG_VALUE = sql.identifier("__value__");
|
|
30
|
-
const AGG_COUNT = sql.identifier("__count__");
|
|
31
|
-
const aggUpsertSql = (aggTable, key, value, count, set) => sql`INSERT INTO ${sql.identifier(aggTable)} (${AGG_KEY}, ${AGG_VALUE}, ${AGG_COUNT}) VALUES (${key}, ${value}, ${count}) ON CONFLICT(${AGG_KEY}) DO UPDATE SET ${set}`;
|
|
32
|
-
const tableColumns = (definition) => {
|
|
33
|
-
const columns = [];
|
|
34
|
-
for (const [field, validator] of Object.entries(definition.shape)) {
|
|
35
|
-
const column = validator._meta?.column;
|
|
36
|
-
if (column) {
|
|
37
|
-
columns.push([field, column]);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return columns;
|
|
41
|
-
};
|
|
42
|
-
const rowToDocument = (row) => {
|
|
43
|
-
if (!row) {
|
|
44
|
-
return void 0;
|
|
45
|
-
}
|
|
46
|
-
const raw = row[DOC_COLUMN];
|
|
47
|
-
let parsed;
|
|
48
|
-
if (typeof raw === "string") {
|
|
49
|
-
parsed = JSON.parse(raw);
|
|
50
|
-
} else if (raw && typeof raw === "object") {
|
|
51
|
-
parsed = raw;
|
|
52
|
-
} else {
|
|
53
|
-
parsed = {};
|
|
54
|
-
}
|
|
55
|
-
const { id } = row;
|
|
56
|
-
if (typeof id === "string") {
|
|
57
|
-
parsed["_id"] = id;
|
|
58
|
-
}
|
|
59
|
-
const creationTime = row["_creationTime"];
|
|
60
|
-
if (typeof creationTime === "number") {
|
|
61
|
-
parsed["_creationTime"] = creationTime;
|
|
62
|
-
}
|
|
63
|
-
return parsed;
|
|
64
|
-
};
|
|
65
|
-
const ftsAvailabilityCache = /* @__PURE__ */ new WeakMap();
|
|
66
|
-
const isFtsAvailable = (sql$1) => {
|
|
67
|
-
const cached = ftsAvailabilityCache.get(sql$1);
|
|
68
|
-
if (cached !== void 0) {
|
|
69
|
-
return cached;
|
|
70
|
-
}
|
|
71
|
-
let available;
|
|
72
|
-
try {
|
|
73
|
-
runDrizzle(sql$1, sql`CREATE VIRTUAL TABLE IF NOT EXISTS ${sql.identifier("__lunora_fts_probe")} USING fts5(x)`);
|
|
74
|
-
available = true;
|
|
75
|
-
} catch {
|
|
76
|
-
available = false;
|
|
77
|
-
} finally {
|
|
78
|
-
try {
|
|
79
|
-
runDrizzle(sql$1, sql`DROP TABLE IF EXISTS ${sql.identifier("__lunora_fts_probe")}`);
|
|
80
|
-
} catch {
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
ftsAvailabilityCache.set(sql$1, available);
|
|
84
|
-
return available;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
export { AGG_KEY as A, DOC_COLUMN as D, AGG_VALUE as a, AGG_COUNT as b, createIndexSql as c, aggUpsertSql as d, qualifiedJsonPathSql as e, isFtsAvailable as i, jsonPathSql as j, quoteIdentifier as q, rowToDocument as r, tableColumns as t };
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
const DEFAULT_BATCH_SIZE = 200;
|
|
2
|
-
const selectExportTables = (schema, requested) => {
|
|
3
|
-
const isShardLocal = (table) => {
|
|
4
|
-
const definition = schema.tables[table];
|
|
5
|
-
if (!definition) {
|
|
6
|
-
return false;
|
|
7
|
-
}
|
|
8
|
-
return definition.shardMode?.kind !== "global";
|
|
9
|
-
};
|
|
10
|
-
if (requested && requested.length > 0) {
|
|
11
|
-
const filtered = [];
|
|
12
|
-
for (const name of requested) {
|
|
13
|
-
if (isShardLocal(name)) {
|
|
14
|
-
filtered.push(name);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
return filtered;
|
|
18
|
-
}
|
|
19
|
-
const result = [];
|
|
20
|
-
for (const name of Object.keys(schema.tables)) {
|
|
21
|
-
if (isShardLocal(name)) {
|
|
22
|
-
result.push(name);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return result;
|
|
26
|
-
};
|
|
27
|
-
const exportShardTable = async function* (writer, table, batchSize = DEFAULT_BATCH_SIZE) {
|
|
28
|
-
let cursor = null;
|
|
29
|
-
let done = false;
|
|
30
|
-
while (!done) {
|
|
31
|
-
const page = await writer.findMany(table, { cursor, limit: batchSize });
|
|
32
|
-
for (const record of page.page) {
|
|
33
|
-
yield { doc: record, table };
|
|
34
|
-
}
|
|
35
|
-
cursor = page.continueCursor;
|
|
36
|
-
done = page.isDone || cursor === null;
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
const exportShardRows = async function* (writer, schema, args) {
|
|
40
|
-
const tables = selectExportTables(schema, args.tables);
|
|
41
|
-
const batchSize = args.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
42
|
-
for (const table of tables) {
|
|
43
|
-
yield* exportShardTable(writer, table, batchSize);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
const FRAMEWORK_FIELDS = /* @__PURE__ */ new Set(["_creationTime", "_id"]);
|
|
47
|
-
const validateAgainstShape = (definition, payload) => {
|
|
48
|
-
for (const [field, validator] of Object.entries(definition.shape)) {
|
|
49
|
-
const candidate = payload[field];
|
|
50
|
-
if (candidate === void 0 && validator.kind === "optional") {
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
const parser = validator.parse;
|
|
54
|
-
if (typeof parser !== "function") {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
try {
|
|
58
|
-
parser(candidate);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
-
return `field "${field}": ${message}`;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return void 0;
|
|
65
|
-
};
|
|
66
|
-
const validateImportRow = (schema, table, record) => {
|
|
67
|
-
const definition = schema.tables[table];
|
|
68
|
-
if (!definition) {
|
|
69
|
-
return `unknown table: ${table}`;
|
|
70
|
-
}
|
|
71
|
-
if (definition.shardMode?.kind === "global") {
|
|
72
|
-
return `table "${table}" is a global (.global()) table and is not importable through the shard import path`;
|
|
73
|
-
}
|
|
74
|
-
const payload = Object.fromEntries(Object.entries(record).filter(([key]) => !FRAMEWORK_FIELDS.has(key)));
|
|
75
|
-
for (const key of Object.keys(payload)) {
|
|
76
|
-
if (!(key in definition.shape)) {
|
|
77
|
-
return `unexpected field "${key}": not declared in table "${table}"`;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return validateAgainstShape(definition, payload);
|
|
81
|
-
};
|
|
82
|
-
const idAlreadyExists = async (writer, explicitId) => {
|
|
83
|
-
try {
|
|
84
|
-
const existing = await writer.get(explicitId);
|
|
85
|
-
return existing !== null;
|
|
86
|
-
} catch {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
const importOneRow = async (writer, schema, row, line) => {
|
|
91
|
-
const { doc, table } = row;
|
|
92
|
-
if (typeof table !== "string" || table.length === 0) {
|
|
93
|
-
return { error: { code: "BAD_ROW", line, message: "row is missing `table`", table }, kind: "error" };
|
|
94
|
-
}
|
|
95
|
-
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
96
|
-
return { error: { code: "BAD_ROW", line, message: "row is missing or malformed `doc`", table }, kind: "error" };
|
|
97
|
-
}
|
|
98
|
-
const failure = validateImportRow(schema, table, doc);
|
|
99
|
-
if (failure !== void 0) {
|
|
100
|
-
return { error: { code: "VALIDATION_ERROR", line, message: failure, table }, kind: "error" };
|
|
101
|
-
}
|
|
102
|
-
const explicitId = typeof doc["_id"] === "string" ? doc["_id"] : void 0;
|
|
103
|
-
if (explicitId !== void 0 && await idAlreadyExists(writer, explicitId)) {
|
|
104
|
-
return { kind: "conflict" };
|
|
105
|
-
}
|
|
106
|
-
try {
|
|
107
|
-
await writer.insert(table, doc, { allowExplicitId: true });
|
|
108
|
-
return { kind: "inserted", table };
|
|
109
|
-
} catch (error) {
|
|
110
|
-
const code = error.code ?? "INSERT_FAILED";
|
|
111
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
-
return { error: { code, line, message, table }, kind: "error" };
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
const importShardRows = async (writer, schema, args) => {
|
|
116
|
-
const errors = [];
|
|
117
|
-
const inserted = {};
|
|
118
|
-
let conflicts = 0;
|
|
119
|
-
let line = (args.startLine ?? 1) - 1;
|
|
120
|
-
for (const row of args.rows) {
|
|
121
|
-
line += 1;
|
|
122
|
-
const outcome = await importOneRow(writer, schema, row, line);
|
|
123
|
-
if (outcome.kind === "error") {
|
|
124
|
-
errors.push(outcome.error);
|
|
125
|
-
} else if (outcome.kind === "conflict") {
|
|
126
|
-
conflicts += 1;
|
|
127
|
-
} else {
|
|
128
|
-
inserted[outcome.table] = (inserted[outcome.table] ?? 0) + 1;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return { conflicts, errors, inserted };
|
|
132
|
-
};
|
|
133
|
-
const parseExportShardArgs = (args) => {
|
|
134
|
-
const tables = Array.isArray(args["tables"]) ? args["tables"].filter((entry) => typeof entry === "string") : void 0;
|
|
135
|
-
const batchSize = typeof args["batchSize"] === "number" ? args["batchSize"] : void 0;
|
|
136
|
-
return { batchSize, tables };
|
|
137
|
-
};
|
|
138
|
-
const parseImportShardArgs = (args) => {
|
|
139
|
-
const rawRows = Array.isArray(args["rows"]) ? args["rows"] : [];
|
|
140
|
-
const rows = [];
|
|
141
|
-
for (const entry of rawRows) {
|
|
142
|
-
if (!entry || typeof entry !== "object") {
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
const candidate = entry;
|
|
146
|
-
if (typeof candidate.table !== "string" || !candidate.doc || typeof candidate.doc !== "object" || Array.isArray(candidate.doc)) {
|
|
147
|
-
rows.push({ doc: candidate.doc ?? {}, table: typeof candidate.table === "string" ? candidate.table : "" });
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
rows.push({ doc: candidate.doc, table: candidate.table });
|
|
151
|
-
}
|
|
152
|
-
const startLine = typeof args["startLine"] === "number" ? args["startLine"] : void 0;
|
|
153
|
-
return { rows, startLine };
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
export { exportShardRows, exportShardTable, importShardRows, parseExportShardArgs, parseImportShardArgs, selectExportTables, validateImportRow };
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
const runTriggers = async (options) => {
|
|
2
|
-
const definitions = options.schema.tables[options.tableName]?.triggerMap;
|
|
3
|
-
if (!definitions) {
|
|
4
|
-
return;
|
|
5
|
-
}
|
|
6
|
-
for (const definition of Object.values(definitions)) {
|
|
7
|
-
if (definition.timing === options.timing && definition.op === options.op) {
|
|
8
|
-
await definition.handler(options.ctx, options.event);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
const hasTrigger = (schema, tableName, op) => {
|
|
13
|
-
const definitions = schema.tables[tableName]?.triggerMap;
|
|
14
|
-
if (!definitions) {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
return Object.values(definitions).some((definition) => definition.op === op);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export { hasTrigger, runTriggers };
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
const serializeSqlValue = (value) => {
|
|
2
|
-
if (typeof value === "boolean") {
|
|
3
|
-
return value ? 1 : 0;
|
|
4
|
-
}
|
|
5
|
-
if (value === null || typeof value === "string" || typeof value === "number") {
|
|
6
|
-
return value;
|
|
7
|
-
}
|
|
8
|
-
if (typeof value === "bigint") {
|
|
9
|
-
return value.toString();
|
|
10
|
-
}
|
|
11
|
-
return JSON.stringify(value);
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const compareStrings = (a, b) => {
|
|
15
|
-
if (a < b) {
|
|
16
|
-
return -1;
|
|
17
|
-
}
|
|
18
|
-
return a > b ? 1 : 0;
|
|
19
|
-
};
|
|
20
|
-
const NOT_RESOLVABLE = /* @__PURE__ */ Symbol("not-resolvable");
|
|
21
|
-
const resolvePartitionValue = (value) => {
|
|
22
|
-
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
23
|
-
const operatorKeys = Object.keys(value);
|
|
24
|
-
if (operatorKeys.length === 1 && operatorKeys[0] === "eq") {
|
|
25
|
-
return value.eq;
|
|
26
|
-
}
|
|
27
|
-
return NOT_RESOLVABLE;
|
|
28
|
-
}
|
|
29
|
-
return value;
|
|
30
|
-
};
|
|
31
|
-
const encodePartitionKey = (partitionBy, source) => {
|
|
32
|
-
if (partitionBy.length === 0) {
|
|
33
|
-
return "";
|
|
34
|
-
}
|
|
35
|
-
const ordered = {};
|
|
36
|
-
for (const field of [...partitionBy].toSorted(compareStrings)) {
|
|
37
|
-
ordered[field] = source[field] ?? null;
|
|
38
|
-
}
|
|
39
|
-
return JSON.stringify(ordered);
|
|
40
|
-
};
|
|
41
|
-
const RANK_TIEBREAK = "__id__";
|
|
42
|
-
const sortColumnName = (i) => `__sort_k${String(i)}__`;
|
|
43
|
-
const matchesRankStaticWhere = (document, predicate) => {
|
|
44
|
-
for (const [field, expected] of Object.entries(predicate)) {
|
|
45
|
-
const actual = document[field];
|
|
46
|
-
if (expected !== null && typeof expected === "object" && !Array.isArray(expected)) {
|
|
47
|
-
const operatorKeys = Object.keys(expected);
|
|
48
|
-
if (operatorKeys.length === 1 && operatorKeys[0] === "eq") {
|
|
49
|
-
if (actual !== expected.eq) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
if (actual !== expected) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return true;
|
|
61
|
-
};
|
|
62
|
-
const resolveRankPartition = (index, where) => {
|
|
63
|
-
const partitionBy = index.partitionBy ?? [];
|
|
64
|
-
const requested = where ?? {};
|
|
65
|
-
for (const key of Object.keys(requested)) {
|
|
66
|
-
if (key === "AND" || key === "OR" || key === "NOT") {
|
|
67
|
-
return void 0;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
const resolved = {};
|
|
71
|
-
for (const field of partitionBy) {
|
|
72
|
-
if (field in requested) {
|
|
73
|
-
const value = resolvePartitionValue(requested[field]);
|
|
74
|
-
if (value === NOT_RESOLVABLE) {
|
|
75
|
-
return void 0;
|
|
76
|
-
}
|
|
77
|
-
resolved[field] = value;
|
|
78
|
-
} else if (index.where && field in index.where) {
|
|
79
|
-
resolved[field] = index.where[field];
|
|
80
|
-
} else {
|
|
81
|
-
return void 0;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return resolved;
|
|
85
|
-
};
|
|
86
|
-
const rankTableName = (table, indexName) => `${table}__rank_${indexName}`;
|
|
87
|
-
const rankKeyFromDocument = (index, document_) => {
|
|
88
|
-
return {
|
|
89
|
-
partitionKey: encodePartitionKey(index.partitionBy ?? [], document_),
|
|
90
|
-
rowId: document_["_id"],
|
|
91
|
-
// Serialize each sort value the same way `syncRankIndexEntry` writes the
|
|
92
|
-
// stored `__sort_k<i>__` column, so a peer shard's comparison matches
|
|
93
|
-
// byte-for-byte. This also makes the values JSON-safe for the
|
|
94
|
-
// cross-shard RPC wire — `serializeSqlValue` turns bigint/Date/object
|
|
95
|
-
// into a string|number|null, where raw bigint would crash JSON.stringify
|
|
96
|
-
// and a raw Date would serialize to a different shape than the store.
|
|
97
|
-
// eslint-disable-next-line unicorn/no-null -- mirrors syncRankIndexEntry: a missing field serializes via `?? null` so the stored and wire bytes agree
|
|
98
|
-
sortValues: index.sortBy.map((key) => serializeSqlValue(document_[key.field] ?? null))
|
|
99
|
-
};
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export { RANK_TIEBREAK as R, resolveRankPartition as a, serializeSqlValue as b, rankKeyFromDocument as c, encodePartitionKey as e, matchesRankStaticWhere as m, rankTableName as r, sortColumnName as s };
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { sql } from 'drizzle-orm';
|
|
2
|
-
import { MySqlDialect } from 'drizzle-orm/mysql-core';
|
|
3
|
-
import { PgDialect } from 'drizzle-orm/pg-core';
|
|
4
|
-
import { SQLiteSyncDialect } from 'drizzle-orm/sqlite-core';
|
|
5
|
-
|
|
6
|
-
const PG_DIALECT = new PgDialect();
|
|
7
|
-
const MYSQL_DIALECT = new MySqlDialect();
|
|
8
|
-
const SQLITE_DIALECT = new SQLiteSyncDialect();
|
|
9
|
-
const DIALECTS = { mysql: MYSQL_DIALECT, postgres: PG_DIALECT, sqlite: SQLITE_DIALECT };
|
|
10
|
-
const renderSql = (engine, query) => {
|
|
11
|
-
const { params, sql: text } = DIALECTS[engine].sqlToQuery(query);
|
|
12
|
-
return { params, sql: text };
|
|
13
|
-
};
|
|
14
|
-
const param = (value) => sql`${value}`;
|
|
15
|
-
|
|
16
|
-
export { param, renderSql };
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { sql } from 'drizzle-orm';
|
|
2
|
-
import { aggregateTableName } from './aggregateTableName-CxNqY1Sl.mjs';
|
|
3
|
-
import { migrateCdcLog, migrateCdcMeta } from './CDC_LOG_TABLE-Ctdmxmrv.mjs';
|
|
4
|
-
import { m as migrateIdempotency } from './ctx-db-idempotency-DkC9rP91.mjs';
|
|
5
|
-
import { r as runDrizzle } from './do-exec-5eQy5cEi.mjs';
|
|
6
|
-
import { D as DOC_COLUMN, j as jsonPathSql, c as createIndexSql, t as tableColumns, i as isFtsAvailable, A as AGG_KEY, a as AGG_VALUE, b as AGG_COUNT } from './do-sql-BCHCWtrD.mjs';
|
|
7
|
-
import { s as sortColumnName, r as rankTableName } from './rank-CrkEIpF4.mjs';
|
|
8
|
-
import { ftsTableName } from './buildFtsMatch-BLEMawrp.mjs';
|
|
9
|
-
|
|
10
|
-
const migrateSecondaryIndexes = (sql$1, tableName, definition) => {
|
|
11
|
-
for (const index of definition.indexes) {
|
|
12
|
-
const indexName = `${tableName}_${index.name}`;
|
|
13
|
-
const expressions = sql.join(
|
|
14
|
-
index.fields.map((field) => jsonPathSql(field)),
|
|
15
|
-
sql`, `
|
|
16
|
-
);
|
|
17
|
-
runDrizzle(sql$1, createIndexSql(indexName, tableName, expressions, index.unique ?? false));
|
|
18
|
-
}
|
|
19
|
-
for (const [field, column] of tableColumns(definition)) {
|
|
20
|
-
if (!column.unique) {
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
const indexName = `${tableName}_unique_${field}`;
|
|
24
|
-
runDrizzle(sql$1, createIndexSql(indexName, tableName, jsonPathSql(field), true));
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
const migrateSearchIndexes = (sql$1, tableName, definition) => {
|
|
28
|
-
if (!definition.searchIndexes || definition.searchIndexes.length === 0 || !isFtsAvailable(sql$1)) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
for (const index of definition.searchIndexes) {
|
|
32
|
-
const ftName = ftsTableName(tableName, index.name);
|
|
33
|
-
runDrizzle(
|
|
34
|
-
sql$1,
|
|
35
|
-
sql`CREATE VIRTUAL TABLE IF NOT EXISTS ${sql.identifier(ftName)} USING fts5(${sql.identifier("__text__")}, ${sql.identifier("__id__")} UNINDEXED)`
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
const migrateAggregateIndexes = (sql$1, tableName, definition) => {
|
|
40
|
-
if (!definition.aggregateIndexes) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
for (const index of definition.aggregateIndexes) {
|
|
44
|
-
const aggTable = aggregateTableName(tableName, index.name);
|
|
45
|
-
runDrizzle(
|
|
46
|
-
sql$1,
|
|
47
|
-
sql`CREATE TABLE IF NOT EXISTS ${sql.identifier(aggTable)} (${AGG_KEY} TEXT PRIMARY KEY, ${AGG_VALUE} REAL, ${AGG_COUNT} INTEGER NOT NULL DEFAULT 0)`
|
|
48
|
-
);
|
|
49
|
-
const columns = runDrizzle(sql$1, sql`PRAGMA table_info(${sql.identifier(aggTable)})`).toArray();
|
|
50
|
-
if (!columns.some((column) => column.name === "__count__")) {
|
|
51
|
-
runDrizzle(sql$1, sql`ALTER TABLE ${sql.identifier(aggTable)} ADD COLUMN ${AGG_COUNT} INTEGER NOT NULL DEFAULT 0`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
const migrateRankIndexes = (sql$1, tableName, definition) => {
|
|
56
|
-
if (!definition.rankIndexes) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
for (const index of definition.rankIndexes) {
|
|
60
|
-
const rankTable = rankTableName(tableName, index.name);
|
|
61
|
-
const sortColumns = index.sortBy.map((_, i) => sortColumnName(i));
|
|
62
|
-
const columnDdls = sortColumns.map((column) => sql`${sql.identifier(column)} BLOB`);
|
|
63
|
-
const columnPart = columnDdls.length > 0 ? sql`, ${sql.join(columnDdls, sql`, `)}` : sql``;
|
|
64
|
-
runDrizzle(
|
|
65
|
-
sql$1,
|
|
66
|
-
sql`CREATE TABLE IF NOT EXISTS ${sql.identifier(rankTable)} (${sql.identifier("__id__")} TEXT PRIMARY KEY, ${sql.identifier("__partition__")} TEXT NOT NULL${columnPart})`
|
|
67
|
-
);
|
|
68
|
-
const orderedColumns = [sql`${sql.identifier("__partition__")} ASC`];
|
|
69
|
-
for (const [i, column] of sortColumns.entries()) {
|
|
70
|
-
const direction = index.sortBy[i]?.direction;
|
|
71
|
-
orderedColumns.push(sql`${sql.identifier(column)} ${sql.raw(direction === "desc" ? "DESC" : "ASC")}`);
|
|
72
|
-
}
|
|
73
|
-
orderedColumns.push(sql`${sql.identifier("__id__")} ASC`);
|
|
74
|
-
const btreeName = `${tableName}__rank_${index.name}__btree`;
|
|
75
|
-
runDrizzle(sql$1, createIndexSql(btreeName, rankTable, sql.join(orderedColumns, sql`, `), false));
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
const runShardMigrations = (sql$1, schema, options = {}) => {
|
|
79
|
-
for (const [tableName, definition] of Object.entries(schema.tables)) {
|
|
80
|
-
if (definition.shardMode?.kind === "global") {
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
runDrizzle(
|
|
84
|
-
sql$1,
|
|
85
|
-
sql`CREATE TABLE IF NOT EXISTS ${sql.identifier(tableName)} (
|
|
86
|
-
id TEXT PRIMARY KEY,
|
|
87
|
-
_creationTime REAL NOT NULL,
|
|
88
|
-
${sql.identifier(DOC_COLUMN)} TEXT NOT NULL
|
|
89
|
-
)`
|
|
90
|
-
);
|
|
91
|
-
migrateSecondaryIndexes(sql$1, tableName, definition);
|
|
92
|
-
migrateSearchIndexes(sql$1, tableName, definition);
|
|
93
|
-
migrateAggregateIndexes(sql$1, tableName, definition);
|
|
94
|
-
migrateRankIndexes(sql$1, tableName, definition);
|
|
95
|
-
}
|
|
96
|
-
if (options.cdc) {
|
|
97
|
-
migrateCdcLog(sql$1);
|
|
98
|
-
migrateCdcMeta(sql$1);
|
|
99
|
-
}
|
|
100
|
-
migrateIdempotency(sql$1);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export { runShardMigrations };
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
const SECRET_NAME_PATTERN = /key|secret|token|password|passwd|credential|private|auth|bearer|session|cookie|salt|signature|dsn|webhook/iu;
|
|
2
|
-
const LUNORA_INTERNAL_VARS = /* @__PURE__ */ new Set(["LUNORA_ADMIN_TOKEN", "LUNORA_WS_BEARER"]);
|
|
3
|
-
const WORKER_URL_VARS = ["CF_PAGES_URL", "WORKER_URL", "LUNORA_WORKER_URL"];
|
|
4
|
-
const ENVIRONMENT_VARS = ["CF_ENV", "ENVIRONMENT", "WORKER_ENV", "NODE_ENV"];
|
|
5
|
-
const redact = (value) => {
|
|
6
|
-
if (value.length <= 4) {
|
|
7
|
-
return "••••";
|
|
8
|
-
}
|
|
9
|
-
return `${value.slice(0, 4)}${"•".repeat(Math.min(8, value.length - 4))}`;
|
|
10
|
-
};
|
|
11
|
-
const looksSecret = (name) => SECRET_NAME_PATTERN.test(name);
|
|
12
|
-
const bindingType = (value) => {
|
|
13
|
-
const has = (key) => typeof value[key] === "function";
|
|
14
|
-
if (has("idFromName") || has("idFromString")) {
|
|
15
|
-
return "durable-object";
|
|
16
|
-
}
|
|
17
|
-
if (has("createMultipartUpload") || has("put") && has("get") && has("list") && has("head")) {
|
|
18
|
-
return "r2";
|
|
19
|
-
}
|
|
20
|
-
if (has("getWithMetadata")) {
|
|
21
|
-
return "kv";
|
|
22
|
-
}
|
|
23
|
-
if (has("prepare") && has("dump")) {
|
|
24
|
-
return "d1";
|
|
25
|
-
}
|
|
26
|
-
if (has("send") && has("sendBatch")) {
|
|
27
|
-
return "queue";
|
|
28
|
-
}
|
|
29
|
-
if (has("fetch") && !has("get")) {
|
|
30
|
-
return "service";
|
|
31
|
-
}
|
|
32
|
-
return "object";
|
|
33
|
-
};
|
|
34
|
-
const readDeployInfo = (env) => {
|
|
35
|
-
const deploy = {};
|
|
36
|
-
for (const key of WORKER_URL_VARS) {
|
|
37
|
-
if (typeof env[key] === "string" && env[key] !== "") {
|
|
38
|
-
deploy.workerUrl = env[key];
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
for (const key of ENVIRONMENT_VARS) {
|
|
43
|
-
if (typeof env[key] === "string" && env[key] !== "") {
|
|
44
|
-
deploy.environment = env[key];
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const versionMetadata = env["CF_VERSION_METADATA"];
|
|
49
|
-
if (versionMetadata !== null && typeof versionMetadata === "object") {
|
|
50
|
-
const meta = versionMetadata;
|
|
51
|
-
if (typeof meta["id"] === "string") {
|
|
52
|
-
deploy.deploymentId = meta["id"];
|
|
53
|
-
}
|
|
54
|
-
if (typeof meta["tag"] === "string") {
|
|
55
|
-
deploy.versionTag = meta["tag"];
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return deploy;
|
|
59
|
-
};
|
|
60
|
-
const DEV_ENVIRONMENT_PATTERN = /^(?:dev(?:elopment)?|local(?:host)?|test)$/iu;
|
|
61
|
-
const isDevEnvironment = (rawEnv) => {
|
|
62
|
-
const env = rawEnv ?? {};
|
|
63
|
-
for (const key of ENVIRONMENT_VARS) {
|
|
64
|
-
const value = env[key];
|
|
65
|
-
if (typeof value === "string" && DEV_ENVIRONMENT_PATTERN.test(value)) {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return false;
|
|
70
|
-
};
|
|
71
|
-
const buildSettings = (rawEnv) => {
|
|
72
|
-
const env = rawEnv ?? {};
|
|
73
|
-
const settings = [];
|
|
74
|
-
for (const [name, value] of Object.entries(env)) {
|
|
75
|
-
if (LUNORA_INTERNAL_VARS.has(name)) {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (typeof value === "string") {
|
|
79
|
-
const secret = looksSecret(name);
|
|
80
|
-
settings.push({
|
|
81
|
-
kind: secret ? "secret" : "var",
|
|
82
|
-
name,
|
|
83
|
-
// Both vars and secrets are masked — a "var" may still hold a
|
|
84
|
-
// sensitive value, and the studio never needs the raw text.
|
|
85
|
-
value: redact(value)
|
|
86
|
-
});
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (typeof value === "number" || typeof value === "boolean") {
|
|
90
|
-
settings.push({ kind: "var", name, value: String(value) });
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (value !== null && typeof value === "object") {
|
|
94
|
-
settings.push({ bindingType: bindingType(value), kind: "binding", name, value: null });
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
settings.push({ kind: "var", name, value: null });
|
|
98
|
-
}
|
|
99
|
-
settings.sort((a, b) => a.name.localeCompare(b.name));
|
|
100
|
-
return { deploy: readDeployInfo(env), settings };
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const MIN_ADMIN_TOKEN_LENGTH = 24;
|
|
104
|
-
const MIN_AUTH_SECRET_LENGTH = 32;
|
|
105
|
-
const LEVEL_ORDER = { error: 0, info: 2, warning: 1 };
|
|
106
|
-
const DISABLED_ENV_VALUES = /* @__PURE__ */ new Set(["0", "disabled", "false", "no", "off"]);
|
|
107
|
-
const ENABLED_ENV_VALUES = /* @__PURE__ */ new Set(["1", "enabled", "on", "true", "yes"]);
|
|
108
|
-
const readFlag = (value) => typeof value === "string" ? value.trim().toLowerCase() : void 0;
|
|
109
|
-
const auditAuthSecret = (env) => {
|
|
110
|
-
const authSecret = env["AUTH_SECRET"] ?? env["BETTER_AUTH_SECRET"];
|
|
111
|
-
const length = typeof authSecret === "string" ? authSecret.trim().length : 0;
|
|
112
|
-
if (length > 0 && length < MIN_AUTH_SECRET_LENGTH) {
|
|
113
|
-
return [{ detail: { length, min: MIN_AUTH_SECRET_LENGTH }, kind: "auth-secret-weak", level: "warning" }];
|
|
114
|
-
}
|
|
115
|
-
return [];
|
|
116
|
-
};
|
|
117
|
-
const auditCors = (env) => {
|
|
118
|
-
const allowedOrigins = readFlag(env["LUNORA_ALLOWED_ORIGINS"]);
|
|
119
|
-
const corsCredentials = ENABLED_ENV_VALUES.has(readFlag(env["LUNORA_CORS_ALLOW_CREDENTIALS"]) ?? "");
|
|
120
|
-
const hasWildcard = allowedOrigins?.split(",").some((entry) => entry.trim() === "*") ?? false;
|
|
121
|
-
return hasWildcard && corsCredentials ? [{ kind: "cors-wildcard-credentials", level: "error" }] : [];
|
|
122
|
-
};
|
|
123
|
-
const auditSecurityLayers = (env, dev) => {
|
|
124
|
-
if (dev) {
|
|
125
|
-
return [];
|
|
126
|
-
}
|
|
127
|
-
const findings = [];
|
|
128
|
-
if (DISABLED_ENV_VALUES.has(readFlag(env["LUNORA_SECURITY_HEADERS"]) ?? "")) {
|
|
129
|
-
findings.push({ kind: "security-headers-disabled", level: "warning" });
|
|
130
|
-
}
|
|
131
|
-
if (DISABLED_ENV_VALUES.has(readFlag(env["LUNORA_SECURITY_CSRF"]) ?? "")) {
|
|
132
|
-
findings.push({ kind: "csrf-disabled", level: "warning" });
|
|
133
|
-
}
|
|
134
|
-
if (readFlag(env["BETTER_AUTH_URL"])?.startsWith("http://") === true) {
|
|
135
|
-
findings.push({ kind: "cookies-insecure", level: "warning" });
|
|
136
|
-
}
|
|
137
|
-
return findings;
|
|
138
|
-
};
|
|
139
|
-
const buildSecurityAudit = (rawEnv) => {
|
|
140
|
-
const env = rawEnv ?? {};
|
|
141
|
-
const findings = [];
|
|
142
|
-
const adminToken = env["LUNORA_ADMIN_TOKEN"];
|
|
143
|
-
if (typeof adminToken === "string" && adminToken.length > 0 && adminToken.length < MIN_ADMIN_TOKEN_LENGTH) {
|
|
144
|
-
findings.push({ detail: { length: adminToken.length, min: MIN_ADMIN_TOKEN_LENGTH }, kind: "admin-token-weak", level: "warning" });
|
|
145
|
-
}
|
|
146
|
-
const wsBearer = env["LUNORA_WS_BEARER"];
|
|
147
|
-
const dev = isDevEnvironment(env);
|
|
148
|
-
if (typeof wsBearer !== "string" || wsBearer === "") {
|
|
149
|
-
findings.push({ kind: "ws-gate-open", level: dev ? "info" : "error" });
|
|
150
|
-
}
|
|
151
|
-
if (dev) {
|
|
152
|
-
findings.push({ kind: "dev-args-unredacted", level: "warning" });
|
|
153
|
-
}
|
|
154
|
-
findings.push(...auditAuthSecret(env), ...auditCors(env), ...auditSecurityLayers(env, dev));
|
|
155
|
-
return { findings: findings.toSorted((a, b) => LEVEL_ORDER[a.level] - LEVEL_ORDER[b.level]) };
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
export { MIN_ADMIN_TOKEN_LENGTH as M, MIN_AUTH_SECRET_LENGTH as a, buildSecurityAudit as b, buildSettings as c, isDevEnvironment as i };
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { RELATION_FUNCTION_PREFIX } from './ADMIN_FUNCTIONS-CHcC8fKV.mjs';
|
|
2
|
-
|
|
3
|
-
const serveRelationFanout = async (schema, database, functionPath, args) => {
|
|
4
|
-
const table = typeof args["table"] === "string" ? args["table"] : "";
|
|
5
|
-
const definition = schema.tables[table];
|
|
6
|
-
if (!definition) {
|
|
7
|
-
throw Object.assign(new Error(`${RELATION_FUNCTION_PREFIX} unknown table "${table}"`), { code: "UNKNOWN_TABLE", name: "LunoraError", status: 404 });
|
|
8
|
-
}
|
|
9
|
-
if (definition.shardMode?.kind === "global") {
|
|
10
|
-
throw Object.assign(new Error(`${RELATION_FUNCTION_PREFIX} table "${table}" is global, not shard-local`), {
|
|
11
|
-
code: "BAD_REQUEST",
|
|
12
|
-
name: "LunoraError",
|
|
13
|
-
status: 400
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
const where = args["where"] ?? void 0;
|
|
17
|
-
if (functionPath === `${RELATION_FUNCTION_PREFIX}count`) {
|
|
18
|
-
return database.count(table, where);
|
|
19
|
-
}
|
|
20
|
-
const result = await database.findMany(table, { orderBy: args["orderBy"], where, with: args["with"] });
|
|
21
|
-
return result.page;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export { serveRelationFanout };
|