@rexezuge/d1 1.0.8 → 1.1.0
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/BaseDAO.d.ts +8 -5
- package/dist/BaseDAO.d.ts.map +1 -1
- package/dist/BaseDAO.js +8 -5
- package/dist/BaseDAO.js.map +1 -1
- package/package.json +3 -3
package/dist/BaseDAO.d.ts
CHANGED
|
@@ -29,12 +29,15 @@ declare abstract class BaseDAO {
|
|
|
29
29
|
*/
|
|
30
30
|
protected run(sql: string, bindings?: unknown[]): Promise<D1Result>;
|
|
31
31
|
/**
|
|
32
|
-
* Read one row.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* Read one row. A `null` result is "no row", a success, not an error; only a
|
|
33
|
+
* rejected statement is retried and normalized.
|
|
34
|
+
*
|
|
35
|
+
* `null` rather than `undefined` because that is what the platform says
|
|
36
|
+
* (`D1Result.first<T>()` returns `T | null`) and an adapter should hand back
|
|
37
|
+
* what it was given. It also makes this layer agree with `KvCache`, which has
|
|
38
|
+
* always returned `null` for a miss, so one convention covers both.
|
|
36
39
|
*/
|
|
37
|
-
protected first<T>(sql: string, bindings?: unknown[]): Promise<T |
|
|
40
|
+
protected first<T>(sql: string, bindings?: unknown[]): Promise<T | null>;
|
|
38
41
|
/** A list read. `.all()` returning `[]` is a success like `first()`'s null. */
|
|
39
42
|
protected all<T>(sql: string, bindings?: unknown[]): Promise<T[]>;
|
|
40
43
|
/** A scalar count from a `SELECT count(*)` row; 0 when absent. */
|
package/dist/BaseDAO.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseDAO.d.ts","sourceRoot":"","sources":["../src/BaseDAO.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAoB,MAAM,WAAW,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAU5E;;;;;;;;;;;;;;;GAeG;AACH,uBAAe,OAAO;IACE,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,WAAW;IAA9D,SAAS,aAAgC,QAAQ,EAAE,WAAW;IAE9D,2GAA2G;IAC3G,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,OAAO,EAAO,GAAG,mBAAmB;IAI/E;;;;;OAKG;cACa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAI7E
|
|
1
|
+
{"version":3,"file":"BaseDAO.d.ts","sourceRoot":"","sources":["../src/BaseDAO.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAoB,MAAM,WAAW,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAU5E;;;;;;;;;;;;;;;GAeG;AACH,uBAAe,OAAO;IACE,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,WAAW;IAA9D,SAAS,aAAgC,QAAQ,EAAE,WAAW;IAE9D,2GAA2G;IAC3G,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,OAAO,EAAO,GAAG,mBAAmB;IAI/E;;;;;OAKG;cACa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAI7E;;;;;;;;OAQG;cACa,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAQlF,+EAA+E;cAC/D,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAQ3E,kEAAkE;cAClD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAK7E;;;;OAIG;cACa,KAAK,CAAC,UAAU,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAa7E;;;;;OAKG;qBACoB,eAAe,CACpC,EAAE,EAAE,WAAW,EACf,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC;IAsBlB,8EAA8E;IAC9E,SAAS,CAAC,MAAM,KAAK,aAAa,IAAI,OAAO,iBAAiB,CAE7D;CACF;AAED;;;;GAIG;AACH,iBAAS,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAI/D;AAED,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|
package/dist/BaseDAO.js
CHANGED
|
@@ -35,17 +35,20 @@ class BaseDAO {
|
|
|
35
35
|
return executeWithRetry(() => this.statement(sql, bindings).run(), 'run statement');
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* Read one row.
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
38
|
+
* Read one row. A `null` result is "no row", a success, not an error; only a
|
|
39
|
+
* rejected statement is retried and normalized.
|
|
40
|
+
*
|
|
41
|
+
* `null` rather than `undefined` because that is what the platform says
|
|
42
|
+
* (`D1Result.first<T>()` returns `T | null`) and an adapter should hand back
|
|
43
|
+
* what it was given. It also makes this layer agree with `KvCache`, which has
|
|
44
|
+
* always returned `null` for a miss, so one convention covers both.
|
|
42
45
|
*/
|
|
43
46
|
async first(sql, bindings = []) {
|
|
44
47
|
const envelope = await executeWithRetry(async () => {
|
|
45
48
|
const row = await this.statement(sql, bindings).first();
|
|
46
49
|
return { success: true, value: row };
|
|
47
50
|
}, 'read a row');
|
|
48
|
-
return envelope.value
|
|
51
|
+
return envelope.value;
|
|
49
52
|
}
|
|
50
53
|
/** A list read. `.all()` returning `[]` is a success like `first()`'s null. */
|
|
51
54
|
async all(sql, bindings = []) {
|
package/dist/BaseDAO.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseDAO.js","sourceRoot":"","sources":["../src/BaseDAO.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAWhE;;;;;;;;;;;;;;;GAeG;AACH,MAAe,OAAO;IACqB;IAAzC,YAAyC,QAAqB;QAArB,aAAQ,GAAR,QAAQ,CAAa;IAAG,CAAC;IAElE,2GAA2G;IACjG,SAAS,CAAC,GAAW,EAAE,WAAsB,EAAE;QACvD,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACzG,CAAC;IAED;;;;;OAKG;IACO,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,WAAsB,EAAE;QACvD,OAAO,gBAAgB,CAAC,GAAsB,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,GAAG,EAAE,EAAE,eAAe,CAAC,CAAC;IACzG,CAAC;IAED
|
|
1
|
+
{"version":3,"file":"BaseDAO.js","sourceRoot":"","sources":["../src/BaseDAO.ts"],"names":[],"mappings":"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","sourcesContent":["import { DatabaseError } from '@rexezuge/errors';\nimport { D1_RETRY_DEFAULTS, executeWithRetry } from './D1Retry';\nimport type { D1PreparedStatement, D1Queryable, D1Result } from './D1Types';\n\n/**\n * A statement result plus the payload it produced, so the retry engine (which\n * inspects `success`) also handles wrapped results like batch arrays.\n */\ninterface StatementEnvelope<T> extends D1Result {\n value: T;\n}\n\n/**\n * Canonical DAO base: retry is a policy, methods are a promise.\n *\n * Converged from the nine consumer repos' BaseDAOs. The API is the **sql-first**\n * shape CalDAV-Bridge and ChordDHT-Tracker used, not the closure-first shape\n * Durable-DAV's family used: a DAO states its SQL and bindings directly, and\n * the retry/normalization wrapper is invisible at the call site. The closure\n * form made every read site pay for a wrapper 90% of them never needed, and\n * each of the two shapes drifted its own method names (`withRetry` vs\n * `run(sql, bindings)`); the sql-first form is what survived.\n *\n * Non-DAO concerns stay out: cursor pagination lives in `@rexezuge/shared`\n * (`CursorCodec`), subrequest accounting is an injectable port. Retry policy\n * lives in `D1Retry` — one schedule, one classifier, one place the\n * `Failed to <context>` prefix is added.\n */\nabstract class BaseDAO {\n protected constructor(protected readonly database: D1Queryable) {}\n\n /** A prepared statement with its bindings applied. Bindings are always parameters, never interpolation. */\n protected statement(sql: string, bindings: unknown[] = []): D1PreparedStatement {\n return bindings.length > 0 ? this.database.prepare(sql).bind(...bindings) : this.database.prepare(sql);\n }\n\n /**\n * Run a statement. D1 reports a constraint violation or a missing table by\n * returning `success: false` rather than by throwing, so both forms are\n * normalized to a `DatabaseError` here — ignoring the result form is what\n * let a `UNIQUE` failure reach a caller as an opaque 500.\n */\n protected async run(sql: string, bindings: unknown[] = []): Promise<D1Result> {\n return executeWithRetry((): Promise<D1Result> => this.statement(sql, bindings).run(), 'run statement');\n }\n\n /**\n * Read one row. A `null` result is \"no row\", a success, not an error; only a\n * rejected statement is retried and normalized.\n *\n * `null` rather than `undefined` because that is what the platform says\n * (`D1Result.first<T>()` returns `T | null`) and an adapter should hand back\n * what it was given. It also makes this layer agree with `KvCache`, which has\n * always returned `null` for a miss, so one convention covers both.\n */\n protected async first<T>(sql: string, bindings: unknown[] = []): Promise<T | null> {\n const envelope = await executeWithRetry(async (): Promise<StatementEnvelope<T | null>> => {\n const row: T | null = await this.statement(sql, bindings).first<T>();\n return { success: true, value: row };\n }, 'read a row');\n return envelope.value;\n }\n\n /** A list read. `.all()` returning `[]` is a success like `first()`'s null. */\n protected async all<T>(sql: string, bindings: unknown[] = []): Promise<T[]> {\n const envelope = await executeWithRetry(async (): Promise<StatementEnvelope<T[]>> => {\n const rows = await this.statement(sql, bindings).all<T>();\n return { success: true, value: rows.results ?? [] };\n }, 'read rows');\n return envelope.value;\n }\n\n /** A scalar count from a `SELECT count(*)` row; 0 when absent. */\n protected async count(sql: string, bindings: unknown[] = []): Promise<number> {\n const row = await this.first<{ count: number }>(sql, bindings);\n return row?.count ?? 0;\n }\n\n /**\n * A batch of prepared statements — one transaction and one subrequest,\n * however many statements it carries. Each result is checked, so a single\n * failure inside the batch raises rather than passing silently.\n */\n protected async batch(statements: D1PreparedStatement[]): Promise<D1Result[]> {\n if (statements.length === 0) return [];\n if (this.database.batch === undefined) {\n throw new DatabaseError('The D1 binding exposes no batch() — a multi-statement transaction is not available here');\n }\n const envelope = await executeWithRetry(async (): Promise<StatementEnvelope<D1Result[]>> => {\n const results: D1Result[] = await (this.database.batch as NonNullable<D1Queryable['batch']>)(statements);\n const failed: D1Result | undefined = results.find((result): boolean => !result.success);\n return { success: failed === undefined, error: failed?.error, value: results };\n }, 'run a batch');\n return envelope.value;\n }\n\n /**\n * Generic batched delete of rows older than a cutoff, in ID order, with\n * retry. Mirrors the per-DAO `DELETE ... WHERE id IN (SELECT ... LIMIT ?)`\n * pattern that every repo re-implemented per table. The cutoff is exclusive:\n * a delete never touches a row at exactly the boundary timestamp.\n */\n protected static async deleteOlderThan(\n db: D1Queryable,\n table: string,\n timeColumn: string,\n cutoff: number | string,\n limit: number,\n idColumn: string,\n ): Promise<number> {\n assertSqlIdentifier(table, 'table');\n assertSqlIdentifier(timeColumn, 'timeColumn');\n assertSqlIdentifier(idColumn, 'idColumn');\n const result: D1Result = await executeWithRetry(\n (): Promise<D1Result> =>\n db\n .prepare(\n `DELETE FROM ${table}\n WHERE ${idColumn} IN (\n SELECT ${idColumn} FROM ${table}\n WHERE ${timeColumn} < ?\n LIMIT ?\n )`,\n )\n .bind(cutoff, limit)\n .run(),\n `delete old rows from ${table}`,\n );\n return (result.meta as { changes?: number })?.changes ?? 0;\n }\n\n /** The retry schedule, for callers that surface it (health checks, tests). */\n protected static get retryDefaults(): typeof D1_RETRY_DEFAULTS {\n return D1_RETRY_DEFAULTS;\n }\n}\n\n/**\n * Allow-list one SQL identifier segment. Values never go through this — they\n * are always bound; this exists purely for the table/column names that\n * parameter binding cannot express.\n */\nfunction assertSqlIdentifier(value: string, label: string): void {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {\n throw new Error(`Unsafe SQL identifier for ${label}: ${JSON.stringify(value)}`);\n }\n}\n\nexport { BaseDAO, assertSqlIdentifier };\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rexezuge/d1",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"dist"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@rexezuge/errors": "1.0
|
|
29
|
-
"@rexezuge/shared": "1.0
|
|
28
|
+
"@rexezuge/errors": "1.1.0",
|
|
29
|
+
"@rexezuge/shared": "1.1.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "26.6.4",
|