@rdlabo/workers-hono-kit 0.1.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 +160 -24
  13. package/dist/db/database.js +51 -7
  14. package/dist/db/index.d.ts +21 -10
  15. package/dist/db/index.js +17 -8
  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 +81 -9
  28. package/dist/firebase/jose-firebase-verifier.js +68 -7
  29. package/dist/firebase/remote-verifier.d.ts +43 -5
  30. package/dist/firebase/remote-verifier.js +60 -11
  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 +41 -30
  42. package/dist/index.js +29 -21
  43. package/dist/middleware/auth.d.ts +75 -14
  44. package/dist/middleware/auth.js +31 -7
  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 +60 -12
  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 +79 -11
  60. package/dist/testing/fakes.js +70 -8
  61. package/dist/testing/index.d.ts +15 -8
  62. package/dist/testing/index.js +15 -10
  63. package/dist/testing/stripe-fixtures.d.ts +93 -3
  64. package/dist/testing/stripe-fixtures.js +93 -3
  65. package/package.json +19 -9
  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 +163 -27
  72. package/src/db/index.ts +21 -12
  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 +81 -11
  80. package/src/firebase/remote-verifier.ts +61 -12
  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 +47 -33
  87. package/src/middleware/auth.ts +79 -17
  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 +60 -12
  93. package/src/testing/configurable-fake.ts +23 -11
  94. package/src/testing/db.ts +82 -13
  95. package/src/testing/fakes.ts +80 -12
  96. package/src/testing/index.ts +18 -13
  97. package/src/testing/stripe-fixtures.ts +93 -3
@@ -1,15 +1,64 @@
1
- /** Firebase boundary replacing the firebase-admin Auth surface used by `/api`. */
1
+ /**
2
+ * Decoded Firebase ID token payload.
3
+ *
4
+ * Shaped to match the subset of `firebase-admin`'s `DecodedIdToken` that consumers
5
+ * typically rely on: a stable `uid` plus an optional `email`. The index signature keeps
6
+ * every other JWT claim (e.g. `name`, `picture`, custom claims) accessible without
7
+ * enumerating them here.
8
+ *
9
+ * @remarks
10
+ * This is the return type of {@link FirebaseVerifier.verifyIdToken}. The `uid` is derived
11
+ * from the token's `sub` claim.
12
+ */
2
13
  export interface DecodedIdToken {
14
+ /** The authenticated user's unique id, taken from the token's `sub` claim. */
3
15
  uid: string;
16
+ /** The user's email address, when present on the token. */
4
17
  email?: string;
18
+ /** Any additional JWT claim carried by the token (custom claims, `name`, `picture`, ...). */
5
19
  [claim: string]: unknown;
6
20
  }
7
21
 
22
+ /**
23
+ * Abstract authentication boundary that replaces the `firebase-admin` Auth surface
24
+ * (`verifyIdToken` / `getUser` / `deleteUser`) for environments where the Node SDK cannot
25
+ * run, such as Cloudflare Workers.
26
+ *
27
+ * @remarks
28
+ * Implementations verify Firebase ID tokens and look up or delete accounts without the
29
+ * `firebase-admin` Node dependency. See `JoseFirebaseVerifier` for the `jose`-based
30
+ * implementation and the `createRemoteFirebaseVerifier` / `createServiceAccountVerifier`
31
+ * factories for ready-made instances.
32
+ */
8
33
  export interface FirebaseVerifier {
9
- /** Mirrors firebase-admin getAuth().verifyIdToken(). Throws on invalid token. */
34
+ /**
35
+ * Verify a Firebase ID token and return its decoded payload.
36
+ *
37
+ * Mirrors `firebase-admin` `getAuth().verifyIdToken()`.
38
+ *
39
+ * @param idToken - The raw Firebase ID token (JWT) to verify.
40
+ * @returns The decoded token payload.
41
+ * @throws If the token signature, issuer, audience, expiry, or other required claims are invalid.
42
+ */
10
43
  verifyIdToken(idToken: string): Promise<DecodedIdToken>;
11
- /** Mirrors getAuth().getUser(); returns null when the user is absent. */
44
+ /**
45
+ * Look up a user record by uid.
46
+ *
47
+ * Mirrors `firebase-admin` `getAuth().getUser()`.
48
+ *
49
+ * @param uid - The user's unique id.
50
+ * @returns The user's `uid` and optional `email`, or `null` when the user does not exist.
51
+ * @throws If the backing user-management service is not configured or the lookup fails.
52
+ */
12
53
  getUser(uid: string): Promise<{ uid: string; email?: string } | null>;
13
- /** Mirrors getAuth().deleteUser(). */
54
+ /**
55
+ * Delete a user by uid.
56
+ *
57
+ * Mirrors `firebase-admin` `getAuth().deleteUser()`.
58
+ *
59
+ * @param uid - The user's unique id.
60
+ * @returns A promise that resolves once the user has been deleted.
61
+ * @throws If the backing user-management service is not configured or the deletion fails.
62
+ */
14
63
  deleteUser(uid: string): Promise<void>;
15
64
  }
@@ -1,28 +1,63 @@
1
1
  import { SignJWT, importPKCS8 } from 'jose';
2
2
 
3
3
  /**
4
- * Minimal Google Identity Toolkit client for the operations firebase-admin performed
5
- * that aren't token verification: accounts:lookup (getUser) and accounts:delete
6
- * (deleteUser). Replaces the firebase-admin Node SDK, which won't run on workerd.
4
+ * Minimal service-account credential consumed by {@link IdentityToolkit}.
7
5
  *
8
- * Auth: sign a JWT assertion with the service-account private key (jose), exchange it for
9
- * an OAuth2 access token, then call the REST API. Tokens are cached in-process.
6
+ * @remarks
7
+ * Corresponds to the relevant fields of a Google service-account JSON key file.
10
8
  */
11
9
  export interface ServiceAccount {
10
+ /** The service account's email, used as the JWT assertion issuer and subject. */
12
11
  client_email: string;
12
+ /** The PEM-encoded PKCS#8 RSA private key used to sign the OAuth2 assertion. */
13
13
  private_key: string;
14
+ /** The Google/Firebase project id the Identity Toolkit calls target. */
14
15
  project_id: string;
15
16
  }
16
17
 
18
+ /** Google OAuth2 token endpoint used to exchange a signed JWT assertion for an access token. */
17
19
  const TOKEN_URL = 'https://oauth2.googleapis.com/token';
20
+ /** Base URL of the Google Identity Toolkit v1 REST API. */
18
21
  const IDENTITY_TOOLKIT = 'https://identitytoolkit.googleapis.com/v1';
22
+ /** OAuth2 scopes required for Identity Toolkit account lookup and deletion. */
19
23
  const SCOPE = 'https://www.googleapis.com/auth/identitytoolkit https://www.googleapis.com/auth/firebase';
20
24
 
25
+ /**
26
+ * Minimal Google Identity Toolkit REST client for the user-management operations that token
27
+ * verification does not cover: `accounts:lookup` (getUser) and `accounts:delete` (deleteUser).
28
+ *
29
+ * This replaces the parts of the `firebase-admin` Node SDK that cannot run on Cloudflare
30
+ * Workers (workerd).
31
+ *
32
+ * @remarks
33
+ * Authentication follows the JWT-bearer flow: a JWT assertion is signed with the service
34
+ * account's private key (via `jose`), exchanged at the OAuth2 token endpoint for an access
35
+ * token, and that token is then used to call the REST API. Access tokens are cached in-process
36
+ * and reused until shortly before they expire.
37
+ */
21
38
  export class IdentityToolkit {
39
+ /** Cached OAuth2 access token and its absolute expiry (Unix seconds), or `null` when none. */
22
40
  private accessToken: { value: string; expiresAt: number } | null = null;
23
41
 
42
+ /**
43
+ * Create a client bound to a single service account.
44
+ *
45
+ * @param sa - The service-account credential used to authenticate REST calls.
46
+ */
24
47
  constructor(private readonly sa: ServiceAccount) {}
25
48
 
49
+ /**
50
+ * Return a valid OAuth2 access token, minting a new one when the cache is empty or expiring.
51
+ *
52
+ * Signs a short-lived JWT assertion with the service-account key and exchanges it at the
53
+ * Google OAuth2 token endpoint. The result is cached and reused while it remains valid
54
+ * (with a 60-second safety margin).
55
+ *
56
+ * @param nowSeconds - The current Unix time in seconds, used for cache validity and JWT timestamps.
57
+ * @returns A bearer access token for the Identity Toolkit API.
58
+ * @throws If the token exchange request fails.
59
+ * @internal
60
+ */
26
61
  private async getAccessToken(nowSeconds: number): Promise<string> {
27
62
  if (this.accessToken && this.accessToken.expiresAt > nowSeconds + 60) {
28
63
  return this.accessToken.value;
@@ -53,6 +88,15 @@ export class IdentityToolkit {
53
88
  return json.access_token;
54
89
  }
55
90
 
91
+ /**
92
+ * Look up a user record by uid via the `accounts:lookup` endpoint.
93
+ *
94
+ * @param uid - The user's unique id (`localId`).
95
+ * @param nowSeconds - The current Unix time in seconds, used for access-token caching.
96
+ * @returns The user's `uid` and optional `email`, or `null` when the request is unsuccessful
97
+ * or no matching user is returned.
98
+ * @throws If acquiring an access token fails.
99
+ */
56
100
  async lookup(uid: string, nowSeconds: number): Promise<{ uid: string; email?: string } | null> {
57
101
  const token = await this.getAccessToken(nowSeconds);
58
102
  const res = await fetch(`${IDENTITY_TOOLKIT}/projects/${this.sa.project_id}/accounts:lookup`, {
@@ -68,6 +112,14 @@ export class IdentityToolkit {
68
112
  return user ? { uid: user.localId, email: user.email } : null;
69
113
  }
70
114
 
115
+ /**
116
+ * Delete a user by uid via the `accounts:delete` endpoint.
117
+ *
118
+ * @param uid - The user's unique id (`localId`).
119
+ * @param nowSeconds - The current Unix time in seconds, used for access-token caching.
120
+ * @returns A promise that resolves once the user has been deleted.
121
+ * @throws If acquiring an access token fails or the delete request is unsuccessful.
122
+ */
71
123
  async remove(uid: string, nowSeconds: number): Promise<void> {
72
124
  const token = await this.getAccessToken(nowSeconds);
73
125
  const res = await fetch(`${IDENTITY_TOOLKIT}/projects/${this.sa.project_id}/accounts:delete`, {
@@ -1,26 +1,64 @@
1
1
  import { jwtVerify } from 'jose';
2
2
  import type { CryptoKey, JWK, JWTVerifyGetKey, KeyObject } from 'jose';
3
- import type { DecodedIdToken, FirebaseVerifier } from './firebase-verifier';
4
- import type { IdentityToolkit } from './identity-toolkit';
3
+ import type { DecodedIdToken, FirebaseVerifier } from './firebase-verifier.js';
4
+ import type { IdentityToolkit } from './identity-toolkit.js';
5
5
 
6
- // jose v6 removed `KeyLike`; the verification key is a static key (prod: createRemoteJWKSet,
7
- // test: a CryptoKey) or a dynamic getKey function. Union both overloads' key params.
6
+ /**
7
+ * Union of every key shape `jose`'s `jwtVerify` accepts.
8
+ *
9
+ * @remarks
10
+ * `jose` v6 removed `KeyLike`, so the verification key is modelled here as either a static
11
+ * key (production uses `createRemoteJWKSet`, tests use a generated `CryptoKey`) or a dynamic
12
+ * `JWTVerifyGetKey` resolver function. This union covers both `jwtVerify` overloads' key
13
+ * parameters.
14
+ *
15
+ * @internal
16
+ */
8
17
  type KeyInput = CryptoKey | KeyObject | JWK | Uint8Array | JWTVerifyGetKey;
9
18
 
10
19
  /**
11
- * Replaces firebase-admin getAuth().verifyIdToken() with jose RS256 verification against
12
- * Google's securetoken JWKS. Mirrors the admin SDK's checks: issuer/audience = projectId,
13
- * RS256, a non-empty subject (the uid), and a valid auth_time.
14
- *
15
- * - prod: keyResolver = createRemoteJWKSet(new URL(SECURETOKEN_JWK_URL)).
16
- * - test: keyResolver = the generated public key (offline, no network).
20
+ * URL of Google's securetoken JWKS endpoint, which serves the public keys used to sign
21
+ * Firebase ID tokens.
17
22
  *
18
- * getUser/deleteUser delegate to Identity Toolkit REST (network); absent it throws.
23
+ * @remarks
24
+ * Passed to `createRemoteJWKSet` (see `createRemoteFirebaseVerifier`) so RS256 signatures can
25
+ * be verified against Google's rotating public keys.
19
26
  */
20
27
  export const SECURETOKEN_JWK_URL =
21
28
  'https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com';
22
29
 
30
+ /**
31
+ * Verifies Firebase ID tokens with `jose` RS256 against Google's securetoken JWKS, and
32
+ * optionally looks up or deletes users via the Google Identity Toolkit REST API.
33
+ *
34
+ * This replaces the `firebase-admin` Auth surface (`verifyIdToken` / `getUser` /
35
+ * `deleteUser`) in environments where the Node SDK cannot run, such as Cloudflare Workers.
36
+ * Token verification mirrors the admin SDK's checks: issuer and audience equal to the
37
+ * project id, an RS256 signature, a non-empty subject (the uid), and a valid `auth_time`.
38
+ *
39
+ * @remarks
40
+ * The verification key is supplied as `keyResolver`:
41
+ * - Production: `createRemoteJWKSet(new URL(SECURETOKEN_JWK_URL))`, which fetches and caches
42
+ * Google's public keys.
43
+ * - Tests: a generated public key, allowing fully offline verification with no network.
44
+ *
45
+ * `getUser` / `deleteUser` delegate to {@link IdentityToolkit} (a network call); when no
46
+ * `IdentityToolkit` is configured they throw.
47
+ *
48
+ * @see {@link FirebaseVerifier} for the abstract boundary this implements.
49
+ */
23
50
  export class JoseFirebaseVerifier implements FirebaseVerifier {
51
+ /**
52
+ * Create a verifier.
53
+ *
54
+ * @param opts - Verifier configuration.
55
+ * @param opts.projectId - The Firebase project id, used as both the expected token issuer
56
+ * (`https://securetoken.google.com/<projectId>`) and audience.
57
+ * @param opts.keyResolver - The RS256 verification key or a dynamic key resolver function.
58
+ * @param opts.identity - Optional Identity Toolkit client enabling `getUser` / `deleteUser`.
59
+ * @param opts.now - Optional clock returning the current time in seconds; injectable for
60
+ * deterministic tests. Defaults to the system clock.
61
+ */
24
62
  constructor(
25
63
  private readonly opts: {
26
64
  projectId: string;
@@ -30,6 +68,18 @@ export class JoseFirebaseVerifier implements FirebaseVerifier {
30
68
  },
31
69
  ) {}
32
70
 
71
+ /**
72
+ * Verify a Firebase ID token and return its decoded payload.
73
+ *
74
+ * Checks the RS256 signature against the configured key, enforces the expected issuer and
75
+ * audience (the project id), and applies the admin SDK's extra checks: a non-empty string
76
+ * subject of at most 128 characters and an `auth_time` that is a number not in the future.
77
+ *
78
+ * @param idToken - The raw Firebase ID token (JWT) to verify.
79
+ * @returns The decoded payload, with `uid` set from `sub` and `email` lifted to a top-level field.
80
+ * @throws If the signature, issuer, audience, or expiry are invalid, if the subject is
81
+ * missing/non-string/too long, or if `auth_time` is missing or in the future.
82
+ */
33
83
  async verifyIdToken(idToken: string): Promise<DecodedIdToken> {
34
84
  const options = {
35
85
  issuer: `https://securetoken.google.com/${this.opts.projectId}`,
@@ -51,6 +101,13 @@ export class JoseFirebaseVerifier implements FirebaseVerifier {
51
101
  return { ...payload, uid: payload.sub, email: payload.email as string | undefined };
52
102
  }
53
103
 
104
+ /**
105
+ * Look up a user record by uid via the Identity Toolkit REST API.
106
+ *
107
+ * @param uid - The user's unique id.
108
+ * @returns The user's `uid` and optional `email`, or `null` when the user does not exist.
109
+ * @throws If no Identity Toolkit client was configured on this verifier.
110
+ */
54
111
  async getUser(uid: string): Promise<{ uid: string; email?: string } | null> {
55
112
  if (!this.opts.identity) {
56
113
  throw new Error('Identity Toolkit not configured');
@@ -58,6 +115,13 @@ export class JoseFirebaseVerifier implements FirebaseVerifier {
58
115
  return this.opts.identity.lookup(uid, this.nowSeconds());
59
116
  }
60
117
 
118
+ /**
119
+ * Delete a user by uid via the Identity Toolkit REST API.
120
+ *
121
+ * @param uid - The user's unique id.
122
+ * @returns A promise that resolves once the user has been deleted.
123
+ * @throws If no Identity Toolkit client was configured on this verifier, or the deletion fails.
124
+ */
61
125
  async deleteUser(uid: string): Promise<void> {
62
126
  if (!this.opts.identity) {
63
127
  throw new Error('Identity Toolkit not configured');
@@ -65,6 +129,12 @@ export class JoseFirebaseVerifier implements FirebaseVerifier {
65
129
  await this.opts.identity.remove(uid, this.nowSeconds());
66
130
  }
67
131
 
132
+ /**
133
+ * Return the current time in seconds, using the injected clock when provided.
134
+ *
135
+ * @returns The current Unix time in seconds.
136
+ * @internal
137
+ */
68
138
  private nowSeconds(): number {
69
139
  return this.opts.now ? this.opts.now() : Math.floor(Date.now() / 1000);
70
140
  }
@@ -1,19 +1,46 @@
1
1
  import { createRemoteJWKSet } from 'jose';
2
- import { IdentityToolkit } from './identity-toolkit';
3
- import type { ServiceAccount } from './identity-toolkit';
4
- import { JoseFirebaseVerifier, SECURETOKEN_JWK_URL } from './jose-firebase-verifier';
2
+ import { IdentityToolkit } from './identity-toolkit.js';
3
+ import type { ServiceAccount } from './identity-toolkit.js';
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,