@rdlabo/workers-hono-kit 0.11.2 → 0.12.0-beta.pr48.sha371f5792ce8a

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.
Files changed (56) hide show
  1. package/README.md +73 -37
  2. package/dist/aws/sts.d.ts +2 -2
  3. package/dist/aws/sts.js +4 -4
  4. package/dist/business-time/index.d.ts +68 -122
  5. package/dist/business-time/index.js +55 -222
  6. package/dist/container/middleware.d.ts +1 -2
  7. package/dist/container/middleware.js +1 -1
  8. package/dist/db/index.d.ts +95 -20
  9. package/dist/db/index.js +56 -15
  10. package/dist/db/payment-failed.d.ts +1 -1
  11. package/dist/db/payment-failed.js +1 -1
  12. package/dist/index.d.ts +0 -3
  13. package/dist/index.js +0 -3
  14. package/dist/mysql/index.d.ts +9 -0
  15. package/dist/mysql/index.js +8 -0
  16. package/dist/testing/auth.d.ts +1 -1
  17. package/dist/testing/db.d.ts +19 -106
  18. package/dist/testing/db.js +3 -95
  19. package/dist/testing/fakes.d.ts +15 -97
  20. package/dist/testing/fakes.js +12 -104
  21. package/dist/testing/index.d.ts +8 -3
  22. package/dist/testing/index.js +8 -2
  23. package/docs/api-business-time.md +47 -30
  24. package/docs/api-db.md +48 -29
  25. package/docs/api-offline.md +11 -15
  26. package/docs/api-root.md +64 -58
  27. package/docs/api-testing.md +41 -13
  28. package/docs/api.md +35 -12
  29. package/docs/cli.md +11 -7
  30. package/docs/data-layer.md +68 -5
  31. package/docs/development.md +125 -5
  32. package/docs/http-auth.md +15 -0
  33. package/docs/realtime-offline.md +15 -0
  34. package/docs/role-policies.md +4 -4
  35. package/docs/testing-operations.md +25 -2
  36. package/package.json +24 -18
  37. package/scripts/db-baseline.mjs +3 -68
  38. package/scripts/workspace-package-smoke.mjs +206 -0
  39. package/dist/business-time/types.d.ts +0 -9
  40. package/dist/business-time/types.js +0 -5
  41. package/dist/db/columns.d.ts +0 -38
  42. package/dist/db/columns.js +0 -38
  43. package/dist/db/connection.d.ts +0 -74
  44. package/dist/db/connection.js +0 -67
  45. package/dist/db/database.d.ts +0 -249
  46. package/dist/db/database.js +0 -196
  47. package/dist/db/jst.d.ts +0 -45
  48. package/dist/db/jst.js +0 -47
  49. package/dist/db/migrate.d.ts +0 -51
  50. package/dist/db/migrate.js +0 -109
  51. package/dist/db/orm-config.d.ts +0 -127
  52. package/dist/db/orm-config.js +0 -122
  53. package/dist/db/retry.d.ts +0 -28
  54. package/dist/db/retry.js +0 -56
  55. package/dist/db/write-result.d.ts +0 -39
  56. package/dist/db/write-result.js +0 -34
@@ -1,106 +1,19 @@
1
- import type { Pool } from 'mysql2/promise';
2
- /**
3
- * Connection parameters for the test MySQL server.
4
- *
5
- * @see {@link CreateTestDbOptions.connection} for how defaults are resolved.
6
- */
7
- export interface TestDbConnection {
8
- /** Server host. */
9
- host: string;
10
- /** Server port. */
11
- port: number;
12
- /** User name. */
13
- user: string;
14
- /** Password. */
15
- password: string;
16
- }
17
- /**
18
- * Options for {@link createTestDb}.
19
- */
20
- export interface CreateTestDbOptions {
21
- /**
22
- * Test database name (e.g. `'app_test'`). To isolate parallel runs per feature, resolve a per-run
23
- * name on the caller side and pass it here.
24
- */
25
- dbName: string;
26
- /**
27
- * Absolute path to the Drizzle migrations folder. Resolve it on the caller side, e.g.
28
- * `join(here, '..', 'drizzle')`.
29
- */
30
- migrationsFolder: string;
31
- /**
32
- * Connection overrides. Unspecified fields fall back to environment variables
33
- * (`DB_HOST`/`DB_PORT`/`DB_USER`/`DB_PASSWORD`), then to `127.0.0.1`/`3306`/`root`/`root`.
34
- */
35
- connection?: Partial<TestDbConnection>;
36
- }
37
- /**
38
- * Test database handle returned by {@link createTestDb}, bundling schema setup, pooling, and
39
- * fixture helpers for a single test database.
40
- */
41
- export interface TestDb {
42
- /** The resolved test database name. */
43
- readonly dbName: string;
44
- /** The resolved connection parameters. */
45
- readonly connection: TestDbConnection;
46
- /**
47
- * Drop and recreate the database, then apply the committed Drizzle migrations to build the schema.
48
- *
49
- * @returns A promise that resolves once migrations have been applied.
50
- */
51
- resetSchema(): Promise<void>;
52
- /**
53
- * Create a mysql2 pool connected to the test database.
54
- *
55
- * @remarks Call `pool.end()` (e.g. in `afterAll`) to release connections.
56
- * @returns A connection pool for the test database.
57
- */
58
- createTestPool(): Pool;
59
- /**
60
- * Truncate every base table in the database.
61
- *
62
- * @remarks Table names are discovered dynamically from `information_schema`; the
63
- * `__drizzle_migrations` bookkeeping table is excluded. Foreign-key checks are disabled for the
64
- * duration so truncation order does not matter.
65
- * @param pool - Pool connected to the test database.
66
- */
67
- truncateAll(pool: Pool): Promise<void>;
68
- /**
69
- * Insert a single row, mapping column names to values — a generic fixture helper for specs.
70
- *
71
- * @param pool - Pool connected to the test database.
72
- * @param table - Target table name.
73
- * @param row - Column-name to value map. A no-op if empty.
74
- */
75
- seed(pool: Pool, table: string, row: Record<string, unknown>): Promise<void>;
76
- /**
77
- * Report whether the local MySQL server is reachable.
78
- *
79
- * @remarks Useful as a guard, e.g. `describe.skipIf(!(await mysqlReachable()))`.
80
- * @returns `true` if a connection could be opened, otherwise `false`.
81
- */
82
- mysqlReachable(): Promise<boolean>;
83
- }
84
- /**
85
- * Create a {@link TestDb} handle for a single test database.
86
- *
87
- * @remarks
88
- * The test schema is built from the committed Drizzle migrations as the single source of truth
89
- * (the `db:generate` output under `./drizzle`), rather than a hand-written `schema.sql`. This helper
90
- * is Node-only test infrastructure (run under Vitest) and is unrelated to runtime behavior.
91
- *
92
- * @param options - Database name, migrations folder, and optional connection overrides. See
93
- * {@link CreateTestDbOptions}.
94
- * @returns A handle exposing schema setup, pooling, truncation, seeding, and a reachability probe.
95
- * @example
96
- * ```ts
97
- * const testDb = createTestDb({ dbName: 'app_test', migrationsFolder: join(here, '..', 'drizzle') });
98
- * beforeAll(async () => {
99
- * await testDb.resetSchema();
100
- * });
101
- * const pool = testDb.createTestPool();
102
- * beforeEach(() => testDb.truncateAll(pool));
103
- * afterAll(() => pool.end());
104
- * ```
105
- */
106
- export declare function createTestDb(options: CreateTestDbOptions): TestDb;
1
+ import type { Database as DatabaseCanonical, DisposableDatabase as DisposableDatabaseCanonical, QueryRunner as QueryRunnerCanonical, TxOf as TxOfCanonical } from '@rdlabo/workers-mysql';
2
+ import { createTestDb as createTestDbCanonical } from '@rdlabo/workers-mysql/testing';
3
+ import type { CreateTestDbOptions as CreateTestDbOptionsCanonical, TestDb as TestDbCanonical, TestDbConnection as TestDbConnectionCanonical } from '@rdlabo/workers-mysql/testing';
4
+ /** @deprecated Import from `@rdlabo/workers-mysql/testing` instead. */
5
+ export declare const createTestDb: typeof createTestDbCanonical;
6
+ /** @deprecated Import from `@rdlabo/workers-mysql/testing` instead. */
7
+ export type TestDb = TestDbCanonical;
8
+ /** @deprecated Import from `@rdlabo/workers-mysql/testing` instead. */
9
+ export type CreateTestDbOptions = CreateTestDbOptionsCanonical;
10
+ /** @deprecated Import from `@rdlabo/workers-mysql/testing` instead. */
11
+ export type TestDbConnection = TestDbConnectionCanonical;
12
+ /** @deprecated Import from `@rdlabo/workers-mysql` or `@rdlabo/workers-mysql/testing` instead. */
13
+ export type TxOf<TDrizzle> = TxOfCanonical<TDrizzle>;
14
+ /** @deprecated Import from `@rdlabo/workers-mysql` or `@rdlabo/workers-mysql/testing` instead. */
15
+ export type Database<TDrizzle, TTx = TxOfCanonical<TDrizzle>> = DatabaseCanonical<TDrizzle, TTx>;
16
+ /** @deprecated Import from `@rdlabo/workers-mysql` or `@rdlabo/workers-mysql/testing` instead. */
17
+ export type DisposableDatabase<TDrizzle, TTx = TxOfCanonical<TDrizzle>> = DisposableDatabaseCanonical<TDrizzle, TTx>;
18
+ /** @deprecated Import from `@rdlabo/workers-mysql` or `@rdlabo/workers-mysql/testing` instead. */
19
+ export type QueryRunner = QueryRunnerCanonical;
@@ -1,95 +1,3 @@
1
- import { drizzle } from 'drizzle-orm/mysql2';
2
- import { migrate } from 'drizzle-orm/mysql2/migrator';
3
- import { createConnection, createPool } from 'mysql2/promise';
4
- function resolveConnection(override) {
5
- const env = globalThis.process?.env ?? {};
6
- return {
7
- host: override?.host ?? env.DB_HOST ?? '127.0.0.1',
8
- port: override?.port ?? Number(env.DB_PORT ?? '3306'),
9
- user: override?.user ?? env.DB_USER ?? 'root',
10
- password: override?.password ?? env.DB_PASSWORD ?? 'root',
11
- };
12
- }
13
- /**
14
- * Create a {@link TestDb} handle for a single test database.
15
- *
16
- * @remarks
17
- * The test schema is built from the committed Drizzle migrations as the single source of truth
18
- * (the `db:generate` output under `./drizzle`), rather than a hand-written `schema.sql`. This helper
19
- * is Node-only test infrastructure (run under Vitest) and is unrelated to runtime behavior.
20
- *
21
- * @param options - Database name, migrations folder, and optional connection overrides. See
22
- * {@link CreateTestDbOptions}.
23
- * @returns A handle exposing schema setup, pooling, truncation, seeding, and a reachability probe.
24
- * @example
25
- * ```ts
26
- * const testDb = createTestDb({ dbName: 'app_test', migrationsFolder: join(here, '..', 'drizzle') });
27
- * beforeAll(async () => {
28
- * await testDb.resetSchema();
29
- * });
30
- * const pool = testDb.createTestPool();
31
- * beforeEach(() => testDb.truncateAll(pool));
32
- * afterAll(() => pool.end());
33
- * ```
34
- */
35
- export function createTestDb(options) {
36
- const { dbName, migrationsFolder } = options;
37
- const connection = resolveConnection(options.connection);
38
- return {
39
- dbName,
40
- connection,
41
- async resetSchema() {
42
- const admin = await createConnection({ ...connection, multipleStatements: true });
43
- await admin.query(`DROP DATABASE IF EXISTS \`${dbName}\`; CREATE DATABASE \`${dbName}\` DEFAULT CHARACTER SET utf8mb4;`);
44
- await admin.changeUser({ database: dbName });
45
- await migrate(drizzle(admin), { migrationsFolder });
46
- await admin.end();
47
- },
48
- createTestPool() {
49
- // decimalNumbers / timezone mirror the runtime hyperdriveConnectionOptions so specs read
50
- // DECIMAL columns as numbers and handle datetime in +09:00 (JST), matching production.
51
- const pool = createPool({
52
- ...connection,
53
- database: dbName,
54
- connectionLimit: 5,
55
- decimalNumbers: true,
56
- timezone: '+09:00',
57
- });
58
- // Pin ONLY_FULL_GROUP_BY on every pooled connection so GROUP BY violations surface in specs
59
- // regardless of the server's my.cnf (the policy is centralized here, not left to each server). CONCAT keeps
60
- // the server's other sql_mode flags and is harmless if ONLY_FULL_GROUP_BY is already present.
61
- // mysql2 queues this SET ahead of the consumer's first query on each new physical connection.
62
- pool.on('connection', (conn) => {
63
- void conn.query("SET SESSION sql_mode = CONCAT(@@SESSION.sql_mode, ',ONLY_FULL_GROUP_BY')");
64
- });
65
- return pool;
66
- },
67
- async truncateAll(pool) {
68
- const [rows] = await pool.query("SELECT table_name AS t FROM information_schema.tables WHERE table_schema = ? AND table_type='BASE TABLE' AND table_name <> '__drizzle_migrations'", [dbName]);
69
- const tables = rows.map((r) => r.t);
70
- await pool.query('SET FOREIGN_KEY_CHECKS=0');
71
- for (const t of tables) {
72
- await pool.query(`TRUNCATE TABLE \`${t}\``);
73
- }
74
- await pool.query('SET FOREIGN_KEY_CHECKS=1');
75
- },
76
- async seed(pool, table, row) {
77
- const cols = Object.keys(row);
78
- if (cols.length === 0) {
79
- return;
80
- }
81
- const placeholders = cols.map(() => '?').join(', ');
82
- const columnList = cols.map((c) => `\`${c}\``).join(', ');
83
- await pool.query(`INSERT INTO \`${table}\` (${columnList}) VALUES (${placeholders})`, Object.values(row));
84
- },
85
- async mysqlReachable() {
86
- const connect = async () => createConnection({ ...connection });
87
- const c = await connect().catch(() => undefined);
88
- if (!c) {
89
- return false;
90
- }
91
- const close = async () => c.end();
92
- return close().then(() => true, () => false);
93
- },
94
- };
95
- }
1
+ import { createTestDb as createTestDbCanonical } from '@rdlabo/workers-mysql/testing';
2
+ /** @deprecated Import from `@rdlabo/workers-mysql/testing` instead. */
3
+ export const createTestDb = createTestDbCanonical;
@@ -1,113 +1,31 @@
1
- import type { Pool } from 'mysql2/promise';
2
- import type { DisposableDatabase } from '../db/database.js';
1
+ import { createNoopDatabase as createNoopDatabaseCanonical, createPoolDatabase as createPoolDatabaseCanonical } from '@rdlabo/workers-mysql/testing';
2
+ import type { CreatePoolDatabaseOptions as CreatePoolDatabaseOptionsCanonical } from '@rdlabo/workers-mysql/testing';
3
3
  import type { DecodedIdToken, FirebaseVerifier } from '../firebase/firebase-verifier.js';
4
- /**
5
- * In-memory {@link FirebaseVerifier} implementation for offline route tests.
6
- *
7
- * Seed fake identities with {@link FakeFirebaseVerifier.register | register(token, { uid })}, then
8
- * verification resolves the registered decoded token instead of calling a real Firebase backend.
9
- *
10
- * @example
11
- * ```ts
12
- * const firebase = new FakeFirebaseVerifier();
13
- * firebase.register('tok-1', { uid: 'uid-1' });
14
- * const decoded = await firebase.verifyIdToken('tok-1'); // { uid: 'uid-1' }
15
- * ```
16
- */
4
+ /** @deprecated Import from `@rdlabo/workers-mysql/testing` instead. */
5
+ export declare const createPoolDatabase: typeof createPoolDatabaseCanonical;
6
+ /** @deprecated Import from `@rdlabo/workers-mysql/testing` instead. */
7
+ export declare const createNoopDatabase: typeof createNoopDatabaseCanonical;
8
+ /** @deprecated Import from `@rdlabo/workers-mysql/testing` instead. */
9
+ export type CreatePoolDatabaseOptions<TDrizzle> = CreatePoolDatabaseOptionsCanonical<TDrizzle>;
10
+ /** In-memory Firebase verifier for route tests. */
17
11
  export declare class FakeFirebaseVerifier implements FirebaseVerifier {
18
12
  private readonly tokens;
19
- /** UIDs passed to {@link FakeFirebaseVerifier.deleteUser}, in call order, for assertions. */
13
+ /** UIDs passed to {@link deleteUser}, in call order. */
20
14
  readonly deleted: string[];
21
- /**
22
- * Register a fake decoded token so that {@link FakeFirebaseVerifier.verifyIdToken} resolves it.
23
- *
24
- * @param token - Token string clients will present.
25
- * @param record - Decoded token returned on verification (must include `uid`).
26
- */
15
+ /** Register a decoded token for later verification. */
27
16
  register(token: string, record: DecodedIdToken): void;
28
- /**
29
- * Resolve the decoded token previously registered for `idToken`.
30
- *
31
- * @param idToken - Token string to verify.
32
- * @returns The registered decoded token.
33
- * @throws Error if the token was never registered.
34
- */
17
+ /** Resolve a registered token. */
35
18
  verifyIdToken(idToken: string): Promise<DecodedIdToken>;
36
- /**
37
- * Return a minimal user record echoing the requested UID.
38
- *
39
- * @param uid - UID to look up.
40
- * @returns An object containing the `uid` (never `null` in this fake).
41
- */
19
+ /** Return a minimal fake user. */
42
20
  getUser(uid: string): Promise<{
43
21
  uid: string;
44
22
  email?: string;
45
23
  } | null>;
46
- /**
47
- * Return minimal user records echoing each requested UID.
48
- *
49
- * @param uids - UIDs to look up.
50
- * @returns One `{ uid }` entry per requested uid (never omits any, in this fake).
51
- */
24
+ /** Return one minimal fake user per UID. */
52
25
  getUsers(uids: string[]): Promise<{
53
26
  uid: string;
54
27
  email?: string;
55
28
  }[]>;
56
- /**
57
- * Record a user deletion by appending the UID to {@link FakeFirebaseVerifier.deleted}.
58
- *
59
- * @param uid - UID being deleted.
60
- */
29
+ /** Record a fake user deletion. */
61
30
  deleteUser(uid: string): Promise<void>;
62
31
  }
63
- /**
64
- * Options for {@link createPoolDatabase}.
65
- *
66
- * @typeParam TDrizzle - The Drizzle instance type, supplied by the consumer so that type identity is
67
- * not coupled to this package's copy of `drizzle-orm`.
68
- */
69
- export interface CreatePoolDatabaseOptions<TDrizzle> {
70
- /** Test pool used as both primary and replica. */
71
- pool: Pool;
72
- /** Drizzle instance built by the consumer with its own `drizzle-orm`, e.g. `drizzle(pool, { schema, ... })`. */
73
- orm: TDrizzle;
74
- }
75
- /**
76
- * Create a `Database` backed by a single pool used as both primary and replica, suitable for tests.
77
- *
78
- * @remarks
79
- * `dispose()` ends the pool. The `orm` is provided by the caller (rather than constructed here) so
80
- * the returned database uses the consumer's own `drizzle-orm` types, avoiding type-identity clashes
81
- * across duplicated `drizzle-orm` installs.
82
- *
83
- * @typeParam TDrizzle - The Drizzle instance type provided by the consumer.
84
- * @param options - Pool and Drizzle instance. See {@link CreatePoolDatabaseOptions}.
85
- * @returns A {@link DisposableDatabase} whose `dispose()` closes the pool.
86
- * @example
87
- * ```ts
88
- * const pool = testDb.createTestPool();
89
- * const db = createPoolDatabase({ pool, orm: drizzle(pool, { schema }) });
90
- * // ... run tests ...
91
- * await db.dispose();
92
- * ```
93
- */
94
- export declare function createPoolDatabase<TDrizzle>(options: CreatePoolDatabaseOptions<TDrizzle>): DisposableDatabase<TDrizzle>;
95
- /**
96
- * Create a no-op `Database` stub for routes that never touch the database (e.g. plain GET handlers).
97
- *
98
- * @remarks
99
- * `read` resolves to an empty array, while `write` and `transaction` throw so that any unexpected
100
- * database access is caught as a misuse. `dispose` is a no-op, so this can stand in for a
101
- * {@link DisposableDatabase} backing (e.g. a Hyperdrive- or pool-based one) without changes.
102
- *
103
- * @typeParam TDrizzle - The Drizzle instance type the consumer expects (defaults to `unknown`).
104
- * @returns A {@link DisposableDatabase} that reads empty and throws on writes/transactions.
105
- * @throws Error from `write`/`transaction` if they are accessed.
106
- * @example
107
- * ```ts
108
- * const db = createNoopDatabase();
109
- * await db.read('SELECT 1'); // []
110
- * await db.write(async (dz) => dz); // throws: noopDatabase.write accessed unexpectedly
111
- * ```
112
- */
113
- export declare function createNoopDatabase<TDrizzle = unknown>(): DisposableDatabase<TDrizzle>;
@@ -1,37 +1,18 @@
1
- import { databaseFrom } from '../db/database.js';
2
- /**
3
- * In-memory {@link FirebaseVerifier} implementation for offline route tests.
4
- *
5
- * Seed fake identities with {@link FakeFirebaseVerifier.register | register(token, { uid })}, then
6
- * verification resolves the registered decoded token instead of calling a real Firebase backend.
7
- *
8
- * @example
9
- * ```ts
10
- * const firebase = new FakeFirebaseVerifier();
11
- * firebase.register('tok-1', { uid: 'uid-1' });
12
- * const decoded = await firebase.verifyIdToken('tok-1'); // { uid: 'uid-1' }
13
- * ```
14
- */
1
+ import { createNoopDatabase as createNoopDatabaseCanonical, createPoolDatabase as createPoolDatabaseCanonical, } from '@rdlabo/workers-mysql/testing';
2
+ /** @deprecated Import from `@rdlabo/workers-mysql/testing` instead. */
3
+ export const createPoolDatabase = createPoolDatabaseCanonical;
4
+ /** @deprecated Import from `@rdlabo/workers-mysql/testing` instead. */
5
+ export const createNoopDatabase = createNoopDatabaseCanonical;
6
+ /** In-memory Firebase verifier for route tests. */
15
7
  export class FakeFirebaseVerifier {
16
8
  tokens = new Map();
17
- /** UIDs passed to {@link FakeFirebaseVerifier.deleteUser}, in call order, for assertions. */
9
+ /** UIDs passed to {@link deleteUser}, in call order. */
18
10
  deleted = [];
19
- /**
20
- * Register a fake decoded token so that {@link FakeFirebaseVerifier.verifyIdToken} resolves it.
21
- *
22
- * @param token - Token string clients will present.
23
- * @param record - Decoded token returned on verification (must include `uid`).
24
- */
11
+ /** Register a decoded token for later verification. */
25
12
  register(token, record) {
26
13
  this.tokens.set(token, record);
27
14
  }
28
- /**
29
- * Resolve the decoded token previously registered for `idToken`.
30
- *
31
- * @param idToken - Token string to verify.
32
- * @returns The registered decoded token.
33
- * @throws Error if the token was never registered.
34
- */
15
+ /** Resolve a registered token. */
35
16
  async verifyIdToken(idToken) {
36
17
  const record = this.tokens.get(idToken);
37
18
  if (!record) {
@@ -39,89 +20,16 @@ export class FakeFirebaseVerifier {
39
20
  }
40
21
  return record;
41
22
  }
42
- /**
43
- * Return a minimal user record echoing the requested UID.
44
- *
45
- * @param uid - UID to look up.
46
- * @returns An object containing the `uid` (never `null` in this fake).
47
- */
23
+ /** Return a minimal fake user. */
48
24
  async getUser(uid) {
49
25
  return { uid };
50
26
  }
51
- /**
52
- * Return minimal user records echoing each requested UID.
53
- *
54
- * @param uids - UIDs to look up.
55
- * @returns One `{ uid }` entry per requested uid (never omits any, in this fake).
56
- */
27
+ /** Return one minimal fake user per UID. */
57
28
  async getUsers(uids) {
58
29
  return uids.map((uid) => ({ uid }));
59
30
  }
60
- /**
61
- * Record a user deletion by appending the UID to {@link FakeFirebaseVerifier.deleted}.
62
- *
63
- * @param uid - UID being deleted.
64
- */
31
+ /** Record a fake user deletion. */
65
32
  async deleteUser(uid) {
66
33
  this.deleted.push(uid);
67
34
  }
68
35
  }
69
- /**
70
- * Create a `Database` backed by a single pool used as both primary and replica, suitable for tests.
71
- *
72
- * @remarks
73
- * `dispose()` ends the pool. The `orm` is provided by the caller (rather than constructed here) so
74
- * the returned database uses the consumer's own `drizzle-orm` types, avoiding type-identity clashes
75
- * across duplicated `drizzle-orm` installs.
76
- *
77
- * @typeParam TDrizzle - The Drizzle instance type provided by the consumer.
78
- * @param options - Pool and Drizzle instance. See {@link CreatePoolDatabaseOptions}.
79
- * @returns A {@link DisposableDatabase} whose `dispose()` closes the pool.
80
- * @example
81
- * ```ts
82
- * const pool = testDb.createTestPool();
83
- * const db = createPoolDatabase({ pool, orm: drizzle(pool, { schema }) });
84
- * // ... run tests ...
85
- * await db.dispose();
86
- * ```
87
- */
88
- export function createPoolDatabase(options) {
89
- const { pool, orm } = options;
90
- const base = databaseFrom(orm, pool);
91
- return {
92
- ...base,
93
- async dispose() {
94
- await pool.end();
95
- },
96
- };
97
- }
98
- /**
99
- * Create a no-op `Database` stub for routes that never touch the database (e.g. plain GET handlers).
100
- *
101
- * @remarks
102
- * `read` resolves to an empty array, while `write` and `transaction` throw so that any unexpected
103
- * database access is caught as a misuse. `dispose` is a no-op, so this can stand in for a
104
- * {@link DisposableDatabase} backing (e.g. a Hyperdrive- or pool-based one) without changes.
105
- *
106
- * @typeParam TDrizzle - The Drizzle instance type the consumer expects (defaults to `unknown`).
107
- * @returns A {@link DisposableDatabase} that reads empty and throws on writes/transactions.
108
- * @throws Error from `write`/`transaction` if they are accessed.
109
- * @example
110
- * ```ts
111
- * const db = createNoopDatabase();
112
- * await db.read('SELECT 1'); // []
113
- * await db.write(async (dz) => dz); // throws: noopDatabase.write accessed unexpectedly
114
- * ```
115
- */
116
- export function createNoopDatabase() {
117
- return {
118
- read: async () => [],
119
- write: () => {
120
- throw new Error('noopDatabase.write accessed unexpectedly');
121
- },
122
- transaction: () => {
123
- throw new Error('noopDatabase.transaction accessed unexpectedly');
124
- },
125
- dispose: async () => { },
126
- };
127
- }
@@ -1,5 +1,9 @@
1
1
  /**
2
- * Shared test infrastructure for Hono on Cloudflare Workers projects (depends on `mysql2`/`drizzle-orm`).
2
+ * Shared test infrastructure for Hono on Cloudflare Workers projects.
3
+ * Every import requires `@rdlabo/workers-mysql` and `drizzle-orm` because DB compatibility exports
4
+ * are loaded statically, including when only non-DB helpers are used. Prefer
5
+ * `@rdlabo/workers-mysql/testing` for database helpers; kit-owned Firebase/auth/KV/Stripe fakes remain
6
+ * the supported testing surface here.
3
7
  *
4
8
  * Test-only helpers that are never loaded at runtime. This subpath consolidates the duplicated
5
9
  * test boilerplate (test DB setup, in-memory fakes, auth header builders, Stripe fixtures) that
@@ -7,9 +11,10 @@
7
11
  */
8
12
  export { createTestDb } from './db.js';
9
13
  export type { TestDb, CreateTestDbOptions, TestDbConnection } from './db.js';
10
- export { FakeFirebaseVerifier, createPoolDatabase, createNoopDatabase } from './fakes.js';
14
+ export type { Database, DisposableDatabase, QueryRunner, TxOf } from './db.js';
15
+ export { FakeFirebaseVerifier } from './fakes.js';
16
+ export { createPoolDatabase, createNoopDatabase } from './fakes.js';
11
17
  export type { CreatePoolDatabaseOptions } from './fakes.js';
12
- export type { Database, DisposableDatabase, QueryRunner, TxOf } from '../db/database.js';
13
18
  export { authHeaders, registerFirebaseToken, provisionUser } from './auth.js';
14
19
  export { configurableFake } from './configurable-fake.js';
15
20
  export { fakeApiList, fakePaymentIntent, fakeStripeEvent, fakeCheckoutSession, fakeCustomer, fakePrice, fakeSubscription, } from './stripe-fixtures.js';
@@ -1,12 +1,18 @@
1
1
  /**
2
- * Shared test infrastructure for Hono on Cloudflare Workers projects (depends on `mysql2`/`drizzle-orm`).
2
+ * Shared test infrastructure for Hono on Cloudflare Workers projects.
3
+ * Every import requires `@rdlabo/workers-mysql` and `drizzle-orm` because DB compatibility exports
4
+ * are loaded statically, including when only non-DB helpers are used. Prefer
5
+ * `@rdlabo/workers-mysql/testing` for database helpers; kit-owned Firebase/auth/KV/Stripe fakes remain
6
+ * the supported testing surface here.
3
7
  *
4
8
  * Test-only helpers that are never loaded at runtime. This subpath consolidates the duplicated
5
9
  * test boilerplate (test DB setup, in-memory fakes, auth header builders, Stripe fixtures) that
6
10
  * tends to be copy-pasted across projects into a single, importable surface.
7
11
  */
12
+ /* eslint-disable @typescript-eslint/no-deprecated -- compatibility barrel re-exports DB helpers */
8
13
  export { createTestDb } from './db.js';
9
- export { FakeFirebaseVerifier, createPoolDatabase, createNoopDatabase } from './fakes.js';
14
+ export { FakeFirebaseVerifier } from './fakes.js';
15
+ export { createPoolDatabase, createNoopDatabase } from './fakes.js';
10
16
  // Authentication test helpers (route-spec header builders and user provisioning).
11
17
  export { authHeaders, registerFirebaseToken, provisionUser } from './auth.js';
12
18
  // Test double helper (partial-implementation fake that throws explicitly on unconfigured members).
@@ -1,33 +1,50 @@
1
- # API: `@rdlabo/workers-hono-kit/business-time`
2
-
3
- String-level JST business-time conversions (Workers UTC instant ↔ business calendar date / date-time), with **no `mysql2` / `drizzle-orm` dependency**. This is a different layer from the `./db` column helpers (which handle the MySQL wire format): the DB stays on JST, and the app handles JST explicitly through this module instead of relying implicitly on the connection `timezone`.
4
-
5
- | Export | Description |
6
- | --- | --- |
7
- | `today(ref?)` | The JST business calendar date (`YYYY-MM-DD`) of `ref` (defaults to now). |
8
- | `toBusinessDate(instant)` | UTC instant → JST business calendar date (`YYYY-MM-DD`). |
9
- | `normalizeBusinessDate(value)` | Normalize a `string` / `Date` / nullish to `YYYY-MM-DD`; a `YYYY-MM-DD` string passes through unchanged, nullish/empty/invalid → `null`. |
10
- | `toBusinessDateTime(instant)` | UTC instant → JST business date-time (`YYYY-MM-DD HH:mm:ss`). |
11
- | `parseBusinessDateTime(value)` | JST business date-time string → UTC instant (accepts a space or `T` separator). |
12
- | `formatBusinessDateTime(instant, pattern?)` | Format an instant in the business TZ (Nest `helper.formatDate`-compatible tokens). |
13
- | `startOfBusinessDay(date)` / `endOfBusinessDay(date)` | UTC instant of `00:00:00` / `23:59:59` on a JST business date. |
14
- | `businessDateTimeInstant(date, time)` | JST business date + wall-clock time → UTC instant. |
15
- | `addBusinessDays(date, days)` | Add calendar days to a JST business date. |
16
- | `ageOnBusinessDate(birthDate, asOfDate?)` | Full years of age on a business date (`asOfDate` defaults to `today()`). |
17
- | `DEFAULT_BUSINESS_DATETIME_PATTERN` | Default `formatBusinessDateTime` pattern (`YYYY-MM-DDThh:mm:ss`). |
18
- | `BUSINESS_TIMEZONE` / `BusinessDate` / `BusinessDateTime` | JST timezone constant and the business-date / date-time string types. |
1
+ # API: `@rdlabo/workers-timezone`
2
+
3
+ Install with `npm install @rdlabo/workers-timezone`. The kit's `/business-time` entry point is a
4
+ deprecated compatibility re-export from `0.12.0`.
5
+
6
+ Timezone-aware calendar and wall-clock conversion for Cloudflare Workers, with no database or Node
7
+ runtime dependency. The uninitialized default remains `Asia/Tokyo` for compatibility.
8
+
9
+ ```ts
10
+ import { TIME_ZONES, initializeTimezone, toLocalDateTime } from '@rdlabo/workers-timezone';
11
+
12
+ initializeTimezone({ timeZone: TIME_ZONES.NEW_YORK });
13
+ toLocalDateTime(new Date('2026-07-01T13:00:00Z')); // '2026-07-01 09:00:00'
14
+ ```
15
+
16
+ Initialize once during module evaluation using deployment-wide static configuration. Do not mutate
17
+ the default per request, user, or tenant. Every conversion function accepts an explicit IANA
18
+ timezone override without changing the module-instance default.
19
+
20
+ | Export | Description |
21
+ | ----------------------------------------------- | -------------------------------------------------------------------------------- |
22
+ | `initializeTimezone(config)` | Set the module-instance default once; repeated identical initialization is safe. |
23
+ | `getTimezoneConfig()` | Return the active configuration. |
24
+ | `toLocalDate(instant, timeZone?)` | Instant to local `YYYY-MM-DD`. |
25
+ | `toLocalDateTime(instant, timeZone?)` | Instant to local `YYYY-MM-DD HH:mm:ss`. |
26
+ | `localDateTimeToInstant(date, time, timeZone?)` | Local calendar date and wall clock to an instant. |
27
+ | `startOfDay` / `endOfDay` | First or final representable whole second of a local calendar day. |
28
+ | `addDays(date, days)` | Add calendar days without assuming a 24-hour day. |
29
+ | `TIME_ZONES` / `TimeZone` | Common typed constants and the open IANA timezone type. |
30
+
31
+ IANA rules determine daylight-saving and historical offsets. A skipped local clock throws
32
+ `RangeError`; when a clock occurs twice during a DST overlap, the earlier instant is selected.
33
+
34
+ ## Legacy compatibility
35
+
36
+ `@rdlabo/workers-hono-kit/business-time` is deprecated and re-exports the same module instance from
37
+ `@rdlabo/workers-timezone`. Existing names such as `today`, `normalizeBusinessDate`,
38
+ `toBusinessDateTime`, `businessDateTimeInstant`, `formatBusinessDateTime`, and
39
+ `ageOnBusinessDate` remain available during migration.
40
+
41
+ This is source compatibility for API names, not full output compatibility. Results now follow IANA
42
+ historical offsets instead of the legacy fixed `+09:00`, so historical dates can change when
43
+ Tokyo's offset was not `+09:00`. Impossible `YYYY-MM-DD` values now normalize to `null`; invalid
44
+ date-time construction and parsing throw `RangeError` instead of using JavaScript `Date` rollover.
45
+ Treat both behavior changes as breaking when planning the migration.
19
46
 
20
47
  ```ts
21
- import {
22
- toBusinessDate,
23
- toBusinessDateTime,
24
- formatBusinessDateTime,
25
- addBusinessDays,
26
- } from '@rdlabo/workers-hono-kit/business-time';
27
-
28
- const now = new Date('2026-07-05T21:00:00Z');
29
- toBusinessDate(now); // '2026-07-06' (JST)
30
- toBusinessDateTime(now); // '2026-07-06 06:00:00'
31
- formatBusinessDateTime(now); // '2026-07-06T06:00:00'
32
- addBusinessDays('2026-07-06', 3); // '2026-07-09'
48
+ // Deprecated; migrate the import path when practical.
49
+ import { toBusinessDateTime } from '@rdlabo/workers-hono-kit/business-time';
33
50
  ```