@lunora/d1 1.0.0-alpha.79 → 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/dist/index.d.mts +117 -2
- package/dist/index.d.ts +117 -2
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/D1Client-CzS0Qfa6.mjs +1 -0
- package/dist/packem_shared/D1TimeoutError-D4YUqZPk.mjs +1 -0
- package/dist/packem_shared/{MigrationRunner-IF213ALw.mjs → MigrationRunner-SadkIbU5.mjs} +1 -1
- package/package.json +4 -4
- package/dist/packem_shared/D1Client-IzEOlt9I.mjs +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -109,22 +109,48 @@ interface D1ExecLike {
|
|
|
109
109
|
* `errors`; the rest land.
|
|
110
110
|
*/
|
|
111
111
|
declare const importGlobalRows: (writer: DatabaseWriterLike, schema: SchemaLike, args: ImportGlobalArgs) => Promise<ImportResult>;
|
|
112
|
-
/**
|
|
112
|
+
/**
|
|
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
|
+
*/
|
|
113
129
|
declare class D1Session {
|
|
114
130
|
private readonly session;
|
|
115
131
|
/** See {@link D1Client.stmtCache}. Scoped per session. */
|
|
116
132
|
private readonly stmtCache;
|
|
117
133
|
constructor(session: D1SessionLike);
|
|
118
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
|
+
*/
|
|
119
139
|
run<T = unknown>(sql: string, ...binds: unknown[]): Promise<{
|
|
120
140
|
meta?: Record<string, unknown>;
|
|
121
141
|
results?: T[];
|
|
122
142
|
success: boolean;
|
|
123
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
|
+
*/
|
|
124
149
|
all<T = unknown>(sql: string, ...binds: unknown[]): Promise<{
|
|
125
150
|
results: T[];
|
|
126
151
|
success: boolean;
|
|
127
152
|
}>;
|
|
153
|
+
/** Read the first row, retrying under the same read-only rule as {@link all}. */
|
|
128
154
|
first<T = unknown>(sql: string, ...binds: unknown[]): Promise<T | null>;
|
|
129
155
|
/**
|
|
130
156
|
* Returns the most recent bookmark known to the session, or `undefined`
|
|
@@ -132,6 +158,16 @@ declare class D1Session {
|
|
|
132
158
|
*/
|
|
133
159
|
getBookmark(): string | undefined;
|
|
134
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
|
+
*/
|
|
135
171
|
declare class D1Client {
|
|
136
172
|
private readonly db;
|
|
137
173
|
/**
|
|
@@ -326,10 +362,89 @@ declare class MigrationRunner {
|
|
|
326
362
|
private assertUniqueVersions;
|
|
327
363
|
private assertUniqueSql;
|
|
328
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;
|
|
329
444
|
/**
|
|
330
445
|
* The canonical SQLite dialect: column affinities, the shared SQLite value
|
|
331
446
|
* codec, `RETURNING` support (both D1 and `node:sqlite`), and `sqlite_master`
|
|
332
447
|
* table probing. The rest of the per-statement shaping is drizzle's.
|
|
333
448
|
*/
|
|
334
449
|
declare const sqliteDialect: SqlDialect;
|
|
335
|
-
export { D1Client, type D1ContextDatabaseOptions as D1CtxDbOptions, D1Session, 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, listGlobalTables, readD1CdcChanges, readGlobalTablePage, runD1AggregateMigrations, runD1CdcMigration, runD1GlobalTableMigrations, runD1RankMigrations, runD1SearchMigrations, selectGlobalTables, sqliteDialect, trimD1CdcChanges };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -109,22 +109,48 @@ interface D1ExecLike {
|
|
|
109
109
|
* `errors`; the rest land.
|
|
110
110
|
*/
|
|
111
111
|
declare const importGlobalRows: (writer: DatabaseWriterLike, schema: SchemaLike, args: ImportGlobalArgs) => Promise<ImportResult>;
|
|
112
|
-
/**
|
|
112
|
+
/**
|
|
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
|
+
*/
|
|
113
129
|
declare class D1Session {
|
|
114
130
|
private readonly session;
|
|
115
131
|
/** See {@link D1Client.stmtCache}. Scoped per session. */
|
|
116
132
|
private readonly stmtCache;
|
|
117
133
|
constructor(session: D1SessionLike);
|
|
118
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
|
+
*/
|
|
119
139
|
run<T = unknown>(sql: string, ...binds: unknown[]): Promise<{
|
|
120
140
|
meta?: Record<string, unknown>;
|
|
121
141
|
results?: T[];
|
|
122
142
|
success: boolean;
|
|
123
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
|
+
*/
|
|
124
149
|
all<T = unknown>(sql: string, ...binds: unknown[]): Promise<{
|
|
125
150
|
results: T[];
|
|
126
151
|
success: boolean;
|
|
127
152
|
}>;
|
|
153
|
+
/** Read the first row, retrying under the same read-only rule as {@link all}. */
|
|
128
154
|
first<T = unknown>(sql: string, ...binds: unknown[]): Promise<T | null>;
|
|
129
155
|
/**
|
|
130
156
|
* Returns the most recent bookmark known to the session, or `undefined`
|
|
@@ -132,6 +158,16 @@ declare class D1Session {
|
|
|
132
158
|
*/
|
|
133
159
|
getBookmark(): string | undefined;
|
|
134
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
|
+
*/
|
|
135
171
|
declare class D1Client {
|
|
136
172
|
private readonly db;
|
|
137
173
|
/**
|
|
@@ -326,10 +362,89 @@ declare class MigrationRunner {
|
|
|
326
362
|
private assertUniqueVersions;
|
|
327
363
|
private assertUniqueSql;
|
|
328
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;
|
|
329
444
|
/**
|
|
330
445
|
* The canonical SQLite dialect: column affinities, the shared SQLite value
|
|
331
446
|
* codec, `RETURNING` support (both D1 and `node:sqlite`), and `sqlite_master`
|
|
332
447
|
* table probing. The rest of the per-statement shaping is drizzle's.
|
|
333
448
|
*/
|
|
334
449
|
declare const sqliteDialect: SqlDialect;
|
|
335
|
-
export { D1Client, type D1ContextDatabaseOptions as D1CtxDbOptions, D1Session, 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, listGlobalTables, readD1CdcChanges, readGlobalTablePage, runD1AggregateMigrations, runD1CdcMigration, runD1GlobalTableMigrations, runD1RankMigrations, runD1SearchMigrations, selectGlobalTables, sqliteDialect, trimD1CdcChanges };
|
|
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{exportGlobalRows as
|
|
1
|
+
import{exportGlobalRows as o,importGlobalRows as a,selectGlobalTables as t}from"./packem_shared/exportGlobalRows-Cc8cXltm.mjs";import{D1Client as i,D1Session as n}from"./packem_shared/D1Client-CzS0Qfa6.mjs";import{backfillD1SearchIndexes as D,createD1CtxDb as b,readD1CdcChanges as g,runD1AggregateMigrations as x,runD1CdcMigration as c,runD1GlobalTableMigrations as m,runD1RankMigrations as f,runD1SearchMigrations as p,trimD1CdcChanges as u}from"./packem_shared/backfillD1SearchIndexes-By6bbCLe.mjs";import{facetGlobalColumn as d,listGlobalTables as G,readGlobalTablePage as M}from"./packem_shared/facetGlobalColumn-D1npxpva.mjs";import{MigrationRunner as h}from"./packem_shared/MigrationRunner-SadkIbU5.mjs";import{D1TimeoutError as S,isTransientD1Error as w,retryingExec as E,withD1Retry as k}from"./packem_shared/D1TimeoutError-D4YUqZPk.mjs";import{default as y}from"./packem_shared/sqliteDialect-DZoYJOa-.mjs";import{createSqlCtxDb as I}from"@lunora/sql-store";export{i as D1Client,n as D1Session,S as D1TimeoutError,h as MigrationRunner,D as backfillD1SearchIndexes,b as createD1CtxDb,I as createSqlCtxDb,o as exportGlobalRows,d as facetGlobalColumn,a as importGlobalRows,w as isTransientD1Error,G as listGlobalTables,g as readD1CdcChanges,M as readGlobalTablePage,E as retryingExec,x as runD1AggregateMigrations,c as runD1CdcMigration,m as runD1GlobalTableMigrations,f as runD1RankMigrations,p as runD1SearchMigrations,t as selectGlobalTables,y as sqliteDialect,u as trimD1CdcChanges,k as withD1Retry};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{drizzle as i}from"drizzle-orm/d1";import{isReadOnlyD1Sql as o,withD1Retry as a}from"./D1TimeoutError-D4YUqZPk.mjs";const c=(s,e)=>{if(s.size<e)return;const t=s.keys().next().value;t!==void 0&&s.delete(t)},l=256,h=(s,e,t)=>{const r=s.get(t);if(r)return s.delete(t),s.set(t,r),r;const n=e(t);return c(s,l),s.set(t,n),n},d="first-unconstrained";class u{session;stmtCache=new Map;constructor(e){this.session=e}prepare(e){return h(this.stmtCache,t=>this.session.prepare(t),e)}async run(e,...t){return this.prepare(e).bind(...t).run()}async all(e,...t){const r=async()=>this.prepare(e).bind(...t).all();return o(e)?a(r):r()}async first(e,...t){const r=async()=>this.prepare(e).bind(...t).first();return o(e)?a(r):r()}getBookmark(){return this.session.getBookmark()??void 0}}class b{db;stmtCache=new Map;drizzleHandle;constructor(e){this.db=e}withSession(e){const t=this.db.withSession(e??d);return new u(t)}prepare(e){return h(this.stmtCache,t=>this.db.prepare(t),e)}get drizzle(){return this.drizzleHandle?this.drizzleHandle:(this.drizzleHandle=i(this.db,{logger:!1}),this.drizzleHandle)}drizzleSession(e){const t=this.db.withSession(e??d);return i(t,{logger:!1})}async batch(e){return this.drizzle.batch(e)}get raw(){return this.db}}export{b as D1Client,u as D1Session};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as h,unreachable as f}from"@lunora/errors";const y=["storage operation exceeded timeout","network connection lost","internal error while starting up d1 db storage","exceeded its memory limit","caused object to be reset"],M=3,S=50,b=1e3,D=2e3,p=/^[\s(]*(?:select|pragma|explain)\b/iu,_=t=>p.test(t),A=t=>t instanceof Error?`${t.name}: ${t.message}`.toLowerCase():typeof t=="string"?t.toLowerCase():"",I=t=>{const e=A(t);return e.length===0?!1:y.some(n=>e.includes(n))},N=t=>{const e=Math.min(b,S*2**(t-1));return Math.round(Math.random()*e)},L=async t=>new Promise(e=>{setTimeout(e,t)});class l extends Error{timeoutMs;constructor(e){super(`@lunora/d1: operation exceeded its ${String(e)}ms timeout and was abandoned (the underlying D1 call may still be running)`),this.name="D1TimeoutError",this.timeoutMs=e}}const R=async(t,e)=>{let n;try{return await Promise.race([t(),new Promise((o,r)=>{n=setTimeout(()=>{r(new l(e))},e)})])}finally{n!==void 0&&clearTimeout(n)}},x=async(t,e={})=>{const n=e.attempts??M;if(!Number.isInteger(n)||n<1)throw new h("BAD_REQUEST","@lunora/d1: `attempts` must be an integer >= 1");const o=e.sleep??L,{deadlineMs:r,timeoutMs:i}=e,d=Date.now(),w=r===void 0&&i===void 0,c=()=>r===void 0?Number.POSITIVE_INFINITY:r-(Date.now()-d);for(let s=1;s<=n;s+=1){const u=Math.min(i??Number.POSITIVE_INFINITY,Math.max(c(),0)),E=Date.now();try{return await(Number.isFinite(u)?R(t,u):t())}catch(a){const g=a instanceof l||I(a),T=w&&Date.now()-E>=D,m=c();if(s===n||!g||T||m<=0)throw a;await o(Math.min(N(s),m))}}return f("@lunora/d1: retry loop exited without returning or throwing")},P=(t,e)=>({...t,all:async(n,o)=>_(n)?x(async()=>t.all(n,o),e):t.all(n,o)});export{l as D1TimeoutError,_ as isReadOnlyD1Sql,I as isTransientD1Error,P as retryingExec,x as withD1Retry};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{LunoraError as f}from"@lunora/errors";import{sql as h}from"drizzle-orm";import{D1Client as m}from"./D1Client-
|
|
1
|
+
import{LunoraError as f}from"@lunora/errors";import{sql as h}from"drizzle-orm";import{D1Client as m}from"./D1Client-CzS0Qfa6.mjs";const u="__drizzle_migrations",p=`CREATE TABLE IF NOT EXISTS ${u} (id INTEGER PRIMARY KEY AUTOINCREMENT, hash TEXT NOT NULL UNIQUE, created_at NUMERIC)`,d=/\s/u,E=/^[0-9a-f]{64}$/u,S=new RegExp(String.raw`UNIQUE constraint failed:\s*${u}\.hash`,"iu"),w=c=>{const n=c.sql;let e=!1,i=!1,o=!1,a=!1,r;for(let t=0;t<n.length;t+=1){const s=n[t],l=n[t+1];if(o){s===`
|
|
2
2
|
`&&(o=!1);continue}if(a){s==="*"&&l==="/"&&(a=!1,t+=1);continue}if(e){s==="'"&&(l==="'"?t+=1:e=!1);continue}if(i){s==='"'&&(l==='"'?t+=1:i=!1);continue}if(s==="-"&&l==="-"){o=!0,t+=1;continue}if(s==="/"&&l==="*"){a=!0,t+=1;continue}if(r!==void 0){if(s!==void 0&&!d.test(s))throw new f("INTERNAL",`Migration "${c.name}" (v${String(c.version)}) contains more than one SQL statement. Split it into separate migrations — batch() runs them atomically.`);continue}if(s==="'"){e=!0;continue}if(s==='"'){i=!0;continue}s===";"&&(r=t)}return r},g=async c=>{const n=new TextEncoder().encode(c),e=await crypto.subtle.digest("SHA-256",n);return[...new Uint8Array(e)].map(i=>i.toString(16).padStart(2,"0")).join("")};class A{client;migrations;constructor(n,e){this.client=n instanceof m?n:new m(n),this.migrations=[...e].toSorted((i,o)=>i.version-o.version),this.assertUniqueVersions(),this.assertUniqueSql()}async run(){await this.client.drizzle.run(h.raw(p));const n=await this.client.drizzle.all(h.raw(`SELECT hash FROM ${u}`)),e=new Set(n.map(r=>r.hash)),i=[],o=[],a=await Promise.all(this.migrations.map(async r=>g(r.sql)));for(const[r,t]of this.migrations.entries()){const s=a[r];if(e.has(s)){o.push({name:t.name,version:t.version});continue}await this.applyOne(t,s)?i.push({name:t.name,version:t.version}):o.push({name:t.name,version:t.version})}return{applied:i,skipped:o}}async applyOne(n,e){const i=w(n),o=(i===void 0?n.sql:n.sql.slice(0,i)).trim();if(!E.test(e))throw new f("INTERNAL",`migration "${n.name}" produced a non-hex hash; refusing to inline into SQL`);const a=`INSERT INTO ${u} (hash, created_at) VALUES ('${e}', ${String(Date.now())})`,r=[this.client.drizzle.run(h.raw(o)),this.client.drizzle.run(h.raw(a))];try{await this.client.batch(r)}catch(t){if(S.test(t instanceof Error?t.message:String(t)))return!1;throw t}return!0}assertUniqueVersions(){const n=new Set;for(const e of this.migrations){if(n.has(e.version))throw new f("INTERNAL",`Duplicate migration version ${String(e.version)}`);n.add(e.version)}}assertUniqueSql(){const n=new Map;for(const e of this.migrations){const i=n.get(e.sql);if(i!==void 0)throw new f("INTERNAL",`Migrations ${String(i)} and ${String(e.version)} have identical SQL — bump the content, not just the version.`);n.set(e.sql,e.version)}}}export{A as MigrationRunner};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/d1",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.80",
|
|
4
4
|
"description": "D1 adapter for Lunora .global() tables, wrapping the Sessions API for read-your-writes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@lunora/errors": "1.0.0-alpha.22",
|
|
54
|
-
"@lunora/platform": "1.0.0-alpha.
|
|
55
|
-
"@lunora/shard-engine": "1.0.0-alpha.
|
|
56
|
-
"@lunora/sql-store": "1.0.0-alpha.
|
|
54
|
+
"@lunora/platform": "1.0.0-alpha.13",
|
|
55
|
+
"@lunora/shard-engine": "1.0.0-alpha.30",
|
|
56
|
+
"@lunora/sql-store": "1.0.0-alpha.82",
|
|
57
57
|
"drizzle-orm": "^0.45.2"
|
|
58
58
|
},
|
|
59
59
|
"engines": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{drizzle as i}from"drizzle-orm/d1";const a=(s,e)=>{if(s.size<e)return;const t=s.keys().next().value;t!==void 0&&s.delete(t)},h=256,d=(s,e,t)=>{const r=s.get(t);if(r)return s.delete(t),s.set(t,r),r;const n=e(t);return a(s,h),s.set(t,n),n},o="first-unconstrained";class l{session;stmtCache=new Map;constructor(e){this.session=e}prepare(e){return d(this.stmtCache,t=>this.session.prepare(t),e)}async run(e,...t){return this.prepare(e).bind(...t).run()}async all(e,...t){return this.prepare(e).bind(...t).all()}async first(e,...t){return this.prepare(e).bind(...t).first()}getBookmark(){return this.session.getBookmark()??void 0}}class u{db;stmtCache=new Map;drizzleHandle;constructor(e){this.db=e}withSession(e){const t=this.db.withSession(e??o);return new l(t)}prepare(e){return d(this.stmtCache,t=>this.db.prepare(t),e)}get drizzle(){return this.drizzleHandle?this.drizzleHandle:(this.drizzleHandle=i(this.db,{logger:!1}),this.drizzleHandle)}drizzleSession(e){const t=this.db.withSession(e??o);return i(t,{logger:!1})}async batch(e){return this.drizzle.batch(e)}get raw(){return this.db}}export{u as D1Client,l as D1Session};
|