@rdlabo/workers-hono-kit 0.3.7 → 0.4.2

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 (57) hide show
  1. package/README.md +34 -1
  2. package/dist/business-time/index.d.ts +49 -0
  3. package/dist/business-time/index.js +149 -0
  4. package/dist/business-time/types.d.ts +9 -0
  5. package/dist/business-time/types.js +5 -0
  6. package/dist/db/columns.d.ts +46 -0
  7. package/dist/db/columns.js +36 -0
  8. package/dist/db/connection.js +2 -1
  9. package/dist/db/decimal.d.ts +27 -0
  10. package/dist/db/decimal.js +50 -0
  11. package/dist/db/index.d.ts +4 -1
  12. package/dist/db/index.js +3 -1
  13. package/dist/db/jst.d.ts +10 -72
  14. package/dist/db/jst.js +10 -82
  15. package/dist/testing/index.d.ts +2 -0
  16. package/dist/testing/index.js +2 -0
  17. package/dist/testing/workers-bindings.d.ts +49 -0
  18. package/dist/testing/workers-bindings.js +62 -0
  19. package/package.json +7 -3
  20. package/scripts/db-baseline.mjs +0 -0
  21. package/src/ai/gateway.ts +0 -120
  22. package/src/aws/cloudfront.ts +0 -105
  23. package/src/aws/secrets-manager.ts +0 -112
  24. package/src/cache/kv-cache.ts +0 -316
  25. package/src/db/connection.ts +0 -107
  26. package/src/db/database.ts +0 -269
  27. package/src/db/index.ts +0 -39
  28. package/src/db/jst.ts +0 -122
  29. package/src/db/migrate.ts +0 -155
  30. package/src/db/orm-config.ts +0 -171
  31. package/src/db/retry.ts +0 -43
  32. package/src/db/write-result.ts +0 -46
  33. package/src/firebase/firebase-verifier.ts +0 -76
  34. package/src/firebase/identity-toolkit.ts +0 -179
  35. package/src/firebase/jose-firebase-verifier.ts +0 -159
  36. package/src/firebase/remote-verifier.ts +0 -98
  37. package/src/http/app-env.ts +0 -53
  38. package/src/http/app-info.ts +0 -38
  39. package/src/http/execution-context.ts +0 -11
  40. package/src/http/http-status.ts +0 -71
  41. package/src/http/nest-error.ts +0 -207
  42. package/src/http/trailing-slash.ts +0 -28
  43. package/src/http/user-protocol.ts +0 -36
  44. package/src/index.ts +0 -77
  45. package/src/middleware/auth.ts +0 -129
  46. package/src/middleware/finalize-response.ts +0 -90
  47. package/src/middleware/validation.ts +0 -158
  48. package/src/middleware/zod-coerce.ts +0 -124
  49. package/src/queue/consumer.ts +0 -146
  50. package/src/queue/send.ts +0 -129
  51. package/src/stripe/client.ts +0 -85
  52. package/src/testing/auth.ts +0 -110
  53. package/src/testing/configurable-fake.ts +0 -45
  54. package/src/testing/db.ts +0 -194
  55. package/src/testing/fakes.ts +0 -153
  56. package/src/testing/index.ts +0 -31
  57. package/src/testing/stripe-fixtures.ts +0 -175
@@ -1,179 +0,0 @@
1
- import { SignJWT, importPKCS8 } from 'jose';
2
-
3
- /**
4
- * Minimal service-account credential consumed by {@link IdentityToolkit}.
5
- *
6
- * @remarks
7
- * Corresponds to the relevant fields of a Google service-account JSON key file.
8
- */
9
- export interface ServiceAccount {
10
- /** The service account's email, used as the JWT assertion issuer and subject. */
11
- client_email: string;
12
- /** The PEM-encoded PKCS#8 RSA private key used to sign the OAuth2 assertion. */
13
- private_key: string;
14
- /** The Google/Firebase project id the Identity Toolkit calls target. */
15
- project_id: string;
16
- }
17
-
18
- /** Google OAuth2 token endpoint used to exchange a signed JWT assertion for an access token. */
19
- const TOKEN_URL = 'https://oauth2.googleapis.com/token';
20
- /** Base URL of the Google Identity Toolkit v1 REST API. */
21
- const IDENTITY_TOOLKIT = 'https://identitytoolkit.googleapis.com/v1';
22
- /** OAuth2 scopes required for Identity Toolkit account lookup and deletion. */
23
- const SCOPE = 'https://www.googleapis.com/auth/identitytoolkit https://www.googleapis.com/auth/firebase';
24
- /** Maximum number of `localId`s the `accounts:lookup` endpoint accepts in a single request. */
25
- const LOOKUP_CHUNK_SIZE = 100;
26
-
27
- /**
28
- * Minimal Google Identity Toolkit REST client for the user-management operations that token
29
- * verification does not cover: `accounts:lookup` (getUser) and `accounts:delete` (deleteUser).
30
- *
31
- * This replaces the parts of the `firebase-admin` Node SDK that cannot run on Cloudflare
32
- * Workers (workerd).
33
- *
34
- * @remarks
35
- * Authentication follows the JWT-bearer flow: a JWT assertion is signed with the service
36
- * account's private key (via `jose`), exchanged at the OAuth2 token endpoint for an access
37
- * token, and that token is then used to call the REST API. Access tokens are cached in-process
38
- * and reused until shortly before they expire.
39
- */
40
- export class IdentityToolkit {
41
- /** Cached OAuth2 access token and its absolute expiry (Unix seconds), or `null` when none. */
42
- private accessToken: { value: string; expiresAt: number } | null = null;
43
-
44
- /**
45
- * Create a client bound to a single service account.
46
- *
47
- * @param sa - The service-account credential used to authenticate REST calls.
48
- */
49
- constructor(private readonly sa: ServiceAccount) {}
50
-
51
- /**
52
- * Return a valid OAuth2 access token, minting a new one when the cache is empty or expiring.
53
- *
54
- * Signs a short-lived JWT assertion with the service-account key and exchanges it at the
55
- * Google OAuth2 token endpoint. The result is cached and reused while it remains valid
56
- * (with a 60-second safety margin).
57
- *
58
- * @param nowSeconds - The current Unix time in seconds, used for cache validity and JWT timestamps.
59
- * @returns A bearer access token for the Identity Toolkit API.
60
- * @throws If the token exchange request fails.
61
- * @internal
62
- */
63
- private async getAccessToken(nowSeconds: number): Promise<string> {
64
- if (this.accessToken && this.accessToken.expiresAt > nowSeconds + 60) {
65
- return this.accessToken.value;
66
- }
67
- const key = await importPKCS8(this.sa.private_key, 'RS256');
68
- const assertion = await new SignJWT({ scope: SCOPE })
69
- .setProtectedHeader({ alg: 'RS256', typ: 'JWT' })
70
- .setIssuer(this.sa.client_email)
71
- .setSubject(this.sa.client_email)
72
- .setAudience(TOKEN_URL)
73
- .setIssuedAt(nowSeconds)
74
- .setExpirationTime(nowSeconds + 3600)
75
- .sign(key);
76
-
77
- const res = await fetch(TOKEN_URL, {
78
- method: 'POST',
79
- headers: { 'content-type': 'application/x-www-form-urlencoded' },
80
- body: new URLSearchParams({
81
- grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
82
- assertion,
83
- }),
84
- });
85
- if (!res.ok) {
86
- throw new Error(`Identity Toolkit token exchange failed: ${res.status}`);
87
- }
88
- const json = (await res.json()) as { access_token: string; expires_in: number };
89
- this.accessToken = { value: json.access_token, expiresAt: nowSeconds + json.expires_in };
90
- return json.access_token;
91
- }
92
-
93
- /**
94
- * Call the `accounts:lookup` endpoint for a single chunk of `localId`s.
95
- *
96
- * @param localIds - Up to {@link LOOKUP_CHUNK_SIZE} `localId`s to look up in one request.
97
- * @param nowSeconds - The current Unix time in seconds, used for access-token caching.
98
- * @returns The raw `users` entries returned by the endpoint (empty when the request is
99
- * unsuccessful or no matching users are returned).
100
- * @throws If acquiring an access token fails.
101
- * @internal
102
- */
103
- private async lookupChunk(localIds: string[], nowSeconds: number): Promise<{ localId: string; email?: string }[]> {
104
- const token = await this.getAccessToken(nowSeconds);
105
- const res = await fetch(`${IDENTITY_TOOLKIT}/projects/${this.sa.project_id}/accounts:lookup`, {
106
- method: 'POST',
107
- headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' },
108
- body: JSON.stringify({ localId: localIds }),
109
- });
110
- if (!res.ok) {
111
- return [];
112
- }
113
- const json = (await res.json()) as { users?: { localId: string; email?: string }[] };
114
- return json.users ?? [];
115
- }
116
-
117
- /**
118
- * Look up a user record by uid via the `accounts:lookup` endpoint.
119
- *
120
- * @param uid - The user's unique id (`localId`).
121
- * @param nowSeconds - The current Unix time in seconds, used for access-token caching.
122
- * @returns The user's `uid` and optional `email`, or `null` when the request is unsuccessful
123
- * or no matching user is returned.
124
- * @throws If acquiring an access token fails.
125
- */
126
- async lookup(uid: string, nowSeconds: number): Promise<{ uid: string; email?: string } | null> {
127
- const users = await this.lookupChunk([uid], nowSeconds);
128
- return users.length > 0 ? { uid: users[0].localId, email: users[0].email } : null;
129
- }
130
-
131
- /**
132
- * Look up multiple user records by uid via the `accounts:lookup` endpoint.
133
- *
134
- * `uids` are chunked into groups of at most {@link LOOKUP_CHUNK_SIZE} (the maximum `localId`
135
- * array size the endpoint accepts), issuing one `accounts:lookup` request per chunk. This lets
136
- * callers replace N single-uid lookups with `ceil(N / LOOKUP_CHUNK_SIZE)` requests.
137
- *
138
- * @param uids - The users' unique ids (`localId`s) to look up.
139
- * @param nowSeconds - The current Unix time in seconds, used for access-token caching.
140
- * @returns The `uid`/`email` of every matching user. Uids Firebase does not recognize are
141
- * simply absent from the result (never `null` entries), so callers can treat "missing from
142
- * the result" as "not found/invalid".
143
- * @throws If acquiring an access token fails.
144
- */
145
- async lookupMany(uids: string[], nowSeconds: number): Promise<{ uid: string; email?: string }[]> {
146
- if (uids.length === 0) {
147
- return [];
148
- }
149
- const results: { uid: string; email?: string }[] = [];
150
- for (let i = 0; i < uids.length; i += LOOKUP_CHUNK_SIZE) {
151
- const chunk = uids.slice(i, i + LOOKUP_CHUNK_SIZE);
152
- const users = await this.lookupChunk(chunk, nowSeconds);
153
- for (const user of users) {
154
- results.push({ uid: user.localId, email: user.email });
155
- }
156
- }
157
- return results;
158
- }
159
-
160
- /**
161
- * Delete a user by uid via the `accounts:delete` endpoint.
162
- *
163
- * @param uid - The user's unique id (`localId`).
164
- * @param nowSeconds - The current Unix time in seconds, used for access-token caching.
165
- * @returns A promise that resolves once the user has been deleted.
166
- * @throws If acquiring an access token fails or the delete request is unsuccessful.
167
- */
168
- async remove(uid: string, nowSeconds: number): Promise<void> {
169
- const token = await this.getAccessToken(nowSeconds);
170
- const res = await fetch(`${IDENTITY_TOOLKIT}/projects/${this.sa.project_id}/accounts:delete`, {
171
- method: 'POST',
172
- headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' },
173
- body: JSON.stringify({ localId: uid }),
174
- });
175
- if (!res.ok) {
176
- throw new Error(`Identity Toolkit delete failed: ${res.status}`);
177
- }
178
- }
179
- }
@@ -1,159 +0,0 @@
1
- import { jwtVerify } from 'jose';
2
- import type { CryptoKey, JWK, JWTVerifyGetKey, KeyObject } from 'jose';
3
- import type { DecodedIdToken, FirebaseVerifier } from './firebase-verifier.js';
4
- import type { IdentityToolkit } from './identity-toolkit.js';
5
-
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
- */
17
- type KeyInput = CryptoKey | KeyObject | JWK | Uint8Array | JWTVerifyGetKey;
18
-
19
- /**
20
- * URL of Google's securetoken JWKS endpoint, which serves the public keys used to sign
21
- * Firebase ID tokens.
22
- *
23
- * @remarks
24
- * Passed to `createRemoteJWKSet` (see `createRemoteFirebaseVerifier`) so RS256 signatures can
25
- * be verified against Google's rotating public keys.
26
- */
27
- export const SECURETOKEN_JWK_URL =
28
- 'https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com';
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
- */
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
- */
62
- constructor(
63
- private readonly opts: {
64
- projectId: string;
65
- keyResolver: KeyInput;
66
- identity?: IdentityToolkit;
67
- now?: () => number; // seconds; injectable for tests
68
- },
69
- ) {}
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
- */
83
- async verifyIdToken(idToken: string): Promise<DecodedIdToken> {
84
- const options = {
85
- issuer: `https://securetoken.google.com/${this.opts.projectId}`,
86
- audience: this.opts.projectId,
87
- algorithms: ['RS256'] as string[],
88
- };
89
- // Branch so each call matches a single jwtVerify overload (static key vs getKey fn).
90
- const key = this.opts.keyResolver;
91
- const { payload } =
92
- typeof key === 'function' ? await jwtVerify(idToken, key, options) : await jwtVerify(idToken, key, options);
93
- // Mirror firebase-admin's extra checks beyond signature/iss/aud/exp:
94
- if (!payload.sub || typeof payload.sub !== 'string' || payload.sub.length > 128) {
95
- throw new Error('Firebase ID token has an invalid subject');
96
- }
97
- const authTime = payload.auth_time;
98
- if (typeof authTime !== 'number' || authTime > this.nowSeconds()) {
99
- throw new Error('Firebase ID token has an invalid auth_time');
100
- }
101
- return { ...payload, uid: payload.sub, email: payload.email as string | undefined };
102
- }
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
- */
111
- async getUser(uid: string): Promise<{ uid: string; email?: string } | null> {
112
- if (!this.opts.identity) {
113
- throw new Error('Identity Toolkit not configured');
114
- }
115
- return this.opts.identity.lookup(uid, this.nowSeconds());
116
- }
117
-
118
- /**
119
- * Look up multiple user records by uid via the Identity Toolkit REST API.
120
- *
121
- * Batches the lookups into `ceil(uids.length / 100)` `accounts:lookup` requests instead of
122
- * one request per uid.
123
- *
124
- * @param uids - The users' unique ids to look up.
125
- * @returns The `uid`/`email` of every matching user. Uids Firebase does not recognize are
126
- * simply absent from the result (never `null` entries).
127
- * @throws If no Identity Toolkit client was configured on this verifier.
128
- */
129
- async getUsers(uids: string[]): Promise<{ uid: string; email?: string }[]> {
130
- if (!this.opts.identity) {
131
- throw new Error('Identity Toolkit not configured');
132
- }
133
- return this.opts.identity.lookupMany(uids, this.nowSeconds());
134
- }
135
-
136
- /**
137
- * Delete a user by uid via the Identity Toolkit REST API.
138
- *
139
- * @param uid - The user's unique id.
140
- * @returns A promise that resolves once the user has been deleted.
141
- * @throws If no Identity Toolkit client was configured on this verifier, or the deletion fails.
142
- */
143
- async deleteUser(uid: string): Promise<void> {
144
- if (!this.opts.identity) {
145
- throw new Error('Identity Toolkit not configured');
146
- }
147
- await this.opts.identity.remove(uid, this.nowSeconds());
148
- }
149
-
150
- /**
151
- * Return the current time in seconds, using the injected clock when provided.
152
- *
153
- * @returns The current Unix time in seconds.
154
- * @internal
155
- */
156
- private nowSeconds(): number {
157
- return this.opts.now ? this.opts.now() : Math.floor(Date.now() / 1000);
158
- }
159
- }
@@ -1,98 +0,0 @@
1
- import { createRemoteJWKSet } from 'jose';
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
-
6
- /**
7
- * Shared remote JWKS for Google's securetoken keys.
8
- *
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
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
- */
21
- const verifiers = new Map<string, JoseFirebaseVerifier>();
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
- */
44
- export function createRemoteFirebaseVerifier(projectId: string): JoseFirebaseVerifier {
45
- jwks ??= createRemoteJWKSet(new URL(SECURETOKEN_JWK_URL));
46
- let verifier = verifiers.get(projectId);
47
- if (!verifier) {
48
- verifier = new JoseFirebaseVerifier({ projectId, keyResolver: jwks });
49
- verifiers.set(projectId, verifier);
50
- }
51
- return verifier;
52
- }
53
-
54
- /**
55
- * Single-entry cache of the service-account verifier, keyed by the raw service-account JSON.
56
- *
57
- * @internal
58
- */
59
- let saVerifierCache: { key: string; verifier: JoseFirebaseVerifier } | null = null;
60
-
61
- /**
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
- * ```
83
- */
84
- export function createServiceAccountVerifier(serviceAccountJson: string): JoseFirebaseVerifier {
85
- if (saVerifierCache?.key !== serviceAccountJson) {
86
- jwks ??= createRemoteJWKSet(new URL(SECURETOKEN_JWK_URL));
87
- const sa = JSON.parse(serviceAccountJson) as ServiceAccount;
88
- saVerifierCache = {
89
- key: serviceAccountJson,
90
- verifier: new JoseFirebaseVerifier({
91
- projectId: sa.project_id,
92
- keyResolver: jwks,
93
- identity: new IdentityToolkit(sa),
94
- }),
95
- };
96
- }
97
- return saVerifierCache.verifier;
98
- }
@@ -1,53 +0,0 @@
1
- /**
2
- * The resolved runtime environment of the Worker.
3
- */
4
- export type AppEnv = 'development' | 'production';
5
-
6
- /**
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.
20
- *
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'`.
23
- *
24
- * @example
25
- * ```ts
26
- * export default {
27
- * fetch(req, env) {
28
- * if (resolveAppEnv(env) === 'development') {
29
- * // enable verbose logging
30
- * }
31
- * },
32
- * };
33
- * ```
34
- */
35
- export function resolveAppEnv(env: { APP_ENV?: string } | null | undefined): AppEnv {
36
- return env?.APP_ENV === 'development' ? 'development' : 'production';
37
- }
38
-
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
- */
52
- export const isProductionEnv = (env: { APP_ENV?: string } | null | undefined): boolean =>
53
- resolveAppEnv(env) === 'production';
@@ -1,38 +0,0 @@
1
- import type { Context } from 'hono';
2
-
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
- */
10
- export interface AppInfo {
11
- /** Client application version from `x-amz-meta-version`, or `null` when the header is absent. */
12
- version: string | null;
13
- /** Client installation identifier from `x-amz-meta-uuid`, or `null` when the header is absent. */
14
- uuid: string | null;
15
- }
16
-
17
- /**
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
- * ```
34
- */
35
- export const getAppInfo = (c: Context): AppInfo => ({
36
- version: c.req.header('x-amz-meta-version') ?? null,
37
- uuid: c.req.header('x-amz-meta-uuid') ?? null,
38
- });
@@ -1,11 +0,0 @@
1
- /**
2
- * Minimal structural shape of a Workers `ExecutionContext`, limited to `waitUntil`.
3
- *
4
- * @remarks
5
- * Declared structurally to avoid a dependency on `@cloudflare/workers-types`.
6
- * Lives in the root export path so worker entry modules can import it without pulling in `./db`.
7
- */
8
- export interface ExecutionContextLike {
9
- /** Extend the request's lifetime until `promise` settles (used to close connections after the response). */
10
- waitUntil(promise: Promise<unknown>): void;
11
- }
@@ -1,71 +0,0 @@
1
- /**
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.
9
- */
10
- export enum HttpStatus {
11
- CONTINUE = 100,
12
- SWITCHING_PROTOCOLS = 101,
13
- PROCESSING = 102,
14
- EARLYHINTS = 103,
15
- OK = 200,
16
- CREATED = 201,
17
- ACCEPTED = 202,
18
- NON_AUTHORITATIVE_INFORMATION = 203,
19
- NO_CONTENT = 204,
20
- RESET_CONTENT = 205,
21
- PARTIAL_CONTENT = 206,
22
- MULTI_STATUS = 207,
23
- ALREADY_REPORTED = 208,
24
- /** Non-standard WebDAV extension carried over from NestJS. */
25
- CONTENT_DIFFERENT = 210,
26
- /** Multiple Choices (300). Named `AMBIGUOUS` to match NestJS. */
27
- AMBIGUOUS = 300,
28
- MOVED_PERMANENTLY = 301,
29
- FOUND = 302,
30
- SEE_OTHER = 303,
31
- NOT_MODIFIED = 304,
32
- TEMPORARY_REDIRECT = 307,
33
- PERMANENT_REDIRECT = 308,
34
- BAD_REQUEST = 400,
35
- UNAUTHORIZED = 401,
36
- PAYMENT_REQUIRED = 402,
37
- FORBIDDEN = 403,
38
- NOT_FOUND = 404,
39
- METHOD_NOT_ALLOWED = 405,
40
- NOT_ACCEPTABLE = 406,
41
- PROXY_AUTHENTICATION_REQUIRED = 407,
42
- REQUEST_TIMEOUT = 408,
43
- CONFLICT = 409,
44
- GONE = 410,
45
- LENGTH_REQUIRED = 411,
46
- PRECONDITION_FAILED = 412,
47
- PAYLOAD_TOO_LARGE = 413,
48
- URI_TOO_LONG = 414,
49
- UNSUPPORTED_MEDIA_TYPE = 415,
50
- REQUESTED_RANGE_NOT_SATISFIABLE = 416,
51
- EXPECTATION_FAILED = 417,
52
- /** "I'm a teapot" (418), from RFC 2324. */
53
- I_AM_A_TEAPOT = 418,
54
- /** Misdirected Request (421). Named `MISDIRECTED` to match NestJS. */
55
- MISDIRECTED = 421,
56
- UNPROCESSABLE_ENTITY = 422,
57
- LOCKED = 423,
58
- FAILED_DEPENDENCY = 424,
59
- PRECONDITION_REQUIRED = 428,
60
- TOO_MANY_REQUESTS = 429,
61
- /** Non-standard code carried over from NestJS. */
62
- UNRECOVERABLE_ERROR = 456,
63
- INTERNAL_SERVER_ERROR = 500,
64
- NOT_IMPLEMENTED = 501,
65
- BAD_GATEWAY = 502,
66
- SERVICE_UNAVAILABLE = 503,
67
- GATEWAY_TIMEOUT = 504,
68
- HTTP_VERSION_NOT_SUPPORTED = 505,
69
- INSUFFICIENT_STORAGE = 507,
70
- LOOP_DETECTED = 508,
71
- }