@lunora/d1 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.
@@ -1,143 +0,0 @@
1
- import { drizzle } from 'drizzle-orm/d1';
2
-
3
- const STMT_CACHE_CAPACITY = 256;
4
- const D1_FIRST_UNCONSTRAINED = "first-unconstrained";
5
- class D1Session {
6
- session;
7
- /** See {@link D1Client.stmtCache}. Scoped per session. */
8
- stmtCache = /* @__PURE__ */ new Map();
9
- constructor(session) {
10
- this.session = session;
11
- }
12
- prepare(sql) {
13
- const cached = this.stmtCache.get(sql);
14
- if (cached) {
15
- this.stmtCache.delete(sql);
16
- this.stmtCache.set(sql, cached);
17
- return cached;
18
- }
19
- const stmt = this.session.prepare(sql);
20
- if (this.stmtCache.size >= STMT_CACHE_CAPACITY) {
21
- const oldest = this.stmtCache.keys().next().value;
22
- if (oldest !== void 0) {
23
- this.stmtCache.delete(oldest);
24
- }
25
- }
26
- this.stmtCache.set(sql, stmt);
27
- return stmt;
28
- }
29
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T types the result rows for the caller and is forwarded to the prepared statement.
30
- async run(sql, ...binds) {
31
- return this.prepare(sql).bind(...binds).run();
32
- }
33
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T types the result rows for the caller and is forwarded to the prepared statement.
34
- async all(sql, ...binds) {
35
- return this.prepare(sql).bind(...binds).all();
36
- }
37
- async first(sql, ...binds) {
38
- return this.prepare(sql).bind(...binds).first();
39
- }
40
- /**
41
- * Returns the most recent bookmark known to the session, or `undefined`
42
- * when D1 has not issued one yet.
43
- */
44
- getBookmark() {
45
- return this.session.getBookmark() ?? void 0;
46
- }
47
- }
48
- class D1Client {
49
- db;
50
- /**
51
- * SQL string -> prepared statement. Prepared statements are reusable in
52
- * D1; preparing the same SQL twice forces the worker to round-trip the
53
- * statement plan. Caching is per-instance so unit-test isolation holds.
54
- * Bounded to {@link STMT_CACHE_CAPACITY} via LRU eviction.
55
- */
56
- stmtCache = /* @__PURE__ */ new Map();
57
- /**
58
- * Lazily-built drizzle handle over the bare binding. Memoised so a single
59
- * `D1Client` reuses the same dialect/session machinery across calls.
60
- */
61
- drizzleHandle;
62
- constructor(database) {
63
- this.db = database;
64
- }
65
- /**
66
- * Open a Sessions-API scoped session. Pass the bookmark forwarded by
67
- * the client to opt into read-your-writes consistency.
68
- *
69
- * With no bookmark this is the first request of a session — there is no
70
- * prior write to read, so we open with the explicit `"first-unconstrained"`
71
- * constraint (Cloudflare's lowest-latency default: the first read may serve
72
- * from any replica). Read-your-writes for sequenced requests still flows
73
- * through the forwarded bookmark; a caller needing a strongly-consistent
74
- * very-first read should pass `"first-primary"` as the bookmark instead.
75
- */
76
- withSession(bookmark) {
77
- const session = this.db.withSession(bookmark ?? D1_FIRST_UNCONSTRAINED);
78
- return new D1Session(session);
79
- }
80
- /**
81
- * Prepare a statement, reusing a cached one when the SQL text matches.
82
- * `bind()` on a prepared statement returns a new bound statement and
83
- * leaves the underlying prepared plan reusable, so cache hits are safe
84
- * even when the previous caller already called `.bind(...).run()`.
85
- */
86
- prepare(sql) {
87
- const cached = this.stmtCache.get(sql);
88
- if (cached) {
89
- this.stmtCache.delete(sql);
90
- this.stmtCache.set(sql, cached);
91
- return cached;
92
- }
93
- const stmt = this.db.prepare(sql);
94
- if (this.stmtCache.size >= STMT_CACHE_CAPACITY) {
95
- const oldest = this.stmtCache.keys().next().value;
96
- if (oldest !== void 0) {
97
- this.stmtCache.delete(oldest);
98
- }
99
- }
100
- this.stmtCache.set(sql, stmt);
101
- return stmt;
102
- }
103
- /**
104
- * Drizzle handle over the bare `env.DB` binding. Used for typed queries
105
- * against generated `sqliteTable` schemas; does **not** participate in the
106
- * D1 Sessions API (no bookmark pinning). For bookmark-scoped reads, use
107
- * {@link drizzleSession} instead.
108
- */
109
- get drizzle() {
110
- if (this.drizzleHandle) {
111
- return this.drizzleHandle;
112
- }
113
- this.drizzleHandle = drizzle(this.db, { logger: false });
114
- return this.drizzleHandle;
115
- }
116
- /**
117
- * Drizzle handle scoped to a D1 Sessions-API session. The bookmark, when
118
- * supplied, opts into read-your-writes consistency for follow-up reads on
119
- * the same session.
120
- *
121
- * A `D1DatabaseSession` exposes the same `prepare` / `batch` surface
122
- * drizzle calls into, so a single `unknown` cast lets us treat the session
123
- * as a `D1Database` for driver-construction purposes.
124
- */
125
- drizzleSession(bookmark) {
126
- const session = this.db.withSession(bookmark ?? D1_FIRST_UNCONSTRAINED);
127
- return drizzle(session, { logger: false });
128
- }
129
- /**
130
- * Atomic batch over the drizzle d1 driver. Mirrors `db.batch([...])`
131
- * exactly; exposed on the client so callers don't need to hold a drizzle
132
- * handle just to run a typed batch.
133
- */
134
- async batch(items) {
135
- return this.drizzle.batch(items);
136
- }
137
- /** Direct access to the underlying binding (advanced use only). */
138
- get raw() {
139
- return this.db;
140
- }
141
- }
142
-
143
- export { D1Client, D1Session };
@@ -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 };