@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
@@ -4,16 +4,43 @@ import type { ServiceAccount } from './identity-toolkit.js';
4
4
  import { JoseFirebaseVerifier, SECURETOKEN_JWK_URL } from './jose-firebase-verifier.js';
5
5
 
6
6
  /**
7
- * 本番用の便宜ファクトリ。`createRemoteJWKSet` Google securetoken の公開鍵を取り、
8
- * `JoseFirebaseVerifier` を返す。トークン検証のみ用途(getUser/deleteUser は不要 = Identity Toolkit 無し)。
7
+ * Shared remote JWKS for Google's securetoken keys.
9
8
  *
10
- * JWKS は URL 固定なので isolate 内で 1 度だけ生成して共有し(jose が内部メモリにキャッシュ)、
11
- * verifier projectId ごとにメモ化する。winecode の旧 `verifyFirebaseIdToken`(module-level JWKS)の
12
- * キャッシュ挙動を保つための置換。
9
+ * @remarks
10
+ * The JWKS URL is fixed, so the set is created once per isolate and shared across verifiers
11
+ * (`jose` caches the fetched keys internally). Lazily initialised on first use.
12
+ *
13
+ * @internal
13
14
  */
14
15
  let jwks: ReturnType<typeof createRemoteJWKSet> | undefined;
16
+ /**
17
+ * Per-`projectId` cache of token-only verifiers, memoized for the lifetime of the isolate.
18
+ *
19
+ * @internal
20
+ */
15
21
  const verifiers = new Map<string, JoseFirebaseVerifier>();
16
22
 
23
+ /**
24
+ * Create a token-verification-only Firebase verifier for the given project.
25
+ *
26
+ * Uses `createRemoteJWKSet` to fetch Google's securetoken public keys and returns a
27
+ * {@link JoseFirebaseVerifier}. This factory is for token verification only; it configures no
28
+ * Identity Toolkit client, so `getUser` / `deleteUser` are unavailable.
29
+ *
30
+ * @remarks
31
+ * The remote JWKS is created once per isolate and shared, and the returned verifier is
32
+ * memoized per `projectId`. This preserves the caching behaviour of a module-level JWKS so
33
+ * repeated calls do not re-fetch keys or allocate new verifiers.
34
+ *
35
+ * @param projectId - The Firebase project id whose tokens will be verified.
36
+ * @returns A verifier that validates ID tokens for `projectId`.
37
+ * @example
38
+ * ```ts
39
+ * const verifier = createRemoteFirebaseVerifier('my-firebase-project');
40
+ * const decoded = await verifier.verifyIdToken(idToken);
41
+ * console.log(decoded.uid);
42
+ * ```
43
+ */
17
44
  export function createRemoteFirebaseVerifier(projectId: string): JoseFirebaseVerifier {
18
45
  jwks ??= createRemoteJWKSet(new URL(SECURETOKEN_JWK_URL));
19
46
  let verifier = verifiers.get(projectId);
@@ -24,13 +51,35 @@ export function createRemoteFirebaseVerifier(projectId: string): JoseFirebaseVer
24
51
  return verifier;
25
52
  }
26
53
 
54
+ /**
55
+ * Single-entry cache of the service-account verifier, keyed by the raw service-account JSON.
56
+ *
57
+ * @internal
58
+ */
27
59
  let saVerifierCache: { key: string; verifier: JoseFirebaseVerifier } | null = null;
28
60
 
29
61
  /**
30
- * サービスアカウント JSON から検証器を作る便宜ファクトリ(receptray/tipsys hono `firebaseFor` 相当)。
31
- * `getUser`/`deleteUser` のため `IdentityToolkit` を内包する点が `createRemoteFirebaseVerifier` との違い。
32
- * SA JSON 文字列をキーに isolate 内で 1 つだけキャッシュ(秘密が変わったときだけ再生成)し、
33
- * JWKS `createRemoteFirebaseVerifier` と共有する。
62
+ * Create a Firebase verifier from a service-account JSON string.
63
+ *
64
+ * Unlike {@link createRemoteFirebaseVerifier}, the returned {@link JoseFirebaseVerifier}
65
+ * embeds an {@link IdentityToolkit} client, enabling `getUser` and `deleteUser` in addition to
66
+ * token verification.
67
+ *
68
+ * @remarks
69
+ * The verifier is cached for the lifetime of the isolate, keyed by the service-account JSON
70
+ * string, and is only rebuilt when that secret changes. The remote JWKS is shared with
71
+ * {@link createRemoteFirebaseVerifier}.
72
+ *
73
+ * @param serviceAccountJson - The service-account key as a JSON string (parsed into {@link ServiceAccount}).
74
+ * @returns A verifier that validates ID tokens and can look up or delete users.
75
+ * @throws If `serviceAccountJson` is not valid JSON.
76
+ * @example
77
+ * ```ts
78
+ * const verifier = createServiceAccountVerifier(env.FIREBASE_SERVICE_ACCOUNT);
79
+ * const decoded = await verifier.verifyIdToken(idToken);
80
+ * const user = await verifier.getUser(decoded.uid);
81
+ * await verifier.deleteUser(decoded.uid);
82
+ * ```
34
83
  */
35
84
  export function createServiceAccountVerifier(serviceAccountJson: string): JoseFirebaseVerifier {
36
85
  if (saVerifierCache?.key !== serviceAccountJson) {
@@ -1,20 +1,53 @@
1
+ /**
2
+ * The resolved runtime environment of the Worker.
3
+ */
1
4
  export type AppEnv = 'development' | 'production';
2
5
 
3
6
  /**
4
- * 実行環境(development / production)を解決する。フリート共通の判定。
7
+ * Resolve the runtime environment (development or production) from the Worker `env` binding.
8
+ *
9
+ * @remarks
10
+ * Cloudflare Workers have no filesystem or `child_process` at runtime, so the environment cannot be
11
+ * inferred from the presence of a `.git` directory the way a Node/NestJS process might. Instead, the
12
+ * development signal is carried in the committed launch command: `wrangler dev --var APP_ENV:development`
13
+ * injects `APP_ENV`, while `wrangler deploy` injects nothing. Only `env.APP_ENV === 'development'`
14
+ * resolves to `'development'`; every other case (including a missing binding, i.e. a production deploy)
15
+ * resolves to `'production'`. This "absence defaults to production" semantic keeps the safe side as the
16
+ * default.
17
+ *
18
+ * Because it reads from `env` rather than a request header, it works in both `fetch` and `scheduled`
19
+ * contexts and cannot be spoofed by an incoming request.
5
20
  *
6
- * NestJS `/api` は実行時 FS `.git` 有無で判定し「git throw catch → 本番」としていた。
7
- * Workers は実行時に FS / child_process が無いため同じ手は使えない。代わりに dev シグナルを
8
- * **コミット済みの起動コマンド**に置く: `wrangler dev --var APP_ENV:development`(`deploy` は無注入)。
9
- * よって `env.APP_ENV === 'development'` の時だけ development、それ以外(注入無し=本番デプロイ)は
10
- * production に倒す。これは `/api` の「absence/catch = 本番(安全側)」と同じ意味論。
21
+ * @param env - The Worker environment binding, or `null`/`undefined` when unavailable.
22
+ * @returns `'development'` only when `env.APP_ENV` is exactly `'development'`; otherwise `'production'`.
11
23
  *
12
- * `env` 由来なので fetch / scheduled どちらの文脈でも使え、リクエストヘッダ由来でないため詐称されない。
24
+ * @example
25
+ * ```ts
26
+ * export default {
27
+ * fetch(req, env) {
28
+ * if (resolveAppEnv(env) === 'development') {
29
+ * // enable verbose logging
30
+ * }
31
+ * },
32
+ * };
33
+ * ```
13
34
  */
14
35
  export function resolveAppEnv(env: { APP_ENV?: string } | null | undefined): AppEnv {
15
36
  return env?.APP_ENV === 'development' ? 'development' : 'production';
16
37
  }
17
38
 
18
- /** production 判定のショートハンド。 */
39
+ /**
40
+ * Shorthand for checking whether the resolved environment is production.
41
+ *
42
+ * @param env - The Worker environment binding, or `null`/`undefined` when unavailable.
43
+ * @returns `true` when {@link resolveAppEnv} resolves to `'production'`.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * if (isProductionEnv(env)) {
48
+ * // skip dev-only diagnostics
49
+ * }
50
+ * ```
51
+ */
19
52
  export const isProductionEnv = (env: { APP_ENV?: string } | null | undefined): boolean =>
20
53
  resolveAppEnv(env) === 'production';
@@ -1,14 +1,36 @@
1
1
  import type { Context } from 'hono';
2
2
 
3
- /** クライアントアプリのメタ情報(NestJS の x-amz-meta-* ヘッダ由来。3 repo 共通)。 */
3
+ /**
4
+ * Client application metadata derived from the `x-amz-meta-*` request headers.
5
+ *
6
+ * @remarks
7
+ * Reproduces the per-request app identity that a NestJS service would expose so a Hono app
8
+ * can read the same client-supplied version/uuid pair without changing the wire contract.
9
+ */
4
10
  export interface AppInfo {
11
+ /** Client application version from `x-amz-meta-version`, or `null` when the header is absent. */
5
12
  version: string | null;
13
+ /** Client installation identifier from `x-amz-meta-uuid`, or `null` when the header is absent. */
6
14
  uuid: string | null;
7
15
  }
8
16
 
9
17
  /**
10
- * `x-amz-meta-version` / `x-amz-meta-uuid` ヘッダから AppInfo を読む。
11
- * auth middleware が per-request で c.set('appInfo', ...) する値(3 repo で同一仕様)。
18
+ * Read {@link AppInfo} from the `x-amz-meta-version` / `x-amz-meta-uuid` request headers.
19
+ *
20
+ * @remarks
21
+ * Typically called by an auth middleware that stores the result per request via
22
+ * `c.set('appInfo', getAppInfo(c))`. Missing headers resolve to `null` rather than throwing.
23
+ *
24
+ * @param c - The Hono request context to read headers from.
25
+ * @returns The client application metadata for the current request.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * app.use(async (c, next) => {
30
+ * c.set('appInfo', getAppInfo(c));
31
+ * await next();
32
+ * });
33
+ * ```
12
34
  */
13
35
  export const getAppInfo = (c: Context): AppInfo => ({
14
36
  version: c.req.header('x-amz-meta-version') ?? null,
@@ -1,7 +1,11 @@
1
1
  /**
2
- * NestJS `@nestjs/common` HttpStatus enum と同一。フリート(NestJS → Hono 移植)で
3
- * ステータスコードを Nest と同じ名前で参照するための共通定数。`/api` のレスポンス status と
4
- * バイト一致させる際の単一の参照元にする。
2
+ * HTTP status codes mirroring the `HttpStatus` enum from `@nestjs/common`.
3
+ *
4
+ * @remarks
5
+ * Provides a single source of truth for referencing status codes by the same names NestJS uses, so a
6
+ * Hono app can emit responses whose status matches a NestJS service byte-for-byte. The member set and
7
+ * numeric values intentionally track `@nestjs/common` rather than the IANA registry, including a few
8
+ * non-standard codes that NestJS ships.
5
9
  */
6
10
  export enum HttpStatus {
7
11
  CONTINUE = 100,
@@ -17,7 +21,9 @@ export enum HttpStatus {
17
21
  PARTIAL_CONTENT = 206,
18
22
  MULTI_STATUS = 207,
19
23
  ALREADY_REPORTED = 208,
24
+ /** Non-standard WebDAV extension carried over from NestJS. */
20
25
  CONTENT_DIFFERENT = 210,
26
+ /** Multiple Choices (300). Named `AMBIGUOUS` to match NestJS. */
21
27
  AMBIGUOUS = 300,
22
28
  MOVED_PERMANENTLY = 301,
23
29
  FOUND = 302,
@@ -43,13 +49,16 @@ export enum HttpStatus {
43
49
  UNSUPPORTED_MEDIA_TYPE = 415,
44
50
  REQUESTED_RANGE_NOT_SATISFIABLE = 416,
45
51
  EXPECTATION_FAILED = 417,
52
+ /** "I'm a teapot" (418), from RFC 2324. */
46
53
  I_AM_A_TEAPOT = 418,
54
+ /** Misdirected Request (421). Named `MISDIRECTED` to match NestJS. */
47
55
  MISDIRECTED = 421,
48
56
  UNPROCESSABLE_ENTITY = 422,
49
57
  LOCKED = 423,
50
58
  FAILED_DEPENDENCY = 424,
51
59
  PRECONDITION_REQUIRED = 428,
52
60
  TOO_MANY_REQUESTS = 429,
61
+ /** Non-standard code carried over from NestJS. */
53
62
  UNRECOVERABLE_ERROR = 456,
54
63
  INTERNAL_SERVER_ERROR = 500,
55
64
  NOT_IMPLEMENTED = 501,
@@ -2,8 +2,11 @@ import type { Context, Env } from 'hono';
2
2
  import type { ContentfulStatusCode } from 'hono/utils/http-status';
3
3
 
4
4
  /**
5
- * NestJS の既定例外フィルタが付ける reason phrase(フリート共通 = receptray/winecode/foodlabel
6
- * REASON_PHRASE / DEFAULT_MESSAGES と同一)。3 repo がそれぞれ手書きしていたものを一本化する。
5
+ * Reason phrases attached by the NestJS default exception filter, keyed by HTTP status code.
6
+ *
7
+ * @remarks
8
+ * Mirrors the `error` field values NestJS produces for common client-error statuses, so a Hono app can
9
+ * return byte-identical error bodies. Used as the default `reasonPhrases` map by {@link createNestErrorHandler}.
7
10
  */
8
11
  export const NEST_REASON_PHRASES: Record<number, string> = {
9
12
  400: 'Bad Request',
@@ -12,64 +15,100 @@ export const NEST_REASON_PHRASES: Record<number, string> = {
12
15
  404: 'Not Found',
13
16
  };
14
17
 
15
- /** 想定外エラーの通報先に渡す文脈(request id 相関など)。フリート共通の最小形。 */
18
+ /**
19
+ * Contextual metadata passed to an {@link ErrorReporter} when reporting an unexpected error.
20
+ */
16
21
  export interface ErrorReportContext {
22
+ /** Correlation id for the failing request, if one is tracked. */
17
23
  requestId?: string;
18
24
  }
19
25
 
20
26
  /**
21
- * 想定外エラーの通報関数(Sentry 等)の型。各 repo container.reportError がこの形。
22
- * `createNestErrorHandler({ onUnhandledError })` に `(err, c) => reporter(err, { requestId: c.get('requestId') })`
23
- * の形で差し込む。Sentry 呼び出し自体は各 repo(@sentry/cloudflare は workers-hono-kit に持ち込まない)。
27
+ * Signature of a function that reports an unexpected (non-HTTP) error to an external sink such as Sentry.
28
+ *
29
+ * @remarks
30
+ * Wire it into {@link createNestErrorHandler} via `onUnhandledError`, e.g.
31
+ * `(err, c) => reporter(err, { requestId: c.get('requestId') })`. The reporting client itself is
32
+ * intentionally kept out of this kit; the consumer supplies the implementation.
33
+ *
34
+ * @param error - The thrown value being reported.
35
+ * @param context - Optional correlation context for the failing request.
24
36
  */
25
37
  export type ErrorReporter = (error: unknown, context?: ErrorReportContext) => void;
26
38
 
27
- /** http エラーとみなされた値から status / message / body を読むための最小形。 */
39
+ /**
40
+ * Minimal shape read from a value treated as an HTTP error: its status, message, and optional body.
41
+ *
42
+ * @internal
43
+ */
28
44
  interface HttpErrorLike {
45
+ /** HTTP status code to respond with. */
29
46
  status: ContentfulStatusCode;
47
+ /** Human-readable error message placed in the response body. */
30
48
  message: string;
31
- /** repo 固有 body の脱出口(winecode の HttpError.body 相当)。あればそのまま render する。 */
49
+ /**
50
+ * Escape hatch for a fully custom response body. When present, it is rendered verbatim instead of
51
+ * the NestJS-shaped body.
52
+ */
32
53
  body?: unknown;
33
54
  }
34
55
 
56
+ /**
57
+ * Options controlling how {@link createNestErrorHandler} shapes error responses.
58
+ *
59
+ * @typeParam E - The Hono environment type, so `onUnhandledError` receives a correctly typed context.
60
+ */
35
61
  export interface NestErrorHandlerOptions<E extends Env = Env> {
36
- /** reason phrase map。既定 `NEST_REASON_PHRASES`。 */
62
+ /** Status-to-reason-phrase map for the `error` field. Defaults to {@link NEST_REASON_PHRASES}. */
37
63
  reasonPhrases?: Record<number, string>;
38
64
  /**
39
- * `error` フィールドを省いて `{ statusCode, message }` のみ返す status。既定 `[401]`
40
- * NestJS generic `HttpException(msg, 401)` `error` を持たない)。
65
+ * Statuses that return only `{ statusCode, message }`, omitting the `error` field. Defaults to `[401]`,
66
+ * matching NestJS where a generic `HttpException(msg, 401)` carries no `error`.
41
67
  */
42
68
  bareStatuses?: readonly number[];
43
69
  /**
44
- * bare body のフィールド順序。既定 `'statusCode-first'`(= NestJS canonical / receptray・winecode)。
45
- * **foodlabel `'message-first'`** を指定して `{ message, error, statusCode }` byte-parity を保つ。
70
+ * Field order of the non-bare error body. Defaults to `'statusCode-first'` (the NestJS canonical order).
71
+ * Use `'message-first'` to emit `{ message, error, statusCode }` when byte parity requires it.
46
72
  */
47
73
  fieldOrder?: 'statusCode-first' | 'message-first';
48
74
  /**
49
- * reasonPhrases に無い(かつ bare でない)status `error` フォールバック。既定 `undefined`
50
- * (= reason 無しなら `error` を省く)。**winecode `'Error'`** を指定し、全 status `error` を必ず出す
51
- * (NestJS 既定例外フィルタの「error は常に存在」を忠実再現)。
75
+ * Fallback `error` value for statuses that are neither bare nor present in `reasonPhrases`. Defaults to
76
+ * `undefined`, meaning the `error` field is omitted when no reason phrase is known. Set to a string such
77
+ * as `'Error'` to always include an `error` field, faithfully reproducing the NestJS default exception
78
+ * filter behavior where `error` is always present.
52
79
  */
53
80
  fallbackReason?: string;
54
81
  /**
55
- * http エラー判定。既定は hono `HTTPException`。
56
- * **winecode は独自 `HttpError` を使う**ため `(e) => e instanceof HttpError` を渡す。
82
+ * Predicate identifying which thrown values are HTTP errors. Defaults to detecting Hono's `HTTPException`.
83
+ * Override it (e.g. `(e) => e instanceof MyHttpError`) when the app throws a custom HTTP error type.
57
84
  */
58
85
  isHttpError?: (err: unknown) => err is HttpErrorLike;
59
86
  /**
60
- * http エラーでない(= 想定外)エラーを 500 で返す前に呼ぶフック(Sentry 通報など)。
61
- * **receptray `container.reportError?.(err, { requestId })`** を差し込む。例外は握り潰す。
87
+ * Hook invoked before an unexpected (non-HTTP) error is returned as a 500, typically used to report the
88
+ * error (e.g. to Sentry). Any exception thrown by this hook is swallowed so reporting cannot alter the
89
+ * error response.
62
90
  */
63
91
  onUnhandledError?: (err: unknown, c: Context<E>) => void;
64
- /** 想定外エラー時の 500 body。既定 `{ statusCode: 500, message: 'Internal server error' }`。 */
92
+ /**
93
+ * Response body for unexpected errors returned as 500. Defaults to
94
+ * `{ statusCode: 500, message: 'Internal server error' }`.
95
+ */
65
96
  internalServerErrorBody?: unknown;
66
97
  }
67
98
 
68
99
  /**
69
- * hono `HTTPException` **構造的に**判定する(`instanceof` ではない)。workers-hono-kit は consumer に
70
- * symlink 同梱されるため、workers-hono-kit が解決する `hono` と consumer の `hono` が別インスタンスになり得る
71
- * (別コピーの HTTPException は `instanceof` で一致しない)。`getResponse()` と数値 `status` を持つかで
72
- * 判定すればモジュール境界をまたいでも、prod バンドルでも安定する。
100
+ * Structurally detect Hono's `HTTPException` without relying on `instanceof`.
101
+ *
102
+ * @remarks
103
+ * When this kit is symlinked into a consumer, the `hono` instance it resolves can differ from the
104
+ * consumer's `hono`, so an `HTTPException` from one copy fails an `instanceof` check against the other.
105
+ * Detecting the presence of a `getResponse()` method and a numeric `status` is stable across module
106
+ * boundaries and production bundles.
107
+ *
108
+ * @param err - The thrown value to test.
109
+ * @returns `true` when `err` looks like a Hono `HTTPException`.
110
+ *
111
+ * @internal
73
112
  */
74
113
  const isHTTPException = (err: unknown): err is HttpErrorLike =>
75
114
  err instanceof Error &&
@@ -77,13 +116,32 @@ const isHTTPException = (err: unknown): err is HttpErrorLike =>
77
116
  typeof (err as { status?: unknown }).status === 'number';
78
117
 
79
118
  /**
80
- * NestJS の例外フィルタ相当の Hono `onError` ハンドラを作る(フリート共通)。
81
- * - http エラー(既定 `HTTPException`)→ Nest 形 body にマップ。`body` を持つ場合はそれを verbatim で返す。
82
- * - bareStatuses(既定 401)は `error` フィールド無し。
83
- * - それ以外(想定外エラー)→ `onUnhandledError` 通報 + `console.error` + 500。
119
+ * Create a Hono `onError` handler that maps thrown errors to NestJS-shaped error JSON.
84
120
  *
85
- * `app.onError(createNestErrorHandler(...))` の形で使う。各 repo の parity 差異(body 順序・
86
- * エラー型・通報フック)は options で吸収し、本体の分岐ロジックは共有する。
121
+ * @remarks
122
+ * Reproduces the NestJS default exception filter so a Hono app returns byte-identical error bodies:
123
+ * - HTTP errors (by default `HTTPException`) are mapped to a NestJS-shaped body; if the error carries a
124
+ * custom `body`, that body is returned verbatim.
125
+ * - Statuses listed in `bareStatuses` (default `[401]`) omit the `error` field.
126
+ * - Any other (unexpected) error triggers `onUnhandledError`, is logged via `console.error`, and returns 500.
127
+ *
128
+ * Per-app differences in body field order, HTTP error type, and reporting hook are absorbed through
129
+ * {@link NestErrorHandlerOptions}, while the branching logic stays shared.
130
+ *
131
+ * @typeParam E - The Hono environment type propagated to `onUnhandledError`.
132
+ * @param options - Overrides for reason phrases, bare statuses, field order, error detection, and reporting.
133
+ * @returns A handler suitable for `app.onError(...)`.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * app.onError(
138
+ * createNestErrorHandler({
139
+ * fieldOrder: 'message-first',
140
+ * fallbackReason: 'Error',
141
+ * onUnhandledError: (err, c) => reportError(err, { requestId: c.get('requestId') }),
142
+ * }),
143
+ * );
144
+ * ```
87
145
  */
88
146
  export function createNestErrorHandler<E extends Env = Env>(options: NestErrorHandlerOptions<E> = {}) {
89
147
  const {
@@ -98,12 +156,12 @@ export function createNestErrorHandler<E extends Env = Env>(options: NestErrorHa
98
156
 
99
157
  return (err: Error, c: Context<E>): Response => {
100
158
  if (isHttpError(err)) {
101
- // repo 固有 body の脱出口(winecode)。
159
+ // Escape hatch for a custom error body: render it verbatim.
102
160
  if (err.body !== undefined) {
103
161
  return c.json(err.body as object, err.status);
104
162
  }
105
- // reasonPhrases[status] は型上 string だが noUncheckedIndexedAccess 無効のため実際は未定義になり得る。
106
- // 未登録 status fallbackReason フォールバックは意図的。
163
+ // reasonPhrases[status] is typed as string, but with noUncheckedIndexedAccess disabled it can be
164
+ // undefined at runtime. The fallbackReason fallback for unregistered statuses is intentional.
107
165
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
108
166
  const reason = bareStatuses.includes(err.status) ? undefined : (reasonPhrases[err.status] ?? fallbackReason);
109
167
  if (reason === undefined) {
@@ -119,7 +177,7 @@ export function createNestErrorHandler<E extends Env = Env>(options: NestErrorHa
119
177
  try {
120
178
  onUnhandledError?.(err, c);
121
179
  } catch {
122
- // 通報はエラーレスポンスの挙動を変えてはならない。
180
+ // Reporting must never change the behavior of the error response.
123
181
  }
124
182
  console.error(err);
125
183
  return c.json(internalServerErrorBody as object, 500);
@@ -127,8 +185,19 @@ export function createNestErrorHandler<E extends Env = Env>(options: NestErrorHa
127
185
  }
128
186
 
129
187
  /**
130
- * Express/Nest 既定の未マッチルート 404 body を返す `notFound` ハンドラ。
131
- * `app.notFound(nestNotFoundHandler)` で使う(receptray/winecode は未実装の parity ギャップ)。
188
+ * Hono `notFound` handler that returns the canonical Express/NestJS unmatched-route 404 body.
189
+ *
190
+ * @remarks
191
+ * Produces `{ message: "Cannot <METHOD> <path>", error: 'Not Found', statusCode: 404 }`, matching the
192
+ * NestJS default 404 response so unmatched routes stay byte-identical.
193
+ *
194
+ * @param c - The Hono request context for the unmatched route.
195
+ * @returns A 404 JSON response.
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * app.notFound(nestNotFoundHandler);
200
+ * ```
132
201
  */
133
202
  export function nestNotFoundHandler(c: Context): Response {
134
203
  return c.json(
@@ -1,14 +1,34 @@
1
1
  import type { Context } from 'hono';
2
2
 
3
- /** クライアントの IP / UA(NestJS の @UserProtocol デコレータ相当)。 */
3
+ /**
4
+ * The client's network identity: IP address and user agent.
5
+ *
6
+ * @remarks
7
+ * Equivalent to the data a NestJS `@UserProtocol` decorator would expose, so a Hono app can persist the
8
+ * same client metadata. Both fields are nullable to map directly onto nullable database columns.
9
+ */
4
10
  export interface IUserProtocol {
11
+ /** Client IP address, or `null` when no source header is present. */
5
12
  ipAddress: string | null;
13
+ /** Client user-agent string, or `null` when the `User-Agent` header is absent. */
6
14
  userAgent: string | null;
7
15
  }
8
16
 
9
17
  /**
10
- * Hono Context からクライアント IP / UA を取得する。Cloudflare は実 IP `CF-Connecting-IP` に入れる
11
- * (`X-Forwarded-For` はフォールバック)。未取得は null(DB の nullable カラムにそのまま入る)。
18
+ * Read the client's IP address and user agent from the Hono request context.
19
+ *
20
+ * @remarks
21
+ * On Cloudflare the real client IP is provided in `CF-Connecting-IP`, with `X-Forwarded-For` used as a
22
+ * fallback. Missing values resolve to `null` so they map cleanly onto nullable storage.
23
+ *
24
+ * @param c - The Hono request context to read headers from.
25
+ * @returns The client's IP address and user agent for the current request.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const { ipAddress, userAgent } = getUserProtocol(c);
30
+ * await auditLog.insert({ ipAddress, userAgent });
31
+ * ```
12
32
  */
13
33
  export const getUserProtocol = (c: Context): IUserProtocol => ({
14
34
  ipAddress: c.req.header('cf-connecting-ip') ?? c.req.header('x-forwarded-for') ?? null,
package/src/index.ts CHANGED
@@ -1,6 +1,14 @@
1
- // @rdlabo/workers-hono-kit — フリート共通のインフラ層ヘルパ(receptray / winecode / foodlabel)。
2
- // ドメイン・DB・各 repo 固有の parity 差異(auth エラー status/body、secretId、Secret スキーマ)は
3
- // 各 repo 側に残し、ここには「設定注入で汎用化できるインフラ」だけを置く。
1
+ /**
2
+ * `@rdlabo/workers-hono-kit` infrastructure-layer helpers for Hono on Cloudflare Workers.
3
+ *
4
+ * This package collects reusable, configuration-injected building blocks (HTTP middleware,
5
+ * caching, Stripe, Drizzle helpers, AI Gateway, AWS/Firebase integrations) that can be shared
6
+ * across services. Domain logic, database schemas, and application-specific behavior are
7
+ * intentionally left to the consuming application; only generic infrastructure that can be made
8
+ * reusable through dependency/configuration injection lives here.
9
+ *
10
+ * @packageDocumentation
11
+ */
4
12
 
5
13
  // middleware
6
14
  export { finalizeResponse } from './middleware/finalize-response.js';
@@ -4,34 +4,95 @@ import type { ContentfulStatusCode } from 'hono/utils/http-status';
4
4
  import { getAppInfo } from '../http/app-info.js';
5
5
  import type { AppInfo } from '../http/app-info.js';
6
6
 
7
+ /**
8
+ * Configuration for {@link createAuthMiddleware}.
9
+ *
10
+ * @typeParam E - The Hono `Env` (bindings/variables) of the application.
11
+ * @typeParam Verified - The value produced by {@link AuthMiddlewareOptions.verify} (e.g. a decoded token or user record).
12
+ * @typeParam Id - The resolved user identifier type.
13
+ */
7
14
  export interface AuthMiddlewareOptions<E extends Env, Verified, Id = unknown> {
8
- /** ID トークンを載せるヘッダ。既定 `'x-amz-security-token'`(フリート共通)。 */
15
+ /** Header carrying the ID token. Defaults to `'x-amz-security-token'`. */
9
16
  tokenHeader?: string;
10
- /** 生トークンを検証して record/decoded を返す。無効なら throw / reject すること。 */
17
+ /**
18
+ * Verify the raw token and return the decoded value or user record.
19
+ *
20
+ * @param token - The raw token read from {@link AuthMiddlewareOptions.tokenHeader} (empty string if absent).
21
+ * @param c - The current Hono context.
22
+ * @returns The verified value passed to {@link AuthMiddlewareOptions.resolveUserId}/{@link AuthMiddlewareOptions.setContext}.
23
+ * @throws If the token is invalid; rejecting/throwing triggers the failure path.
24
+ */
11
25
  verify: (token: string, c: Context<E>) => Promise<Verified>;
12
26
  /**
13
- * DB userId を解決(必要なら新規作成)する。**省略すると token-only**(検証のみ・login 用)になる。
14
- * create-on-miss(`getUserIdFromFirebase(...).catch(() => createUser(...))`)は repo 側でここに合成する。
27
+ * Resolve the database user id (creating the user if necessary).
28
+ *
29
+ * @remarks
30
+ * Omit this to run in **token-only** mode (verification only, e.g. for login). Create-on-miss
31
+ * behavior (such as `getUserId(...).catch(() => createUser(...))`) should be composed here by the
32
+ * caller.
33
+ *
34
+ * @param verified - The value returned by {@link AuthMiddlewareOptions.verify}.
35
+ * @param c - The current Hono context.
36
+ * @param appInfo - The resolved application info for the request.
37
+ * @returns The resolved user id.
15
38
  */
16
39
  resolveUserId?: (verified: Verified, c: Context<E>, appInfo: AppInfo) => Promise<Id>;
17
- /** 検証結果を c.var に載せる。repo 固有の var 名(`decodedToken` / `userRecord` / `userProtocol` 等)を注入する。 */
40
+ /**
41
+ * Store the verification result on the context variables.
42
+ *
43
+ * @remarks
44
+ * Inject the application-specific variable names here (e.g. `decodedToken`, `userRecord`, `userProtocol`).
45
+ *
46
+ * @param c - The current Hono context.
47
+ * @param data - The verified value, resolved app info, and (when available) the user id.
48
+ */
18
49
  setContext: (c: Context<E>, data: { verified: Verified; appInfo: AppInfo; userId?: Id }) => void;
19
50
  /**
20
- * 失敗時の挙動。既定は `throw new HTTPException(failureStatus, { message: failureMessage })`
21
- * (foodlabel/receptray と同形)。**winecode は `c.json(BODY, n)` を返す**ため上書きする。
51
+ * Override the failure behavior.
52
+ *
53
+ * @remarks
54
+ * Defaults to `throw new HTTPException(failureStatus, { message: failureMessage })`. Provide this to
55
+ * return a custom `Response` instead (e.g. `c.json(body, status)`).
56
+ *
57
+ * @param err - The error thrown during verification/resolution.
58
+ * @param c - The current Hono context.
59
+ * @returns The failure response to send.
22
60
  */
23
61
  onFailure?: (err: unknown, c: Context<E>) => Response;
24
- /** 既定 onFailure status。既定 `403`(token-only 401 等は repo が上書き)。 */
62
+ /** Status used by the default `onFailure`. Defaults to `403`. */
25
63
  failureStatus?: ContentfulStatusCode;
26
- /** 既定 onFailure message。既定 `'Forbidden resource'`。 */
64
+ /** Message used by the default `onFailure`. Defaults to `'Forbidden resource'`. */
27
65
  failureMessage?: string;
28
66
  }
29
67
 
30
68
  /**
31
- * NestJS AuthGuard / TokenGuard 相当の認証 middleware を作る(フリート共通)。
32
- * スケルトン(ヘッダ読取 → verify → getAppInfo → resolveUserId → setContext、失敗で console.error +
33
- * onFailure)を共有し、repo 固有部分(verify / userId 解決 / var / 失敗レスポンス)だけ注入させる。
34
- * `resolveUserId` を省けば token-only middleware になる。
69
+ * Create an authentication middleware equivalent to a NestJS `AuthGuard` / `TokenGuard`.
70
+ *
71
+ * The middleware runs a fixed skeleton read the token header, `verify`, `getAppInfo`,
72
+ * `resolveUserId`, `setContext`, and on error `console.error` then `onFailure` — while the
73
+ * application injects the variable parts (token verification, user-id resolution, context variable
74
+ * names, and the failure response). Omitting {@link AuthMiddlewareOptions.resolveUserId} yields a
75
+ * token-only middleware.
76
+ *
77
+ * @typeParam E - The Hono `Env` of the application.
78
+ * @typeParam Verified - The value produced by `verify`.
79
+ * @typeParam Id - The resolved user identifier type.
80
+ * @param options - The verification, resolution, and failure hooks; see {@link AuthMiddlewareOptions}.
81
+ * @returns A {@link MiddlewareHandler} that authenticates the request and populates the context.
82
+ * @throws HTTPException From the default failure handler when `onFailure` is not supplied.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const auth = createAuthMiddleware({
87
+ * verify: (token, c) => verifier.verifyIdToken(token),
88
+ * resolveUserId: (decoded) => findUserId(decoded.uid),
89
+ * setContext: (c, { verified, userId }) => {
90
+ * c.set('decodedToken', verified);
91
+ * c.set('userId', userId);
92
+ * },
93
+ * });
94
+ * app.use('/api/*', auth);
95
+ * ```
35
96
  */
36
97
  export function createAuthMiddleware<E extends Env = Env, Verified = unknown, Id = unknown>(
37
98
  options: AuthMiddlewareOptions<E, Verified, Id>,
@@ -54,8 +115,9 @@ export function createAuthMiddleware<E extends Env = Env, Verified = unknown, Id
54
115
  const userId = resolveUserId ? await resolveUserId(verified, c, appInfo) : undefined;
55
116
  setContext(c, { verified, appInfo, userId });
56
117
  } catch (e) {
57
- // Nest guard false → ForbiddenException('Forbidden resource')。原因をログし、既定では throw して
58
- // app.onError Nest body を描かせる(repo onFailure return 形に上書き可能)。
118
+ // Equivalent to a guard returning false → ForbiddenException('Forbidden resource'). Log the
119
+ // cause and, by default, throw so the app's onError renders the error body (callers can
120
+ // override with onFailure to return a custom response instead).
59
121
  console.error(e);
60
122
  if (onFailure) {
61
123
  return onFailure(e, c);