@rexezuge/d1 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/dist/BaseDAO.d.ts +77 -0
- package/dist/BaseDAO.d.ts.map +1 -0
- package/dist/BaseDAO.js +126 -0
- package/dist/BaseDAO.js.map +1 -0
- package/dist/D1ErrorClassifier.d.ts +31 -0
- package/dist/D1ErrorClassifier.d.ts.map +1 -0
- package/dist/D1ErrorClassifier.js +81 -0
- package/dist/D1ErrorClassifier.js.map +1 -0
- package/dist/D1Retry.d.ts +40 -0
- package/dist/D1Retry.d.ts.map +1 -0
- package/dist/D1Retry.js +94 -0
- package/dist/D1Retry.js.map +1 -0
- package/dist/D1Types.d.ts +28 -0
- package/dist/D1Types.d.ts.map +1 -0
- package/dist/D1Types.js +2 -0
- package/dist/D1Types.js.map +1 -0
- package/dist/aes-gcm.d.ts +44 -0
- package/dist/aes-gcm.d.ts.map +1 -0
- package/dist/aes-gcm.js +78 -0
- package/dist/aes-gcm.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/isMissingSchemaError.d.ts +21 -0
- package/dist/isMissingSchemaError.d.ts.map +1 -0
- package/dist/isMissingSchemaError.js +27 -0
- package/dist/isMissingSchemaError.js.map +1 -0
- package/dist/splitSql.d.ts +38 -0
- package/dist/splitSql.d.ts.map +1 -0
- package/dist/splitSql.js +168 -0
- package/dist/splitSql.js.map +1 -0
- package/package.json +38 -3
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rexezuge
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { D1_RETRY_DEFAULTS } from './D1Retry';
|
|
2
|
+
import type { D1PreparedStatement, D1Queryable, D1Result } from './D1Types';
|
|
3
|
+
/**
|
|
4
|
+
* Canonical DAO base: retry is a policy, methods are a promise.
|
|
5
|
+
*
|
|
6
|
+
* Converged from the nine consumer repos' `BaseDAO`s, which had drifted into
|
|
7
|
+
* seven spellings of the same idea (`withRetry`, `runWithRetry`,
|
|
8
|
+
* `runWriteStatement`, `firstWithRetry`, `allWithRetry`, `runWriteBatch`, plus
|
|
9
|
+
* Mail-Meow's static `findById`). The kit ships one straight-line surface —
|
|
10
|
+
* **clients adapt to these names** — with the non-DAO concerns extracted:
|
|
11
|
+
*
|
|
12
|
+
* - cursor pagination moved to `@rexezuge/shared`'s `CursorCodec` (it is a
|
|
13
|
+
* codec, not a DAO method);
|
|
14
|
+
* - subrequest accounting became an injected concern, so the seven repos that
|
|
15
|
+
* never needed it do not inherit it;
|
|
16
|
+
* - retry policy lives in `D1Retry` — one schedule, one classifier, one place
|
|
17
|
+
* the `Failed to <context>` prefix is added.
|
|
18
|
+
*
|
|
19
|
+
* Identifier safety: `table`/`column` names cannot be bound, so the two methods
|
|
20
|
+
* that interpolate them (`deleteOlderThan`) allow-list each segment against
|
|
21
|
+
* `/^[A-Za-z_][A-Za-z0-9_]*$/`. Everything else in a statement goes through
|
|
22
|
+
* `.bind()`.
|
|
23
|
+
*/
|
|
24
|
+
declare abstract class BaseDAO {
|
|
25
|
+
protected readonly database: D1Queryable;
|
|
26
|
+
protected constructor(database: D1Queryable);
|
|
27
|
+
/**
|
|
28
|
+
* A mutating statement with retry and a guaranteed `DatabaseError` on
|
|
29
|
+
* failure. Every write in this layer goes through here: hand-rolling
|
|
30
|
+
* `.run()` plus `if (!result.success) throw` silently produced
|
|
31
|
+
* `retryable: false` and skipped the retry entirely — which left the most
|
|
32
|
+
* failure-sensitive writes with no recovery from a transient D1 lock.
|
|
33
|
+
*/
|
|
34
|
+
protected run(statement: () => Promise<D1Result>, context: string): Promise<D1Result>;
|
|
35
|
+
/**
|
|
36
|
+
* A point read. `first()` resolving to `null` is "no row", a success, not an
|
|
37
|
+
* error — only a rejected statement is retried and normalized.
|
|
38
|
+
*/
|
|
39
|
+
protected first<T>(statement: () => Promise<T | null>, context: string): Promise<T | null>;
|
|
40
|
+
/**
|
|
41
|
+
* A list read. `.all()` returning `[]` is a success like `first()`'s `null`.
|
|
42
|
+
* Separate from `run` because that one is typed for a write's `D1Result`;
|
|
43
|
+
* a list read had no retry at all in the drifted copies.
|
|
44
|
+
*/
|
|
45
|
+
protected all<T>(statement: () => Promise<{
|
|
46
|
+
results?: T[];
|
|
47
|
+
}>, context: string): Promise<T[]>;
|
|
48
|
+
/** A scalar count from a `SELECT count(*) AS count` row; 0 when absent. */
|
|
49
|
+
protected count(statement: () => Promise<{
|
|
50
|
+
count: number;
|
|
51
|
+
} | null>, context: string): Promise<number>;
|
|
52
|
+
/**
|
|
53
|
+
* A batch of prepared statements, retried as a unit and unwrapped to the
|
|
54
|
+
* per-statement results D1 returns. A single non-success result inside the
|
|
55
|
+
* batch is what the engine classifies and retries.
|
|
56
|
+
*/
|
|
57
|
+
protected batch(statements: D1PreparedStatement[], context: string): Promise<D1Result[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Generic batched delete of rows older than a cutoff, in ID order, with
|
|
60
|
+
* retry. Mirrors the per-DAO `DELETE ... WHERE id IN (SELECT ... LIMIT ?)`
|
|
61
|
+
* pattern that every repo re-implemented per table.
|
|
62
|
+
*
|
|
63
|
+
* `timeColumn` is compared against `<` — the cutoff is exclusive, so a
|
|
64
|
+
* delete never touches a row at exactly the boundary timestamp.
|
|
65
|
+
*/
|
|
66
|
+
protected static deleteOlderThan(db: D1Queryable, table: string, timeColumn: string, cutoff: number | string, limit: number, idColumn: string): Promise<number>;
|
|
67
|
+
/** The retry schedule, for callers that surface it (health checks, tests). */
|
|
68
|
+
protected static get retryDefaults(): typeof D1_RETRY_DEFAULTS;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Allow-list one SQL identifier segment. Values never go through this — they
|
|
72
|
+
* are always bound; this exists purely for the table/column names that
|
|
73
|
+
* parameter binding cannot express.
|
|
74
|
+
*/
|
|
75
|
+
declare function assertSqlIdentifier(value: string, label: string): void;
|
|
76
|
+
export { BaseDAO, assertSqlIdentifier };
|
|
77
|
+
//# sourceMappingURL=BaseDAO.d.ts.map
|
|
@@ -0,0 +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;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,uBAAe,OAAO;IACE,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,WAAW;IAA9D,SAAS,aAAgC,QAAQ,EAAE,WAAW;IAE9D;;;;;;OAMG;cACa,GAAG,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAI3F;;;OAGG;cACa,KAAK,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAQhG;;;;OAIG;cACa,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAA;KAAE,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAQlG,2EAA2E;cAC3D,KAAK,CACnB,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,EAClD,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC;IAKlB;;;;OAIG;cACa,KAAK,CAAC,UAAU,EAAE,mBAAmB,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAgB9F;;;;;;;OAOG;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
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { DatabaseError } from '@rexezuge/errors';
|
|
2
|
+
import { D1_RETRY_DEFAULTS, executeWithRetry } from './D1Retry';
|
|
3
|
+
/**
|
|
4
|
+
* Canonical DAO base: retry is a policy, methods are a promise.
|
|
5
|
+
*
|
|
6
|
+
* Converged from the nine consumer repos' `BaseDAO`s, which had drifted into
|
|
7
|
+
* seven spellings of the same idea (`withRetry`, `runWithRetry`,
|
|
8
|
+
* `runWriteStatement`, `firstWithRetry`, `allWithRetry`, `runWriteBatch`, plus
|
|
9
|
+
* Mail-Meow's static `findById`). The kit ships one straight-line surface —
|
|
10
|
+
* **clients adapt to these names** — with the non-DAO concerns extracted:
|
|
11
|
+
*
|
|
12
|
+
* - cursor pagination moved to `@rexezuge/shared`'s `CursorCodec` (it is a
|
|
13
|
+
* codec, not a DAO method);
|
|
14
|
+
* - subrequest accounting became an injected concern, so the seven repos that
|
|
15
|
+
* never needed it do not inherit it;
|
|
16
|
+
* - retry policy lives in `D1Retry` — one schedule, one classifier, one place
|
|
17
|
+
* the `Failed to <context>` prefix is added.
|
|
18
|
+
*
|
|
19
|
+
* Identifier safety: `table`/`column` names cannot be bound, so the two methods
|
|
20
|
+
* that interpolate them (`deleteOlderThan`) allow-list each segment against
|
|
21
|
+
* `/^[A-Za-z_][A-Za-z0-9_]*$/`. Everything else in a statement goes through
|
|
22
|
+
* `.bind()`.
|
|
23
|
+
*/
|
|
24
|
+
class BaseDAO {
|
|
25
|
+
database;
|
|
26
|
+
constructor(database) {
|
|
27
|
+
this.database = database;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A mutating statement with retry and a guaranteed `DatabaseError` on
|
|
31
|
+
* failure. Every write in this layer goes through here: hand-rolling
|
|
32
|
+
* `.run()` plus `if (!result.success) throw` silently produced
|
|
33
|
+
* `retryable: false` and skipped the retry entirely — which left the most
|
|
34
|
+
* failure-sensitive writes with no recovery from a transient D1 lock.
|
|
35
|
+
*/
|
|
36
|
+
async run(statement, context) {
|
|
37
|
+
return executeWithRetry(statement, context);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A point read. `first()` resolving to `null` is "no row", a success, not an
|
|
41
|
+
* error — only a rejected statement is retried and normalized.
|
|
42
|
+
*/
|
|
43
|
+
async first(statement, context) {
|
|
44
|
+
const envelope = await executeWithRetry(async () => {
|
|
45
|
+
const value = await statement();
|
|
46
|
+
return { success: true, value };
|
|
47
|
+
}, context);
|
|
48
|
+
return envelope.value;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A list read. `.all()` returning `[]` is a success like `first()`'s `null`.
|
|
52
|
+
* Separate from `run` because that one is typed for a write's `D1Result`;
|
|
53
|
+
* a list read had no retry at all in the drifted copies.
|
|
54
|
+
*/
|
|
55
|
+
async all(statement, context) {
|
|
56
|
+
const envelope = await executeWithRetry(async () => {
|
|
57
|
+
const rows = await statement();
|
|
58
|
+
return { success: true, value: rows.results ?? [] };
|
|
59
|
+
}, context);
|
|
60
|
+
return envelope.value;
|
|
61
|
+
}
|
|
62
|
+
/** A scalar count from a `SELECT count(*) AS count` row; 0 when absent. */
|
|
63
|
+
async count(statement, context) {
|
|
64
|
+
const row = await this.first(statement, context);
|
|
65
|
+
return row?.count ?? 0;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A batch of prepared statements, retried as a unit and unwrapped to the
|
|
69
|
+
* per-statement results D1 returns. A single non-success result inside the
|
|
70
|
+
* batch is what the engine classifies and retries.
|
|
71
|
+
*/
|
|
72
|
+
async batch(statements, context) {
|
|
73
|
+
if (this.database.batch === undefined) {
|
|
74
|
+
throw new DatabaseError(`Batch is not supported for ${context}: the D1 binding exposes no batch()`);
|
|
75
|
+
}
|
|
76
|
+
const envelope = await executeWithRetry(async () => {
|
|
77
|
+
const results = await this.database.batch(statements);
|
|
78
|
+
const failed = results.find((result) => !result.success);
|
|
79
|
+
return {
|
|
80
|
+
success: failed === undefined,
|
|
81
|
+
error: failed?.error,
|
|
82
|
+
value: results,
|
|
83
|
+
};
|
|
84
|
+
}, context);
|
|
85
|
+
return envelope.value;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Generic batched delete of rows older than a cutoff, in ID order, with
|
|
89
|
+
* retry. Mirrors the per-DAO `DELETE ... WHERE id IN (SELECT ... LIMIT ?)`
|
|
90
|
+
* pattern that every repo re-implemented per table.
|
|
91
|
+
*
|
|
92
|
+
* `timeColumn` is compared against `<` — the cutoff is exclusive, so a
|
|
93
|
+
* delete never touches a row at exactly the boundary timestamp.
|
|
94
|
+
*/
|
|
95
|
+
static async deleteOlderThan(db, table, timeColumn, cutoff, limit, idColumn) {
|
|
96
|
+
assertSqlIdentifier(table, 'table');
|
|
97
|
+
assertSqlIdentifier(timeColumn, 'timeColumn');
|
|
98
|
+
assertSqlIdentifier(idColumn, 'idColumn');
|
|
99
|
+
const result = await executeWithRetry(() => db
|
|
100
|
+
.prepare(`DELETE FROM ${table}
|
|
101
|
+
WHERE ${idColumn} IN (
|
|
102
|
+
SELECT ${idColumn} FROM ${table}
|
|
103
|
+
WHERE ${timeColumn} < ?
|
|
104
|
+
LIMIT ?
|
|
105
|
+
)`)
|
|
106
|
+
.bind(cutoff, limit)
|
|
107
|
+
.run(), `delete old rows from ${table}`);
|
|
108
|
+
return result.meta?.changes ?? 0;
|
|
109
|
+
}
|
|
110
|
+
/** The retry schedule, for callers that surface it (health checks, tests). */
|
|
111
|
+
static get retryDefaults() {
|
|
112
|
+
return D1_RETRY_DEFAULTS;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Allow-list one SQL identifier segment. Values never go through this — they
|
|
117
|
+
* are always bound; this exists purely for the table/column names that
|
|
118
|
+
* parameter binding cannot express.
|
|
119
|
+
*/
|
|
120
|
+
function assertSqlIdentifier(value, label) {
|
|
121
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
122
|
+
throw new Error(`Unsafe SQL identifier for ${label}: ${JSON.stringify(value)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export { BaseDAO, assertSqlIdentifier };
|
|
126
|
+
//# sourceMappingURL=BaseDAO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseDAO.js","sourceRoot":"","sources":["../src/BaseDAO.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify a raw D1 (or DO SQLite) error message as transient or permanent.
|
|
3
|
+
*
|
|
4
|
+
* Converged verbatim from Durable-DAV (and ChordDHT's byte-identical copy,
|
|
5
|
+
* modulo the import scope) — the most-documented variant in the family.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Patterns for a transient failure worth retrying.
|
|
9
|
+
*
|
|
10
|
+
* Checked **first**. A message matching both lists is treated as transient: one
|
|
11
|
+
* wasted round trip against a real conflict is cheap, whereas mis-classifying a
|
|
12
|
+
* blip as permanent turns a self-healing condition into a user-visible outage
|
|
13
|
+
* that neither the DAO's retry nor the API's 503 mapping will cover.
|
|
14
|
+
*/
|
|
15
|
+
declare const RETRYABLE_PATTERNS: RegExp[];
|
|
16
|
+
/**
|
|
17
|
+
* Patterns for a failure that will not get better on its own.
|
|
18
|
+
*
|
|
19
|
+
* Every entry is deliberately **narrower than its obvious spelling**. This list
|
|
20
|
+
* used to be consulted first and carried bare `/range/i` and `/not\s+found/i`,
|
|
21
|
+
* both of which match transient conditions:
|
|
22
|
+
*
|
|
23
|
+
* - `/range/i` matched `RangeError: Maximum call stack size exceeded` and
|
|
24
|
+
* `byte range not satisfiable` — neither is a row-level conflict.
|
|
25
|
+
* - `/not\s+found/i` matched `host not found`, i.e. a DNS blip, which
|
|
26
|
+
* `/connection/i` now correctly claims first.
|
|
27
|
+
*/
|
|
28
|
+
declare const NON_RETRYABLE_PATTERNS: RegExp[];
|
|
29
|
+
declare function isD1ErrorRetryable(errorMessage: string): boolean;
|
|
30
|
+
export { isD1ErrorRetryable, RETRYABLE_PATTERNS, NON_RETRYABLE_PATTERNS };
|
|
31
|
+
//# sourceMappingURL=D1ErrorClassifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"D1ErrorClassifier.d.ts","sourceRoot":"","sources":["../src/D1ErrorClassifier.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;GAOG;AACH,QAAA,MAAM,kBAAkB,EAAE,MAAM,EAc/B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,sBAAsB,EAAE,MAAM,EAiBnC,CAAC;AAEF,iBAAS,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAmBzD;AAED,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify a raw D1 (or DO SQLite) error message as transient or permanent.
|
|
3
|
+
*
|
|
4
|
+
* Converged verbatim from Durable-DAV (and ChordDHT's byte-identical copy,
|
|
5
|
+
* modulo the import scope) — the most-documented variant in the family.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Patterns for a transient failure worth retrying.
|
|
9
|
+
*
|
|
10
|
+
* Checked **first**. A message matching both lists is treated as transient: one
|
|
11
|
+
* wasted round trip against a real conflict is cheap, whereas mis-classifying a
|
|
12
|
+
* blip as permanent turns a self-healing condition into a user-visible outage
|
|
13
|
+
* that neither the DAO's retry nor the API's 503 mapping will cover.
|
|
14
|
+
*/
|
|
15
|
+
const RETRYABLE_PATTERNS = [
|
|
16
|
+
/busy/i,
|
|
17
|
+
/locked/i,
|
|
18
|
+
/timeout/i,
|
|
19
|
+
/timed?\s*out/i,
|
|
20
|
+
/internal\s+(server\s+)?error/i,
|
|
21
|
+
/connection/i,
|
|
22
|
+
/network/i,
|
|
23
|
+
/unavailable/i,
|
|
24
|
+
/throttl/i,
|
|
25
|
+
/too\s+many/i,
|
|
26
|
+
/retry/i,
|
|
27
|
+
/deadlock/i,
|
|
28
|
+
/serialization/i,
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Patterns for a failure that will not get better on its own.
|
|
32
|
+
*
|
|
33
|
+
* Every entry is deliberately **narrower than its obvious spelling**. This list
|
|
34
|
+
* used to be consulted first and carried bare `/range/i` and `/not\s+found/i`,
|
|
35
|
+
* both of which match transient conditions:
|
|
36
|
+
*
|
|
37
|
+
* - `/range/i` matched `RangeError: Maximum call stack size exceeded` and
|
|
38
|
+
* `byte range not satisfiable` — neither is a row-level conflict.
|
|
39
|
+
* - `/not\s+found/i` matched `host not found`, i.e. a DNS blip, which
|
|
40
|
+
* `/connection/i` now correctly claims first.
|
|
41
|
+
*/
|
|
42
|
+
const NON_RETRYABLE_PATTERNS = [
|
|
43
|
+
/constraint/i,
|
|
44
|
+
/\bunique\b/i,
|
|
45
|
+
/primary\s+key/i,
|
|
46
|
+
/foreign\s+key/i,
|
|
47
|
+
/not\s+found/i,
|
|
48
|
+
/\bsyntax\b/i,
|
|
49
|
+
/parse\s+error/i,
|
|
50
|
+
/no\s+such\s+(table|column|index)/i,
|
|
51
|
+
/type\s+mismatch/i,
|
|
52
|
+
// Was `/range/i`, which also matched a stack-overflow `RangeError` and
|
|
53
|
+
// "byte range not satisfiable".
|
|
54
|
+
/\bout\s+of\s+range\b/i,
|
|
55
|
+
/invalid\s+argument/i,
|
|
56
|
+
/\bpermission\b/i,
|
|
57
|
+
/\bauthorization\b/i,
|
|
58
|
+
/\bauthentication\b/i,
|
|
59
|
+
];
|
|
60
|
+
function isD1ErrorRetryable(errorMessage) {
|
|
61
|
+
if (!errorMessage)
|
|
62
|
+
return false;
|
|
63
|
+
// Retryable is checked first on purpose. An error can carry both a transient
|
|
64
|
+
// signal and a permanent-sounding word — "connection: host not found",
|
|
65
|
+
// "retry: authentication backend unavailable" — and retrying those is correct,
|
|
66
|
+
// while a bare "row not found" or a UNIQUE/FOREIGN KEY violation still matches
|
|
67
|
+
// nothing in the retryable list and is correctly refused. The ordering is
|
|
68
|
+
// pinned by `hardening-round7.test.ts` in Durable-DAV; the kit carries the
|
|
69
|
+
// equivalent expectations in d1.test.ts.
|
|
70
|
+
for (const pattern of RETRYABLE_PATTERNS) {
|
|
71
|
+
if (pattern.test(errorMessage))
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
for (const pattern of NON_RETRYABLE_PATTERNS) {
|
|
75
|
+
if (pattern.test(errorMessage))
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
export { isD1ErrorRetryable, RETRYABLE_PATTERNS, NON_RETRYABLE_PATTERNS };
|
|
81
|
+
//# sourceMappingURL=D1ErrorClassifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"D1ErrorClassifier.js","sourceRoot":"","sources":["../src/D1ErrorClassifier.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;GAOG;AACH,MAAM,kBAAkB,GAAa;IACnC,OAAO;IACP,SAAS;IACT,UAAU;IACV,eAAe;IACf,+BAA+B;IAC/B,aAAa;IACb,UAAU;IACV,cAAc;IACd,UAAU;IACV,aAAa;IACb,QAAQ;IACR,WAAW;IACX,gBAAgB;CACjB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,sBAAsB,GAAa;IACvC,aAAa;IACb,aAAa;IACb,gBAAgB;IAChB,gBAAgB;IAChB,cAAc;IACd,aAAa;IACb,gBAAgB;IAChB,mCAAmC;IACnC,kBAAkB;IAClB,uEAAuE;IACvE,gCAAgC;IAChC,uBAAuB;IACvB,qBAAqB;IACrB,iBAAiB;IACjB,oBAAoB;IACpB,qBAAqB;CACtB,CAAC;AAEF,SAAS,kBAAkB,CAAC,YAAoB;IAC9C,IAAI,CAAC,YAAY;QAAE,OAAO,KAAK,CAAC;IAEhC,6EAA6E;IAC7E,uEAAuE;IACvE,+EAA+E;IAC/E,+EAA+E;IAC/E,0EAA0E;IAC1E,2EAA2E;IAC3E,yCAAyC;IACzC,KAAK,MAAM,OAAO,IAAI,kBAAkB,EAAE,CAAC;QACzC,IAAI,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC;YAAE,OAAO,IAAI,CAAC;IAC9C,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,sBAAsB,EAAE,CAAC;QAC7C,IAAI,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC;YAAE,OAAO,KAAK,CAAC;IAC/C,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { D1Result } from './D1Types';
|
|
2
|
+
/**
|
|
3
|
+
* The retry schedule, exported so `BaseDAO` and any other caller cannot
|
|
4
|
+
* hard-code a second, divergent one — re-deriving `baseDelayMs * 2 ** n` at a
|
|
5
|
+
* call site is the same drift one level up.
|
|
6
|
+
*/
|
|
7
|
+
declare const D1_RETRY_DEFAULTS: {
|
|
8
|
+
readonly maxRetries: 3;
|
|
9
|
+
readonly baseDelayMs: 100;
|
|
10
|
+
};
|
|
11
|
+
interface D1RetryOptions {
|
|
12
|
+
maxRetries?: number;
|
|
13
|
+
baseDelayMs?: number;
|
|
14
|
+
}
|
|
15
|
+
declare function sleep(ms: number): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Backoff before the next attempt. Exponential, and uncapped by design: the
|
|
18
|
+
* default base (100 ms) tops out under a second by attempt 3, while an operator
|
|
19
|
+
* who sets a larger base gets the longer wait they asked for. The point is to
|
|
20
|
+
* ride out a busy/locked D1, not to implement a rate limiter.
|
|
21
|
+
*/
|
|
22
|
+
declare function backoffMs(baseDelayMs: number, attempt: number): number;
|
|
23
|
+
/**
|
|
24
|
+
* Run a D1 operation, retrying transient failures with exponential backoff.
|
|
25
|
+
*
|
|
26
|
+
* Two failure shapes are handled, because D1 reports both:
|
|
27
|
+
*
|
|
28
|
+
* 1. **A result with `success: false`.** The operation resolved; the database
|
|
29
|
+
* said no. Retried when the error classifies as transient.
|
|
30
|
+
* 2. **A thrown error.** A transport-level rejection, which never carries a
|
|
31
|
+
* `D1Result` at all. Classified from the message the same way.
|
|
32
|
+
*
|
|
33
|
+
* A `DatabaseError` we raised ourselves in (1) arrives back through the `catch`
|
|
34
|
+
* on the retry attempt, so it must be recognised and re-thrown rather than
|
|
35
|
+
* re-wrapped into a second layer of `Failed to …`.
|
|
36
|
+
*/
|
|
37
|
+
declare function executeWithRetry<T extends D1Result>(operation: () => Promise<T>, context: string, options?: D1RetryOptions): Promise<T>;
|
|
38
|
+
export { backoffMs, D1_RETRY_DEFAULTS, executeWithRetry, sleep };
|
|
39
|
+
export type { D1RetryOptions };
|
|
40
|
+
//# sourceMappingURL=D1Retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"D1Retry.d.ts","sourceRoot":"","sources":["../src/D1Retry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAgB1C;;;;GAIG;AACH,QAAA,MAAM,iBAAiB;;;CAAmF,CAAC;AAE3G,UAAU,cAAc;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,iBAAS,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAExC;AAED;;;;;GAKG;AACH,iBAAS,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE/D;AAMD;;;;;;;;;;;;;GAaG;AACH,iBAAe,gBAAgB,CAAC,CAAC,SAAS,QAAQ,EAChD,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,CAAC,CAAC,CA0CZ;AAED,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC;AACjE,YAAY,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/D1Retry.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { DatabaseError } from '@rexezuge/errors';
|
|
2
|
+
import { isD1ErrorRetryable } from './D1ErrorClassifier';
|
|
3
|
+
/**
|
|
4
|
+
* The single retry engine for D1 statements.
|
|
5
|
+
*
|
|
6
|
+
* Converged from Durable-DAV's `D1Utils.ts` (the richest, most-documented
|
|
7
|
+
* variant) with ChordDHT's byte-identical copy and the straight-line port in
|
|
8
|
+
* Mail-Meow. The seven consumer repos each spelled this differently
|
|
9
|
+
* (`executeD1WithRetry`, `runWithRetry`, `firstWithRetry` with its own schedule);
|
|
10
|
+
* the kit ships one engine plus the schedule constants, and `BaseDAO` is the
|
|
11
|
+
* only intended caller.
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
14
|
+
const DEFAULT_BASE_DELAY_MS = 100;
|
|
15
|
+
/**
|
|
16
|
+
* The retry schedule, exported so `BaseDAO` and any other caller cannot
|
|
17
|
+
* hard-code a second, divergent one — re-deriving `baseDelayMs * 2 ** n` at a
|
|
18
|
+
* call site is the same drift one level up.
|
|
19
|
+
*/
|
|
20
|
+
const D1_RETRY_DEFAULTS = { maxRetries: DEFAULT_MAX_RETRIES, baseDelayMs: DEFAULT_BASE_DELAY_MS };
|
|
21
|
+
function sleep(ms) {
|
|
22
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Backoff before the next attempt. Exponential, and uncapped by design: the
|
|
26
|
+
* default base (100 ms) tops out under a second by attempt 3, while an operator
|
|
27
|
+
* who sets a larger base gets the longer wait they asked for. The point is to
|
|
28
|
+
* ride out a busy/locked D1, not to implement a rate limiter.
|
|
29
|
+
*/
|
|
30
|
+
function backoffMs(baseDelayMs, attempt) {
|
|
31
|
+
return baseDelayMs * 2 ** attempt;
|
|
32
|
+
}
|
|
33
|
+
function toDatabaseError(errorMessage, context) {
|
|
34
|
+
return new DatabaseError(`Failed to ${context}: ${errorMessage}`, isD1ErrorRetryable(errorMessage));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Run a D1 operation, retrying transient failures with exponential backoff.
|
|
38
|
+
*
|
|
39
|
+
* Two failure shapes are handled, because D1 reports both:
|
|
40
|
+
*
|
|
41
|
+
* 1. **A result with `success: false`.** The operation resolved; the database
|
|
42
|
+
* said no. Retried when the error classifies as transient.
|
|
43
|
+
* 2. **A thrown error.** A transport-level rejection, which never carries a
|
|
44
|
+
* `D1Result` at all. Classified from the message the same way.
|
|
45
|
+
*
|
|
46
|
+
* A `DatabaseError` we raised ourselves in (1) arrives back through the `catch`
|
|
47
|
+
* on the retry attempt, so it must be recognised and re-thrown rather than
|
|
48
|
+
* re-wrapped into a second layer of `Failed to …`.
|
|
49
|
+
*/
|
|
50
|
+
async function executeWithRetry(operation, context, options) {
|
|
51
|
+
const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
52
|
+
const baseDelayMs = options?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
53
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
54
|
+
const isLastAttempt = attempt === maxRetries;
|
|
55
|
+
try {
|
|
56
|
+
const result = await operation();
|
|
57
|
+
if (result.success)
|
|
58
|
+
return result;
|
|
59
|
+
const errorMessage = result.error ?? 'Unknown database error';
|
|
60
|
+
const failure = toDatabaseError(errorMessage, context);
|
|
61
|
+
if (!isLastAttempt && failure.retryable) {
|
|
62
|
+
await sleep(backoffMs(baseDelayMs, attempt));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
throw failure;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
// Already classified above, or raised by the operation itself. Either way
|
|
69
|
+
// re-wrapping would bury the original context.
|
|
70
|
+
if (error instanceof DatabaseError) {
|
|
71
|
+
if (!isLastAttempt && error.retryable) {
|
|
72
|
+
await sleep(backoffMs(baseDelayMs, attempt));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
if (error instanceof Error) {
|
|
78
|
+
const failure = toDatabaseError(error.message, context);
|
|
79
|
+
if (!isLastAttempt && failure.retryable) {
|
|
80
|
+
await sleep(backoffMs(baseDelayMs, attempt));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
throw failure;
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Unreachable: the loop either returns a result or throws on its final
|
|
89
|
+
// iteration. Present only so the function is total for a reader who has not
|
|
90
|
+
// traced the `continue` paths.
|
|
91
|
+
throw new DatabaseError(`Failed to ${context} after ${maxRetries + 1} attempts`);
|
|
92
|
+
}
|
|
93
|
+
export { backoffMs, D1_RETRY_DEFAULTS, executeWithRetry, sleep };
|
|
94
|
+
//# sourceMappingURL=D1Retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"D1Retry.js","sourceRoot":"","sources":["../src/D1Retry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural D1 types so `@rexezuge/d1` typechecks without
|
|
3
|
+
* @cloudflare/workers-types. A real `D1Database` satisfies these structurally.
|
|
4
|
+
*
|
|
5
|
+
* Converged from the identical copies in the nine consumer repos.
|
|
6
|
+
*/
|
|
7
|
+
interface D1PreparedStatement {
|
|
8
|
+
bind(...values: unknown[]): D1PreparedStatement;
|
|
9
|
+
first<T = unknown>(): Promise<T | null>;
|
|
10
|
+
all<T = unknown>(): Promise<{
|
|
11
|
+
results: T[];
|
|
12
|
+
}>;
|
|
13
|
+
run(): Promise<D1Result>;
|
|
14
|
+
}
|
|
15
|
+
interface D1Result {
|
|
16
|
+
success: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
meta?: {
|
|
19
|
+
changes?: number;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
interface D1Queryable {
|
|
24
|
+
prepare(query: string): D1PreparedStatement;
|
|
25
|
+
batch?(statements: D1PreparedStatement[]): Promise<D1Result[]>;
|
|
26
|
+
}
|
|
27
|
+
export type { D1PreparedStatement, D1Queryable, D1Result };
|
|
28
|
+
//# sourceMappingURL=D1Types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"D1Types.d.ts","sourceRoot":"","sources":["../src/D1Types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,UAAU,mBAAmB;IAC3B,IAAI,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,mBAAmB,CAAC;IAChD,KAAK,CAAC,CAAC,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACxC,GAAG,CAAC,CAAC,GAAG,OAAO,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC,EAAE,CAAA;KAAE,CAAC,CAAC;IAC9C,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC1B;AAED,UAAU,QAAQ;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CACrD;AAED,UAAU,WAAW;IACnB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAAC;IAC5C,KAAK,CAAC,CAAC,UAAU,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;CAChE;AAED,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/D1Types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"D1Types.js","sourceRoot":"","sources":["../src/D1Types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AES-GCM envelope for values that must be recoverable.
|
|
3
|
+
*
|
|
4
|
+
* Converged from the seven drifted copies of `backend-data/crypto/aes-gcm.ts`
|
|
5
|
+
* (AWS, CalDAV, Durable-DAV, Edge-Git, Edge-Sonic, Mail-Meow, Mail-Otter).
|
|
6
|
+
* Durable-DAV's is the canonical shape; names are de-scoped from
|
|
7
|
+
* "replication" because every repo that encrypts a secret — replication remote
|
|
8
|
+
* passwords, SES credentials, OAuth client secrets — has the same problem.
|
|
9
|
+
*
|
|
10
|
+
* Key material is base64 of exactly 32 bytes. A missing or wrong-sized key
|
|
11
|
+
* throws rather than returning plaintext: the alternative — a helper that
|
|
12
|
+
* quietly passes the value through when it cannot encrypt — would store the
|
|
13
|
+
* secret in the clear on exactly the deployments that forgot to configure a
|
|
14
|
+
* key, and nothing would report it.
|
|
15
|
+
*/
|
|
16
|
+
declare const KEY_BYTES = 32;
|
|
17
|
+
declare const IV_BYTES = 12;
|
|
18
|
+
declare class AesGcmKeyError extends Error {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
interface EncryptedEnvelope {
|
|
22
|
+
ciphertext: string;
|
|
23
|
+
iv: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Encrypt one secret. The IV is random per call and returned alongside the
|
|
27
|
+
* ciphertext so the caller can store the pair — reusing an IV under one key
|
|
28
|
+
* destroys GCM's security guarantees outright, which is why it is not
|
|
29
|
+
* internal to the ciphertext.
|
|
30
|
+
*/
|
|
31
|
+
declare function encryptSecret(plaintext: string, encodedKey: string | undefined | null): Promise<EncryptedEnvelope>;
|
|
32
|
+
/**
|
|
33
|
+
* Decrypt one envelope.
|
|
34
|
+
*
|
|
35
|
+
* Throws on a wrong key or tampered ciphertext rather than returning garbage:
|
|
36
|
+
* a fail-closed decrypt turns a config mistake into a loud, immediate error
|
|
37
|
+
* instead of corrupt data flowing into a downstream request.
|
|
38
|
+
*/
|
|
39
|
+
declare function decryptSecret(envelope: EncryptedEnvelope, encodedKey: string | undefined | null): Promise<string>;
|
|
40
|
+
/** Round-trip helper for tests and one-shot migrations. */
|
|
41
|
+
declare function roundTripSecret(plaintext: string, encodedKey: string): Promise<string>;
|
|
42
|
+
export { AesGcmKeyError, decryptSecret, encryptSecret, roundTripSecret, IV_BYTES, KEY_BYTES };
|
|
43
|
+
export type { EncryptedEnvelope };
|
|
44
|
+
//# sourceMappingURL=aes-gcm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aes-gcm.d.ts","sourceRoot":"","sources":["../src/aes-gcm.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,QAAA,MAAM,SAAS,KAAK,CAAC;AACrB,QAAA,MAAM,QAAQ,KAAK,CAAC;AAEpB,cAAM,cAAe,SAAQ,KAAK;gBACpB,OAAO,EAAE,MAAM;CAI5B;AAsBD,UAAU,iBAAiB;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,iBAAe,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC,CASjH;AAED;;;;;;GAMG;AACH,iBAAe,aAAa,CAAC,QAAQ,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAMhH;AAED,2DAA2D;AAC3D,iBAAe,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGrF;AAED,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAC9F,YAAY,EAAE,iBAAiB,EAAE,CAAC"}
|
package/dist/aes-gcm.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { base64ToBytes, bytesToBase64 } from '@rexezuge/shared';
|
|
2
|
+
/**
|
|
3
|
+
* AES-GCM envelope for values that must be recoverable.
|
|
4
|
+
*
|
|
5
|
+
* Converged from the seven drifted copies of `backend-data/crypto/aes-gcm.ts`
|
|
6
|
+
* (AWS, CalDAV, Durable-DAV, Edge-Git, Edge-Sonic, Mail-Meow, Mail-Otter).
|
|
7
|
+
* Durable-DAV's is the canonical shape; names are de-scoped from
|
|
8
|
+
* "replication" because every repo that encrypts a secret — replication remote
|
|
9
|
+
* passwords, SES credentials, OAuth client secrets — has the same problem.
|
|
10
|
+
*
|
|
11
|
+
* Key material is base64 of exactly 32 bytes. A missing or wrong-sized key
|
|
12
|
+
* throws rather than returning plaintext: the alternative — a helper that
|
|
13
|
+
* quietly passes the value through when it cannot encrypt — would store the
|
|
14
|
+
* secret in the clear on exactly the deployments that forgot to configure a
|
|
15
|
+
* key, and nothing would report it.
|
|
16
|
+
*/
|
|
17
|
+
const KEY_BYTES = 32;
|
|
18
|
+
const IV_BYTES = 12;
|
|
19
|
+
class AesGcmKeyError extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = 'AesGcmKeyError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function importKey(encodedKey) {
|
|
26
|
+
if (typeof encodedKey !== 'string' || encodedKey.trim() === '') {
|
|
27
|
+
throw new AesGcmKeyError('Encryption key is not configured; the secret cannot be stored');
|
|
28
|
+
}
|
|
29
|
+
let raw;
|
|
30
|
+
try {
|
|
31
|
+
raw = base64ToBytes(encodedKey.trim());
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new AesGcmKeyError('Encryption key is not valid base64');
|
|
35
|
+
}
|
|
36
|
+
if (raw.byteLength !== KEY_BYTES) {
|
|
37
|
+
throw new AesGcmKeyError(`Encryption key must decode to ${KEY_BYTES} bytes, got ${raw.byteLength}`);
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return await crypto.subtle.importKey('raw', raw, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new AesGcmKeyError('Encryption key could not be imported as an AES-GCM key');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Encrypt one secret. The IV is random per call and returned alongside the
|
|
48
|
+
* ciphertext so the caller can store the pair — reusing an IV under one key
|
|
49
|
+
* destroys GCM's security guarantees outright, which is why it is not
|
|
50
|
+
* internal to the ciphertext.
|
|
51
|
+
*/
|
|
52
|
+
async function encryptSecret(plaintext, encodedKey) {
|
|
53
|
+
const key = await importKey(encodedKey);
|
|
54
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
|
|
55
|
+
const sealed = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, new TextEncoder().encode(plaintext));
|
|
56
|
+
return { ciphertext: bytesToBase64(new Uint8Array(sealed)), iv: bytesToBase64(iv) };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Decrypt one envelope.
|
|
60
|
+
*
|
|
61
|
+
* Throws on a wrong key or tampered ciphertext rather than returning garbage:
|
|
62
|
+
* a fail-closed decrypt turns a config mistake into a loud, immediate error
|
|
63
|
+
* instead of corrupt data flowing into a downstream request.
|
|
64
|
+
*/
|
|
65
|
+
async function decryptSecret(envelope, encodedKey) {
|
|
66
|
+
const key = await importKey(encodedKey);
|
|
67
|
+
const iv = base64ToBytes(envelope.iv);
|
|
68
|
+
const ciphertext = base64ToBytes(envelope.ciphertext);
|
|
69
|
+
const opened = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv }, key, ciphertext);
|
|
70
|
+
return new TextDecoder().decode(opened);
|
|
71
|
+
}
|
|
72
|
+
/** Round-trip helper for tests and one-shot migrations. */
|
|
73
|
+
async function roundTripSecret(plaintext, encodedKey) {
|
|
74
|
+
const envelope = await encryptSecret(plaintext, encodedKey);
|
|
75
|
+
return decryptSecret(envelope, encodedKey);
|
|
76
|
+
}
|
|
77
|
+
export { AesGcmKeyError, decryptSecret, encryptSecret, roundTripSecret, IV_BYTES, KEY_BYTES };
|
|
78
|
+
//# sourceMappingURL=aes-gcm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aes-gcm.js","sourceRoot":"","sources":["../src/aes-gcm.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { BaseDAO, assertSqlIdentifier } from './BaseDAO';
|
|
2
|
+
export { isD1ErrorRetryable, RETRYABLE_PATTERNS, NON_RETRYABLE_PATTERNS } from './D1ErrorClassifier';
|
|
3
|
+
export { isMissingSchemaError, MISSING_SCHEMA_PATTERN } from './isMissingSchemaError';
|
|
4
|
+
export { executableStatements, isExecutable, splitSql } from './splitSql';
|
|
5
|
+
export { backoffMs, D1_RETRY_DEFAULTS, executeWithRetry, sleep } from './D1Retry';
|
|
6
|
+
export type { D1RetryOptions } from './D1Retry';
|
|
7
|
+
export type { D1PreparedStatement, D1Queryable, D1Result } from './D1Types';
|
|
8
|
+
export { AesGcmKeyError, decryptSecret, encryptSecret, roundTripSecret, IV_BYTES, KEY_BYTES } from './aes-gcm';
|
|
9
|
+
export type { EncryptedEnvelope } from './aes-gcm';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACrG,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAClF,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC/G,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { BaseDAO, assertSqlIdentifier } from './BaseDAO';
|
|
2
|
+
export { isD1ErrorRetryable, RETRYABLE_PATTERNS, NON_RETRYABLE_PATTERNS } from './D1ErrorClassifier';
|
|
3
|
+
export { isMissingSchemaError, MISSING_SCHEMA_PATTERN } from './isMissingSchemaError';
|
|
4
|
+
export { executableStatements, isExecutable, splitSql } from './splitSql';
|
|
5
|
+
export { backoffMs, D1_RETRY_DEFAULTS, executeWithRetry, sleep } from './D1Retry';
|
|
6
|
+
export { AesGcmKeyError, decryptSecret, encryptSecret, roundTripSecret, IV_BYTES, KEY_BYTES } from './aes-gcm';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACrG,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAGlF,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Distinguishes "the table or column isn't there" from every other way a SQL
|
|
3
|
+
* statement can fail.
|
|
4
|
+
*
|
|
5
|
+
* Provenance: `shared/src/utils/SqlSchemaError.ts` in Durable-DAV (re-exported
|
|
6
|
+
* from `backend-data`), Byte-comparable copies in Edge-Git, Edge-Sonic and
|
|
7
|
+
* ChordDHT-Tracker. Durable-DAV's own note is the canonical home decision: both
|
|
8
|
+
* SQL surfaces the family uses (D1 and DO SQLite) report a missing relation the
|
|
9
|
+
* same way, so the predicate lives below the layers that degrade on it —
|
|
10
|
+
* in this kit, that layer is `d1`.
|
|
11
|
+
*
|
|
12
|
+
* The rule it exists to enforce is fail-closed: a caller may degrade to `[]` or
|
|
13
|
+
* a fallback query **only** on `true` here, and must rethrow everything else.
|
|
14
|
+
* Catching broadly is how "this resource has no dead properties" comes to mean
|
|
15
|
+
* "the database is corrupt" — a silent data-loss report, which is worse than an
|
|
16
|
+
* error.
|
|
17
|
+
*/
|
|
18
|
+
declare const MISSING_SCHEMA_PATTERN: RegExp;
|
|
19
|
+
declare function isMissingSchemaError(error: unknown): boolean;
|
|
20
|
+
export { isMissingSchemaError, MISSING_SCHEMA_PATTERN };
|
|
21
|
+
//# sourceMappingURL=isMissingSchemaError.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isMissingSchemaError.d.ts","sourceRoot":"","sources":["../src/isMissingSchemaError.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,QAAA,MAAM,sBAAsB,QAAwC,CAAC;AAErE,iBAAS,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKrD;AAED,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Distinguishes "the table or column isn't there" from every other way a SQL
|
|
3
|
+
* statement can fail.
|
|
4
|
+
*
|
|
5
|
+
* Provenance: `shared/src/utils/SqlSchemaError.ts` in Durable-DAV (re-exported
|
|
6
|
+
* from `backend-data`), Byte-comparable copies in Edge-Git, Edge-Sonic and
|
|
7
|
+
* ChordDHT-Tracker. Durable-DAV's own note is the canonical home decision: both
|
|
8
|
+
* SQL surfaces the family uses (D1 and DO SQLite) report a missing relation the
|
|
9
|
+
* same way, so the predicate lives below the layers that degrade on it —
|
|
10
|
+
* in this kit, that layer is `d1`.
|
|
11
|
+
*
|
|
12
|
+
* The rule it exists to enforce is fail-closed: a caller may degrade to `[]` or
|
|
13
|
+
* a fallback query **only** on `true` here, and must rethrow everything else.
|
|
14
|
+
* Catching broadly is how "this resource has no dead properties" comes to mean
|
|
15
|
+
* "the database is corrupt" — a silent data-loss report, which is worse than an
|
|
16
|
+
* error.
|
|
17
|
+
*/
|
|
18
|
+
const MISSING_SCHEMA_PATTERN = /no\s+such\s+(?:table|column|index)/i;
|
|
19
|
+
function isMissingSchemaError(error) {
|
|
20
|
+
if (error instanceof Error)
|
|
21
|
+
return MISSING_SCHEMA_PATTERN.test(error.message);
|
|
22
|
+
// A thrown string is legal; anything else (a number, an object, `undefined`)
|
|
23
|
+
// carries no message to match against.
|
|
24
|
+
return typeof error === 'string' && MISSING_SCHEMA_PATTERN.test(error);
|
|
25
|
+
}
|
|
26
|
+
export { isMissingSchemaError, MISSING_SCHEMA_PATTERN };
|
|
27
|
+
//# sourceMappingURL=isMissingSchemaError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isMissingSchemaError.js","sourceRoot":"","sources":["../src/isMissingSchemaError.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,sBAAsB,GAAG,qCAAqC,CAAC;AAErE,SAAS,oBAAoB,CAAC,KAAc;IAC1C,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9E,6EAA6E;IAC7E,uCAAuC;IACvC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACzE,CAAC;AAED,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal SQL statement splitter for SQLite migration files.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: `test/integration/helpers/migrations.ts`, byte-near-identical in
|
|
5
|
+
* six consumer repos, ported here verbatim because its comment-level history is
|
|
6
|
+
* the specification for the edge cases (`''` escapes, block comments, trigger
|
|
7
|
+
* bodies with semicolons). The kit keeps one copy: `@rexezuge/tooling`'s
|
|
8
|
+
* integration helper and `@rexezuge/identity`'s migration applier both delegate
|
|
9
|
+
* to this module, so the splitter cannot drift between them.
|
|
10
|
+
*
|
|
11
|
+
* Handles: single/double/backtick quoted strings (incl. `''` escapes), `--`
|
|
12
|
+
* line comments, block comments. Semicolons inside strings or comments do not
|
|
13
|
+
* split. Statements come back trimmed, without the terminating semicolon, and
|
|
14
|
+
* with their leading comments attached — use `executableStatements` to drop
|
|
15
|
+
* comment-only runs before sending anything to D1.
|
|
16
|
+
*
|
|
17
|
+
* Trigger-aware: `CREATE TRIGGER ... BEGIN ...; ... END;` bodies contain
|
|
18
|
+
* semicolons that must not split. The splitter tracks the outermost BEGIN/END
|
|
19
|
+
* depth after a CREATE TRIGGER header and only splits on the terminating
|
|
20
|
+
* semicolon after the final END.
|
|
21
|
+
*/
|
|
22
|
+
declare function splitSql(sql: string): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Whether a statement is executable SQL rather than a comment.
|
|
25
|
+
*
|
|
26
|
+
* Checked after the split, because the splitter has to keep comments attached to
|
|
27
|
+
* the statement they sit above — dropping them there would change what an error
|
|
28
|
+
* message quotes.
|
|
29
|
+
*/
|
|
30
|
+
declare function isExecutable(statement: string): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The executable statements of one migration file, in order: split first, then
|
|
33
|
+
* filtered. A splitter that dropped comments itself would also drop the ones a
|
|
34
|
+
* migration keeps beside the statement they describe.
|
|
35
|
+
*/
|
|
36
|
+
declare function executableStatements(sql: string): string[];
|
|
37
|
+
export { executableStatements, isExecutable, splitSql };
|
|
38
|
+
//# sourceMappingURL=splitSql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitSql.d.ts","sourceRoot":"","sources":["../src/splitSql.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,iBAAS,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAsHvC;AAED;;;;;;GAMG;AACH,iBAAS,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAKhD;AAED;;;;GAIG;AACH,iBAAS,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAEnD;AAED,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/splitSql.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal SQL statement splitter for SQLite migration files.
|
|
3
|
+
*
|
|
4
|
+
* Provenance: `test/integration/helpers/migrations.ts`, byte-near-identical in
|
|
5
|
+
* six consumer repos, ported here verbatim because its comment-level history is
|
|
6
|
+
* the specification for the edge cases (`''` escapes, block comments, trigger
|
|
7
|
+
* bodies with semicolons). The kit keeps one copy: `@rexezuge/tooling`'s
|
|
8
|
+
* integration helper and `@rexezuge/identity`'s migration applier both delegate
|
|
9
|
+
* to this module, so the splitter cannot drift between them.
|
|
10
|
+
*
|
|
11
|
+
* Handles: single/double/backtick quoted strings (incl. `''` escapes), `--`
|
|
12
|
+
* line comments, block comments. Semicolons inside strings or comments do not
|
|
13
|
+
* split. Statements come back trimmed, without the terminating semicolon, and
|
|
14
|
+
* with their leading comments attached — use `executableStatements` to drop
|
|
15
|
+
* comment-only runs before sending anything to D1.
|
|
16
|
+
*
|
|
17
|
+
* Trigger-aware: `CREATE TRIGGER ... BEGIN ...; ... END;` bodies contain
|
|
18
|
+
* semicolons that must not split. The splitter tracks the outermost BEGIN/END
|
|
19
|
+
* depth after a CREATE TRIGGER header and only splits on the terminating
|
|
20
|
+
* semicolon after the final END.
|
|
21
|
+
*/
|
|
22
|
+
function splitSql(sql) {
|
|
23
|
+
const statements = [];
|
|
24
|
+
let current = '';
|
|
25
|
+
let inString = false;
|
|
26
|
+
let stringChar = '';
|
|
27
|
+
let inLineComment = false;
|
|
28
|
+
let inBlockComment = false;
|
|
29
|
+
let inTrigger = false;
|
|
30
|
+
let triggerDepth = 0;
|
|
31
|
+
let word = '';
|
|
32
|
+
const recentKeywords = [];
|
|
33
|
+
const flushWord = () => {
|
|
34
|
+
if (word.length === 0)
|
|
35
|
+
return;
|
|
36
|
+
const upper = word.toUpperCase();
|
|
37
|
+
word = '';
|
|
38
|
+
recentKeywords.push(upper);
|
|
39
|
+
if (recentKeywords.length > 6)
|
|
40
|
+
recentKeywords.shift();
|
|
41
|
+
// `CREATE [TEMP|TEMPORARY] TRIGGER` opens a body; a table merely named
|
|
42
|
+
// "trigger" (`CREATE TABLE trigger`) must not. The word before TRIGGER
|
|
43
|
+
// disambiguates.
|
|
44
|
+
if (upper === 'TRIGGER' && ['CREATE', 'TEMP', 'TEMPORARY'].includes(recentKeywords.at(-2) ?? '')) {
|
|
45
|
+
inTrigger = true;
|
|
46
|
+
triggerDepth = 0;
|
|
47
|
+
}
|
|
48
|
+
else if (inTrigger && upper === 'BEGIN') {
|
|
49
|
+
triggerDepth += 1;
|
|
50
|
+
}
|
|
51
|
+
else if (inTrigger && upper === 'END') {
|
|
52
|
+
triggerDepth -= 1;
|
|
53
|
+
if (triggerDepth <= 0) {
|
|
54
|
+
inTrigger = false;
|
|
55
|
+
triggerDepth = 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
let index = 0;
|
|
60
|
+
while (index < sql.length) {
|
|
61
|
+
const char = sql[index];
|
|
62
|
+
const next = sql[index + 1] ?? '';
|
|
63
|
+
if (inLineComment) {
|
|
64
|
+
current += char;
|
|
65
|
+
if (char === '\n')
|
|
66
|
+
inLineComment = false;
|
|
67
|
+
index += 1;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (inBlockComment) {
|
|
71
|
+
current += char;
|
|
72
|
+
if (char === '*' && next === '/') {
|
|
73
|
+
current += next;
|
|
74
|
+
index += 2;
|
|
75
|
+
inBlockComment = false;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
index += 1;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (inString) {
|
|
82
|
+
current += char;
|
|
83
|
+
if (char === stringChar) {
|
|
84
|
+
// SQL escapes a quote by doubling it ('it''s'); do not end the string.
|
|
85
|
+
if (sql[index + 1] === stringChar) {
|
|
86
|
+
current += sql[index + 1];
|
|
87
|
+
index += 2;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (sql[index - 1] !== '\\')
|
|
91
|
+
inString = false;
|
|
92
|
+
}
|
|
93
|
+
index += 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Buffer word characters for CREATE TRIGGER / BEGIN / END tracking.
|
|
97
|
+
// Any other character ends the current word.
|
|
98
|
+
if (/[\w$]/.test(char)) {
|
|
99
|
+
word += char;
|
|
100
|
+
current += char;
|
|
101
|
+
index += 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
flushWord();
|
|
105
|
+
if (char === '-' && next === '-') {
|
|
106
|
+
inLineComment = true;
|
|
107
|
+
current += char;
|
|
108
|
+
index += 1;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (char === '/' && next === '*') {
|
|
112
|
+
inBlockComment = true;
|
|
113
|
+
current += char;
|
|
114
|
+
index += 1;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (char === "'" || char === '"' || char === '`') {
|
|
118
|
+
inString = true;
|
|
119
|
+
stringChar = char;
|
|
120
|
+
current += char;
|
|
121
|
+
index += 1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (char === ';') {
|
|
125
|
+
// Semicolons inside a trigger body do not terminate the statement.
|
|
126
|
+
if (inTrigger) {
|
|
127
|
+
current += char;
|
|
128
|
+
index += 1;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const trimmed = current.trim();
|
|
132
|
+
if (trimmed.length > 0)
|
|
133
|
+
statements.push(trimmed);
|
|
134
|
+
current = '';
|
|
135
|
+
index += 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
current += char;
|
|
139
|
+
index += 1;
|
|
140
|
+
}
|
|
141
|
+
const trimmed = current.trim();
|
|
142
|
+
if (trimmed.length > 0)
|
|
143
|
+
statements.push(trimmed);
|
|
144
|
+
return statements;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Whether a statement is executable SQL rather than a comment.
|
|
148
|
+
*
|
|
149
|
+
* Checked after the split, because the splitter has to keep comments attached to
|
|
150
|
+
* the statement they sit above — dropping them there would change what an error
|
|
151
|
+
* message quotes.
|
|
152
|
+
*/
|
|
153
|
+
function isExecutable(statement) {
|
|
154
|
+
const withoutComments = statement
|
|
155
|
+
.replaceAll(/--[^\n]*/g, '')
|
|
156
|
+
.replaceAll(/\/\*[\s\S]*?\*\//g, '');
|
|
157
|
+
return withoutComments.trim().length > 0;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The executable statements of one migration file, in order: split first, then
|
|
161
|
+
* filtered. A splitter that dropped comments itself would also drop the ones a
|
|
162
|
+
* migration keeps beside the statement they describe.
|
|
163
|
+
*/
|
|
164
|
+
function executableStatements(sql) {
|
|
165
|
+
return splitSql(sql).filter(isExecutable);
|
|
166
|
+
}
|
|
167
|
+
export { executableStatements, isExecutable, splitSql };
|
|
168
|
+
//# sourceMappingURL=splitSql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitSql.js","sourceRoot":"","sources":["../src/splitSql.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rexezuge/d1",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Rexezuge-CloudflareWorkers/SDK.git",
|
|
9
|
+
"directory": "packages/d1"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./*": {
|
|
20
|
+
"types": "./dist/*.d.ts",
|
|
21
|
+
"default": "./dist/*.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@rexezuge/shared": "1.0.0",
|
|
29
|
+
"@rexezuge/errors": "1.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "26.6.4",
|
|
33
|
+
"typescript": "6.0.3",
|
|
34
|
+
"vitest": "4.1.11"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc -p tsconfig.json",
|
|
38
|
+
"typecheck": "tsc -p tsconfig.test.json --noEmit",
|
|
39
|
+
"test": "vitest run"
|
|
40
|
+
}
|
|
6
41
|
}
|
package/README.md
DELETED