@rdlabo/workers-hono-kit 0.1.0 → 0.2.1
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/README.md +126 -11
- package/dist/ai/gateway.d.ts +54 -16
- package/dist/ai/gateway.js +37 -12
- package/dist/aws/cloudfront.d.ts +23 -5
- package/dist/aws/cloudfront.js +45 -6
- package/dist/aws/secrets-manager.d.ts +38 -4
- package/dist/aws/secrets-manager.js +48 -3
- package/dist/cache/kv-cache.d.ts +173 -10
- package/dist/cache/kv-cache.js +139 -7
- package/dist/db/connection.d.ts +56 -14
- package/dist/db/connection.js +39 -13
- package/dist/db/database.d.ts +160 -24
- package/dist/db/database.js +51 -7
- package/dist/db/index.d.ts +21 -10
- package/dist/db/index.js +17 -8
- package/dist/db/jst.d.ts +89 -6
- package/dist/db/jst.js +89 -23
- package/dist/db/orm-config.d.ts +61 -19
- package/dist/db/orm-config.js +43 -14
- package/dist/db/retry.d.ts +25 -3
- package/dist/db/retry.js +25 -3
- package/dist/db/write-result.d.ts +27 -4
- package/dist/db/write-result.js +22 -1
- package/dist/firebase/firebase-verifier.d.ts +53 -4
- package/dist/firebase/identity-toolkit.d.ts +54 -5
- package/dist/firebase/identity-toolkit.js +51 -0
- package/dist/firebase/jose-firebase-verifier.d.ts +81 -9
- package/dist/firebase/jose-firebase-verifier.js +68 -7
- package/dist/firebase/remote-verifier.d.ts +43 -5
- package/dist/firebase/remote-verifier.js +60 -11
- package/dist/http/app-env.d.ts +41 -8
- package/dist/http/app-env.js +38 -8
- package/dist/http/app-info.d.ts +25 -3
- package/dist/http/app-info.js +16 -2
- package/dist/http/http-status.d.ts +12 -3
- package/dist/http/http-status.js +12 -3
- package/dist/http/nest-error.d.ts +90 -29
- package/dist/http/nest-error.js +59 -18
- package/dist/http/user-protocol.d.ts +23 -3
- package/dist/http/user-protocol.js +14 -2
- package/dist/index.d.ts +41 -30
- package/dist/index.js +29 -21
- package/dist/middleware/auth.d.ts +75 -14
- package/dist/middleware/auth.js +31 -7
- package/dist/middleware/finalize-response.d.ts +30 -0
- package/dist/middleware/finalize-response.js +41 -12
- package/dist/middleware/validation.d.ts +83 -9
- package/dist/middleware/validation.js +52 -9
- package/dist/middleware/zod-coerce.d.ts +56 -2
- package/dist/middleware/zod-coerce.js +68 -9
- package/dist/stripe/client.d.ts +46 -9
- package/dist/stripe/client.js +41 -3
- package/dist/testing/auth.d.ts +60 -12
- package/dist/testing/auth.js +58 -10
- package/dist/testing/configurable-fake.d.ts +20 -9
- package/dist/testing/configurable-fake.js +23 -11
- package/dist/testing/db.d.ts +81 -12
- package/dist/testing/db.js +23 -1
- package/dist/testing/fakes.d.ts +79 -11
- package/dist/testing/fakes.js +70 -8
- package/dist/testing/index.d.ts +15 -8
- package/dist/testing/index.js +15 -10
- package/dist/testing/stripe-fixtures.d.ts +93 -3
- package/dist/testing/stripe-fixtures.js +93 -3
- package/package.json +19 -9
- package/src/ai/gateway.ts +66 -27
- package/src/aws/cloudfront.ts +46 -6
- package/src/aws/secrets-manager.ts +56 -7
- package/src/cache/kv-cache.ts +194 -12
- package/src/db/connection.ts +56 -14
- package/src/db/database.ts +163 -27
- package/src/db/index.ts +21 -12
- package/src/db/jst.ts +89 -23
- package/src/db/orm-config.ts +61 -19
- package/src/db/retry.ts +25 -3
- package/src/db/write-result.ts +27 -4
- package/src/firebase/firebase-verifier.ts +53 -4
- package/src/firebase/identity-toolkit.ts +57 -5
- package/src/firebase/jose-firebase-verifier.ts +81 -11
- package/src/firebase/remote-verifier.ts +61 -12
- package/src/http/app-env.ts +41 -8
- package/src/http/app-info.ts +25 -3
- package/src/http/http-status.ts +12 -3
- package/src/http/nest-error.ts +106 -37
- package/src/http/user-protocol.ts +23 -3
- package/src/index.ts +47 -33
- package/src/middleware/auth.ts +79 -17
- package/src/middleware/finalize-response.ts +41 -12
- package/src/middleware/validation.ts +89 -15
- package/src/middleware/zod-coerce.ts +68 -9
- package/src/stripe/client.ts +46 -9
- package/src/testing/auth.ts +60 -12
- package/src/testing/configurable-fake.ts +23 -11
- package/src/testing/db.ts +82 -13
- package/src/testing/fakes.ts +80 -12
- package/src/testing/index.ts +18 -13
- package/src/testing/stripe-fixtures.ts +93 -3
package/src/db/database.ts
CHANGED
|
@@ -1,43 +1,108 @@
|
|
|
1
1
|
import { createConnection } from 'mysql2/promise';
|
|
2
2
|
import type { Connection, Pool } from 'mysql2/promise';
|
|
3
|
-
import { hyperdriveConnectionOptions } from './connection';
|
|
4
|
-
import type { HyperdriveLike } from './connection';
|
|
5
|
-
import { retryWhenDeadlock } from './retry';
|
|
3
|
+
import { hyperdriveConnectionOptions } from './connection.js';
|
|
4
|
+
import type { HyperdriveLike } from './connection.js';
|
|
5
|
+
import { retryWhenDeadlock } from './retry.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
8
|
+
* Dual-connection data layer that separates reads from writes.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* The two sides of the database are deliberately handled differently:
|
|
12
|
+
*
|
|
13
|
+
* - Reads go to the **replica** as raw SQL (`QueryRunner.query`) for transparency, returning plain
|
|
14
|
+
* rows.
|
|
15
|
+
* - Writes and transactions go to the **primary** through the Drizzle ORM for type safety, but only
|
|
16
|
+
* via `write(fn)` / `transaction(fn)` — the raw query builder is never exposed. The builder is
|
|
17
|
+
* awaited inside those methods, which removes a foot-gun: a Drizzle builder is a lazy thenable, so
|
|
18
|
+
* a bare `return builder` would silently become a no-op.
|
|
19
|
+
*
|
|
20
|
+
* Both sides retry on `ER_LOCK_DEADLOCK`.
|
|
21
|
+
*
|
|
22
|
+
* The kit deliberately avoids depending on the type identity of `drizzle-orm`: the consumer creates
|
|
23
|
+
* the ORM instance with its own copy of `drizzle-orm` and passes it in, and {@link Database} is
|
|
24
|
+
* generic over that ORM type (`TDrizzle`). This keeps the ORM's `MySqlTable`/`SQL` brands from
|
|
25
|
+
* clashing even when the kit and the consumer resolve separate copies of `drizzle-orm`.
|
|
17
26
|
*/
|
|
18
27
|
|
|
19
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Minimal connection interface used for reads.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* A mysql2 `Connection` or `Pool` satisfies this structurally.
|
|
33
|
+
*/
|
|
20
34
|
export interface QueryRunner {
|
|
35
|
+
/**
|
|
36
|
+
* Run a parameterized SQL query.
|
|
37
|
+
*
|
|
38
|
+
* @param sql - the SQL text, with `?` placeholders for `params`.
|
|
39
|
+
* @param params - optional positional parameters.
|
|
40
|
+
* @returns the driver's raw result (typically `[rows, fields]`).
|
|
41
|
+
*/
|
|
21
42
|
query(sql: string, params?: unknown[]): Promise<unknown>;
|
|
22
43
|
}
|
|
23
44
|
|
|
24
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Extract the transaction-handle type that a Drizzle instance passes to its `.transaction(cb)`
|
|
47
|
+
* callback.
|
|
48
|
+
*
|
|
49
|
+
* @typeParam TDrizzle - the consumer's Drizzle ORM type.
|
|
50
|
+
*/
|
|
25
51
|
export type TxOf<TDrizzle> = TDrizzle extends {
|
|
26
52
|
transaction(cb: (tx: infer Tx) => Promise<unknown>): Promise<unknown>;
|
|
27
53
|
}
|
|
28
54
|
? Tx
|
|
29
55
|
: unknown;
|
|
30
56
|
|
|
57
|
+
/**
|
|
58
|
+
* The read/write surface of the data layer.
|
|
59
|
+
*
|
|
60
|
+
* @typeParam TDrizzle - the consumer's Drizzle ORM type used for writes and transactions.
|
|
61
|
+
* @typeParam TTx - the transaction-handle type, inferred from `TDrizzle` by default.
|
|
62
|
+
*/
|
|
31
63
|
export interface Database<TDrizzle, TTx = TxOf<TDrizzle>> {
|
|
64
|
+
/**
|
|
65
|
+
* Run a raw SQL read against the replica, with deadlock retry.
|
|
66
|
+
*
|
|
67
|
+
* @typeParam T - the row shape.
|
|
68
|
+
* @param sql - the SQL text, with `?` placeholders for `params`.
|
|
69
|
+
* @param params - optional positional parameters.
|
|
70
|
+
* @returns the rows returned by the query.
|
|
71
|
+
*/
|
|
32
72
|
read<T>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
33
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Run a single INSERT/UPDATE/DELETE against the primary, awaited with deadlock retry.
|
|
75
|
+
*
|
|
76
|
+
* @typeParam T - the value resolved by `fn`.
|
|
77
|
+
* @param fn - callback that receives the Drizzle ORM and returns the awaited write.
|
|
78
|
+
* @returns the value resolved by `fn`.
|
|
79
|
+
*/
|
|
34
80
|
write<T>(fn: (dz: TDrizzle) => Promise<T>): Promise<T>;
|
|
35
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Run multiple writes inside a single transaction; the whole transaction is retried on deadlock.
|
|
83
|
+
*
|
|
84
|
+
* @typeParam T - the value resolved by `fn`.
|
|
85
|
+
* @param fn - callback that receives the transaction handle and returns the awaited work.
|
|
86
|
+
* @returns the value resolved by `fn`.
|
|
87
|
+
*/
|
|
36
88
|
transaction<T>(fn: (tx: TTx) => Promise<T>): Promise<T>;
|
|
37
89
|
}
|
|
38
90
|
|
|
39
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* A {@link Database} that owns its connections and must be disposed.
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
95
|
+
* Used by the variants that open connections internally (Hyperdrive- or Pool-backed).
|
|
96
|
+
*
|
|
97
|
+
* @typeParam TDrizzle - the consumer's Drizzle ORM type.
|
|
98
|
+
* @typeParam TTx - the transaction-handle type, inferred from `TDrizzle` by default.
|
|
99
|
+
*/
|
|
40
100
|
export interface DisposableDatabase<TDrizzle, TTx = TxOf<TDrizzle>> extends Database<TDrizzle, TTx> {
|
|
101
|
+
/**
|
|
102
|
+
* Close the connections opened by this database.
|
|
103
|
+
*
|
|
104
|
+
* @returns a promise that settles once both connections are closed.
|
|
105
|
+
*/
|
|
41
106
|
dispose(): Promise<void>;
|
|
42
107
|
}
|
|
43
108
|
|
|
@@ -45,33 +110,91 @@ interface DrizzleLike<TTx> {
|
|
|
45
110
|
transaction<T>(cb: (tx: TTx) => Promise<T>): Promise<T>;
|
|
46
111
|
}
|
|
47
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Options for {@link createMysqlDatabase}.
|
|
115
|
+
*
|
|
116
|
+
* @typeParam TDrizzle - the consumer's Drizzle ORM type.
|
|
117
|
+
*/
|
|
48
118
|
export interface CreateMysqlDatabaseOptions<TDrizzle> {
|
|
49
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* The Drizzle ORM used for writes, created by the consumer with its own `drizzle-orm`
|
|
121
|
+
* (e.g. `drizzle(primary, { schema, ... })`).
|
|
122
|
+
*/
|
|
50
123
|
orm: TDrizzle;
|
|
51
|
-
/** reads
|
|
124
|
+
/** The connection used for reads (raw SQL). */
|
|
52
125
|
replica: QueryRunner;
|
|
53
126
|
}
|
|
54
127
|
|
|
55
128
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
129
|
+
* Assemble a {@link Database} from an already-connected ORM and replica.
|
|
130
|
+
*
|
|
131
|
+
* @remarks
|
|
132
|
+
* The caller (typically the worker entry point) owns creating the connections and the ORM, and is
|
|
133
|
+
* responsible for closing the connections; this variant does not manage their lifecycle.
|
|
134
|
+
*
|
|
135
|
+
* @typeParam TDrizzle - the consumer's Drizzle ORM type.
|
|
136
|
+
* @param options - the write ORM and the read connection.
|
|
137
|
+
* @returns a {@link Database} backed by the supplied ORM and replica.
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* const db = createMysqlDatabase({
|
|
141
|
+
* orm: drizzle(primary, { schema, ...DRIZZLE_ORM_OPTIONS }),
|
|
142
|
+
* replica,
|
|
143
|
+
* });
|
|
144
|
+
* const rows = await db.read<User>('SELECT * FROM users WHERE id = ?', [id]);
|
|
145
|
+
* ```
|
|
58
146
|
*/
|
|
59
147
|
export function createMysqlDatabase<TDrizzle>(options: CreateMysqlDatabaseOptions<TDrizzle>): Database<TDrizzle> {
|
|
60
148
|
return databaseFrom(options.orm, options.replica);
|
|
61
149
|
}
|
|
62
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Options for {@link createHyperdriveDatabase}.
|
|
153
|
+
*
|
|
154
|
+
* @typeParam TDrizzle - the consumer's Drizzle ORM type.
|
|
155
|
+
*/
|
|
63
156
|
export interface CreateHyperdriveDatabaseOptions<TDrizzle> {
|
|
157
|
+
/** The Hyperdrive binding for the primary (write) connection. */
|
|
64
158
|
primaryHyperdrive: HyperdriveLike;
|
|
159
|
+
/** The Hyperdrive binding for the replica (read) connection. */
|
|
65
160
|
replicaHyperdrive: HyperdriveLike;
|
|
66
|
-
/**
|
|
161
|
+
/**
|
|
162
|
+
* Factory that builds the write ORM from the primary connection, using the consumer's
|
|
163
|
+
* `drizzle-orm`.
|
|
164
|
+
*/
|
|
67
165
|
createOrm: (primary: Connection) => TDrizzle;
|
|
68
|
-
/**
|
|
166
|
+
/**
|
|
167
|
+
* Extra options forwarded to mysql2 `createConnection`, merged on top of the defaults applied by
|
|
168
|
+
* {@link hyperdriveConnectionOptions} (`disableEval: true`, `decimalNumbers: true`, and
|
|
169
|
+
* `timezone: '+09:00'`). Pass a field here to override any of those defaults.
|
|
170
|
+
*/
|
|
69
171
|
connectionOptions?: Record<string, unknown>;
|
|
70
172
|
}
|
|
71
173
|
|
|
72
174
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
175
|
+
* Create a {@link DisposableDatabase} that lazily opens its connections from Hyperdrive bindings.
|
|
176
|
+
*
|
|
177
|
+
* @remarks
|
|
178
|
+
* Construct one per request and call `dispose()` after the response to close the connections.
|
|
179
|
+
* Connections and the ORM are created on first use and reused for the lifetime of the instance; the
|
|
180
|
+
* read/write/transaction surface is identical to {@link createMysqlDatabase}.
|
|
181
|
+
*
|
|
182
|
+
* @typeParam TDrizzle - the consumer's Drizzle ORM type.
|
|
183
|
+
* @param options - the primary/replica Hyperdrive bindings, the ORM factory, and connection options.
|
|
184
|
+
* @returns a {@link DisposableDatabase} that must be disposed when done.
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* const db = createHyperdriveDatabase({
|
|
188
|
+
* primaryHyperdrive: env.PRIMARY,
|
|
189
|
+
* replicaHyperdrive: env.REPLICA,
|
|
190
|
+
* createOrm: (primary) => drizzle(primary, { schema, ...DRIZZLE_ORM_OPTIONS }),
|
|
191
|
+
* });
|
|
192
|
+
* try {
|
|
193
|
+
* await db.write((dz) => dz.insert(users).values(user));
|
|
194
|
+
* } finally {
|
|
195
|
+
* await db.dispose();
|
|
196
|
+
* }
|
|
197
|
+
* ```
|
|
75
198
|
*/
|
|
76
199
|
export function createHyperdriveDatabase<TDrizzle>(
|
|
77
200
|
options: CreateHyperdriveDatabaseOptions<TDrizzle>,
|
|
@@ -106,7 +229,15 @@ export function createHyperdriveDatabase<TDrizzle>(
|
|
|
106
229
|
};
|
|
107
230
|
}
|
|
108
231
|
|
|
109
|
-
/**
|
|
232
|
+
/**
|
|
233
|
+
* Internal helper that assembles a {@link Database} from an ORM and a replica connection.
|
|
234
|
+
*
|
|
235
|
+
* @typeParam TDrizzle - the consumer's Drizzle ORM type.
|
|
236
|
+
* @param orm - the Drizzle ORM used for writes and transactions.
|
|
237
|
+
* @param replica - the connection used for reads.
|
|
238
|
+
* @returns a {@link Database} wiring reads to `replica` and writes to `orm`, both with deadlock retry.
|
|
239
|
+
* @internal
|
|
240
|
+
*/
|
|
110
241
|
export function databaseFrom<TDrizzle>(orm: TDrizzle, replica: QueryRunner): Database<TDrizzle> {
|
|
111
242
|
const drizzleLike = orm as DrizzleLike<TxOf<TDrizzle>>;
|
|
112
243
|
return {
|
|
@@ -125,7 +256,12 @@ export function databaseFrom<TDrizzle>(orm: TDrizzle, replica: QueryRunner): Dat
|
|
|
125
256
|
};
|
|
126
257
|
}
|
|
127
258
|
|
|
128
|
-
/**
|
|
259
|
+
/**
|
|
260
|
+
* Re-export of the mysql2 `Connection` and `Pool` types.
|
|
261
|
+
*
|
|
262
|
+
* @remarks
|
|
263
|
+
* Both are structurally assignable to the kit's {@link QueryRunner}.
|
|
264
|
+
*/
|
|
129
265
|
export type { Connection, Pool };
|
|
130
266
|
|
|
131
267
|
function connect(hyperdrive: HyperdriveLike, extra?: Record<string, unknown>): Promise<Connection> {
|
package/src/db/index.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Data-layer helpers that depend on `mysql2` (exposed under the `/db` subpath because the package
|
|
3
|
+
* root is reserved for web-standard-only code).
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* This module never depends on the type identity of `drizzle-orm`: the ORM instance is always
|
|
7
|
+
* supplied by the consumer. That keeps the kit safe to use even when the kit and the consuming app
|
|
8
|
+
* resolve separate copies of `drizzle-orm`.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
3
12
|
|
|
4
|
-
export { retryWhenDeadlock } from './retry';
|
|
13
|
+
export { retryWhenDeadlock } from './retry.js';
|
|
5
14
|
|
|
6
|
-
export { createMysqlDatabase, createHyperdriveDatabase, databaseFrom } from './database';
|
|
15
|
+
export { createMysqlDatabase, createHyperdriveDatabase, databaseFrom } from './database.js';
|
|
7
16
|
export type {
|
|
8
17
|
Database,
|
|
9
18
|
DisposableDatabase,
|
|
@@ -13,15 +22,15 @@ export type {
|
|
|
13
22
|
CreateHyperdriveDatabaseOptions,
|
|
14
23
|
Connection,
|
|
15
24
|
Pool,
|
|
16
|
-
} from './database';
|
|
25
|
+
} from './database.js';
|
|
17
26
|
|
|
18
|
-
export { insertIdOf, affectedRowsOf, insertedIdsOf } from './write-result';
|
|
19
|
-
export type { DzWriteResult } from './write-result';
|
|
27
|
+
export { insertIdOf, affectedRowsOf, insertedIdsOf } from './write-result.js';
|
|
28
|
+
export type { DzWriteResult } from './write-result.js';
|
|
20
29
|
|
|
21
|
-
export { hyperdriveConnectionOptions, withMysqlConnections } from './connection';
|
|
22
|
-
export type { HyperdriveLike, ExecutionContextLike } from './connection';
|
|
30
|
+
export { hyperdriveConnectionOptions, withMysqlConnections } from './connection.js';
|
|
31
|
+
export type { HyperdriveLike, ExecutionContextLike } from './connection.js';
|
|
23
32
|
|
|
24
|
-
export { toJstDate, jstTimestampParams, jstDatetimeParams, jstDateParams } from './jst';
|
|
33
|
+
export { toJstDate, jstTimestampParams, jstDatetimeParams, jstDateParams } from './jst.js';
|
|
25
34
|
|
|
26
|
-
export { DRIZZLE_ORM_OPTIONS, honoDrizzleConfig } from './orm-config';
|
|
27
|
-
export type { HonoDrizzleConfigOptions } from './orm-config';
|
|
35
|
+
export { DRIZZLE_ORM_OPTIONS, honoDrizzleConfig } from './orm-config.js';
|
|
36
|
+
export type { HonoDrizzleConfigOptions } from './orm-config.js';
|
package/src/db/jst.ts
CHANGED
|
@@ -1,27 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Shared building blocks for normalizing JST (Asia/Tokyo) date/time values.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* JST normalization is applied at the column boundary as a write-time side effect, and neither
|
|
6
|
+
* values nor types from `drizzle-orm` are imported here on purpose. Exporting a fully-built
|
|
7
|
+
* `customType` column from the kit would cause type collisions when the kit and the consumer
|
|
8
|
+
* resolve separate copies of `drizzle-orm` (the private `SQL` brand stops being nominally
|
|
9
|
+
* compatible). Instead the kit ships only the params and helpers, and the consumer builds the
|
|
10
|
+
* column with its own `customType`:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { customType } from 'drizzle-orm/mysql-core';
|
|
14
|
+
* import { jstTimestampParams, jstDateParams } from '@rdlabo/workers-hono-kit/db';
|
|
15
|
+
*
|
|
16
|
+
* export const jstTimestamp = (name: string, opts?: { fsp?: number }) =>
|
|
17
|
+
* customType<{ data: string | Date; driverData: string | Date }>(jstTimestampParams(opts?.fsp))(name);
|
|
18
|
+
* export const jstDate = (name: string) =>
|
|
19
|
+
* customType<{ data: string | null; driverData: string | null }>(jstDateParams())(name);
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* `timestamp`/`datetime` columns omit `toDriver` and pass `Date` values straight through, so the
|
|
23
|
+
* connection's `timezone: '+09:00'` default makes mysql2 format them as JST; pre-formatted strings
|
|
24
|
+
* also pass through. Drizzle's native `mode: 'date'` is avoided because it stringifies `Date` to
|
|
25
|
+
* UTC before the timezone layer, shifting values by -9h. `date` columns keep `toJstDate` because
|
|
26
|
+
* MySQL `DATE` rejects ISO/empty strings and a JST day-boundary normalization is required.
|
|
27
|
+
*/
|
|
18
28
|
|
|
19
29
|
const JST_OFFSET_MS = 9 * 60 * 60 * 1000;
|
|
20
30
|
|
|
21
31
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
32
|
+
* Normalize a client-supplied date to the `YYYY-MM-DD` (JST) form accepted by a MySQL `DATE` column.
|
|
33
|
+
*
|
|
34
|
+
* Accepts ISO 8601 (`...Z`), `YYYY-MM-DD`, or an empty string. Nullish, empty, or unparseable input
|
|
35
|
+
* resolves to `null`.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* MySQL `DATE` rejects ISO strings with `ER_TRUNCATED_WRONG_VALUE`, so this cannot be handled by the
|
|
39
|
+
* driver alone; it is needed as the `toDriver` transform for a `date` column.
|
|
40
|
+
*
|
|
41
|
+
* @param value - the raw date string from the client (ISO 8601, `YYYY-MM-DD`, or empty), or nullish.
|
|
42
|
+
* @returns the JST calendar date as `YYYY-MM-DD`, or `null` when the input is empty or unparseable.
|
|
25
43
|
*/
|
|
26
44
|
export function toJstDate(value: string | null | undefined): string | null {
|
|
27
45
|
if (!value) {
|
|
@@ -36,17 +54,65 @@ export function toJstDate(value: string | null | undefined): string | null {
|
|
|
36
54
|
return `${jst.getUTCFullYear()}-${p(jst.getUTCMonth() + 1)}-${p(jst.getUTCDate())}`;
|
|
37
55
|
}
|
|
38
56
|
|
|
39
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Build the params for a `customType` backing a MySQL `timestamp` column with `Date` pass-through.
|
|
59
|
+
*
|
|
60
|
+
* The column omits `toDriver`, so `Date` values flow straight to mysql2 and are formatted as JST by
|
|
61
|
+
* the connection's `timezone: '+09:00'` default.
|
|
62
|
+
*
|
|
63
|
+
* @param fsp - optional fractional-seconds precision; when provided, emits `timestamp(fsp)`.
|
|
64
|
+
* @returns the `customType` params object exposing the column's `dataType`.
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* import { customType } from 'drizzle-orm/mysql-core';
|
|
68
|
+
* import { jstTimestampParams } from '@rdlabo/workers-hono-kit/db';
|
|
69
|
+
*
|
|
70
|
+
* const jstTimestamp = (name: string) =>
|
|
71
|
+
* customType<{ data: string | Date; driverData: string | Date }>(jstTimestampParams())(name);
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
40
74
|
export const jstTimestampParams = (fsp?: number): { dataType: () => string } => ({
|
|
41
75
|
dataType: () => (fsp != null ? `timestamp(${fsp})` : 'timestamp'),
|
|
42
76
|
});
|
|
43
77
|
|
|
44
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Build the params for a `customType` backing a MySQL `datetime` column with `Date` pass-through.
|
|
80
|
+
*
|
|
81
|
+
* Behaves like {@link jstTimestampParams} but emits a `datetime` data type; `Date` values pass
|
|
82
|
+
* through and are formatted as JST by the connection's `timezone: '+09:00'` default.
|
|
83
|
+
*
|
|
84
|
+
* @param fsp - optional fractional-seconds precision; when provided, emits `datetime(fsp)`.
|
|
85
|
+
* @returns the `customType` params object exposing the column's `dataType`.
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { customType } from 'drizzle-orm/mysql-core';
|
|
89
|
+
* import { jstDatetimeParams } from '@rdlabo/workers-hono-kit/db';
|
|
90
|
+
*
|
|
91
|
+
* const jstDatetime = (name: string) =>
|
|
92
|
+
* customType<{ data: string | Date; driverData: string | Date }>(jstDatetimeParams())(name);
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
45
95
|
export const jstDatetimeParams = (fsp?: number): { dataType: () => string } => ({
|
|
46
96
|
dataType: () => (fsp != null ? `datetime(${fsp})` : 'datetime'),
|
|
47
97
|
});
|
|
48
98
|
|
|
49
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* Build the params for a `customType` backing a MySQL `date` column with JST normalization.
|
|
101
|
+
*
|
|
102
|
+
* Unlike the timestamp/datetime params, this defines a `toDriver` transform that runs
|
|
103
|
+
* {@link toJstDate} so client-supplied ISO/empty strings are normalized to a JST `YYYY-MM-DD` value
|
|
104
|
+
* the column accepts.
|
|
105
|
+
*
|
|
106
|
+
* @returns the `customType` params object exposing the column's `dataType` and `toDriver`.
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* import { customType } from 'drizzle-orm/mysql-core';
|
|
110
|
+
* import { jstDateParams } from '@rdlabo/workers-hono-kit/db';
|
|
111
|
+
*
|
|
112
|
+
* const jstDate = (name: string) =>
|
|
113
|
+
* customType<{ data: string | null; driverData: string | null }>(jstDateParams())(name);
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
50
116
|
export const jstDateParams = (): {
|
|
51
117
|
dataType: () => string;
|
|
52
118
|
toDriver: (value: string | null) => string | null;
|
package/src/db/orm-config.ts
CHANGED
|
@@ -1,45 +1,87 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Drizzle
|
|
2
|
+
* Centralizes Drizzle column-name casing so it is fixed (standard: `snake_case`) in both the
|
|
3
|
+
* config and the runtime ORM.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* ② database.ts の `drizzle(conn, { …casing })` … **実行時の書き込みビルダが参照する列名** を決める(→ DRIZZLE_ORM_OPTIONS)
|
|
5
|
+
* @remarks
|
|
6
|
+
* Casing is configured in two distinct places:
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* 1. The top-level `casing` in `drizzle.config.ts` decides the column names that `db:generate`
|
|
9
|
+
* **creates** (see {@link honoDrizzleConfig}).
|
|
10
|
+
* 2. The `drizzle(conn, { …casing })` call decides the column names the **runtime write builder**
|
|
11
|
+
* resolves to (see {@link DRIZZLE_ORM_OPTIONS}).
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* If these two disagree, a multi-word camelCase column without an explicit column name will be
|
|
14
|
+
* generated with one name but queried with another, producing a runtime `Unknown column` error —
|
|
15
|
+
* something neither the type-check nor the migration surface, so it is caught late. Sourcing both
|
|
16
|
+
* from here makes the mismatch structurally impossible. Casing is ignored for columns that declare
|
|
17
|
+
* an explicit name, so this is a pure safety net that does not change existing behavior.
|
|
18
|
+
*
|
|
19
|
+
* The runtime `drizzle()` call itself is made by the consuming app with its own `drizzle-orm`; the
|
|
20
|
+
* kit only ever provides values, never the ORM instance, to avoid splitting `drizzle-orm` into two
|
|
21
|
+
* copies and breaking type identity.
|
|
14
22
|
*/
|
|
15
23
|
|
|
16
24
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
25
|
+
* Runtime ORM options shared by the consuming app's `drizzle()` call.
|
|
26
|
+
*
|
|
27
|
+
* Spread into the runtime ORM as `drizzle(conn, { schema, ...DRIZZLE_ORM_OPTIONS })` so the write
|
|
28
|
+
* builder resolves column names as `snake_case`, matching what `db:generate` creates.
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* Fixes `mode: 'default'` and `casing: 'snake_case'`. See the module-level documentation for why
|
|
32
|
+
* the same casing must be used by both the config and the runtime ORM.
|
|
19
33
|
*/
|
|
20
34
|
export const DRIZZLE_ORM_OPTIONS = { mode: 'default', casing: 'snake_case' } as const;
|
|
21
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Options for {@link honoDrizzleConfig}.
|
|
38
|
+
*/
|
|
22
39
|
export interface HonoDrizzleConfigOptions {
|
|
23
|
-
/** drizzle-kit
|
|
40
|
+
/** drizzle-kit `dbCredentials.database` — the database name to connect to. */
|
|
24
41
|
database: string;
|
|
42
|
+
/** Database host; defaults to `process.env.DB_HOST` then `127.0.0.1`. */
|
|
25
43
|
host?: string;
|
|
44
|
+
/** Database port; defaults to `process.env.DB_PORT` then `3306`. */
|
|
26
45
|
port?: number;
|
|
46
|
+
/** Database user; defaults to `process.env.DB_USER` then `root`. */
|
|
27
47
|
user?: string;
|
|
48
|
+
/** Database password; defaults to `process.env.DB_PASSWORD` then `root`. */
|
|
28
49
|
password?: string;
|
|
29
|
-
/**
|
|
50
|
+
/** Path to the schema directory; defaults to `'./src/db/schemes'`. */
|
|
30
51
|
schema?: string;
|
|
31
|
-
/**
|
|
52
|
+
/** Output directory for generated migrations; defaults to `'./drizzle'`. */
|
|
32
53
|
out?: string;
|
|
33
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Optional table allow-list. Use this to restrict drizzle-kit to the schema's own tables when the
|
|
56
|
+
* database is shared with another application.
|
|
57
|
+
*/
|
|
34
58
|
tablesFilter?: string[];
|
|
35
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Optional `db:introspect` (DB → JS) casing. This is an independent axis from the generation-side
|
|
61
|
+
* `casing: 'snake_case'` and only affects introspection output.
|
|
62
|
+
*/
|
|
36
63
|
introspect?: { casing: 'camel' | 'preserve' };
|
|
37
64
|
}
|
|
38
65
|
|
|
39
66
|
/**
|
|
40
|
-
* drizzle.config.ts
|
|
41
|
-
*
|
|
42
|
-
*
|
|
67
|
+
* Build a `drizzle.config.ts` configuration object with the kit's standard defaults.
|
|
68
|
+
*
|
|
69
|
+
* Fixes `casing: 'snake_case'`, the `schema`/`out` paths, and `dbCredentials` (with env-based
|
|
70
|
+
* defaults), while leaving `tablesFilter` and `introspect` opt-in.
|
|
71
|
+
*
|
|
72
|
+
* @remarks
|
|
73
|
+
* Returns a plain object rather than a typed drizzle-kit config so that `drizzle-kit` need not be a
|
|
74
|
+
* dependency of the kit; the drizzle-kit CLI only reads the default export.
|
|
75
|
+
*
|
|
76
|
+
* @param options - configuration overrides; only `database` is required.
|
|
77
|
+
* @returns a plain configuration object suitable for `export default` in `drizzle.config.ts`.
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* // drizzle.config.ts
|
|
81
|
+
* import { honoDrizzleConfig } from '@rdlabo/workers-hono-kit/db';
|
|
82
|
+
*
|
|
83
|
+
* export default honoDrizzleConfig({ database: 'app' });
|
|
84
|
+
* ```
|
|
43
85
|
*/
|
|
44
86
|
export function honoDrizzleConfig(options: HonoDrizzleConfigOptions) {
|
|
45
87
|
const {
|
package/src/db/retry.ts
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Run an async unit of work, retrying it on MySQL deadlock errors with exponential backoff.
|
|
3
|
+
*
|
|
4
|
+
* Retries are triggered only by the `ER_LOCK_DEADLOCK` error code. Each failed attempt waits
|
|
5
|
+
* `delay * attempt` milliseconds (linear growth of the base delay) before the next try, and any
|
|
6
|
+
* non-deadlock error is rethrown immediately without retrying.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* MySQL rolls back the entire transaction when it detects a deadlock, so re-running the same unit
|
|
10
|
+
* of work is safe. Pass a `fn` that represents one complete unit — a single statement or an entire
|
|
11
|
+
* transaction — because the whole `fn` is re-executed on each retry.
|
|
12
|
+
*
|
|
13
|
+
* @typeParam T - resolved value produced by `fn`.
|
|
14
|
+
* @param fn - the unit of work to execute; it is invoked again from scratch on each retry.
|
|
15
|
+
* @param retries - maximum number of attempts (default `3`).
|
|
16
|
+
* @param delay - base backoff in milliseconds; attempt N waits `delay * N` (default `100`).
|
|
17
|
+
* @returns the value resolved by the first successful call to `fn`.
|
|
18
|
+
* @throws the last error thrown by `fn` once retries are exhausted, or any non-deadlock error on
|
|
19
|
+
* the first occurrence.
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* await retryWhenDeadlock(() => db.transaction(async (tx) => {
|
|
23
|
+
* await tx.insert(orders).values(order);
|
|
24
|
+
* await tx.update(stock).set({ qty: sql`qty - 1` }).where(eq(stock.id, order.itemId));
|
|
25
|
+
* }));
|
|
26
|
+
* ```
|
|
5
27
|
*/
|
|
6
28
|
export async function retryWhenDeadlock<T>(fn: () => Promise<T>, retries = 3, delay = 100): Promise<T> {
|
|
7
29
|
for (let attempt = 0; attempt < retries; attempt++) {
|
package/src/db/write-result.ts
CHANGED
|
@@ -1,21 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Drizzle(mysql2)
|
|
3
|
-
* 生の builder/ResultSetHeader を repo 側に晒さずに、よく使う値だけを取り出す。
|
|
2
|
+
* Shape of a Drizzle (mysql2) write result, narrowed to the fields callers actually read.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* A mysql2 INSERT/UPDATE/DELETE result is the tuple `[ResultSetHeader, FieldPacket[]]`. Typing the
|
|
6
|
+
* result this way lets repositories extract the common values without exposing the raw query
|
|
7
|
+
* builder or the full `ResultSetHeader` to the rest of the codebase.
|
|
6
8
|
*/
|
|
7
9
|
export type DzWriteResult = readonly [{ insertId: number; affectedRows: number }, ...unknown[]];
|
|
8
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Extract the auto-increment `insertId` from a write result.
|
|
13
|
+
*
|
|
14
|
+
* @param result - the result of a Drizzle (mysql2) INSERT/UPDATE/DELETE.
|
|
15
|
+
* @returns the `insertId` reported by mysql2 (the id of the first inserted row).
|
|
16
|
+
*/
|
|
9
17
|
export function insertIdOf(result: DzWriteResult): number {
|
|
10
18
|
return result[0].insertId;
|
|
11
19
|
}
|
|
12
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Extract the number of affected rows from a write result.
|
|
23
|
+
*
|
|
24
|
+
* @param result - the result of a Drizzle (mysql2) INSERT/UPDATE/DELETE.
|
|
25
|
+
* @returns the `affectedRows` count reported by mysql2.
|
|
26
|
+
*/
|
|
13
27
|
export function affectedRowsOf(result: DzWriteResult): number {
|
|
14
28
|
return result[0].affectedRows;
|
|
15
29
|
}
|
|
16
30
|
|
|
17
31
|
/**
|
|
18
|
-
*
|
|
32
|
+
* Reconstruct the auto-increment ids assigned by a bulk INSERT.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* mysql2 reports only the first `insertId` for a multi-row INSERT, so the remaining ids are derived
|
|
36
|
+
* by assuming a contiguous sequence (`base`, `base + 1`, …). This holds for tables with a standard
|
|
37
|
+
* `AUTO_INCREMENT` column and the default `innodb_autoinc_lock_mode`.
|
|
38
|
+
*
|
|
39
|
+
* @param result - the result of a bulk INSERT.
|
|
40
|
+
* @param count - the number of rows that were inserted.
|
|
41
|
+
* @returns an array of the `count` auto-increment ids, starting at the reported `insertId`.
|
|
19
42
|
*/
|
|
20
43
|
export function insertedIdsOf(result: DzWriteResult, count: number): number[] {
|
|
21
44
|
const base = result[0].insertId;
|