@lunora/d1 1.0.0-alpha.8 → 1.0.0-alpha.80
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/dialect.d.mts +41 -29
- package/dist/dialect.d.ts +41 -29
- package/dist/dialect.mjs +1 -35
- package/dist/index.d.mts +274 -163
- package/dist/index.d.ts +274 -163
- package/dist/index.mjs +1 -7
- package/dist/packem_shared/D1Client-CzS0Qfa6.mjs +1 -0
- package/dist/packem_shared/D1TimeoutError-D4YUqZPk.mjs +1 -0
- package/dist/packem_shared/MigrationRunner-SadkIbU5.mjs +2 -0
- package/dist/packem_shared/backfillD1SearchIndexes-By6bbCLe.mjs +1 -0
- package/dist/packem_shared/exportGlobalRows-Cc8cXltm.mjs +1 -0
- package/dist/packem_shared/facetGlobalColumn-D1npxpva.mjs +1 -0
- package/dist/packem_shared/quoteIdentifier-CObIFRhb.mjs +1 -0
- package/dist/packem_shared/sqliteDialect-DZoYJOa-.mjs +1 -0
- package/package.json +5 -3
- 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
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { SchemaLike, DatabaseWriterLike } from '@lunora/
|
|
2
|
-
import { SqlCtxDbOptions,
|
|
1
|
+
import { SchemaLike, DatabaseWriterLike } from '@lunora/shard-engine';
|
|
2
|
+
import { SqlCtxDbOptions, SqlCtxExec, createSqlCtxDb, readSqlCdcChanges, SqlDialect } from '@lunora/sql-store';
|
|
3
3
|
export { type SqlCtxExec as D1Exec, type SqlCtxDbOptions, type SqlCtxExec, createSqlCtxDb } from '@lunora/sql-store';
|
|
4
|
+
import { D1DatabaseLike, D1SessionLike, D1PreparedStatementLike } from '@lunora/platform';
|
|
5
|
+
export type { D1DatabaseLike, D1PreparedStatementLike, D1SessionLike } from '@lunora/platform';
|
|
4
6
|
import { BatchItem, BatchResponse } from 'drizzle-orm/batch';
|
|
5
7
|
import { DrizzleD1Database } from 'drizzle-orm/d1';
|
|
6
8
|
/** The D1 store options — the shared store options minus `dialect` (the SQLite dialect is injected for you). */
|
|
@@ -9,12 +11,14 @@ type D1ContextDatabaseOptions = Omit<SqlCtxDbOptions, "dialect">;
|
|
|
9
11
|
declare const createD1ContextDatabase: (options: D1ContextDatabaseOptions) => ReturnType<typeof createSqlCtxDb>;
|
|
10
12
|
/** Auto-provision the schema's `.global()` tables in D1 (idempotent `CREATE TABLE IF NOT EXISTS`). */
|
|
11
13
|
declare const runD1GlobalTableMigrations: (exec: SqlCtxExec, schema: SchemaLike) => Promise<void>;
|
|
12
|
-
/** Materialize the `__agg_
|
|
14
|
+
/** Materialize the `__agg_<index>` companion tables for the schema's aggregate indexes. */
|
|
13
15
|
declare const runD1AggregateMigrations: (exec: SqlCtxExec, schema: SchemaLike) => Promise<void>;
|
|
14
|
-
/** Materialize the `__rank_
|
|
16
|
+
/** Materialize the `__rank_<index>` companion tables for the schema's rank indexes. */
|
|
15
17
|
declare const runD1RankMigrations: (exec: SqlCtxExec, schema: SchemaLike) => Promise<void>;
|
|
16
|
-
/** Materialize the `__fts_
|
|
18
|
+
/** Materialize (and backfill) the `__fts_<index>` fts5 shadow tables for the schema's search indexes. */
|
|
17
19
|
declare const runD1SearchMigrations: (exec: SqlCtxExec, schema: SchemaLike) => Promise<void>;
|
|
20
|
+
/** Index existing rows into every search companion, including the `staged: true` ones migrations leave empty. */
|
|
21
|
+
declare const backfillD1SearchIndexes: (exec: SqlCtxExec, schema: SchemaLike) => Promise<void>;
|
|
18
22
|
/** Create the `__cdc_log` table in D1 (idempotent; only run when CDC is enabled). */
|
|
19
23
|
declare const runD1CdcMigration: (exec: SqlCtxExec) => Promise<void>;
|
|
20
24
|
/** Read changelog entries newer than `sinceSeq` in commit order. */
|
|
@@ -27,6 +31,17 @@ declare const trimD1CdcChanges: (exec: SqlCtxExec, throughSeq: number) => Promis
|
|
|
27
31
|
/** One exported row: `doc` is reconstructed from the column tuple. */
|
|
28
32
|
interface ExportRow {
|
|
29
33
|
doc: Record<string, unknown>;
|
|
34
|
+
/**
|
|
35
|
+
* True physical source line, when the caller has it (e.g. a global row
|
|
36
|
+
* pulled out of an interspersed NDJSON import stream by
|
|
37
|
+
* `@lunora/runtime`'s `import-stream.ts`). When present this overrides the
|
|
38
|
+
* position-derived line below — the caller-side positions of a
|
|
39
|
+
* global-only subset don't line up with the original file once
|
|
40
|
+
* non-global rows have been filtered out. Absent for callers with no
|
|
41
|
+
* physical source to attribute (e.g. a fresh `exportGlobalRows` roundtrip
|
|
42
|
+
* in tests), which keeps the positional fallback.
|
|
43
|
+
*/
|
|
44
|
+
line?: number;
|
|
30
45
|
table: string;
|
|
31
46
|
}
|
|
32
47
|
interface ImportError {
|
|
@@ -42,32 +57,42 @@ interface ImportResult {
|
|
|
42
57
|
inserted: Record<string, number>;
|
|
43
58
|
}
|
|
44
59
|
/**
|
|
45
|
-
* Return every `.global()` table in the schema, optionally narrowed by an
|
|
46
|
-
* allowlist. Shard-local tables are skipped here — they're handled by the DO
|
|
47
|
-
* helpers — so callers get a clean separation between the two storage planes.
|
|
48
|
-
*/
|
|
60
|
+
* Return every `.global()` table in the schema, optionally narrowed by an
|
|
61
|
+
* allowlist. Shard-local tables are skipped here — they're handled by the DO
|
|
62
|
+
* helpers — so callers get a clean separation between the two storage planes.
|
|
63
|
+
*/
|
|
49
64
|
declare const selectGlobalTables: (schema: SchemaLike, requested?: ReadonlyArray<string>) => string[];
|
|
50
65
|
interface ExportGlobalArgs {
|
|
51
66
|
batchSize?: number;
|
|
52
67
|
tables?: ReadonlyArray<string>;
|
|
53
68
|
}
|
|
54
69
|
/**
|
|
55
|
-
* Yield rows from every requested `.global()` table in batches.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
|
|
70
|
+
* Yield rows from every requested `.global()` table in batches.
|
|
71
|
+
*
|
|
72
|
+
* Keyset-paginates on the physical primary key (`id` — every `.global()` table
|
|
73
|
+
* carries it, see `frameworkColumnDdl`): `WHERE "id" > ? ORDER BY "id" LIMIT ?`,
|
|
74
|
+
* carrying the last id forward. A plain `LIMIT/OFFSET` scan would be wrong here —
|
|
75
|
+
* SQLite gives no ordering guarantee for an unordered SELECT and each page is a
|
|
76
|
+
* separate query, so pages could overlap or skip rows (and any concurrent
|
|
77
|
+
* insert/delete would shift offsets and silently drop/duplicate rows in the
|
|
78
|
+
* snapshot). Keyset paging is deterministic under concurrent writes and avoids
|
|
79
|
+
* O(n^2) OFFSET scans on large tables.
|
|
80
|
+
*
|
|
81
|
+
* Tables are provisioned first (idempotent `CREATE … IF NOT EXISTS`): `.global()`
|
|
82
|
+
* tables are created lazily on first write, so a fresh deployment — or any table
|
|
83
|
+
* never written — would otherwise abort the stream with a raw `no such table`
|
|
84
|
+
* instead of exporting it as empty.
|
|
85
|
+
*/
|
|
61
86
|
declare const exportGlobalRows: (exec: SqlCtxExec, schema: SchemaLike, args: ExportGlobalArgs) => AsyncGenerator<ExportRow, void, undefined>;
|
|
62
87
|
interface ImportGlobalArgs {
|
|
63
88
|
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
89
|
+
* Optional direct exec handle to the same D1 database the writer targets.
|
|
90
|
+
* When supplied, the conflict pre-probe issues a single
|
|
91
|
+
* `SELECT 1 FROM <table> WHERE id = ? LIMIT 1` against the row's declared
|
|
92
|
+
* table instead of falling back to `writer.get(id)`, which scans every
|
|
93
|
+
* global table looking for the id. Strongly recommended for large schemas
|
|
94
|
+
* — the writer-fallback is O(N tables) per row.
|
|
95
|
+
*/
|
|
71
96
|
exec?: D1ExecLike;
|
|
72
97
|
rows: ReadonlyArray<ExportRow>;
|
|
73
98
|
startLine?: number;
|
|
@@ -77,121 +102,128 @@ interface D1ExecLike {
|
|
|
77
102
|
all: (sql: string, parameters: ReadonlyArray<unknown>) => Promise<Record<string, unknown>[]>;
|
|
78
103
|
}
|
|
79
104
|
/**
|
|
80
|
-
* Import rows into `.global()` tables via the schema-aware D1 writer. The
|
|
81
|
-
* writer rejects unknown ids on `insert` (the writer assigns one when `_id` is
|
|
82
|
-
* absent); we pre-probe each row's `_id` so a collision is reported as a
|
|
83
|
-
* conflict instead of bubbled as a UNIQUE error. Schema-failed rows surface in
|
|
84
|
-
* `errors`; the rest land.
|
|
85
|
-
*/
|
|
105
|
+
* Import rows into `.global()` tables via the schema-aware D1 writer. The
|
|
106
|
+
* writer rejects unknown ids on `insert` (the writer assigns one when `_id` is
|
|
107
|
+
* absent); we pre-probe each row's `_id` so a collision is reported as a
|
|
108
|
+
* conflict instead of bubbled as a UNIQUE error. Schema-failed rows surface in
|
|
109
|
+
* `errors`; the rest land.
|
|
110
|
+
*/
|
|
86
111
|
declare const importGlobalRows: (writer: DatabaseWriterLike, schema: SchemaLike, args: ImportGlobalArgs) => Promise<ImportResult>;
|
|
87
112
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
results: T[];
|
|
105
|
-
success: boolean;
|
|
106
|
-
}>;
|
|
107
|
-
bind: (...values: unknown[]) => D1PreparedStatementLike;
|
|
108
|
-
first: <T = unknown>(column?: string) => Promise<T | null>;
|
|
109
|
-
raw: <T = unknown>() => Promise<T[][]>;
|
|
110
|
-
run: <T = unknown>() => Promise<{
|
|
111
|
-
meta?: Record<string, unknown>;
|
|
112
|
-
results?: T[];
|
|
113
|
-
success: boolean;
|
|
114
|
-
}>;
|
|
115
|
-
}
|
|
116
|
-
/** Thin wrapper over a `D1DatabaseSession` exposing bookmark plumbing. */
|
|
113
|
+
* Thin wrapper over a `D1DatabaseSession` exposing bookmark plumbing.
|
|
114
|
+
*
|
|
115
|
+
* `all` and `first` retry D1's expected transient failures — but only for a
|
|
116
|
+
* statement that is provably read-only. D1 produces a documented
|
|
117
|
+
* baseline of infrastructural errors even when healthy (Cloudflare's team calls
|
|
118
|
+
* a handful every few hours "not unexpected") and their guidance is to retry;
|
|
119
|
+
* a client that does not has adopted that error rate as the application's own.
|
|
120
|
+
*
|
|
121
|
+
* Nothing else on this class or on {@link D1Client} retries. `run` is the write
|
|
122
|
+
* path, and every transient D1 error is ambiguous about whether the statement
|
|
123
|
+
* applied — re-running a non-idempotent write after a lost response
|
|
124
|
+
* double-applies it. `all` is not a read path either: D1 runs
|
|
125
|
+
* `UPDATE … RETURNING` through it, so the retry is gated on the statement's
|
|
126
|
+
* leading keyword rather than on the method name. Wrap a genuinely idempotent
|
|
127
|
+
* write in {@link withD1Retry} yourself, per call.
|
|
128
|
+
*/
|
|
117
129
|
declare class D1Session {
|
|
118
130
|
private readonly session;
|
|
119
131
|
/** See {@link D1Client.stmtCache}. Scoped per session. */
|
|
120
132
|
private readonly stmtCache;
|
|
121
133
|
constructor(session: D1SessionLike);
|
|
122
134
|
prepare(sql: string): D1PreparedStatementLike;
|
|
135
|
+
/**
|
|
136
|
+
* Execute a statement. **Not retried** — `run` is the write path, and a
|
|
137
|
+
* transient D1 error does not say whether the write applied.
|
|
138
|
+
*/
|
|
123
139
|
run<T = unknown>(sql: string, ...binds: unknown[]): Promise<{
|
|
124
140
|
meta?: Record<string, unknown>;
|
|
125
141
|
results?: T[];
|
|
126
142
|
success: boolean;
|
|
127
143
|
}>;
|
|
144
|
+
/**
|
|
145
|
+
* Read all rows, retrying D1's transient failures when `sql` is a
|
|
146
|
+
* read-only statement. An `UPDATE … RETURNING` runs through here too and is
|
|
147
|
+
* left unretried.
|
|
148
|
+
*/
|
|
128
149
|
all<T = unknown>(sql: string, ...binds: unknown[]): Promise<{
|
|
129
150
|
results: T[];
|
|
130
151
|
success: boolean;
|
|
131
152
|
}>;
|
|
153
|
+
/** Read the first row, retrying under the same read-only rule as {@link all}. */
|
|
132
154
|
first<T = unknown>(sql: string, ...binds: unknown[]): Promise<T | null>;
|
|
133
155
|
/**
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
156
|
+
* Returns the most recent bookmark known to the session, or `undefined`
|
|
157
|
+
* when D1 has not issued one yet.
|
|
158
|
+
*/
|
|
137
159
|
getBookmark(): string | undefined;
|
|
138
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* A D1 handle: prepared-statement caching, Sessions-API sessions, and the
|
|
163
|
+
* drizzle-typed accessors.
|
|
164
|
+
*
|
|
165
|
+
* **Nothing on this class retries.** `prepare`, `drizzle`, `drizzleSession`,
|
|
166
|
+
* `batch` and `raw` all hand back the underlying D1 surface untouched. The
|
|
167
|
+
* automatic retry lives on {@link D1Session.all} / {@link D1Session.first} and
|
|
168
|
+
* on `retryingExec` (the seam `.global()` tables read through); wrap anything
|
|
169
|
+
* else in {@link withD1Retry} yourself, and only when it is safe to run twice.
|
|
170
|
+
*/
|
|
139
171
|
declare class D1Client {
|
|
140
172
|
private readonly db;
|
|
141
173
|
/**
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
174
|
+
* SQL string -> prepared statement. Prepared statements are reusable in
|
|
175
|
+
* D1; preparing the same SQL twice forces the worker to round-trip the
|
|
176
|
+
* statement plan. Caching is per-instance so unit-test isolation holds.
|
|
177
|
+
* Bounded to {@link STMT_CACHE_CAPACITY} via LRU eviction.
|
|
178
|
+
*/
|
|
147
179
|
private readonly stmtCache;
|
|
148
180
|
/**
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
181
|
+
* Lazily-built drizzle handle over the bare binding. Memoised so a single
|
|
182
|
+
* `D1Client` reuses the same dialect/session machinery across calls.
|
|
183
|
+
*/
|
|
152
184
|
private drizzleHandle;
|
|
153
185
|
constructor(database: D1DatabaseLike);
|
|
154
186
|
/**
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
187
|
+
* Open a Sessions-API scoped session. Pass the bookmark forwarded by
|
|
188
|
+
* the client to opt into read-your-writes consistency.
|
|
189
|
+
*
|
|
190
|
+
* With no bookmark this is the first request of a session — there is no
|
|
191
|
+
* prior write to read, so we open with the explicit `"first-unconstrained"`
|
|
192
|
+
* constraint (Cloudflare's lowest-latency default: the first read may serve
|
|
193
|
+
* from any replica). Read-your-writes for sequenced requests still flows
|
|
194
|
+
* through the forwarded bookmark; a caller needing a strongly-consistent
|
|
195
|
+
* very-first read should pass `"first-primary"` as the bookmark instead.
|
|
196
|
+
*/
|
|
165
197
|
withSession(bookmark?: string): D1Session;
|
|
166
198
|
/**
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
199
|
+
* Prepare a statement, reusing a cached one when the SQL text matches.
|
|
200
|
+
* `bind()` on a prepared statement returns a new bound statement and
|
|
201
|
+
* leaves the underlying prepared plan reusable, so cache hits are safe
|
|
202
|
+
* even when the previous caller already called `.bind(...).run()`.
|
|
203
|
+
*/
|
|
172
204
|
prepare(sql: string): D1PreparedStatementLike;
|
|
173
205
|
/**
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
206
|
+
* Drizzle handle over the bare `env.DB` binding. Used for typed queries
|
|
207
|
+
* against generated `sqliteTable` schemas; does **not** participate in the
|
|
208
|
+
* D1 Sessions API (no bookmark pinning). For bookmark-scoped reads, use
|
|
209
|
+
* {@link drizzleSession} instead.
|
|
210
|
+
*/
|
|
179
211
|
get drizzle(): DrizzleD1Database<Record<string, unknown>>;
|
|
180
212
|
/**
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
213
|
+
* Drizzle handle scoped to a D1 Sessions-API session. The bookmark, when
|
|
214
|
+
* supplied, opts into read-your-writes consistency for follow-up reads on
|
|
215
|
+
* the same session.
|
|
216
|
+
*
|
|
217
|
+
* A `D1DatabaseSession` exposes the same `prepare` / `batch` surface
|
|
218
|
+
* drizzle calls into, so a single `unknown` cast lets us hand the session to
|
|
219
|
+
* the driver.
|
|
220
|
+
*/
|
|
189
221
|
drizzleSession(bookmark?: string): DrizzleD1Database<Record<string, unknown>>;
|
|
190
222
|
/**
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
223
|
+
* Atomic batch over the drizzle d1 driver. Mirrors `db.batch([...])`
|
|
224
|
+
* exactly; exposed on the client so callers don't need to hold a drizzle
|
|
225
|
+
* handle just to run a typed batch.
|
|
226
|
+
*/
|
|
195
227
|
batch<U extends BatchItem<"sqlite">, T extends Readonly<[U, ...U[]]>>(items: T): Promise<BatchResponse<T>>;
|
|
196
228
|
/** Direct access to the underlying binding (advanced use only). */
|
|
197
229
|
get raw(): D1DatabaseLike;
|
|
@@ -205,24 +237,24 @@ interface GlobalTableInfo {
|
|
|
205
237
|
interface GlobalTablePage {
|
|
206
238
|
columns: string[];
|
|
207
239
|
/**
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
240
|
+
* Foreign-key columns (local column → referenced table) for tables that carry
|
|
241
|
+
* real SQL `REFERENCES` constraints — recovered from `PRAGMA foreign_key_list`.
|
|
242
|
+
* Schema `.global()` tables omit this (their refs come from `describeTables`);
|
|
243
|
+
* external tables (e.g. better-auth's `session`/`twoFactor`) expose it so the
|
|
244
|
+
* schema diagram can draw their global→global FK edges.
|
|
245
|
+
*/
|
|
214
246
|
refs?: Record<string, string>;
|
|
215
247
|
rows: Record<string, unknown>[];
|
|
216
248
|
total: number;
|
|
217
249
|
}
|
|
218
250
|
/**
|
|
219
|
-
* One equality constraint a facet-value click adds to the global browser's view:
|
|
220
|
-
* `column = value` (or `column IS NULL` when `value` is nullish). `column` is a
|
|
221
|
-
* displayed column name, validated against the table's columns and mapped to its
|
|
222
|
-
* physical column (`_id` → `id`) before it is quoted; `value` is the **raw stored
|
|
223
|
-
* value** the facet returned (a SQLite scalar), bound as a parameter and never
|
|
224
|
-
* interpolated. AND-combined with the other clauses.
|
|
225
|
-
*/
|
|
251
|
+
* One equality constraint a facet-value click adds to the global browser's view:
|
|
252
|
+
* `column = value` (or `column IS NULL` when `value` is nullish). `column` is a
|
|
253
|
+
* displayed column name, validated against the table's columns and mapped to its
|
|
254
|
+
* physical column (`_id` → `id`) before it is quoted; `value` is the **raw stored
|
|
255
|
+
* value** the facet returned (a SQLite scalar), bound as a parameter and never
|
|
256
|
+
* interpolated. AND-combined with the other clauses.
|
|
257
|
+
*/
|
|
226
258
|
interface GlobalFilterClause {
|
|
227
259
|
column: string;
|
|
228
260
|
value: unknown;
|
|
@@ -234,13 +266,13 @@ interface ReadGlobalTablePageOptions {
|
|
|
234
266
|
table: string;
|
|
235
267
|
}
|
|
236
268
|
/**
|
|
237
|
-
* Options for {@link facetGlobalColumn} — the read-only "what values does this
|
|
238
|
-
* column hold?" summary for the global (D1) browser. `column` is the displayed
|
|
239
|
-
* column to group by (validated and mapped to its physical column, never
|
|
240
|
-
* interpolated); `filters` mirrors {@link ReadGlobalTablePageOptions}'s eq
|
|
241
|
-
* constraints so the facet reflects the **active view** (the same rows the
|
|
242
|
-
* browser is previewing); `limit` caps the distinct values returned (clamped).
|
|
243
|
-
*/
|
|
269
|
+
* Options for {@link facetGlobalColumn} — the read-only "what values does this
|
|
270
|
+
* column hold?" summary for the global (D1) browser. `column` is the displayed
|
|
271
|
+
* column to group by (validated and mapped to its physical column, never
|
|
272
|
+
* interpolated); `filters` mirrors {@link ReadGlobalTablePageOptions}'s eq
|
|
273
|
+
* constraints so the facet reflects the **active view** (the same rows the
|
|
274
|
+
* browser is previewing); `limit` caps the distinct values returned (clamped).
|
|
275
|
+
*/
|
|
244
276
|
interface FacetGlobalColumnOptions {
|
|
245
277
|
column: string;
|
|
246
278
|
filters?: GlobalFilterClause[];
|
|
@@ -253,43 +285,43 @@ interface GlobalFacetValue {
|
|
|
253
285
|
value: unknown;
|
|
254
286
|
}
|
|
255
287
|
/**
|
|
256
|
-
* Payload of a {@link facetGlobalColumn} call: the top-N distinct `values` (each
|
|
257
|
-
* with a `count`) ordered by frequency, plus `truncated` — `true` when more
|
|
258
|
-
* distinct values existed beyond the cap, so the UI can say so rather than imply
|
|
259
|
-
* the list is exhaustive. Mirrors the shard browser's `FacetColumnResult`.
|
|
260
|
-
*/
|
|
288
|
+
* Payload of a {@link facetGlobalColumn} call: the top-N distinct `values` (each
|
|
289
|
+
* with a `count`) ordered by frequency, plus `truncated` — `true` when more
|
|
290
|
+
* distinct values existed beyond the cap, so the UI can say so rather than imply
|
|
291
|
+
* the list is exhaustive. Mirrors the shard browser's `FacetColumnResult`.
|
|
292
|
+
*/
|
|
261
293
|
interface GlobalFacetResult {
|
|
262
294
|
truncated: boolean;
|
|
263
295
|
values: GlobalFacetValue[];
|
|
264
296
|
}
|
|
265
297
|
/**
|
|
266
|
-
* List every browsable D1 table with its row count, ordered by name. Surfaces
|
|
267
|
-
* both the schema's `.global()` tables (provisioned first) and external tables
|
|
268
|
-
* (auth, etc.); internal/companion tables are excluded.
|
|
269
|
-
*/
|
|
298
|
+
* List every browsable D1 table with its row count, ordered by name. Surfaces
|
|
299
|
+
* both the schema's `.global()` tables (provisioned first) and external tables
|
|
300
|
+
* (auth, etc.); internal/companion tables are excluded.
|
|
301
|
+
*/
|
|
270
302
|
declare const listGlobalTables: (exec: SqlCtxExec, schema: SchemaLike) => Promise<GlobalTableInfo[]>;
|
|
271
303
|
/**
|
|
272
|
-
* Read a page of rows from one D1 table. The table is validated against the live
|
|
273
|
-
* browsable-table list before its name is interpolated, so this can't be coerced
|
|
274
|
-
* into reading an internal table or injecting SQL. `limit` is clamped to
|
|
275
|
-
* `[1, 500]`; `offset` floors at `0`. `filters` AND-narrows the page to rows
|
|
276
|
-
* matching each `column = value` eq constraint (a facet-value drill-down), bound
|
|
277
|
-
* through {@link buildEqPredicate} so they never inject SQL.
|
|
278
|
-
*/
|
|
304
|
+
* Read a page of rows from one D1 table. The table is validated against the live
|
|
305
|
+
* browsable-table list before its name is interpolated, so this can't be coerced
|
|
306
|
+
* into reading an internal table or injecting SQL. `limit` is clamped to
|
|
307
|
+
* `[1, 500]`; `offset` floors at `0`. `filters` AND-narrows the page to rows
|
|
308
|
+
* matching each `column = value` eq constraint (a facet-value drill-down), bound
|
|
309
|
+
* through {@link buildEqPredicate} so they never inject SQL.
|
|
310
|
+
*/
|
|
279
311
|
declare const readGlobalTablePage: (exec: SqlCtxExec, schema: SchemaLike, options: ReadGlobalTablePageOptions) => Promise<GlobalTablePage>;
|
|
280
312
|
/**
|
|
281
|
-
* Summarise the distinct values of one displayed column over the **active view**
|
|
282
|
-
* (the same eq `filters` the global browser is previewing) — the D1 twin of the
|
|
283
|
-
* shard browser's `facetColumn`. Read-only: a `SELECT col AS value, COUNT(*) AS
|
|
284
|
-
* count … GROUP BY col ORDER BY count DESC LIMIT N+1`, with the column validated
|
|
285
|
-
* against the table's displayed columns (typed 404 if unknown), mapped to its
|
|
286
|
-
* physical column, and quoted — never interpolated from caller input. The extra
|
|
287
|
-
* over-fetched row is dropped and surfaced as `truncated`. A sensitive column on
|
|
288
|
-
* an external (non-schema) table is never grouped — it collapses to a single
|
|
289
|
-
* redacted `•••` bucket — mirroring the page browser's value redaction so the
|
|
290
|
-
* facet can't leak credentials. The returned `value` is the raw stored scalar, so
|
|
291
|
-
* a click feeds it straight back as an eq filter.
|
|
292
|
-
*/
|
|
313
|
+
* Summarise the distinct values of one displayed column over the **active view**
|
|
314
|
+
* (the same eq `filters` the global browser is previewing) — the D1 twin of the
|
|
315
|
+
* shard browser's `facetColumn`. Read-only: a `SELECT col AS value, COUNT(*) AS
|
|
316
|
+
* count … GROUP BY col ORDER BY count DESC LIMIT N+1`, with the column validated
|
|
317
|
+
* against the table's displayed columns (typed 404 if unknown), mapped to its
|
|
318
|
+
* physical column, and quoted — never interpolated from caller input. The extra
|
|
319
|
+
* over-fetched row is dropped and surfaced as `truncated`. A sensitive column on
|
|
320
|
+
* an external (non-schema) table is never grouped — it collapses to a single
|
|
321
|
+
* redacted `•••` bucket — mirroring the page browser's value redaction so the
|
|
322
|
+
* facet can't leak credentials. The returned `value` is the raw stored scalar, so
|
|
323
|
+
* a click feeds it straight back as an eq filter.
|
|
324
|
+
*/
|
|
293
325
|
declare const facetGlobalColumn: (exec: SqlCtxExec, schema: SchemaLike, options: FacetGlobalColumnOptions) => Promise<GlobalFacetResult>;
|
|
294
326
|
interface Migration {
|
|
295
327
|
/** Human-readable name, e.g. `001_init` (used in logs). */
|
|
@@ -310,30 +342,109 @@ interface MigrationRunnerResult {
|
|
|
310
342
|
}[];
|
|
311
343
|
}
|
|
312
344
|
/**
|
|
313
|
-
* Sequentially applies pending migrations against a D1 database via the
|
|
314
|
-
* drizzle-orm/d1 driver. Each migration is hashed (SHA-256 over its SQL
|
|
315
|
-
* text); the hash is stored in `__drizzle_migrations`, so re-applying the
|
|
316
|
-
* same SQL under a different `version` is rejected and identical migrations
|
|
317
|
-
* are skipped idempotently.
|
|
318
|
-
*/
|
|
345
|
+
* Sequentially applies pending migrations against a D1 database via the
|
|
346
|
+
* drizzle-orm/d1 driver. Each migration is hashed (SHA-256 over its SQL
|
|
347
|
+
* text); the hash is stored in `__drizzle_migrations`, so re-applying the
|
|
348
|
+
* same SQL under a different `version` is rejected and identical migrations
|
|
349
|
+
* are skipped idempotently.
|
|
350
|
+
*/
|
|
319
351
|
declare class MigrationRunner {
|
|
320
352
|
private readonly client;
|
|
321
353
|
private readonly migrations;
|
|
322
354
|
/**
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
355
|
+
* Accepts either a {@link D1Client} (preferred — gets typed batches +
|
|
356
|
+
* drizzle handle for free) or a raw `D1DatabaseLike` binding (wrapped on
|
|
357
|
+
* the caller's behalf so existing `@lunora/cli` callers keep working).
|
|
358
|
+
*/
|
|
327
359
|
constructor(database: D1Client | D1DatabaseLike, migrations: Migration[]);
|
|
328
360
|
run(): Promise<MigrationRunnerResult>;
|
|
329
361
|
private applyOne;
|
|
330
362
|
private assertUniqueVersions;
|
|
331
363
|
private assertUniqueSql;
|
|
332
364
|
}
|
|
365
|
+
/** Tuning for {@link withD1Retry}. */
|
|
366
|
+
interface D1RetryOptions {
|
|
367
|
+
/** Total attempts including the first. Default 3. Must be >= 1. */
|
|
368
|
+
attempts?: number;
|
|
369
|
+
/**
|
|
370
|
+
* Total budget across every attempt and backoff, in ms. An attempt still
|
|
371
|
+
* running when the budget expires is abandoned, and a backoff never sleeps
|
|
372
|
+
* past it.
|
|
373
|
+
*
|
|
374
|
+
* `timeoutMs` bounds one attempt; this bounds the operation.
|
|
375
|
+
*/
|
|
376
|
+
deadlineMs?: number;
|
|
377
|
+
/** Sleep implementation. Injected so tests need no timers. */
|
|
378
|
+
sleep?: (ms: number) => Promise<void>;
|
|
379
|
+
/**
|
|
380
|
+
* Abandon a single attempt that has not settled within this many ms, and
|
|
381
|
+
* treat it as a transient failure.
|
|
382
|
+
*
|
|
383
|
+
* There is no default — a legitimate analytical query and a stalled one
|
|
384
|
+
* look identical from outside, so the value has to come from what your
|
|
385
|
+
* workload actually needs. Unset, {@link SLOW_FAILURE_MS} stops a slow
|
|
386
|
+
* failure from being retried rather than guessing a bound for you.
|
|
387
|
+
*
|
|
388
|
+
* Only ever applied to operations that are safe to abandon. It does not
|
|
389
|
+
* cancel the underlying D1 call.
|
|
390
|
+
*/
|
|
391
|
+
timeoutMs?: number;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* True when `error` looks like one of D1's expected transient failures.
|
|
395
|
+
*
|
|
396
|
+
* Deliberately conservative: an unrecognised error is treated as **permanent**
|
|
397
|
+
* and surfaces immediately. Retrying a genuine bug — a syntax error, a
|
|
398
|
+
* constraint violation — turns one fast failure into three slow ones and
|
|
399
|
+
* hides the cause.
|
|
400
|
+
* @experimental
|
|
401
|
+
*/
|
|
402
|
+
declare const isTransientD1Error: (error: unknown) => boolean;
|
|
403
|
+
/**
|
|
404
|
+
* Thrown when an attempt is abandoned for exceeding
|
|
405
|
+
* {@link D1RetryOptions.timeoutMs}.
|
|
406
|
+
*
|
|
407
|
+
* Its own class so a caller can tell "D1 hung" apart from "D1 returned an
|
|
408
|
+
* error" — they have different remedies, and collapsing them hides which one
|
|
409
|
+
* is happening.
|
|
410
|
+
* @experimental
|
|
411
|
+
*/
|
|
412
|
+
declare class D1TimeoutError extends Error {
|
|
413
|
+
/** Milliseconds waited before the attempt was abandoned. */
|
|
414
|
+
readonly timeoutMs: number;
|
|
415
|
+
constructor(timeoutMs: number);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Run `operation`, retrying D1's transient failures with exponential backoff
|
|
419
|
+
* and jitter.
|
|
420
|
+
*
|
|
421
|
+
* **Only wrap operations that are safe to run more than once.** Reads always
|
|
422
|
+
* are. A write is only if it is idempotent — an upsert keyed on a primary key,
|
|
423
|
+
* a delete by id, an `INSERT OR IGNORE`. A bare `INSERT` or a relative
|
|
424
|
+
* `UPDATE` is not, and re-running one after a lost response double-applies it.
|
|
425
|
+
* @experimental
|
|
426
|
+
*/
|
|
427
|
+
declare const withD1Retry: <T>(operation: () => Promise<T>, options?: D1RetryOptions) => Promise<T>;
|
|
428
|
+
/**
|
|
429
|
+
* Wrap a `.global()` exec so its **read-only** statements retry D1's transient
|
|
430
|
+
* failures.
|
|
431
|
+
*
|
|
432
|
+
* This is where the retry has to live to reach an application: `.global()`
|
|
433
|
+
* tables run every read through the exec codegen builds over the raw D1
|
|
434
|
+
* binding, not through a `D1Client`. `run` and `batch` are passed straight
|
|
435
|
+
* through — they are the write path, and a transient D1 error does not say
|
|
436
|
+
* whether the write applied.
|
|
437
|
+
*
|
|
438
|
+
* `all` is not the read path either. It only retries when
|
|
439
|
+
* {@link isReadOnlyD1Sql} proves the statement is one, because `UPDATE …
|
|
440
|
+
* RETURNING` runs through `all` too.
|
|
441
|
+
* @experimental
|
|
442
|
+
*/
|
|
443
|
+
declare const retryingExec: (exec: SqlCtxExec, options?: D1RetryOptions) => SqlCtxExec;
|
|
333
444
|
/**
|
|
334
|
-
* The canonical SQLite dialect: column affinities, the shared SQLite value
|
|
335
|
-
* codec, `RETURNING` support (both D1 and `node:sqlite`), and `sqlite_master`
|
|
336
|
-
* table probing. The rest of the per-statement shaping is drizzle's.
|
|
337
|
-
*/
|
|
445
|
+
* The canonical SQLite dialect: column affinities, the shared SQLite value
|
|
446
|
+
* codec, `RETURNING` support (both D1 and `node:sqlite`), and `sqlite_master`
|
|
447
|
+
* table probing. The rest of the per-statement shaping is drizzle's.
|
|
448
|
+
*/
|
|
338
449
|
declare const sqliteDialect: SqlDialect;
|
|
339
|
-
export { D1Client, type D1ContextDatabaseOptions as D1CtxDbOptions, type
|
|
450
|
+
export { D1Client, type D1ContextDatabaseOptions as D1CtxDbOptions, type D1RetryOptions, D1Session, D1TimeoutError, type ExportGlobalArgs, type FacetGlobalColumnOptions, type ExportRow as GlobalExportRow, type GlobalFacetResult, type GlobalFacetValue, type GlobalFilterClause, type ImportError as GlobalImportError, type ImportResult as GlobalImportResult, type GlobalTableInfo, type GlobalTablePage, type ImportGlobalArgs, type Migration, MigrationRunner, type MigrationRunnerResult, type ReadGlobalTablePageOptions, backfillD1SearchIndexes, createD1ContextDatabase as createD1CtxDb, exportGlobalRows, facetGlobalColumn, importGlobalRows, isTransientD1Error, listGlobalTables, readD1CdcChanges, readGlobalTablePage, retryingExec, runD1AggregateMigrations, runD1CdcMigration, runD1GlobalTableMigrations, runD1RankMigrations, runD1SearchMigrations, selectGlobalTables, sqliteDialect, trimD1CdcChanges, withD1Retry };
|