@rdlabo/workers-hono-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +247 -0
  3. package/dist/ai/gateway.d.ts +40 -0
  4. package/dist/ai/gateway.js +36 -0
  5. package/dist/aws/cloudfront.d.ts +9 -0
  6. package/dist/aws/cloudfront.js +43 -0
  7. package/dist/aws/secrets-manager.d.ts +14 -0
  8. package/dist/aws/secrets-manager.js +43 -0
  9. package/dist/cache/kv-cache.d.ts +51 -0
  10. package/dist/cache/kv-cache.js +88 -0
  11. package/dist/db/connection.d.ts +41 -0
  12. package/dist/db/connection.js +48 -0
  13. package/dist/db/database.d.ts +60 -0
  14. package/dist/db/database.js +63 -0
  15. package/dist/db/index.d.ts +10 -0
  16. package/dist/db/index.js +8 -0
  17. package/dist/db/jst.d.ts +19 -0
  18. package/dist/db/jst.js +48 -0
  19. package/dist/db/orm-config.d.ts +62 -0
  20. package/dist/db/orm-config.js +42 -0
  21. package/dist/db/retry.d.ts +6 -0
  22. package/dist/db/retry.js +22 -0
  23. package/dist/db/write-result.d.ts +16 -0
  24. package/dist/db/write-result.js +13 -0
  25. package/dist/firebase/firebase-verifier.d.ts +17 -0
  26. package/dist/firebase/firebase-verifier.js +1 -0
  27. package/dist/firebase/identity-toolkit.d.ts +24 -0
  28. package/dist/firebase/identity-toolkit.js +64 -0
  29. package/dist/firebase/jose-firebase-verifier.d.ts +32 -0
  30. package/dist/firebase/jose-firebase-verifier.js +52 -0
  31. package/dist/firebase/remote-verifier.d.ts +9 -0
  32. package/dist/firebase/remote-verifier.js +44 -0
  33. package/dist/http/app-env.d.ts +19 -0
  34. package/dist/http/app-env.js +16 -0
  35. package/dist/http/app-info.d.ts +11 -0
  36. package/dist/http/app-info.js +8 -0
  37. package/dist/http/http-status.d.ts +62 -0
  38. package/dist/http/http-status.js +63 -0
  39. package/dist/http/nest-error.d.ts +72 -0
  40. package/dist/http/nest-error.js +65 -0
  41. package/dist/http/user-protocol.d.ts +11 -0
  42. package/dist/http/user-protocol.js +8 -0
  43. package/dist/index.d.ts +30 -0
  44. package/dist/index.js +28 -0
  45. package/dist/middleware/auth.d.ts +36 -0
  46. package/dist/middleware/auth.js +30 -0
  47. package/dist/middleware/finalize-response.d.ts +2 -0
  48. package/dist/middleware/finalize-response.js +53 -0
  49. package/dist/middleware/validation.d.ts +77 -0
  50. package/dist/middleware/validation.js +53 -0
  51. package/dist/middleware/zod-coerce.d.ts +9 -0
  52. package/dist/middleware/zod-coerce.js +50 -0
  53. package/dist/stripe/client.d.ts +19 -0
  54. package/dist/stripe/client.js +23 -0
  55. package/dist/testing/auth.d.ts +36 -0
  56. package/dist/testing/auth.js +42 -0
  57. package/dist/testing/configurable-fake.d.ts +14 -0
  58. package/dist/testing/configurable-fake.js +33 -0
  59. package/dist/testing/db.d.ts +37 -0
  60. package/dist/testing/db.js +74 -0
  61. package/dist/testing/fakes.d.ts +35 -0
  62. package/dist/testing/fakes.js +56 -0
  63. package/dist/testing/index.d.ts +8 -0
  64. package/dist/testing/index.js +10 -0
  65. package/dist/testing/stripe-fixtures.d.ts +13 -0
  66. package/dist/testing/stripe-fixtures.js +76 -0
  67. package/package.json +113 -0
  68. package/scripts/sync-dev-aws.mjs +59 -0
  69. package/src/ai/gateway.ts +81 -0
  70. package/src/aws/cloudfront.ts +65 -0
  71. package/src/aws/secrets-manager.ts +63 -0
  72. package/src/cache/kv-cache.ts +134 -0
  73. package/src/db/connection.ts +73 -0
  74. package/src/db/database.ts +133 -0
  75. package/src/db/index.ts +27 -0
  76. package/src/db/jst.ts +56 -0
  77. package/src/db/orm-config.ts +71 -0
  78. package/src/db/retry.ts +21 -0
  79. package/src/db/write-result.ts +23 -0
  80. package/src/firebase/firebase-verifier.ts +15 -0
  81. package/src/firebase/identity-toolkit.ts +82 -0
  82. package/src/firebase/jose-firebase-verifier.ts +71 -0
  83. package/src/firebase/remote-verifier.ts +49 -0
  84. package/src/http/app-env.ts +20 -0
  85. package/src/http/app-info.ts +16 -0
  86. package/src/http/http-status.ts +62 -0
  87. package/src/http/nest-error.ts +138 -0
  88. package/src/http/user-protocol.ts +16 -0
  89. package/src/index.ts +55 -0
  90. package/src/middleware/auth.ts +67 -0
  91. package/src/middleware/finalize-response.ts +61 -0
  92. package/src/middleware/validation.ts +84 -0
  93. package/src/middleware/zod-coerce.ts +65 -0
  94. package/src/stripe/client.ts +48 -0
  95. package/src/testing/auth.ts +62 -0
  96. package/src/testing/configurable-fake.ts +33 -0
  97. package/src/testing/db.ts +125 -0
  98. package/src/testing/fakes.ts +75 -0
  99. package/src/testing/index.ts +26 -0
  100. package/src/testing/stripe-fixtures.ts +85 -0
@@ -0,0 +1,125 @@
1
+ import { drizzle } from 'drizzle-orm/mysql2';
2
+ import { migrate } from 'drizzle-orm/mysql2/migrator';
3
+ import { createConnection, createPool } from 'mysql2/promise';
4
+ import type { Pool } from 'mysql2/promise';
5
+
6
+ /**
7
+ * フリート共通のテスト DB ヘルパ(各 repo の testing/db.ts を集約)。
8
+ * テストスキーマは「コミット済み Drizzle マイグレーション」を単一ソースとして構築する
9
+ * (手書き schema.sql ではなく `db:generate` 由来の ./drizzle)。
10
+ *
11
+ * Node 専用(vitest 下で実行)。実行時 parity には無関係なテスト基盤。
12
+ */
13
+ export interface TestDbConnection {
14
+ host: string;
15
+ port: number;
16
+ user: string;
17
+ password: string;
18
+ }
19
+
20
+ export interface CreateTestDbOptions {
21
+ /** テスト DB 名(例 'tipsys_test')。並列実行で feature 毎に分けたい場合は呼び出し側で TEST_DB を解決して渡す。 */
22
+ dbName: string;
23
+ /** Drizzle マイグレーションフォルダの絶対パス(呼び出し側で `join(here, '..', 'drizzle')` を解決して渡す)。 */
24
+ migrationsFolder: string;
25
+ /** 接続情報。未指定は env(DB_HOST/DB_PORT/DB_USER/DB_PASSWORD)→ 127.0.0.1/3306/root/root。 */
26
+ connection?: Partial<TestDbConnection>;
27
+ }
28
+
29
+ export interface TestDb {
30
+ readonly dbName: string;
31
+ readonly connection: TestDbConnection;
32
+ /** DROP/CREATE して Drizzle マイグレーションを適用しスキーマを構築。 */
33
+ resetSchema(): Promise<void>;
34
+ /** テスト DB に繋いだ mysql2 プールを返す(afterAll で pool.end())。 */
35
+ createTestPool(): Pool;
36
+ /** 全テーブルを TRUNCATE(information_schema から動的取得。__drizzle_migrations は除外)。 */
37
+ truncateAll(pool: Pool): Promise<void>;
38
+ /** 1 行 insert する汎用 seed(列名→値)。route spec の fixture 用。 */
39
+ seed(pool: Pool, table: string, row: Record<string, unknown>): Promise<void>;
40
+ /** ローカル MySQL が到達可能か(`describe.skipIf(!(await mysqlReachable()))` のガード用)。 */
41
+ mysqlReachable(): Promise<boolean>;
42
+ }
43
+
44
+ function resolveConnection(override?: Partial<TestDbConnection>): TestDbConnection {
45
+ const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env ?? {};
46
+ return {
47
+ host: override?.host ?? env.DB_HOST ?? '127.0.0.1',
48
+ port: override?.port ?? Number(env.DB_PORT ?? '3306'),
49
+ user: override?.user ?? env.DB_USER ?? 'root',
50
+ password: override?.password ?? env.DB_PASSWORD ?? 'root',
51
+ };
52
+ }
53
+
54
+ export function createTestDb(options: CreateTestDbOptions): TestDb {
55
+ const { dbName, migrationsFolder } = options;
56
+ const connection = resolveConnection(options.connection);
57
+
58
+ return {
59
+ dbName,
60
+ connection,
61
+
62
+ async resetSchema(): Promise<void> {
63
+ const admin = await createConnection({ ...connection, multipleStatements: true });
64
+ await admin.query(
65
+ `DROP DATABASE IF EXISTS \`${dbName}\`; CREATE DATABASE \`${dbName}\` DEFAULT CHARACTER SET utf8mb4;`,
66
+ );
67
+ await admin.changeUser({ database: dbName });
68
+ await migrate(drizzle(admin), { migrationsFolder });
69
+ await admin.end();
70
+ },
71
+
72
+ createTestPool(): Pool {
73
+ // decimalNumbers / timezone mirror the runtime hyperdriveConnectionOptions so specs read
74
+ // DECIMAL columns as numbers and handle datetime in +09:00 (JST), matching production.
75
+ const pool = createPool({
76
+ ...connection,
77
+ database: dbName,
78
+ connectionLimit: 5,
79
+ decimalNumbers: true,
80
+ timezone: '+09:00',
81
+ });
82
+ // Pin ONLY_FULL_GROUP_BY on every pooled connection so GROUP BY violations surface in specs
83
+ // regardless of the server's my.cnf (fleet policy centralized here, not per-repo). CONCAT keeps
84
+ // the server's other sql_mode flags and is harmless if ONLY_FULL_GROUP_BY is already present.
85
+ // mysql2 queues this SET ahead of the consumer's first query on each new physical connection.
86
+ pool.on('connection', (conn) => {
87
+ void conn.query("SET SESSION sql_mode = CONCAT(@@SESSION.sql_mode, ',ONLY_FULL_GROUP_BY')");
88
+ });
89
+ return pool;
90
+ },
91
+
92
+ async truncateAll(pool: Pool): Promise<void> {
93
+ const [rows] = await pool.query(
94
+ "SELECT table_name AS t FROM information_schema.tables WHERE table_schema = ? AND table_type='BASE TABLE' AND table_name <> '__drizzle_migrations'",
95
+ [dbName],
96
+ );
97
+ const tables = (rows as { t: string }[]).map((r) => r.t);
98
+ await pool.query('SET FOREIGN_KEY_CHECKS=0');
99
+ for (const t of tables) {
100
+ await pool.query(`TRUNCATE TABLE \`${t}\``);
101
+ }
102
+ await pool.query('SET FOREIGN_KEY_CHECKS=1');
103
+ },
104
+
105
+ async seed(pool: Pool, table: string, row: Record<string, unknown>): Promise<void> {
106
+ const cols = Object.keys(row);
107
+ if (cols.length === 0) {
108
+ return;
109
+ }
110
+ const placeholders = cols.map(() => '?').join(', ');
111
+ const columnList = cols.map((c) => `\`${c}\``).join(', ');
112
+ await pool.query(`INSERT INTO \`${table}\` (${columnList}) VALUES (${placeholders})`, Object.values(row));
113
+ },
114
+
115
+ async mysqlReachable(): Promise<boolean> {
116
+ try {
117
+ const c = await createConnection({ ...connection });
118
+ await c.end();
119
+ return true;
120
+ } catch {
121
+ return false;
122
+ }
123
+ },
124
+ };
125
+ }
@@ -0,0 +1,75 @@
1
+ import type { Pool } from 'mysql2/promise';
2
+ import { databaseFrom } from '../db/database';
3
+ import type { DisposableDatabase } from '../db/database';
4
+ import type { DecodedIdToken, FirebaseVerifier } from '../firebase/firebase-verifier';
5
+
6
+ /**
7
+ * オフライン route テスト用の in-memory FirebaseVerifier(4 repo 同一実装を集約)。
8
+ * `register(token, { uid })` で偽 ID を仕込む。
9
+ */
10
+ export class FakeFirebaseVerifier implements FirebaseVerifier {
11
+ private readonly tokens = new Map<string, DecodedIdToken>();
12
+ readonly deleted: string[] = [];
13
+
14
+ register(token: string, record: DecodedIdToken): void {
15
+ this.tokens.set(token, record);
16
+ }
17
+
18
+ async verifyIdToken(idToken: string): Promise<DecodedIdToken> {
19
+ const record = this.tokens.get(idToken);
20
+ if (!record) {
21
+ throw new Error('invalid firebase id token');
22
+ }
23
+ return record;
24
+ }
25
+
26
+ async getUser(uid: string): Promise<{ uid: string; email?: string } | null> {
27
+ return { uid };
28
+ }
29
+
30
+ async deleteUser(uid: string): Promise<void> {
31
+ this.deleted.push(uid);
32
+ }
33
+ }
34
+
35
+ export interface CreatePoolDatabaseOptions<TDrizzle> {
36
+ /** テスト用プール(primary/replica 兼用)。 */
37
+ pool: Pool;
38
+ /** 消費側の drizzle-orm で `drizzle(pool, { schema, ... })` を作って渡す。 */
39
+ orm: TDrizzle;
40
+ }
41
+
42
+ /**
43
+ * テスト用にプール 1 本を primary/replica 兼用にした Database(foodlabel の PoolDatabase 相当)。
44
+ * `dispose()` はプールを閉じる。orm は消費側が自分の drizzle-orm で作って渡す(型同一性の分離)。
45
+ */
46
+ export function createPoolDatabase<TDrizzle>(
47
+ options: CreatePoolDatabaseOptions<TDrizzle>,
48
+ ): DisposableDatabase<TDrizzle> {
49
+ const { pool, orm } = options;
50
+ const base = databaseFrom(orm, pool);
51
+ return {
52
+ ...base,
53
+ async dispose(): Promise<void> {
54
+ await pool.end();
55
+ },
56
+ };
57
+ }
58
+
59
+ /**
60
+ * DB に触れない route(GET / 等)用の Database スタブ。write/transaction は誤用検知のため throw。
61
+ * dispose は no-op(Hyperdrive/Pool 背面の DisposableDatabase を期待する repo でもそのまま使える)。
62
+ * orm 型は呼び出し側が指定(既定 unknown)。
63
+ */
64
+ export function createNoopDatabase<TDrizzle = unknown>(): DisposableDatabase<TDrizzle> {
65
+ return {
66
+ read: async () => [],
67
+ write: () => {
68
+ throw new Error('noopDatabase.write accessed unexpectedly');
69
+ },
70
+ transaction: () => {
71
+ throw new Error('noopDatabase.transaction accessed unexpectedly');
72
+ },
73
+ dispose: async () => {},
74
+ };
75
+ }
@@ -0,0 +1,26 @@
1
+ // @rdlabo/workers-hono-kit/testing — フリート共通のテスト基盤(mysql2/drizzle 依存)。
2
+ // 実行時には読み込まれないテスト専用ヘルパ。各 repo の testing/db.ts・fakes.ts を集約。
3
+
4
+ export { createTestDb } from './db';
5
+ export type { TestDb, CreateTestDbOptions, TestDbConnection } from './db';
6
+
7
+ export { FakeFirebaseVerifier, createPoolDatabase, createNoopDatabase } from './fakes';
8
+ export type { CreatePoolDatabaseOptions } from './fakes';
9
+ export type { Database, DisposableDatabase, QueryRunner, TxOf } from '../db/database';
10
+
11
+ // 認証テストヘルパ(route spec のヘッダ生成・ユーザ provision を集約)。
12
+ export { authHeaders, registerFirebaseToken, provisionUser } from './auth';
13
+
14
+ // test double ヘルパ(未設定メソッドで明示 throw する部分実装 fake)。
15
+ export { configurableFake } from './configurable-fake';
16
+
17
+ // Stripe オブジェクトの test fixture factory。
18
+ export {
19
+ fakeApiList,
20
+ fakePaymentIntent,
21
+ fakeStripeEvent,
22
+ fakeCheckoutSession,
23
+ fakeCustomer,
24
+ fakePrice,
25
+ fakeSubscription,
26
+ } from './stripe-fixtures';
@@ -0,0 +1,85 @@
1
+ import type Stripe from 'stripe';
2
+
3
+ /**
4
+ * Stripe オブジェクトの test fixture factory。実 SDK 型は巨大なので、テストが参照する範囲だけを
5
+ * 妥当な既定値で組み、`over` で上書きする(最後に 1 度だけ Stripe 型へキャスト)。fleet 各 repo の
6
+ * 課金テストで同じダミー PaymentIntent/Event/... を手組みしていた重複を集約する。
7
+ */
8
+
9
+ export function fakeApiList<T>(data: T[], over: Partial<Stripe.ApiList<T>> = {}): Stripe.ApiList<T> {
10
+ return {
11
+ object: 'list',
12
+ data,
13
+ has_more: false,
14
+ url: '/v1/_test',
15
+ ...over,
16
+ };
17
+ }
18
+
19
+ export function fakePaymentIntent(over: Partial<Stripe.PaymentIntent> = {}): Stripe.PaymentIntent {
20
+ return {
21
+ id: 'pi_test_1',
22
+ object: 'payment_intent',
23
+ amount: 1000,
24
+ currency: 'jpy',
25
+ status: 'succeeded',
26
+ created: 1_700_000_000,
27
+ ...over,
28
+ } as Stripe.PaymentIntent;
29
+ }
30
+
31
+ export function fakeStripeEvent(type: string, dataObject: unknown, over: Partial<Stripe.Event> = {}): Stripe.Event {
32
+ return {
33
+ id: 'evt_test_1',
34
+ object: 'event',
35
+ api_version: '2024-06-20',
36
+ created: 1_700_000_000,
37
+ livemode: false,
38
+ type,
39
+ data: { object: dataObject },
40
+ ...over,
41
+ } as Stripe.Event;
42
+ }
43
+
44
+ export function fakeCheckoutSession(over: Partial<Stripe.Checkout.Session> = {}): Stripe.Checkout.Session {
45
+ return {
46
+ id: 'cs_test_1',
47
+ object: 'checkout.session',
48
+ url: 'https://checkout.stripe.test/cs_test_1',
49
+ mode: 'subscription',
50
+ status: 'open',
51
+ ...over,
52
+ } as Stripe.Checkout.Session;
53
+ }
54
+
55
+ export function fakeCustomer(over: Partial<Stripe.Customer> = {}): Stripe.Customer {
56
+ return {
57
+ id: 'cus_test_1',
58
+ object: 'customer',
59
+ created: 1_700_000_000,
60
+ livemode: false,
61
+ ...over,
62
+ } as Stripe.Customer;
63
+ }
64
+
65
+ export function fakePrice(over: Partial<Stripe.Price> = {}): Stripe.Price {
66
+ return {
67
+ id: 'price_test_1',
68
+ object: 'price',
69
+ active: true,
70
+ currency: 'jpy',
71
+ unit_amount: 1000,
72
+ ...over,
73
+ } as Stripe.Price;
74
+ }
75
+
76
+ export function fakeSubscription(over: Partial<Stripe.Subscription> = {}): Stripe.Subscription {
77
+ return {
78
+ id: 'sub_test_1',
79
+ object: 'subscription',
80
+ status: 'active',
81
+ customer: 'cus_test_1',
82
+ created: 1_700_000_000,
83
+ ...over,
84
+ } as Stripe.Subscription;
85
+ }