@lunora/d1 1.0.0-alpha.4 → 1.0.0-alpha.41
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/__assets__/package-og.svg +1 -1
- package/dist/dialect.d.mts +29 -27
- package/dist/dialect.d.ts +29 -27
- package/dist/dialect.mjs +1 -35
- package/dist/index.d.mts +141 -132
- package/dist/index.d.ts +141 -132
- package/dist/index.mjs +1 -7
- package/dist/packem_shared/D1Client-B9m0fFLr.mjs +1 -0
- package/dist/packem_shared/MigrationRunner-DvnPBDVT.mjs +2 -0
- package/dist/packem_shared/createD1CtxDb-oii8T0ys.mjs +1 -0
- package/dist/packem_shared/exportGlobalRows-CVw1hOdA.mjs +1 -0
- package/dist/packem_shared/facetGlobalColumn-T4arPSaV.mjs +1 -0
- package/dist/packem_shared/quoteIdentifier-CObIFRhb.mjs +1 -0
- package/dist/packem_shared/sqliteDialect-JvN44Hjf.mjs +1 -0
- package/package.json +5 -4
- package/dist/packem_shared/D1Client-DA3flo1o.mjs +0 -143
- package/dist/packem_shared/MigrationRunner-BkEwQ-Ya.mjs +0 -149
- package/dist/packem_shared/createD1CtxDb-BMR8J0dT.mjs +0 -14
- package/dist/packem_shared/exportGlobalRows-BGCPm_nA.mjs +0 -122
- package/dist/packem_shared/facetGlobalColumn-C6u_WMIY.mjs +0 -142
- package/dist/packem_shared/sqliteDialect-DqYnHPuu.mjs +0 -27
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { sql } from 'drizzle-orm';
|
|
2
|
-
import { D1Client } from './D1Client-DA3flo1o.mjs';
|
|
3
|
-
|
|
4
|
-
const TRACKING_TABLE_NAME = "__drizzle_migrations";
|
|
5
|
-
const TRACKING_TABLE_DDL = `CREATE TABLE IF NOT EXISTS ${TRACKING_TABLE_NAME} (id INTEGER PRIMARY KEY AUTOINCREMENT, hash TEXT NOT NULL, created_at NUMERIC)`;
|
|
6
|
-
const WHITESPACE_RE = /\s/u;
|
|
7
|
-
const TRAILING_SEMICOLON_RE = /;\s*$/u;
|
|
8
|
-
const SHA256_HEX_RE = /^[0-9a-f]{64}$/u;
|
|
9
|
-
const assertSingleStatement = (migration) => {
|
|
10
|
-
const text = migration.sql;
|
|
11
|
-
let inSingle = false;
|
|
12
|
-
let inDouble = false;
|
|
13
|
-
let inLineComment = false;
|
|
14
|
-
let inBlockComment = false;
|
|
15
|
-
let seenStatement = false;
|
|
16
|
-
for (let index = 0; index < text.length; index += 1) {
|
|
17
|
-
const character = text[index];
|
|
18
|
-
const next = text[index + 1];
|
|
19
|
-
if (inLineComment) {
|
|
20
|
-
if (character === "\n") {
|
|
21
|
-
inLineComment = false;
|
|
22
|
-
}
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
if (inBlockComment) {
|
|
26
|
-
if (character === "*" && next === "/") {
|
|
27
|
-
inBlockComment = false;
|
|
28
|
-
index += 1;
|
|
29
|
-
}
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
if (inSingle) {
|
|
33
|
-
if (character === "'") {
|
|
34
|
-
if (next === "'") {
|
|
35
|
-
index += 1;
|
|
36
|
-
} else {
|
|
37
|
-
inSingle = false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (inDouble) {
|
|
43
|
-
if (character === '"') {
|
|
44
|
-
if (next === '"') {
|
|
45
|
-
index += 1;
|
|
46
|
-
} else {
|
|
47
|
-
inDouble = false;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (character === "'") {
|
|
53
|
-
inSingle = true;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (character === '"') {
|
|
57
|
-
inDouble = true;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (character === "-" && next === "-") {
|
|
61
|
-
inLineComment = true;
|
|
62
|
-
index += 1;
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (character === "/" && next === "*") {
|
|
66
|
-
inBlockComment = true;
|
|
67
|
-
index += 1;
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
if (character === ";") {
|
|
71
|
-
seenStatement = true;
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (seenStatement && character !== void 0 && !WHITESPACE_RE.test(character)) {
|
|
75
|
-
throw new Error(
|
|
76
|
-
`Migration "${migration.name}" (v${String(migration.version)}) contains more than one SQL statement. Split it into separate migrations — batch() runs them atomically.`
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
const hashMigration = async (text) => {
|
|
82
|
-
const bytes = new TextEncoder().encode(text);
|
|
83
|
-
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
84
|
-
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
85
|
-
};
|
|
86
|
-
class MigrationRunner {
|
|
87
|
-
client;
|
|
88
|
-
migrations;
|
|
89
|
-
/**
|
|
90
|
-
* Accepts either a {@link D1Client} (preferred — gets typed batches +
|
|
91
|
-
* drizzle handle for free) or a raw `D1DatabaseLike` binding (wrapped on
|
|
92
|
-
* the caller's behalf so existing `@lunora/cli` callers keep working).
|
|
93
|
-
*/
|
|
94
|
-
constructor(database, migrations) {
|
|
95
|
-
this.client = database instanceof D1Client ? database : new D1Client(database);
|
|
96
|
-
this.migrations = [...migrations].toSorted((a, b) => a.version - b.version);
|
|
97
|
-
this.assertUniqueVersions();
|
|
98
|
-
this.assertUniqueSql();
|
|
99
|
-
}
|
|
100
|
-
async run() {
|
|
101
|
-
await this.client.drizzle.run(sql.raw(TRACKING_TABLE_DDL));
|
|
102
|
-
const appliedRows = await this.client.drizzle.all(sql.raw(`SELECT hash FROM ${TRACKING_TABLE_NAME}`));
|
|
103
|
-
const appliedHashes = new Set(appliedRows.map((row) => row.hash));
|
|
104
|
-
const applied = [];
|
|
105
|
-
const skipped = [];
|
|
106
|
-
const hashes = await Promise.all(this.migrations.map(async (migration) => hashMigration(migration.sql)));
|
|
107
|
-
for (const [index, migration] of this.migrations.entries()) {
|
|
108
|
-
const hash = hashes[index];
|
|
109
|
-
if (appliedHashes.has(hash)) {
|
|
110
|
-
skipped.push({ name: migration.name, version: migration.version });
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
await this.applyOne(migration, hash);
|
|
114
|
-
applied.push({ name: migration.name, version: migration.version });
|
|
115
|
-
}
|
|
116
|
-
return { applied, skipped };
|
|
117
|
-
}
|
|
118
|
-
async applyOne(migration, hash) {
|
|
119
|
-
assertSingleStatement(migration);
|
|
120
|
-
const statementText = migration.sql.replace(TRAILING_SEMICOLON_RE, "").trim();
|
|
121
|
-
if (!SHA256_HEX_RE.test(hash)) {
|
|
122
|
-
throw new Error(`migration "${migration.name}" produced a non-hex hash; refusing to inline into SQL`);
|
|
123
|
-
}
|
|
124
|
-
const trackingInsertSql = `INSERT INTO ${TRACKING_TABLE_NAME} (hash, created_at) VALUES ('${hash}', ${String(Date.now())})`;
|
|
125
|
-
const items = [this.client.drizzle.run(sql.raw(statementText)), this.client.drizzle.run(sql.raw(trackingInsertSql))];
|
|
126
|
-
await this.client.batch(items);
|
|
127
|
-
}
|
|
128
|
-
assertUniqueVersions() {
|
|
129
|
-
const seen = /* @__PURE__ */ new Set();
|
|
130
|
-
for (const m of this.migrations) {
|
|
131
|
-
if (seen.has(m.version)) {
|
|
132
|
-
throw new Error(`Duplicate migration version ${String(m.version)}`);
|
|
133
|
-
}
|
|
134
|
-
seen.add(m.version);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
assertUniqueSql() {
|
|
138
|
-
const seen = /* @__PURE__ */ new Map();
|
|
139
|
-
for (const m of this.migrations) {
|
|
140
|
-
const previousVersion = seen.get(m.sql);
|
|
141
|
-
if (previousVersion !== void 0) {
|
|
142
|
-
throw new Error(`Migrations ${String(previousVersion)} and ${String(m.version)} have identical SQL — bump the content, not just the version.`);
|
|
143
|
-
}
|
|
144
|
-
seen.set(m.sql, m.version);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export { MigrationRunner };
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { createSqlCtxDb, readSqlCdcChanges, runSqlAggregateMigrations, runSqlCdcMigration, runSqlGlobalTableMigrations, runSqlRankMigrations, runSqlSearchMigrations, trimSqlCdcChanges } from '@lunora/sql-store';
|
|
2
|
-
export { createSqlCtxDb, decodeGlobalRow } from '@lunora/sql-store';
|
|
3
|
-
import sqliteDialect from './sqliteDialect-DqYnHPuu.mjs';
|
|
4
|
-
|
|
5
|
-
const createD1ContextDatabase = (options) => createSqlCtxDb({ ...options, dialect: sqliteDialect });
|
|
6
|
-
const runD1GlobalTableMigrations = (exec, schema) => runSqlGlobalTableMigrations(exec, schema, sqliteDialect);
|
|
7
|
-
const runD1AggregateMigrations = (exec, schema) => runSqlAggregateMigrations(exec, schema, sqliteDialect);
|
|
8
|
-
const runD1RankMigrations = (exec, schema) => runSqlRankMigrations(exec, schema, sqliteDialect);
|
|
9
|
-
const runD1SearchMigrations = (exec, schema) => runSqlSearchMigrations(exec, schema, sqliteDialect);
|
|
10
|
-
const runD1CdcMigration = (exec) => runSqlCdcMigration(exec, sqliteDialect);
|
|
11
|
-
const readD1CdcChanges = (exec, options = {}) => readSqlCdcChanges(exec, options, sqliteDialect);
|
|
12
|
-
const trimD1CdcChanges = (exec, throughSeq) => trimSqlCdcChanges(exec, throughSeq, sqliteDialect);
|
|
13
|
-
|
|
14
|
-
export { createD1ContextDatabase as createD1CtxDb, readD1CdcChanges, runD1AggregateMigrations, runD1CdcMigration, runD1GlobalTableMigrations, runD1RankMigrations, runD1SearchMigrations, trimD1CdcChanges };
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { decodeGlobalRow } from '@lunora/sql-store';
|
|
2
|
-
|
|
3
|
-
const DEFAULT_BATCH_SIZE = 200;
|
|
4
|
-
const quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
5
|
-
const selectGlobalTables = (schema, requested) => {
|
|
6
|
-
const isGlobal = (table) => schema.tables[table]?.shardMode?.kind === "global";
|
|
7
|
-
if (requested && requested.length > 0) {
|
|
8
|
-
return requested.filter((name) => isGlobal(name));
|
|
9
|
-
}
|
|
10
|
-
return Object.keys(schema.tables).filter((name) => isGlobal(name));
|
|
11
|
-
};
|
|
12
|
-
const decodeRow = (schema, table, row) => {
|
|
13
|
-
const definition = schema.tables[table];
|
|
14
|
-
if (!definition) {
|
|
15
|
-
return { _creationTime: row["_creationTime"], _id: row["id"] };
|
|
16
|
-
}
|
|
17
|
-
return decodeGlobalRow(definition, row);
|
|
18
|
-
};
|
|
19
|
-
const exportGlobalRows = async function* (exec, schema, args) {
|
|
20
|
-
const tables = selectGlobalTables(schema, args.tables);
|
|
21
|
-
const batchSize = args.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
22
|
-
for (const table of tables) {
|
|
23
|
-
let offset = 0;
|
|
24
|
-
let hasMore = true;
|
|
25
|
-
while (hasMore) {
|
|
26
|
-
const rows = await exec.all(`SELECT * FROM ${quoteIdentifier(table)} LIMIT ? OFFSET ?`, [batchSize, offset]);
|
|
27
|
-
for (const row of rows) {
|
|
28
|
-
yield { doc: decodeRow(schema, table, row), table };
|
|
29
|
-
}
|
|
30
|
-
hasMore = rows.length === batchSize;
|
|
31
|
-
offset += rows.length;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
const validateRow = (schema, table, document) => {
|
|
36
|
-
const definition = schema.tables[table];
|
|
37
|
-
if (!definition) {
|
|
38
|
-
return `unknown table: ${table}`;
|
|
39
|
-
}
|
|
40
|
-
for (const [field, validator] of Object.entries(definition.shape)) {
|
|
41
|
-
const candidate = document[field];
|
|
42
|
-
const optional = validator.kind === "optional";
|
|
43
|
-
if (candidate === void 0 && optional) {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
const parser = validator.parse;
|
|
47
|
-
if (typeof parser !== "function") {
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
try {
|
|
51
|
-
parser(candidate);
|
|
52
|
-
} catch (error) {
|
|
53
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
54
|
-
return `field "${field}": ${message}`;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return void 0;
|
|
58
|
-
};
|
|
59
|
-
const explicitIdConflicts = async (writer, exec, table, explicitId) => {
|
|
60
|
-
try {
|
|
61
|
-
if (exec) {
|
|
62
|
-
const probe = await exec.all(`SELECT 1 AS hit FROM ${quoteIdentifier(table)} WHERE "id" = ? LIMIT 1`, [explicitId]);
|
|
63
|
-
return probe.length > 0;
|
|
64
|
-
}
|
|
65
|
-
const existing = await writer.get(explicitId);
|
|
66
|
-
return existing !== null;
|
|
67
|
-
} catch {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
const importOneRow = async (writer, schema, args, row, line) => {
|
|
72
|
-
const { doc, table } = row;
|
|
73
|
-
if (schema.tables[table]?.shardMode?.kind !== "global") {
|
|
74
|
-
return { kind: "skip" };
|
|
75
|
-
}
|
|
76
|
-
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
77
|
-
return { error: { code: "BAD_ROW", line, message: "row is missing or malformed `doc`", table }, kind: "error" };
|
|
78
|
-
}
|
|
79
|
-
const failure = validateRow(schema, table, doc);
|
|
80
|
-
if (failure !== void 0) {
|
|
81
|
-
return { error: { code: "VALIDATION_ERROR", line, message: failure, table }, kind: "error" };
|
|
82
|
-
}
|
|
83
|
-
const explicitId = typeof doc["_id"] === "string" ? doc["_id"] : void 0;
|
|
84
|
-
if (explicitId !== void 0 && await explicitIdConflicts(writer, args.exec, table, explicitId)) {
|
|
85
|
-
return { kind: "conflict" };
|
|
86
|
-
}
|
|
87
|
-
try {
|
|
88
|
-
await writer.insert(table, doc, { allowExplicitId: true });
|
|
89
|
-
return { inserted: table, kind: "inserted" };
|
|
90
|
-
} catch (error) {
|
|
91
|
-
const code = error.code ?? "INSERT_FAILED";
|
|
92
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
93
|
-
return { error: { code, line, message, table }, kind: "error" };
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
const importGlobalRows = async (writer, schema, args) => {
|
|
97
|
-
const errors = [];
|
|
98
|
-
const inserted = {};
|
|
99
|
-
let conflicts = 0;
|
|
100
|
-
let line = (args.startLine ?? 1) - 1;
|
|
101
|
-
for (const row of args.rows) {
|
|
102
|
-
line += 1;
|
|
103
|
-
const outcome = await importOneRow(writer, schema, args, row, line);
|
|
104
|
-
switch (outcome.kind) {
|
|
105
|
-
case "conflict": {
|
|
106
|
-
conflicts += 1;
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
case "error": {
|
|
110
|
-
errors.push(outcome.error);
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
case "inserted": {
|
|
114
|
-
inserted[outcome.inserted] = (inserted[outcome.inserted] ?? 0) + 1;
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return { conflicts, errors, inserted };
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
export { exportGlobalRows, importGlobalRows, selectGlobalTables };
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { runD1GlobalTableMigrations } from './createD1CtxDb-BMR8J0dT.mjs';
|
|
2
|
-
import { decodeGlobalRow } from '@lunora/sql-store';
|
|
3
|
-
|
|
4
|
-
const ensureGlobalTables = (exec, schema) => runD1GlobalTableMigrations(exec, schema);
|
|
5
|
-
const DEFAULT_PAGE_SIZE = 50;
|
|
6
|
-
const MAX_PAGE_SIZE = 500;
|
|
7
|
-
const DEFAULT_FACET_LIMIT = 30;
|
|
8
|
-
const MAX_FACET_LIMIT = 200;
|
|
9
|
-
const quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
10
|
-
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
11
|
-
const INTERNAL_TABLE = /^sqlite_|^_cf_|^d1_|^__cdc|__agg_|__rank_|__fts_/u;
|
|
12
|
-
const isInternalTable = (name) => INTERNAL_TABLE.test(name);
|
|
13
|
-
const SENSITIVE_COLUMN = /password|secret|token|hash|salt|credential/iu;
|
|
14
|
-
const listTableNames = async (exec) => {
|
|
15
|
-
const rows = await exec.all("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name", []);
|
|
16
|
-
return rows.map((row) => String(row["name"])).filter((name) => !isInternalTable(name));
|
|
17
|
-
};
|
|
18
|
-
const countRows = async (exec, quotedTable, whereSql = "", whereParams = []) => {
|
|
19
|
-
const rows = await exec.all(`SELECT COUNT(*) AS c FROM ${quotedTable}${whereSql}`, whereParams);
|
|
20
|
-
return Number(rows[0]?.["c"] ?? 0);
|
|
21
|
-
};
|
|
22
|
-
const physicalColumnName = (schema, table, displayColumn) => schema.tables[table] !== void 0 && displayColumn === "_id" ? "id" : displayColumn;
|
|
23
|
-
const buildEqPredicate = (schema, table, displayColumns, filters) => {
|
|
24
|
-
if (filters === void 0 || filters.length === 0) {
|
|
25
|
-
return void 0;
|
|
26
|
-
}
|
|
27
|
-
const clauses = [];
|
|
28
|
-
const params = [];
|
|
29
|
-
for (const filter of filters) {
|
|
30
|
-
if (!displayColumns.includes(filter.column)) {
|
|
31
|
-
throw Object.assign(new Error(`unknown column: ${filter.column}`), { code: "UNKNOWN_COLUMN", name: "LunoraError", status: 404 });
|
|
32
|
-
}
|
|
33
|
-
const quoted = quoteIdentifier(physicalColumnName(schema, table, filter.column));
|
|
34
|
-
if (filter.value === null || filter.value === void 0) {
|
|
35
|
-
clauses.push(`${quoted} IS NULL`);
|
|
36
|
-
} else {
|
|
37
|
-
clauses.push(`${quoted} = ?`);
|
|
38
|
-
params.push(filter.value);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return { params, where: clauses.join(" AND ") };
|
|
42
|
-
};
|
|
43
|
-
const decodeRow = (schema, table, row) => {
|
|
44
|
-
const definition = schema.tables[table];
|
|
45
|
-
if (definition) {
|
|
46
|
-
return decodeGlobalRow(definition, row);
|
|
47
|
-
}
|
|
48
|
-
const redacted = {};
|
|
49
|
-
for (const [key, value] of Object.entries(row)) {
|
|
50
|
-
redacted[key] = value !== null && value !== void 0 && SENSITIVE_COLUMN.test(key) ? "•••" : value;
|
|
51
|
-
}
|
|
52
|
-
return redacted;
|
|
53
|
-
};
|
|
54
|
-
const resolveColumns = async (exec, schema, table) => {
|
|
55
|
-
const definition = schema.tables[table];
|
|
56
|
-
if (definition) {
|
|
57
|
-
return ["_id", "_creationTime", ...Object.keys(definition.shape)];
|
|
58
|
-
}
|
|
59
|
-
const info = await exec.all(`PRAGMA table_info(${quoteIdentifier(table)})`, []);
|
|
60
|
-
return info.map((column) => String(column["name"]));
|
|
61
|
-
};
|
|
62
|
-
const resolveReferences = async (exec, schema, table) => {
|
|
63
|
-
if (schema.tables[table]) {
|
|
64
|
-
return void 0;
|
|
65
|
-
}
|
|
66
|
-
const rows = await exec.all(`PRAGMA foreign_key_list(${quoteIdentifier(table)})`, []);
|
|
67
|
-
if (rows.length === 0) {
|
|
68
|
-
return void 0;
|
|
69
|
-
}
|
|
70
|
-
const references = {};
|
|
71
|
-
for (const row of rows) {
|
|
72
|
-
const from = String(row["from"]);
|
|
73
|
-
const target = String(row["table"]);
|
|
74
|
-
references[from] ??= target;
|
|
75
|
-
}
|
|
76
|
-
return references;
|
|
77
|
-
};
|
|
78
|
-
const listGlobalTables = async (exec, schema) => {
|
|
79
|
-
await ensureGlobalTables(exec, schema);
|
|
80
|
-
const names = await listTableNames(exec);
|
|
81
|
-
return Promise.all(
|
|
82
|
-
names.map(async (name) => {
|
|
83
|
-
return { name, rowCount: await countRows(exec, quoteIdentifier(name)) };
|
|
84
|
-
})
|
|
85
|
-
);
|
|
86
|
-
};
|
|
87
|
-
const readGlobalTablePage = async (exec, schema, options) => {
|
|
88
|
-
const { table } = options;
|
|
89
|
-
await ensureGlobalTables(exec, schema);
|
|
90
|
-
const tableNames = await listTableNames(exec);
|
|
91
|
-
if (!tableNames.includes(table)) {
|
|
92
|
-
throw Object.assign(new Error(`unknown table: ${table}`), { code: "UNKNOWN_TABLE", name: "LunoraError", status: 404 });
|
|
93
|
-
}
|
|
94
|
-
const limit = clamp(Math.trunc(options.limit ?? DEFAULT_PAGE_SIZE), 1, MAX_PAGE_SIZE);
|
|
95
|
-
const offset = Math.max(0, Math.trunc(options.offset ?? 0));
|
|
96
|
-
const quoted = quoteIdentifier(table);
|
|
97
|
-
const columns = await resolveColumns(exec, schema, table);
|
|
98
|
-
const predicate = buildEqPredicate(schema, table, columns, options.filters);
|
|
99
|
-
const whereSql = predicate === void 0 ? "" : ` WHERE ${predicate.where}`;
|
|
100
|
-
const whereParams = predicate?.params ?? [];
|
|
101
|
-
const total = await countRows(exec, quoted, whereSql, whereParams);
|
|
102
|
-
const raw = await exec.all(`SELECT * FROM ${quoted}${whereSql} LIMIT ? OFFSET ?`, [...whereParams, limit, offset]);
|
|
103
|
-
const rows = raw.map((row) => decodeRow(schema, table, row));
|
|
104
|
-
const references = await resolveReferences(exec, schema, table);
|
|
105
|
-
return references === void 0 ? { columns, rows, total } : { columns, refs: references, rows, total };
|
|
106
|
-
};
|
|
107
|
-
const facetGlobalColumn = async (exec, schema, options) => {
|
|
108
|
-
const { column, table } = options;
|
|
109
|
-
await ensureGlobalTables(exec, schema);
|
|
110
|
-
const tableNames = await listTableNames(exec);
|
|
111
|
-
if (!tableNames.includes(table)) {
|
|
112
|
-
throw Object.assign(new Error(`unknown table: ${table}`), { code: "UNKNOWN_TABLE", name: "LunoraError", status: 404 });
|
|
113
|
-
}
|
|
114
|
-
const columns = await resolveColumns(exec, schema, table);
|
|
115
|
-
if (!columns.includes(column)) {
|
|
116
|
-
throw Object.assign(new Error(`unknown column: ${column}`), { code: "UNKNOWN_COLUMN", name: "LunoraError", status: 404 });
|
|
117
|
-
}
|
|
118
|
-
const quoted = quoteIdentifier(table);
|
|
119
|
-
const predicate = buildEqPredicate(schema, table, columns, options.filters);
|
|
120
|
-
const whereSql = predicate === void 0 ? "" : ` WHERE ${predicate.where}`;
|
|
121
|
-
const whereParams = predicate?.params ?? [];
|
|
122
|
-
if (schema.tables[table] === void 0 && SENSITIVE_COLUMN.test(column)) {
|
|
123
|
-
const total = await countRows(exec, quoted, whereSql, whereParams);
|
|
124
|
-
return { truncated: false, values: total === 0 ? [] : [{ count: total, value: "•••" }] };
|
|
125
|
-
}
|
|
126
|
-
const limit = clamp(Math.trunc(options.limit ?? DEFAULT_FACET_LIMIT), 1, MAX_FACET_LIMIT);
|
|
127
|
-
const physical = quoteIdentifier(physicalColumnName(schema, table, column));
|
|
128
|
-
const rows = await exec.all(`SELECT ${physical} AS value, COUNT(*) AS count FROM ${quoted}${whereSql} GROUP BY ${physical} ORDER BY count DESC LIMIT ?`, [
|
|
129
|
-
...whereParams,
|
|
130
|
-
limit + 1
|
|
131
|
-
]);
|
|
132
|
-
const truncated = rows.length > limit;
|
|
133
|
-
const kept = truncated ? rows.slice(0, limit) : rows;
|
|
134
|
-
return {
|
|
135
|
-
truncated,
|
|
136
|
-
values: kept.map((row) => {
|
|
137
|
-
return { count: Number(row["count"]), value: row["value"] };
|
|
138
|
-
})
|
|
139
|
-
};
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
export { facetGlobalColumn, listGlobalTables, readGlobalTablePage };
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { sqliteEncode, sqliteDecode } from '@lunora/sql-store';
|
|
2
|
-
import { sql } from 'drizzle-orm';
|
|
3
|
-
import { sqlAffinityForKind } from '../dialect.mjs';
|
|
4
|
-
|
|
5
|
-
const UNIQUE_VIOLATION_RE = /unique constraint failed/iu;
|
|
6
|
-
const sqliteDialect = {
|
|
7
|
-
companionTypes: {
|
|
8
|
-
autoincrementPrimaryKey: "INTEGER PRIMARY KEY AUTOINCREMENT",
|
|
9
|
-
integer: "INTEGER",
|
|
10
|
-
key: "TEXT",
|
|
11
|
-
real: "REAL",
|
|
12
|
-
text: "TEXT"
|
|
13
|
-
},
|
|
14
|
-
columnType: (kind) => sqlAffinityForKind(kind),
|
|
15
|
-
decode: (value, kind) => sqliteDecode(value, kind),
|
|
16
|
-
encode: (value) => sqliteEncode(value),
|
|
17
|
-
frameworkColumns: () => [
|
|
18
|
-
{ name: "id", type: "TEXT PRIMARY KEY" },
|
|
19
|
-
{ name: "_creationTime", type: "REAL NOT NULL" }
|
|
20
|
-
],
|
|
21
|
-
isUniqueViolation: (error) => error instanceof Error && UNIQUE_VIOLATION_RE.test(error.message),
|
|
22
|
-
name: "sqlite",
|
|
23
|
-
supportsReturning: true,
|
|
24
|
-
tableExists: (table) => sql`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ${table}`
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export { sqliteDialect as default };
|