@rdlabo/workers-hono-kit 0.2.0 → 0.3.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 (104) 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 +15 -0
  42. package/dist/index.js +14 -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/queue/consumer.d.ts +112 -0
  52. package/dist/queue/consumer.js +80 -0
  53. package/dist/queue/send.d.ts +90 -0
  54. package/dist/queue/send.js +85 -0
  55. package/dist/stripe/client.d.ts +46 -9
  56. package/dist/stripe/client.js +41 -3
  57. package/dist/testing/auth.d.ts +58 -10
  58. package/dist/testing/auth.js +58 -10
  59. package/dist/testing/configurable-fake.d.ts +20 -9
  60. package/dist/testing/configurable-fake.js +23 -11
  61. package/dist/testing/db.d.ts +81 -12
  62. package/dist/testing/db.js +23 -1
  63. package/dist/testing/fakes.d.ts +77 -9
  64. package/dist/testing/fakes.js +69 -7
  65. package/dist/testing/index.d.ts +7 -0
  66. package/dist/testing/index.js +10 -5
  67. package/dist/testing/stripe-fixtures.d.ts +93 -3
  68. package/dist/testing/stripe-fixtures.js +93 -3
  69. package/package.json +3 -2
  70. package/scripts/check-subrequest-fanout.mjs +86 -0
  71. package/src/ai/gateway.ts +66 -27
  72. package/src/aws/cloudfront.ts +46 -6
  73. package/src/aws/secrets-manager.ts +56 -7
  74. package/src/cache/kv-cache.ts +194 -12
  75. package/src/db/connection.ts +56 -14
  76. package/src/db/database.ts +160 -24
  77. package/src/db/index.ts +11 -2
  78. package/src/db/jst.ts +89 -23
  79. package/src/db/orm-config.ts +61 -19
  80. package/src/db/retry.ts +25 -3
  81. package/src/db/write-result.ts +27 -4
  82. package/src/firebase/firebase-verifier.ts +53 -4
  83. package/src/firebase/identity-toolkit.ts +57 -5
  84. package/src/firebase/jose-firebase-verifier.ts +79 -9
  85. package/src/firebase/remote-verifier.ts +58 -9
  86. package/src/http/app-env.ts +41 -8
  87. package/src/http/app-info.ts +25 -3
  88. package/src/http/http-status.ts +12 -3
  89. package/src/http/nest-error.ts +106 -37
  90. package/src/http/user-protocol.ts +23 -3
  91. package/src/index.ts +17 -3
  92. package/src/middleware/auth.ts +77 -15
  93. package/src/middleware/finalize-response.ts +41 -12
  94. package/src/middleware/validation.ts +89 -15
  95. package/src/middleware/zod-coerce.ts +68 -9
  96. package/src/queue/consumer.ts +146 -0
  97. package/src/queue/send.ts +129 -0
  98. package/src/stripe/client.ts +46 -9
  99. package/src/testing/auth.ts +58 -10
  100. package/src/testing/configurable-fake.ts +23 -11
  101. package/src/testing/db.ts +82 -13
  102. package/src/testing/fakes.ts +77 -9
  103. package/src/testing/index.ts +10 -5
  104. package/src/testing/stripe-fixtures.ts +93 -3
@@ -1,29 +1,92 @@
1
1
  /**
2
- * Workers KV を使った cache-aside キャッシュ(フリート共通 = winecode/tipsys hono CacheService)。
3
- * 同一 DB を参照する透過キャッシュなので `/api` とレスポンスは一致する(perf 層であり parity に影響しない)。
2
+ * Cache-aside helper backed by Cloudflare Workers KV.
4
3
  *
5
- * キー構成は各 repo `/api`(旧 Valkey)と一致させる:
6
- * `${appName}${version}${table}_${type}_${column}` (column: id string なら sha256hex、number はそのまま)
7
- * KV expirationTtl 60s 下限のため lifetime 60 でクランプする。
4
+ * Provides a thin, JSON-serializing wrapper around a {@link KVNamespace} for the common
5
+ * "look in cache, fall back to the source of truth" pattern. Reads and writes are best-effort:
6
+ * any KV error, serialization failure, or oversized key is swallowed so callers transparently
7
+ * fall through to their backing store instead of throwing.
8
+ *
9
+ * Cache keys are namespaced as `<appName><version><table>_<type>_<id>`, where a string `id` is
10
+ * hashed with SHA-256 (hex) and a numeric `id` is used verbatim.
11
+ *
12
+ * @remarks
13
+ * Workers KV enforces a 60-second minimum on `expirationTtl`, so every write clamps its lifetime
14
+ * up to at least {@link KVCacheOptions.minTtlSeconds} (60 by default). Keys whose UTF-8 byte length
15
+ * exceeds the KV 1024-byte limit are skipped entirely, leaving the value uncached.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const cache = new KVCache(env.KV, { appName: 'myapp' });
20
+ * const cached = await cache.get<User>('users', 'profile', userId);
21
+ * if (!cached) {
22
+ * const user = await db.loadUser(userId);
23
+ * await cache.set('users', 'profile', userId, user);
24
+ * }
25
+ * ```
26
+ */
27
+ /**
28
+ * Minimal subset of `@cloudflare/workers-types`' `KVNamespace` used by {@link KVCache}.
29
+ *
30
+ * Declared locally so consumers are not forced to depend on `@cloudflare/workers-types`. Only the
31
+ * three operations the cache actually needs are modeled.
8
32
  */
9
- /** @cloudflare/workers-types の KVNamespace 最小サブセット(get/put/delete のみ使用)。 */
10
33
  export interface KVNamespace {
34
+ /**
35
+ * Read the string value stored under `key`.
36
+ *
37
+ * @param key - Fully namespaced cache key.
38
+ * @returns The stored value, or `null` when the key is absent or expired.
39
+ */
11
40
  get(key: string): Promise<string | null>;
41
+ /**
42
+ * Write `value` under `key`, optionally with a time-to-live.
43
+ *
44
+ * @param key - Fully namespaced cache key.
45
+ * @param value - String payload to store.
46
+ * @param options - Optional write options; `expirationTtl` is the lifetime in seconds.
47
+ * @returns A promise that resolves once the write is accepted.
48
+ */
12
49
  put(key: string, value: string, options?: {
13
50
  expirationTtl?: number;
14
51
  }): Promise<void>;
52
+ /**
53
+ * Remove the entry stored under `key`.
54
+ *
55
+ * @param key - Fully namespaced cache key.
56
+ * @returns A promise that resolves once the delete is accepted.
57
+ */
15
58
  delete(key: string): Promise<void>;
16
59
  }
60
+ /**
61
+ * Configuration for a {@link KVCache} instance.
62
+ */
17
63
  export interface KVCacheOptions {
18
- /** キー前置(repo 名)。例 `'winecode'` / `'tipsys'`。 */
64
+ /**
65
+ * Application-level key prefix used to isolate this app's entries within a shared namespace.
66
+ * For example `'myapp'`.
67
+ */
19
68
  appName: string;
20
- /** バージョン前置。既定 `'v8_'`。 */
69
+ /**
70
+ * Schema/version prefix applied after {@link appName}, letting you invalidate every key at once
71
+ * by bumping it. Defaults to `'v8_'`.
72
+ */
21
73
  version?: string;
22
- /** lifetime の下限秒(KV の最小 TTL)。既定 `60`。 */
74
+ /**
75
+ * Lower bound, in seconds, applied to every write's TTL. Matches the Workers KV 60-second
76
+ * minimum and defaults to `60`.
77
+ */
23
78
  minTtlSeconds?: number;
24
- /** lifetime 未指定時の既定秒。既定 `600`。 */
79
+ /**
80
+ * Default TTL, in seconds, used by {@link KVCache.set} when no per-call `lifetime` is supplied.
81
+ * Defaults to `600`.
82
+ */
25
83
  defaultLifetime?: number;
26
84
  }
85
+ /**
86
+ * A single entry to store via {@link KVCache.setMany}.
87
+ *
88
+ * @internal
89
+ */
27
90
  interface CacheSetItem {
28
91
  table: string;
29
92
  type: string | number;
@@ -31,21 +94,121 @@ interface CacheSetItem {
31
94
  data: unknown;
32
95
  lifetime?: number;
33
96
  }
97
+ /**
98
+ * Key coordinates identifying a single entry for {@link KVCache.getMany}.
99
+ *
100
+ * @internal
101
+ */
34
102
  interface CacheKeyItem {
35
103
  table: string;
36
104
  type: string | number;
37
105
  id: string | number;
38
106
  }
107
+ /**
108
+ * Cache-aside wrapper over a Workers {@link KVNamespace}.
109
+ *
110
+ * Serializes values to JSON, namespaces keys, and clamps TTLs to the KV minimum. All operations are
111
+ * fail-soft: errors are swallowed so a cache miss or backend failure degrades to a source-of-truth
112
+ * lookup rather than propagating.
113
+ *
114
+ * @example
115
+ * ```ts
116
+ * const cache = new KVCache(env.KV, { appName: 'myapp', defaultLifetime: 300 });
117
+ * await cache.set('users', 'profile', 42, { name: 'Ada' });
118
+ * const user = await cache.get<{ name: string }>('users', 'profile', 42);
119
+ * ```
120
+ */
39
121
  export declare class KVCache {
40
122
  #private;
123
+ /**
124
+ * Create a cache bound to a specific KV namespace.
125
+ *
126
+ * @param kv - The Workers KV namespace that backs this cache.
127
+ * @param options - Key-prefix and TTL configuration; see {@link KVCacheOptions}.
128
+ */
41
129
  constructor(kv: KVNamespace, options: KVCacheOptions);
130
+ /**
131
+ * Read and JSON-parse a cached value.
132
+ *
133
+ * @typeParam T - Expected shape of the cached value.
134
+ * @param table - Logical table or entity name.
135
+ * @param type - Sub-key discriminator.
136
+ * @param id - Entity identifier.
137
+ * @returns The parsed value, or `undefined` on a miss, oversized key, or any read/parse error.
138
+ * @example
139
+ * ```ts
140
+ * const user = await cache.get<User>('users', 'profile', userId);
141
+ * ```
142
+ */
42
143
  get<T>(table: string, type: string | number, id: string | number): Promise<T | undefined>;
144
+ /**
145
+ * JSON-serialize and store a value.
146
+ *
147
+ * Falsy `data` is ignored. The effective TTL is `max(minTtlSeconds, lifetime ?? defaultLifetime)`,
148
+ * honoring the KV 60-second floor. Oversized keys and serialization/write failures are silently
149
+ * skipped.
150
+ *
151
+ * @param table - Logical table or entity name.
152
+ * @param type - Sub-key discriminator.
153
+ * @param id - Entity identifier.
154
+ * @param data - Value to cache; serialized with `JSON.stringify`.
155
+ * @param lifetime - Optional TTL in seconds; defaults to {@link KVCacheOptions.defaultLifetime}.
156
+ * @returns A promise that resolves once the write attempt completes.
157
+ * @example
158
+ * ```ts
159
+ * await cache.set('users', 'profile', userId, user, 300);
160
+ * ```
161
+ */
43
162
  set(table: string, type: string | number, id: string | number, data: unknown, lifetime?: number): Promise<void>;
163
+ /**
164
+ * Store many values concurrently.
165
+ *
166
+ * Each item is written via {@link KVCache.set}, so the same fail-soft and TTL rules apply per item.
167
+ * An empty array is a no-op.
168
+ *
169
+ * @param items - Entries to store; see {@link CacheSetItem}.
170
+ * @returns A promise that resolves once every write attempt completes.
171
+ * @example
172
+ * ```ts
173
+ * await cache.setMany([
174
+ * { table: 'users', type: 'profile', id: 1, data: userA },
175
+ * { table: 'users', type: 'profile', id: 2, data: userB, lifetime: 120 },
176
+ * ]);
177
+ * ```
178
+ */
44
179
  setMany(items: CacheSetItem[]): Promise<void>;
180
+ /**
181
+ * Read many values concurrently.
182
+ *
183
+ * @typeParam T - Expected shape of each cached value.
184
+ * @param items - Key coordinates to look up; see {@link CacheKeyItem}.
185
+ * @returns One `{ id, value }` pair per input item, preserving order; `value` is `undefined` on miss.
186
+ * @example
187
+ * ```ts
188
+ * const rows = await cache.getMany<User>([
189
+ * { table: 'users', type: 'profile', id: 1 },
190
+ * { table: 'users', type: 'profile', id: 2 },
191
+ * ]);
192
+ * ```
193
+ */
45
194
  getMany<T>(items: CacheKeyItem[]): Promise<{
46
195
  id: string | number;
47
196
  value: T | undefined;
48
197
  }[]>;
198
+ /**
199
+ * Remove a cached entry.
200
+ *
201
+ * Oversized keys and delete failures are silently ignored.
202
+ *
203
+ * @param table - Logical table or entity name.
204
+ * @param type - Sub-key discriminator.
205
+ * @param id - Entity identifier.
206
+ * @returns A promise that resolves once the delete attempt completes.
207
+ * @example
208
+ * ```ts
209
+ * await cache.delete('users', 'profile', userId);
210
+ * ```
211
+ */
49
212
  delete(table: string, type: string | number, id: string | number): Promise<void>;
50
213
  }
51
214
  export {};
@@ -1,22 +1,67 @@
1
1
  /**
2
- * Workers KV を使った cache-aside キャッシュ(フリート共通 = winecode/tipsys hono CacheService)。
3
- * 同一 DB を参照する透過キャッシュなので `/api` とレスポンスは一致する(perf 層であり parity に影響しない)。
2
+ * Cache-aside helper backed by Cloudflare Workers KV.
4
3
  *
5
- * キー構成は各 repo `/api`(旧 Valkey)と一致させる:
6
- * `${appName}${version}${table}_${type}_${column}` (column: id string なら sha256hex、number はそのまま)
7
- * KV expirationTtl 60s 下限のため lifetime 60 でクランプする。
4
+ * Provides a thin, JSON-serializing wrapper around a {@link KVNamespace} for the common
5
+ * "look in cache, fall back to the source of truth" pattern. Reads and writes are best-effort:
6
+ * any KV error, serialization failure, or oversized key is swallowed so callers transparently
7
+ * fall through to their backing store instead of throwing.
8
+ *
9
+ * Cache keys are namespaced as `<appName><version><table>_<type>_<id>`, where a string `id` is
10
+ * hashed with SHA-256 (hex) and a numeric `id` is used verbatim.
11
+ *
12
+ * @remarks
13
+ * Workers KV enforces a 60-second minimum on `expirationTtl`, so every write clamps its lifetime
14
+ * up to at least {@link KVCacheOptions.minTtlSeconds} (60 by default). Keys whose UTF-8 byte length
15
+ * exceeds the KV 1024-byte limit are skipped entirely, leaving the value uncached.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const cache = new KVCache(env.KV, { appName: 'myapp' });
20
+ * const cached = await cache.get<User>('users', 'profile', userId);
21
+ * if (!cached) {
22
+ * const user = await db.loadUser(userId);
23
+ * await cache.set('users', 'profile', userId, user);
24
+ * }
25
+ * ```
8
26
  */
9
27
  const encoder = new TextEncoder();
28
+ /**
29
+ * Compute the lowercase hex SHA-256 digest of a UTF-8 string.
30
+ *
31
+ * @param input - String to hash.
32
+ * @returns The 64-character hex-encoded digest.
33
+ * @internal
34
+ */
10
35
  async function sha256Hex(input) {
11
36
  const digest = await crypto.subtle.digest('SHA-256', encoder.encode(input));
12
37
  return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, '0')).join('');
13
38
  }
39
+ /**
40
+ * Cache-aside wrapper over a Workers {@link KVNamespace}.
41
+ *
42
+ * Serializes values to JSON, namespaces keys, and clamps TTLs to the KV minimum. All operations are
43
+ * fail-soft: errors are swallowed so a cache miss or backend failure degrades to a source-of-truth
44
+ * lookup rather than propagating.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const cache = new KVCache(env.KV, { appName: 'myapp', defaultLifetime: 300 });
49
+ * await cache.set('users', 'profile', 42, { name: 'Ada' });
50
+ * const user = await cache.get<{ name: string }>('users', 'profile', 42);
51
+ * ```
52
+ */
14
53
  export class KVCache {
15
54
  #kv;
16
55
  #appName;
17
56
  #version;
18
57
  #minTtl;
19
58
  #defaultLifetime;
59
+ /**
60
+ * Create a cache bound to a specific KV namespace.
61
+ *
62
+ * @param kv - The Workers KV namespace that backs this cache.
63
+ * @param options - Key-prefix and TTL configuration; see {@link KVCacheOptions}.
64
+ */
20
65
  constructor(kv, options) {
21
66
  this.#kv = kv;
22
67
  this.#appName = options.appName;
@@ -24,15 +69,40 @@ export class KVCache {
24
69
  this.#minTtl = options.minTtlSeconds ?? 60;
25
70
  this.#defaultLifetime = options.defaultLifetime ?? 600;
26
71
  }
72
+ /**
73
+ * Build the fully namespaced KV key for the given coordinates.
74
+ *
75
+ * A string `id` is hashed with SHA-256 (hex); a numeric `id` is used as-is.
76
+ *
77
+ * @param table - Logical table or entity name.
78
+ * @param type - Sub-key discriminator (e.g. lookup variant).
79
+ * @param id - Entity identifier; strings are hashed, numbers used verbatim.
80
+ * @returns The key, or `undefined` when it would exceed the KV 1024-byte limit.
81
+ * @internal
82
+ */
27
83
  async #buildKey(table, type, id) {
28
84
  const column = typeof id === 'string' ? await sha256Hex(id) : id;
29
85
  const key = `${this.#appName}${this.#version}${table}_${type}_${column}`;
30
- // KV のキーは 512〜1024 バイト上限。超えるものはキャッシュ対象外(cache-aside なので DB 直読みに落ちる)。
86
+ // KV keys are capped at 1024 bytes. Oversized keys are left uncached; cache-aside callers fall
87
+ // back to reading directly from their source of truth.
31
88
  if (encoder.encode(key).byteLength > 1024) {
32
89
  return undefined;
33
90
  }
34
91
  return key;
35
92
  }
93
+ /**
94
+ * Read and JSON-parse a cached value.
95
+ *
96
+ * @typeParam T - Expected shape of the cached value.
97
+ * @param table - Logical table or entity name.
98
+ * @param type - Sub-key discriminator.
99
+ * @param id - Entity identifier.
100
+ * @returns The parsed value, or `undefined` on a miss, oversized key, or any read/parse error.
101
+ * @example
102
+ * ```ts
103
+ * const user = await cache.get<User>('users', 'profile', userId);
104
+ * ```
105
+ */
36
106
  async get(table, type, id) {
37
107
  const key = await this.#buildKey(table, type, id);
38
108
  if (!key) {
@@ -49,6 +119,24 @@ export class KVCache {
49
119
  return undefined;
50
120
  }
51
121
  }
122
+ /**
123
+ * JSON-serialize and store a value.
124
+ *
125
+ * Falsy `data` is ignored. The effective TTL is `max(minTtlSeconds, lifetime ?? defaultLifetime)`,
126
+ * honoring the KV 60-second floor. Oversized keys and serialization/write failures are silently
127
+ * skipped.
128
+ *
129
+ * @param table - Logical table or entity name.
130
+ * @param type - Sub-key discriminator.
131
+ * @param id - Entity identifier.
132
+ * @param data - Value to cache; serialized with `JSON.stringify`.
133
+ * @param lifetime - Optional TTL in seconds; defaults to {@link KVCacheOptions.defaultLifetime}.
134
+ * @returns A promise that resolves once the write attempt completes.
135
+ * @example
136
+ * ```ts
137
+ * await cache.set('users', 'profile', userId, user, 300);
138
+ * ```
139
+ */
52
140
  async set(table, type, id, data, lifetime) {
53
141
  if (!data) {
54
142
  return;
@@ -67,17 +155,61 @@ export class KVCache {
67
155
  const ttl = Math.max(this.#minTtl, lifetime ?? this.#defaultLifetime);
68
156
  await this.#kv.put(key, payload, { expirationTtl: ttl }).catch(() => undefined);
69
157
  }
158
+ /**
159
+ * Store many values concurrently.
160
+ *
161
+ * Each item is written via {@link KVCache.set}, so the same fail-soft and TTL rules apply per item.
162
+ * An empty array is a no-op.
163
+ *
164
+ * @param items - Entries to store; see {@link CacheSetItem}.
165
+ * @returns A promise that resolves once every write attempt completes.
166
+ * @example
167
+ * ```ts
168
+ * await cache.setMany([
169
+ * { table: 'users', type: 'profile', id: 1, data: userA },
170
+ * { table: 'users', type: 'profile', id: 2, data: userB, lifetime: 120 },
171
+ * ]);
172
+ * ```
173
+ */
70
174
  async setMany(items) {
71
175
  if (items.length === 0) {
72
176
  return;
73
177
  }
74
178
  await Promise.all(items.map((i) => this.set(i.table, i.type, i.id, i.data, i.lifetime)));
75
179
  }
76
- // T は呼び出し側が指定する戻り値型(get<T> と同じく ergonomics 目的)。
180
+ /**
181
+ * Read many values concurrently.
182
+ *
183
+ * @typeParam T - Expected shape of each cached value.
184
+ * @param items - Key coordinates to look up; see {@link CacheKeyItem}.
185
+ * @returns One `{ id, value }` pair per input item, preserving order; `value` is `undefined` on miss.
186
+ * @example
187
+ * ```ts
188
+ * const rows = await cache.getMany<User>([
189
+ * { table: 'users', type: 'profile', id: 1 },
190
+ * { table: 'users', type: 'profile', id: 2 },
191
+ * ]);
192
+ * ```
193
+ */
194
+ // The generic `T` is the caller-specified return type, mirroring `get<T>` for ergonomics.
77
195
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
78
196
  async getMany(items) {
79
197
  return Promise.all(items.map(async (i) => ({ id: i.id, value: await this.get(i.table, i.type, i.id) })));
80
198
  }
199
+ /**
200
+ * Remove a cached entry.
201
+ *
202
+ * Oversized keys and delete failures are silently ignored.
203
+ *
204
+ * @param table - Logical table or entity name.
205
+ * @param type - Sub-key discriminator.
206
+ * @param id - Entity identifier.
207
+ * @returns A promise that resolves once the delete attempt completes.
208
+ * @example
209
+ * ```ts
210
+ * await cache.delete('users', 'profile', userId);
211
+ * ```
212
+ */
81
213
  async delete(table, type, id) {
82
214
  const key = await this.#buildKey(table, type, id);
83
215
  if (!key) {
@@ -1,36 +1,78 @@
1
1
  import type { Connection } from 'mysql2/promise';
2
2
  /**
3
- * Hyperdrive バインディングの最小形(@cloudflare/workers-types への依存を避けるための構造型)。
3
+ * Minimal structural shape of a Cloudflare Hyperdrive binding.
4
+ *
5
+ * @remarks
6
+ * Declared structurally to avoid a dependency on `@cloudflare/workers-types`; any object with these
7
+ * connection fields satisfies it.
4
8
  */
5
9
  export interface HyperdriveLike {
10
+ /** Database host to connect to. */
6
11
  host: string;
12
+ /** Database user. */
7
13
  user: string;
14
+ /** Database password. */
8
15
  password: string;
16
+ /** Database name. */
9
17
  database: string;
18
+ /** Database port. */
10
19
  port: number;
11
20
  }
12
21
  /**
13
- * Hyperdrive バインディングから mysql2 createConnection 用オプションを作る。
14
- * `disableEval: true`(Workers で eval 不可)は既定で付与。`extra` で timezone 等を上書き/追加。
22
+ * Build mysql2 `createConnection` options from a Hyperdrive binding, applying the kit's defaults.
23
+ *
24
+ * @remarks
25
+ * Three defaults are applied and can each be overridden via `extra`:
15
26
  *
16
- * `decimalNumbers: true`: DECIMAL/NEWDECIMAL を文字列でなく JS number で返す。Drizzle
17
- * `$inferSelect`(decimal→string)と生 SQL reads の戻り値を、各 repo の数値ドメイン型
18
- * (nutrition number 等)に揃えるため既定で有効化。precision/scale JS の安全整数域
19
- * (decimal(15,2) 程度まで)を超える列が無いことが前提。
27
+ * - `disableEval: true` `eval` is unavailable in the Workers runtime, so the driver's eval-based
28
+ * fast paths must be disabled.
29
+ * - `decimalNumbers: true` return `DECIMAL`/`NEWDECIMAL` columns as JS `number` rather than
30
+ * strings, so raw-SQL reads and Drizzle's inferred types align on a single numeric domain type.
31
+ * This assumes no column's precision exceeds the JS safe-integer range.
32
+ * - `timezone: '+09:00'` — set the driver's session timezone to JST. mysql2 defaults to `'local'`,
33
+ * which is UTC in the Workers runtime; pinning the driver timezone keeps `datetime`/`timestamp`
34
+ * round-trips independent of the database's session timezone (only the internally stored UTC
35
+ * value differs, which is invisible to the application). Non-JST deployments can override this
36
+ * via `extra: { timezone: '...' }`.
20
37
  *
21
- * `timezone: '+09:00'`: フリートの接続先 RDB session time_zone=Asia/Tokyo。mysql2 driver
22
- * `timezone` 既定は `'local'`=Workers では UTC で、揃わないと `datetime/timestamp` の生 Date 読みが
23
- * +9h・生 Date 書きが −9h ズレる(NestJS JST 実行で一致=移植で顕在化する潜在バグ)。driver
24
- * 固定すれば round-trip の観測値は DB の session tz に非依存(内部格納 UTC 値だけ変わるが app 不可視)。
25
- * 非 JST repo は `extra: { timezone: '...' }` で上書き可。
38
+ * @param hyperdrive - the Hyperdrive binding to derive connection fields from.
39
+ * @param extra - additional mysql2 options merged last, overriding the defaults above.
40
+ * @returns a plain options object to pass to mysql2 `createConnection`.
26
41
  */
27
42
  export declare function hyperdriveConnectionOptions(hyperdrive: HyperdriveLike, extra?: Record<string, unknown>): Record<string, unknown>;
43
+ /**
44
+ * Minimal structural shape of a Workers `ExecutionContext`, limited to `waitUntil`.
45
+ *
46
+ * @remarks
47
+ * Declared structurally to avoid a dependency on `@cloudflare/workers-types`.
48
+ */
28
49
  export interface ExecutionContextLike {
50
+ /** Extend the request's lifetime until `promise` settles (used to close connections after the response). */
29
51
  waitUntil(promise: Promise<unknown>): void;
30
52
  }
31
53
  /**
32
- * primary/replica の接続を開いて `fn` を実行し、finally `ctx.waitUntil` 越しに閉じる
33
- * (receptray/tipsys の worker entry の接続ライフサイクル相当)。
54
+ * Open primary and replica connections, run `fn` with them, and close both afterwards.
55
+ *
56
+ * The connections are always closed in a `finally` block; closing is scheduled through
57
+ * `ctx.waitUntil` so it can complete after the response has been returned, without blocking it.
58
+ *
59
+ * @typeParam T - resolved value produced by `fn`.
60
+ * @param hyperdrives - the primary and replica Hyperdrive bindings to connect to.
61
+ * @param ctx - the execution context whose `waitUntil` defers connection teardown past the response.
62
+ * @param fn - callback invoked with the open `primary` and `replica` connections.
63
+ * @param connectionOptions - extra mysql2 options forwarded to {@link hyperdriveConnectionOptions}.
64
+ * @returns the value resolved by `fn`.
65
+ * @example
66
+ * ```ts
67
+ * const data = await withMysqlConnections(
68
+ * { primary: env.PRIMARY, replica: env.REPLICA },
69
+ * ctx,
70
+ * async ({ primary, replica }) => {
71
+ * const [rows] = await replica.query('SELECT 1');
72
+ * return rows;
73
+ * },
74
+ * );
75
+ * ```
34
76
  */
35
77
  export declare function withMysqlConnections<T>(hyperdrives: {
36
78
  primary: HyperdriveLike;
@@ -1,18 +1,24 @@
1
1
  import { createConnection } from 'mysql2/promise';
2
2
  /**
3
- * Hyperdrive バインディングから mysql2 createConnection 用オプションを作る。
4
- * `disableEval: true`(Workers で eval 不可)は既定で付与。`extra` で timezone 等を上書き/追加。
3
+ * Build mysql2 `createConnection` options from a Hyperdrive binding, applying the kit's defaults.
5
4
  *
6
- * `decimalNumbers: true`: DECIMAL/NEWDECIMAL を文字列でなく JS number で返す。Drizzle の
7
- * `$inferSelect`(decimal→string)と生 SQL reads の戻り値を、各 repo の数値ドメイン型
8
- * (nutrition の number 等)に揃えるため既定で有効化。precision/scale が JS の安全整数域
9
- * (decimal(15,2) 程度まで)を超える列が無いことが前提。
5
+ * @remarks
6
+ * Three defaults are applied and can each be overridden via `extra`:
10
7
  *
11
- * `timezone: '+09:00'`: フリートの接続先 RDB session time_zone=Asia/Tokyo。mysql2 driver
12
- * `timezone` 既定は `'local'`=Workers では UTC で、揃わないと `datetime/timestamp` の生 Date 読みが
13
- * +9h・生 Date 書きが −9h ズレる(NestJS JST 実行で一致=移植で顕在化する潜在バグ)。driver
14
- * 固定すれば round-trip の観測値は DB session tz に非依存(内部格納 UTC 値だけ変わるが app 不可視)。
15
- * JST repo `extra: { timezone: '...' }` で上書き可。
8
+ * - `disableEval: true` `eval` is unavailable in the Workers runtime, so the driver's eval-based
9
+ * fast paths must be disabled.
10
+ * - `decimalNumbers: true` return `DECIMAL`/`NEWDECIMAL` columns as JS `number` rather than
11
+ * strings, so raw-SQL reads and Drizzle's inferred types align on a single numeric domain type.
12
+ * This assumes no column's precision exceeds the JS safe-integer range.
13
+ * - `timezone: '+09:00'` — set the driver's session timezone to JST. mysql2 defaults to `'local'`,
14
+ * which is UTC in the Workers runtime; pinning the driver timezone keeps `datetime`/`timestamp`
15
+ * round-trips independent of the database's session timezone (only the internally stored UTC
16
+ * value differs, which is invisible to the application). Non-JST deployments can override this
17
+ * via `extra: { timezone: '...' }`.
18
+ *
19
+ * @param hyperdrive - the Hyperdrive binding to derive connection fields from.
20
+ * @param extra - additional mysql2 options merged last, overriding the defaults above.
21
+ * @returns a plain options object to pass to mysql2 `createConnection`.
16
22
  */
17
23
  export function hyperdriveConnectionOptions(hyperdrive, extra) {
18
24
  return {
@@ -28,8 +34,28 @@ export function hyperdriveConnectionOptions(hyperdrive, extra) {
28
34
  };
29
35
  }
30
36
  /**
31
- * primary/replica の接続を開いて `fn` を実行し、finally `ctx.waitUntil` 越しに閉じる
32
- * (receptray/tipsys の worker entry の接続ライフサイクル相当)。
37
+ * Open primary and replica connections, run `fn` with them, and close both afterwards.
38
+ *
39
+ * The connections are always closed in a `finally` block; closing is scheduled through
40
+ * `ctx.waitUntil` so it can complete after the response has been returned, without blocking it.
41
+ *
42
+ * @typeParam T - resolved value produced by `fn`.
43
+ * @param hyperdrives - the primary and replica Hyperdrive bindings to connect to.
44
+ * @param ctx - the execution context whose `waitUntil` defers connection teardown past the response.
45
+ * @param fn - callback invoked with the open `primary` and `replica` connections.
46
+ * @param connectionOptions - extra mysql2 options forwarded to {@link hyperdriveConnectionOptions}.
47
+ * @returns the value resolved by `fn`.
48
+ * @example
49
+ * ```ts
50
+ * const data = await withMysqlConnections(
51
+ * { primary: env.PRIMARY, replica: env.REPLICA },
52
+ * ctx,
53
+ * async ({ primary, replica }) => {
54
+ * const [rows] = await replica.query('SELECT 1');
55
+ * return rows;
56
+ * },
57
+ * );
58
+ * ```
33
59
  */
34
60
  export async function withMysqlConnections(hyperdrives, ctx, fn, connectionOptions) {
35
61
  let primary;