@rdlabo/workers-hono-kit 0.2.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.
Files changed (97) hide show
  1. package/README.md +126 -11
  2. package/dist/ai/gateway.d.ts +54 -16
  3. package/dist/ai/gateway.js +37 -12
  4. package/dist/aws/cloudfront.d.ts +23 -5
  5. package/dist/aws/cloudfront.js +45 -6
  6. package/dist/aws/secrets-manager.d.ts +38 -4
  7. package/dist/aws/secrets-manager.js +48 -3
  8. package/dist/cache/kv-cache.d.ts +173 -10
  9. package/dist/cache/kv-cache.js +139 -7
  10. package/dist/db/connection.d.ts +56 -14
  11. package/dist/db/connection.js +39 -13
  12. package/dist/db/database.d.ts +159 -23
  13. package/dist/db/database.js +49 -5
  14. package/dist/db/index.d.ts +11 -0
  15. package/dist/db/index.js +11 -2
  16. package/dist/db/jst.d.ts +89 -6
  17. package/dist/db/jst.js +89 -23
  18. package/dist/db/orm-config.d.ts +61 -19
  19. package/dist/db/orm-config.js +43 -14
  20. package/dist/db/retry.d.ts +25 -3
  21. package/dist/db/retry.js +25 -3
  22. package/dist/db/write-result.d.ts +27 -4
  23. package/dist/db/write-result.js +22 -1
  24. package/dist/firebase/firebase-verifier.d.ts +53 -4
  25. package/dist/firebase/identity-toolkit.d.ts +54 -5
  26. package/dist/firebase/identity-toolkit.js +51 -0
  27. package/dist/firebase/jose-firebase-verifier.d.ts +79 -7
  28. package/dist/firebase/jose-firebase-verifier.js +68 -7
  29. package/dist/firebase/remote-verifier.d.ts +42 -4
  30. package/dist/firebase/remote-verifier.js +58 -9
  31. package/dist/http/app-env.d.ts +41 -8
  32. package/dist/http/app-env.js +38 -8
  33. package/dist/http/app-info.d.ts +25 -3
  34. package/dist/http/app-info.js +16 -2
  35. package/dist/http/http-status.d.ts +12 -3
  36. package/dist/http/http-status.js +12 -3
  37. package/dist/http/nest-error.d.ts +90 -29
  38. package/dist/http/nest-error.js +59 -18
  39. package/dist/http/user-protocol.d.ts +23 -3
  40. package/dist/http/user-protocol.js +14 -2
  41. package/dist/index.d.ts +11 -0
  42. package/dist/index.js +11 -3
  43. package/dist/middleware/auth.d.ts +74 -13
  44. package/dist/middleware/auth.js +30 -6
  45. package/dist/middleware/finalize-response.d.ts +30 -0
  46. package/dist/middleware/finalize-response.js +41 -12
  47. package/dist/middleware/validation.d.ts +83 -9
  48. package/dist/middleware/validation.js +52 -9
  49. package/dist/middleware/zod-coerce.d.ts +56 -2
  50. package/dist/middleware/zod-coerce.js +68 -9
  51. package/dist/stripe/client.d.ts +46 -9
  52. package/dist/stripe/client.js +41 -3
  53. package/dist/testing/auth.d.ts +58 -10
  54. package/dist/testing/auth.js +58 -10
  55. package/dist/testing/configurable-fake.d.ts +20 -9
  56. package/dist/testing/configurable-fake.js +23 -11
  57. package/dist/testing/db.d.ts +81 -12
  58. package/dist/testing/db.js +23 -1
  59. package/dist/testing/fakes.d.ts +77 -9
  60. package/dist/testing/fakes.js +69 -7
  61. package/dist/testing/index.d.ts +7 -0
  62. package/dist/testing/index.js +10 -5
  63. package/dist/testing/stripe-fixtures.d.ts +93 -3
  64. package/dist/testing/stripe-fixtures.js +93 -3
  65. package/package.json +1 -1
  66. package/src/ai/gateway.ts +66 -27
  67. package/src/aws/cloudfront.ts +46 -6
  68. package/src/aws/secrets-manager.ts +56 -7
  69. package/src/cache/kv-cache.ts +194 -12
  70. package/src/db/connection.ts +56 -14
  71. package/src/db/database.ts +160 -24
  72. package/src/db/index.ts +11 -2
  73. package/src/db/jst.ts +89 -23
  74. package/src/db/orm-config.ts +61 -19
  75. package/src/db/retry.ts +25 -3
  76. package/src/db/write-result.ts +27 -4
  77. package/src/firebase/firebase-verifier.ts +53 -4
  78. package/src/firebase/identity-toolkit.ts +57 -5
  79. package/src/firebase/jose-firebase-verifier.ts +79 -9
  80. package/src/firebase/remote-verifier.ts +58 -9
  81. package/src/http/app-env.ts +41 -8
  82. package/src/http/app-info.ts +25 -3
  83. package/src/http/http-status.ts +12 -3
  84. package/src/http/nest-error.ts +106 -37
  85. package/src/http/user-protocol.ts +23 -3
  86. package/src/index.ts +11 -3
  87. package/src/middleware/auth.ts +77 -15
  88. package/src/middleware/finalize-response.ts +41 -12
  89. package/src/middleware/validation.ts +89 -15
  90. package/src/middleware/zod-coerce.ts +68 -9
  91. package/src/stripe/client.ts +46 -9
  92. package/src/testing/auth.ts +58 -10
  93. package/src/testing/configurable-fake.ts +23 -11
  94. package/src/testing/db.ts +82 -13
  95. package/src/testing/fakes.ts +77 -9
  96. package/src/testing/index.ts +10 -5
  97. package/src/testing/stripe-fixtures.ts +93 -3
@@ -2,11 +2,27 @@ import type { Pool } from 'mysql2/promise';
2
2
  import type { DecodedIdToken } from '../firebase/firebase-verifier.js';
3
3
  import type { FakeFirebaseVerifier } from './fakes.js';
4
4
  /**
5
- * app interceptor 互換の認証ヘッダ(`x-amz-security-token` + `x-amz-meta-*`)を組む。
6
- * fleet hono repo の route spec で同形に重複していたものを集約。
5
+ * Build authentication headers compatible with the client interceptor convention
6
+ * (`x-amz-security-token` + `x-amz-meta-*`).
7
7
  *
8
- * - `version` `app_version`(varchar(10)) に入るため 10 文字以内。
9
- * - `contentType: null` を渡すと content-type を付けない(GET 等)。
8
+ * Consolidates the identically-shaped header boilerplate that route specs tend to duplicate.
9
+ *
10
+ * @remarks
11
+ * `version` is persisted into an `app_version` column (`varchar(10)`), so keep it to 10 characters
12
+ * or fewer. Passing `contentType: null` omits the `content-type` header entirely (e.g. for GET requests).
13
+ *
14
+ * @param token - Security token placed in the `x-amz-security-token` header.
15
+ * @param opts - Optional overrides.
16
+ * @param opts.version - App version for `x-amz-meta-version` (defaults to `'1.0.0'`).
17
+ * @param opts.uuid - Device/client UUID for `x-amz-meta-uuid` (defaults to `'test-uuid'`).
18
+ * @param opts.contentType - Content type; defaults to `'application/json'`. Pass `null` to omit the header.
19
+ * @returns A plain header record suitable for `fetch`/`app.request` calls.
20
+ * @example
21
+ * ```ts
22
+ * const res = await app.request('/me', { headers: authHeaders(token) });
23
+ * // GET without a content-type header:
24
+ * await app.request('/items', { headers: authHeaders(token, { contentType: null }) });
25
+ * ```
10
26
  */
11
27
  export declare function authHeaders(token: string, opts?: {
12
28
  version?: string;
@@ -14,15 +30,47 @@ export declare function authHeaders(token: string, opts?: {
14
30
  contentType?: string | null;
15
31
  }): Record<string, string>;
16
32
  /**
17
- * fake firebase にトークンを登録するだけの薄いヘルパ(DB を触らない)。戻り値はトークン。
18
- * `users` テーブル形が repo 固有(例: airlec は email 主)で provisionUser が合わない場合に使う。
33
+ * Register a token on a fake Firebase verifier without touching the database.
34
+ *
35
+ * Use this when {@link provisionUser} does not fit because the project's `users` table has a
36
+ * non-conventional shape (for example, keyed by email rather than `firebase_uid`); pair it with a
37
+ * project-specific provisioning step.
38
+ *
39
+ * @param firebase - In-memory verifier to register the token on.
40
+ * @param uid - Firebase UID associated with the token.
41
+ * @param record - Additional decoded-token fields to merge in (e.g. `email`).
42
+ * @param token - Token string to register (defaults to `` `tok-${uid}` ``).
43
+ * @returns The registered token string.
44
+ * @example
45
+ * ```ts
46
+ * const token = registerFirebaseToken(firebase, 'uid-1', { email: 'a@example.com' });
47
+ * const res = await app.request('/me', { headers: authHeaders(token) });
48
+ * ```
19
49
  */
20
50
  export declare function registerFirebaseToken(firebase: FakeFirebaseVerifier, uid: string, record?: Partial<DecodedIdToken>, token?: string): string;
21
51
  /**
22
- * fake firebase にトークンを登録し、`users(firebase_uid)` 行を用意して userId を返す。
23
- * `users(id, firebase_uid, agree)` の fleet 共通形を前提(foodlabel/receptray など)。同 uid の
24
- * 既存行があれば再利用する(冪等)。users テーブル形が異なる repo は registerFirebaseToken + 独自
25
- * provision を使う。
52
+ * Register a token on a fake Firebase verifier and ensure a matching `users` row exists, returning
53
+ * its id.
54
+ *
55
+ * @remarks
56
+ * Assumes a conventional `users(id, firebase_uid, agree)` table. The operation is idempotent: if a
57
+ * row with the same `firebase_uid` already exists it is reused rather than re-inserted. For projects
58
+ * whose `users` table has a different shape, use {@link registerFirebaseToken} plus project-specific
59
+ * provisioning instead.
60
+ *
61
+ * @param pool - mysql2 pool connected to the test database.
62
+ * @param firebase - In-memory verifier to register the token on.
63
+ * @param opts - Provisioning options.
64
+ * @param opts.uid - Firebase UID for the user.
65
+ * @param opts.token - Token string to register (defaults to `` `tok-${uid}` ``).
66
+ * @param opts.agree - Value for the `agree` column on insert (defaults to `1`).
67
+ * @param opts.email - Optional email merged into the decoded token record.
68
+ * @returns The resolved `userId`, along with the `uid` and registered `token`.
69
+ * @example
70
+ * ```ts
71
+ * const { userId, token } = await provisionUser(pool, firebase, { uid: 'uid-1' });
72
+ * const res = await app.request('/me', { headers: authHeaders(token) });
73
+ * ```
26
74
  */
27
75
  export declare function provisionUser(pool: Pool, firebase: FakeFirebaseVerifier, opts: {
28
76
  uid: string;
@@ -1,9 +1,25 @@
1
1
  /**
2
- * app interceptor 互換の認証ヘッダ(`x-amz-security-token` + `x-amz-meta-*`)を組む。
3
- * fleet hono repo の route spec で同形に重複していたものを集約。
2
+ * Build authentication headers compatible with the client interceptor convention
3
+ * (`x-amz-security-token` + `x-amz-meta-*`).
4
4
  *
5
- * - `version` `app_version`(varchar(10)) に入るため 10 文字以内。
6
- * - `contentType: null` を渡すと content-type を付けない(GET 等)。
5
+ * Consolidates the identically-shaped header boilerplate that route specs tend to duplicate.
6
+ *
7
+ * @remarks
8
+ * `version` is persisted into an `app_version` column (`varchar(10)`), so keep it to 10 characters
9
+ * or fewer. Passing `contentType: null` omits the `content-type` header entirely (e.g. for GET requests).
10
+ *
11
+ * @param token - Security token placed in the `x-amz-security-token` header.
12
+ * @param opts - Optional overrides.
13
+ * @param opts.version - App version for `x-amz-meta-version` (defaults to `'1.0.0'`).
14
+ * @param opts.uuid - Device/client UUID for `x-amz-meta-uuid` (defaults to `'test-uuid'`).
15
+ * @param opts.contentType - Content type; defaults to `'application/json'`. Pass `null` to omit the header.
16
+ * @returns A plain header record suitable for `fetch`/`app.request` calls.
17
+ * @example
18
+ * ```ts
19
+ * const res = await app.request('/me', { headers: authHeaders(token) });
20
+ * // GET without a content-type header:
21
+ * await app.request('/items', { headers: authHeaders(token, { contentType: null }) });
22
+ * ```
7
23
  */
8
24
  export function authHeaders(token, opts = {}) {
9
25
  const headers = {
@@ -17,18 +33,50 @@ export function authHeaders(token, opts = {}) {
17
33
  return headers;
18
34
  }
19
35
  /**
20
- * fake firebase にトークンを登録するだけの薄いヘルパ(DB を触らない)。戻り値はトークン。
21
- * `users` テーブル形が repo 固有(例: airlec は email 主)で provisionUser が合わない場合に使う。
36
+ * Register a token on a fake Firebase verifier without touching the database.
37
+ *
38
+ * Use this when {@link provisionUser} does not fit because the project's `users` table has a
39
+ * non-conventional shape (for example, keyed by email rather than `firebase_uid`); pair it with a
40
+ * project-specific provisioning step.
41
+ *
42
+ * @param firebase - In-memory verifier to register the token on.
43
+ * @param uid - Firebase UID associated with the token.
44
+ * @param record - Additional decoded-token fields to merge in (e.g. `email`).
45
+ * @param token - Token string to register (defaults to `` `tok-${uid}` ``).
46
+ * @returns The registered token string.
47
+ * @example
48
+ * ```ts
49
+ * const token = registerFirebaseToken(firebase, 'uid-1', { email: 'a@example.com' });
50
+ * const res = await app.request('/me', { headers: authHeaders(token) });
51
+ * ```
22
52
  */
23
53
  export function registerFirebaseToken(firebase, uid, record = {}, token = `tok-${uid}`) {
24
54
  firebase.register(token, { uid, ...record });
25
55
  return token;
26
56
  }
27
57
  /**
28
- * fake firebase にトークンを登録し、`users(firebase_uid)` 行を用意して userId を返す。
29
- * `users(id, firebase_uid, agree)` の fleet 共通形を前提(foodlabel/receptray など)。同 uid の
30
- * 既存行があれば再利用する(冪等)。users テーブル形が異なる repo は registerFirebaseToken + 独自
31
- * provision を使う。
58
+ * Register a token on a fake Firebase verifier and ensure a matching `users` row exists, returning
59
+ * its id.
60
+ *
61
+ * @remarks
62
+ * Assumes a conventional `users(id, firebase_uid, agree)` table. The operation is idempotent: if a
63
+ * row with the same `firebase_uid` already exists it is reused rather than re-inserted. For projects
64
+ * whose `users` table has a different shape, use {@link registerFirebaseToken} plus project-specific
65
+ * provisioning instead.
66
+ *
67
+ * @param pool - mysql2 pool connected to the test database.
68
+ * @param firebase - In-memory verifier to register the token on.
69
+ * @param opts - Provisioning options.
70
+ * @param opts.uid - Firebase UID for the user.
71
+ * @param opts.token - Token string to register (defaults to `` `tok-${uid}` ``).
72
+ * @param opts.agree - Value for the `agree` column on insert (defaults to `1`).
73
+ * @param opts.email - Optional email merged into the decoded token record.
74
+ * @returns The resolved `userId`, along with the `uid` and registered `token`.
75
+ * @example
76
+ * ```ts
77
+ * const { userId, token } = await provisionUser(pool, firebase, { uid: 'uid-1' });
78
+ * const res = await app.request('/me', { headers: authHeaders(token) });
79
+ * ```
32
80
  */
33
81
  export async function provisionUser(pool, firebase, opts) {
34
82
  const token = registerFirebaseToken(firebase, opts.uid, opts.email ? { email: opts.email } : {}, opts.token);
@@ -1,14 +1,25 @@
1
1
  /**
2
- * 部分実装から test double を作る。設定済みメソッドはそのまま、未設定メソッドを呼ぶと
3
- * `${name}.${method} not configured` で明示的に失敗する。
2
+ * Build a test double from a partial implementation: configured members are returned as-is, while
3
+ * calling any unconfigured member fails explicitly with `` `${name}.${method} not configured` ``.
4
4
  *
5
- * 各 repo の Fake*Gateway に散っていた「`Partial<impl>` を受け取り、未設定なら throw する手書き
6
- * クラス」の定型を一本化する。interface がドメインごとに異なる gateway(Stripe 等)でも、これで
7
- * 1 行で必要メソッドだけ差した fake を作れる:
5
+ * @remarks
6
+ * Replaces the hand-written "accept a `Partial<impl>` and throw on anything unset" fake classes that
7
+ * tend to proliferate per gateway. Because gateway interfaces differ by domain (Stripe, etc.), this
8
+ * lets you stub only the members a given test exercises in a single line.
8
9
  *
9
- * const stripe = configurableFake<StripeGateway>(
10
- * { listPaymentIntents: async () => fakeApiList([fakePaymentIntent()]) },
11
- * 'FakeStripeGateway',
12
- * );
10
+ * @typeParam T - The interface being faked.
11
+ * @param impl - Partial implementation; only the members the test needs.
12
+ * @param name - Label used in the "not configured" error message (defaults to `'fake'`).
13
+ * @returns A proxy typed as `T` that delegates to `impl` and throws on unconfigured members.
14
+ * @throws Error `` `${name}.${method} not configured` `` when an unconfigured string-keyed member is called.
15
+ * @example
16
+ * ```ts
17
+ * const stripe = configurableFake<StripeGateway>(
18
+ * { listPaymentIntents: async () => fakeApiList([fakePaymentIntent()]) },
19
+ * 'FakeStripeGateway',
20
+ * );
21
+ * await stripe.listPaymentIntents(); // ok
22
+ * await stripe.cancelPaymentIntent('pi_1'); // throws: FakeStripeGateway.cancelPaymentIntent not configured
23
+ * ```
13
24
  */
14
25
  export declare function configurableFake<T extends object>(impl: Partial<T>, name?: string): T;
@@ -1,15 +1,26 @@
1
1
  /**
2
- * 部分実装から test double を作る。設定済みメソッドはそのまま、未設定メソッドを呼ぶと
3
- * `${name}.${method} not configured` で明示的に失敗する。
2
+ * Build a test double from a partial implementation: configured members are returned as-is, while
3
+ * calling any unconfigured member fails explicitly with `` `${name}.${method} not configured` ``.
4
4
  *
5
- * 各 repo の Fake*Gateway に散っていた「`Partial<impl>` を受け取り、未設定なら throw する手書き
6
- * クラス」の定型を一本化する。interface がドメインごとに異なる gateway(Stripe 等)でも、これで
7
- * 1 行で必要メソッドだけ差した fake を作れる:
5
+ * @remarks
6
+ * Replaces the hand-written "accept a `Partial<impl>` and throw on anything unset" fake classes that
7
+ * tend to proliferate per gateway. Because gateway interfaces differ by domain (Stripe, etc.), this
8
+ * lets you stub only the members a given test exercises in a single line.
8
9
  *
9
- * const stripe = configurableFake<StripeGateway>(
10
- * { listPaymentIntents: async () => fakeApiList([fakePaymentIntent()]) },
11
- * 'FakeStripeGateway',
12
- * );
10
+ * @typeParam T - The interface being faked.
11
+ * @param impl - Partial implementation; only the members the test needs.
12
+ * @param name - Label used in the "not configured" error message (defaults to `'fake'`).
13
+ * @returns A proxy typed as `T` that delegates to `impl` and throws on unconfigured members.
14
+ * @throws Error `` `${name}.${method} not configured` `` when an unconfigured string-keyed member is called.
15
+ * @example
16
+ * ```ts
17
+ * const stripe = configurableFake<StripeGateway>(
18
+ * { listPaymentIntents: async () => fakeApiList([fakePaymentIntent()]) },
19
+ * 'FakeStripeGateway',
20
+ * );
21
+ * await stripe.listPaymentIntents(); // ok
22
+ * await stripe.cancelPaymentIntent('pi_1'); // throws: FakeStripeGateway.cancelPaymentIntent not configured
23
+ * ```
13
24
  */
14
25
  export function configurableFake(impl, name = 'fake') {
15
26
  return new Proxy(impl, {
@@ -17,8 +28,9 @@ export function configurableFake(impl, name = 'fake') {
17
28
  if (prop in target) {
18
29
  return target[prop];
19
30
  }
20
- // Promise インターロップ用プロパティには「未設定メソッド」関数を返さない。返すと fake 自身が
21
- // thenable 扱いされ、誤って await / Promise.resolve した瞬間に then() が呼ばれて throw する罠になる。
31
+ // Never return the "unconfigured member" function for Promise-interop properties. Doing so would
32
+ // make the fake itself look thenable, so accidentally awaiting it (or passing it to
33
+ // Promise.resolve) would invoke then() and throw — a subtle footgun.
22
34
  if (prop === 'then' || prop === 'catch' || prop === 'finally') {
23
35
  return undefined;
24
36
  }
@@ -1,37 +1,106 @@
1
1
  import type { Pool } from 'mysql2/promise';
2
2
  /**
3
- * フリート共通のテスト DB ヘルパ(各 repo testing/db.ts を集約)。
4
- * テストスキーマは「コミット済み Drizzle マイグレーション」を単一ソースとして構築する
5
- * (手書き schema.sql ではなく `db:generate` 由来の ./drizzle)。
3
+ * Connection parameters for the test MySQL server.
6
4
  *
7
- * Node 専用(vitest 下で実行)。実行時 parity には無関係なテスト基盤。
5
+ * @see {@link CreateTestDbOptions.connection} for how defaults are resolved.
8
6
  */
9
7
  export interface TestDbConnection {
8
+ /** Server host. */
10
9
  host: string;
10
+ /** Server port. */
11
11
  port: number;
12
+ /** User name. */
12
13
  user: string;
14
+ /** Password. */
13
15
  password: string;
14
16
  }
17
+ /**
18
+ * Options for {@link createTestDb}.
19
+ */
15
20
  export interface CreateTestDbOptions {
16
- /** テスト DB 名(例 'tipsys_test')。並列実行で feature 毎に分けたい場合は呼び出し側で TEST_DB を解決して渡す。 */
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
+ */
17
25
  dbName: string;
18
- /** Drizzle マイグレーションフォルダの絶対パス(呼び出し側で `join(here, '..', 'drizzle')` を解決して渡す)。 */
26
+ /**
27
+ * Absolute path to the Drizzle migrations folder. Resolve it on the caller side, e.g.
28
+ * `join(here, '..', 'drizzle')`.
29
+ */
19
30
  migrationsFolder: string;
20
- /** 接続情報。未指定は env(DB_HOST/DB_PORT/DB_USER/DB_PASSWORD)→ 127.0.0.1/3306/root/root。 */
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
+ */
21
35
  connection?: Partial<TestDbConnection>;
22
36
  }
37
+ /**
38
+ * Test database handle returned by {@link createTestDb}, bundling schema setup, pooling, and
39
+ * fixture helpers for a single test database.
40
+ */
23
41
  export interface TestDb {
42
+ /** The resolved test database name. */
24
43
  readonly dbName: string;
44
+ /** The resolved connection parameters. */
25
45
  readonly connection: TestDbConnection;
26
- /** DROP/CREATE して Drizzle マイグレーションを適用しスキーマを構築。 */
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
+ */
27
51
  resetSchema(): Promise<void>;
28
- /** テスト DB に繋いだ mysql2 プールを返す(afterAll で pool.end())。 */
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
+ */
29
58
  createTestPool(): Pool;
30
- /** 全テーブルを TRUNCATE(information_schema から動的取得。__drizzle_migrations は除外)。 */
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
+ */
31
67
  truncateAll(pool: Pool): Promise<void>;
32
- /** 1 行 insert する汎用 seed(列名→値)。route spec の fixture 用。 */
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
+ */
33
75
  seed(pool: Pool, table: string, row: Record<string, unknown>): Promise<void>;
34
- /** ローカル MySQL が到達可能か(`describe.skipIf(!(await mysqlReachable()))` のガード用)。 */
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
+ */
35
82
  mysqlReachable(): Promise<boolean>;
36
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
+ */
37
106
  export declare function createTestDb(options: CreateTestDbOptions): TestDb;
@@ -10,6 +10,28 @@ function resolveConnection(override) {
10
10
  password: override?.password ?? env.DB_PASSWORD ?? 'root',
11
11
  };
12
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
+ */
13
35
  export function createTestDb(options) {
14
36
  const { dbName, migrationsFolder } = options;
15
37
  const connection = resolveConnection(options.connection);
@@ -34,7 +56,7 @@ export function createTestDb(options) {
34
56
  timezone: '+09:00',
35
57
  });
36
58
  // Pin ONLY_FULL_GROUP_BY on every pooled connection so GROUP BY violations surface in specs
37
- // regardless of the server's my.cnf (fleet policy centralized here, not per-repo). CONCAT keeps
59
+ // regardless of the server's my.cnf (the policy is centralized here, not left to each server). CONCAT keeps
38
60
  // the server's other sql_mode flags and is harmless if ONLY_FULL_GROUP_BY is already present.
39
61
  // mysql2 queues this SET ahead of the consumer's first query on each new physical connection.
40
62
  pool.on('connection', (conn) => {
@@ -2,34 +2,102 @@ import type { Pool } from 'mysql2/promise';
2
2
  import type { DisposableDatabase } from '../db/database.js';
3
3
  import type { DecodedIdToken, FirebaseVerifier } from '../firebase/firebase-verifier.js';
4
4
  /**
5
- * オフライン route テスト用の in-memory FirebaseVerifier(4 repo 同一実装を集約)。
6
- * `register(token, { uid })` で偽 ID を仕込む。
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
+ * ```
7
16
  */
8
17
  export declare class FakeFirebaseVerifier implements FirebaseVerifier {
9
18
  private readonly tokens;
19
+ /** UIDs passed to {@link FakeFirebaseVerifier.deleteUser}, in call order, for assertions. */
10
20
  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
+ */
11
27
  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
+ */
12
35
  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
+ */
13
42
  getUser(uid: string): Promise<{
14
43
  uid: string;
15
44
  email?: string;
16
45
  } | null>;
46
+ /**
47
+ * Record a user deletion by appending the UID to {@link FakeFirebaseVerifier.deleted}.
48
+ *
49
+ * @param uid - UID being deleted.
50
+ */
17
51
  deleteUser(uid: string): Promise<void>;
18
52
  }
53
+ /**
54
+ * Options for {@link createPoolDatabase}.
55
+ *
56
+ * @typeParam TDrizzle - The Drizzle instance type, supplied by the consumer so that type identity is
57
+ * not coupled to this package's copy of `drizzle-orm`.
58
+ */
19
59
  export interface CreatePoolDatabaseOptions<TDrizzle> {
20
- /** テスト用プール(primary/replica 兼用)。 */
60
+ /** Test pool used as both primary and replica. */
21
61
  pool: Pool;
22
- /** 消費側の drizzle-orm `drizzle(pool, { schema, ... })` を作って渡す。 */
62
+ /** Drizzle instance built by the consumer with its own `drizzle-orm`, e.g. `drizzle(pool, { schema, ... })`. */
23
63
  orm: TDrizzle;
24
64
  }
25
65
  /**
26
- * テスト用にプール 1 本を primary/replica 兼用にした Database(foodlabel PoolDatabase 相当)。
27
- * `dispose()` はプールを閉じる。orm は消費側が自分の drizzle-orm で作って渡す(型同一性の分離)。
66
+ * Create a `Database` backed by a single pool used as both primary and replica, suitable for tests.
67
+ *
68
+ * @remarks
69
+ * `dispose()` ends the pool. The `orm` is provided by the caller (rather than constructed here) so
70
+ * the returned database uses the consumer's own `drizzle-orm` types, avoiding type-identity clashes
71
+ * across duplicated `drizzle-orm` installs.
72
+ *
73
+ * @typeParam TDrizzle - The Drizzle instance type provided by the consumer.
74
+ * @param options - Pool and Drizzle instance. See {@link CreatePoolDatabaseOptions}.
75
+ * @returns A {@link DisposableDatabase} whose `dispose()` closes the pool.
76
+ * @example
77
+ * ```ts
78
+ * const pool = testDb.createTestPool();
79
+ * const db = createPoolDatabase({ pool, orm: drizzle(pool, { schema }) });
80
+ * // ... run tests ...
81
+ * await db.dispose();
82
+ * ```
28
83
  */
29
84
  export declare function createPoolDatabase<TDrizzle>(options: CreatePoolDatabaseOptions<TDrizzle>): DisposableDatabase<TDrizzle>;
30
85
  /**
31
- * DB に触れない route(GET / 等)用の Database スタブ。write/transaction は誤用検知のため throw。
32
- * dispose は no-op(Hyperdrive/Pool 背面の DisposableDatabase を期待する repo でもそのまま使える)。
33
- * orm 型は呼び出し側が指定(既定 unknown)。
86
+ * Create a no-op `Database` stub for routes that never touch the database (e.g. plain GET handlers).
87
+ *
88
+ * @remarks
89
+ * `read` resolves to an empty array, while `write` and `transaction` throw so that any unexpected
90
+ * database access is caught as a misuse. `dispose` is a no-op, so this can stand in for a
91
+ * {@link DisposableDatabase} backing (e.g. a Hyperdrive- or pool-based one) without changes.
92
+ *
93
+ * @typeParam TDrizzle - The Drizzle instance type the consumer expects (defaults to `unknown`).
94
+ * @returns A {@link DisposableDatabase} that reads empty and throws on writes/transactions.
95
+ * @throws Error from `write`/`transaction` if they are accessed.
96
+ * @example
97
+ * ```ts
98
+ * const db = createNoopDatabase();
99
+ * await db.read('SELECT 1'); // []
100
+ * await db.write(async (dz) => dz); // throws: noopDatabase.write accessed unexpectedly
101
+ * ```
34
102
  */
35
103
  export declare function createNoopDatabase<TDrizzle = unknown>(): DisposableDatabase<TDrizzle>;
@@ -1,14 +1,37 @@
1
1
  import { databaseFrom } from '../db/database.js';
2
2
  /**
3
- * オフライン route テスト用の in-memory FirebaseVerifier(4 repo 同一実装を集約)。
4
- * `register(token, { uid })` で偽 ID を仕込む。
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
+ * ```
5
14
  */
6
15
  export class FakeFirebaseVerifier {
7
16
  tokens = new Map();
17
+ /** UIDs passed to {@link FakeFirebaseVerifier.deleteUser}, in call order, for assertions. */
8
18
  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
+ */
9
25
  register(token, record) {
10
26
  this.tokens.set(token, record);
11
27
  }
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
+ */
12
35
  async verifyIdToken(idToken) {
13
36
  const record = this.tokens.get(idToken);
14
37
  if (!record) {
@@ -16,16 +39,42 @@ export class FakeFirebaseVerifier {
16
39
  }
17
40
  return record;
18
41
  }
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
+ */
19
48
  async getUser(uid) {
20
49
  return { uid };
21
50
  }
51
+ /**
52
+ * Record a user deletion by appending the UID to {@link FakeFirebaseVerifier.deleted}.
53
+ *
54
+ * @param uid - UID being deleted.
55
+ */
22
56
  async deleteUser(uid) {
23
57
  this.deleted.push(uid);
24
58
  }
25
59
  }
26
60
  /**
27
- * テスト用にプール 1 本を primary/replica 兼用にした Database(foodlabel PoolDatabase 相当)。
28
- * `dispose()` はプールを閉じる。orm は消費側が自分の drizzle-orm で作って渡す(型同一性の分離)。
61
+ * Create a `Database` backed by a single pool used as both primary and replica, suitable for tests.
62
+ *
63
+ * @remarks
64
+ * `dispose()` ends the pool. The `orm` is provided by the caller (rather than constructed here) so
65
+ * the returned database uses the consumer's own `drizzle-orm` types, avoiding type-identity clashes
66
+ * across duplicated `drizzle-orm` installs.
67
+ *
68
+ * @typeParam TDrizzle - The Drizzle instance type provided by the consumer.
69
+ * @param options - Pool and Drizzle instance. See {@link CreatePoolDatabaseOptions}.
70
+ * @returns A {@link DisposableDatabase} whose `dispose()` closes the pool.
71
+ * @example
72
+ * ```ts
73
+ * const pool = testDb.createTestPool();
74
+ * const db = createPoolDatabase({ pool, orm: drizzle(pool, { schema }) });
75
+ * // ... run tests ...
76
+ * await db.dispose();
77
+ * ```
29
78
  */
30
79
  export function createPoolDatabase(options) {
31
80
  const { pool, orm } = options;
@@ -38,9 +87,22 @@ export function createPoolDatabase(options) {
38
87
  };
39
88
  }
40
89
  /**
41
- * DB に触れない route(GET / 等)用の Database スタブ。write/transaction は誤用検知のため throw。
42
- * dispose は no-op(Hyperdrive/Pool 背面の DisposableDatabase を期待する repo でもそのまま使える)。
43
- * orm 型は呼び出し側が指定(既定 unknown)。
90
+ * Create a no-op `Database` stub for routes that never touch the database (e.g. plain GET handlers).
91
+ *
92
+ * @remarks
93
+ * `read` resolves to an empty array, while `write` and `transaction` throw so that any unexpected
94
+ * database access is caught as a misuse. `dispose` is a no-op, so this can stand in for a
95
+ * {@link DisposableDatabase} backing (e.g. a Hyperdrive- or pool-based one) without changes.
96
+ *
97
+ * @typeParam TDrizzle - The Drizzle instance type the consumer expects (defaults to `unknown`).
98
+ * @returns A {@link DisposableDatabase} that reads empty and throws on writes/transactions.
99
+ * @throws Error from `write`/`transaction` if they are accessed.
100
+ * @example
101
+ * ```ts
102
+ * const db = createNoopDatabase();
103
+ * await db.read('SELECT 1'); // []
104
+ * await db.write(async (dz) => dz); // throws: noopDatabase.write accessed unexpectedly
105
+ * ```
44
106
  */
45
107
  export function createNoopDatabase() {
46
108
  return {
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Shared test infrastructure for Hono on Cloudflare Workers projects (depends on `mysql2`/`drizzle-orm`).
3
+ *
4
+ * Test-only helpers that are never loaded at runtime. This subpath consolidates the duplicated
5
+ * test boilerplate (test DB setup, in-memory fakes, auth header builders, Stripe fixtures) that
6
+ * tends to be copy-pasted across projects into a single, importable surface.
7
+ */
1
8
  export { createTestDb } from './db.js';
2
9
  export type { TestDb, CreateTestDbOptions, TestDbConnection } from './db.js';
3
10
  export { FakeFirebaseVerifier, createPoolDatabase, createNoopDatabase } from './fakes.js';