@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.
- package/README.md +126 -11
- package/dist/ai/gateway.d.ts +54 -16
- package/dist/ai/gateway.js +37 -12
- package/dist/aws/cloudfront.d.ts +23 -5
- package/dist/aws/cloudfront.js +45 -6
- package/dist/aws/secrets-manager.d.ts +38 -4
- package/dist/aws/secrets-manager.js +48 -3
- package/dist/cache/kv-cache.d.ts +173 -10
- package/dist/cache/kv-cache.js +139 -7
- package/dist/db/connection.d.ts +56 -14
- package/dist/db/connection.js +39 -13
- package/dist/db/database.d.ts +159 -23
- package/dist/db/database.js +49 -5
- package/dist/db/index.d.ts +11 -0
- package/dist/db/index.js +11 -2
- package/dist/db/jst.d.ts +89 -6
- package/dist/db/jst.js +89 -23
- package/dist/db/orm-config.d.ts +61 -19
- package/dist/db/orm-config.js +43 -14
- package/dist/db/retry.d.ts +25 -3
- package/dist/db/retry.js +25 -3
- package/dist/db/write-result.d.ts +27 -4
- package/dist/db/write-result.js +22 -1
- package/dist/firebase/firebase-verifier.d.ts +53 -4
- package/dist/firebase/identity-toolkit.d.ts +54 -5
- package/dist/firebase/identity-toolkit.js +51 -0
- package/dist/firebase/jose-firebase-verifier.d.ts +79 -7
- package/dist/firebase/jose-firebase-verifier.js +68 -7
- package/dist/firebase/remote-verifier.d.ts +42 -4
- package/dist/firebase/remote-verifier.js +58 -9
- package/dist/http/app-env.d.ts +41 -8
- package/dist/http/app-env.js +38 -8
- package/dist/http/app-info.d.ts +25 -3
- package/dist/http/app-info.js +16 -2
- package/dist/http/http-status.d.ts +12 -3
- package/dist/http/http-status.js +12 -3
- package/dist/http/nest-error.d.ts +90 -29
- package/dist/http/nest-error.js +59 -18
- package/dist/http/user-protocol.d.ts +23 -3
- package/dist/http/user-protocol.js +14 -2
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -3
- package/dist/middleware/auth.d.ts +74 -13
- package/dist/middleware/auth.js +30 -6
- package/dist/middleware/finalize-response.d.ts +30 -0
- package/dist/middleware/finalize-response.js +41 -12
- package/dist/middleware/validation.d.ts +83 -9
- package/dist/middleware/validation.js +52 -9
- package/dist/middleware/zod-coerce.d.ts +56 -2
- package/dist/middleware/zod-coerce.js +68 -9
- package/dist/stripe/client.d.ts +46 -9
- package/dist/stripe/client.js +41 -3
- package/dist/testing/auth.d.ts +58 -10
- package/dist/testing/auth.js +58 -10
- package/dist/testing/configurable-fake.d.ts +20 -9
- package/dist/testing/configurable-fake.js +23 -11
- package/dist/testing/db.d.ts +81 -12
- package/dist/testing/db.js +23 -1
- package/dist/testing/fakes.d.ts +77 -9
- package/dist/testing/fakes.js +69 -7
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.js +10 -5
- package/dist/testing/stripe-fixtures.d.ts +93 -3
- package/dist/testing/stripe-fixtures.js +93 -3
- package/package.json +1 -1
- package/src/ai/gateway.ts +66 -27
- package/src/aws/cloudfront.ts +46 -6
- package/src/aws/secrets-manager.ts +56 -7
- package/src/cache/kv-cache.ts +194 -12
- package/src/db/connection.ts +56 -14
- package/src/db/database.ts +160 -24
- package/src/db/index.ts +11 -2
- package/src/db/jst.ts +89 -23
- package/src/db/orm-config.ts +61 -19
- package/src/db/retry.ts +25 -3
- package/src/db/write-result.ts +27 -4
- package/src/firebase/firebase-verifier.ts +53 -4
- package/src/firebase/identity-toolkit.ts +57 -5
- package/src/firebase/jose-firebase-verifier.ts +79 -9
- package/src/firebase/remote-verifier.ts +58 -9
- package/src/http/app-env.ts +41 -8
- package/src/http/app-info.ts +25 -3
- package/src/http/http-status.ts +12 -3
- package/src/http/nest-error.ts +106 -37
- package/src/http/user-protocol.ts +23 -3
- package/src/index.ts +11 -3
- package/src/middleware/auth.ts +77 -15
- package/src/middleware/finalize-response.ts +41 -12
- package/src/middleware/validation.ts +89 -15
- package/src/middleware/zod-coerce.ts +68 -9
- package/src/stripe/client.ts +46 -9
- package/src/testing/auth.ts +58 -10
- package/src/testing/configurable-fake.ts +23 -11
- package/src/testing/db.ts +82 -13
- package/src/testing/fakes.ts +77 -9
- package/src/testing/index.ts +10 -5
- package/src/testing/stripe-fixtures.ts +93 -3
|
@@ -1,72 +1,133 @@
|
|
|
1
1
|
import type { Context, Env } from 'hono';
|
|
2
2
|
import type { ContentfulStatusCode } from 'hono/utils/http-status';
|
|
3
3
|
/**
|
|
4
|
-
* NestJS
|
|
5
|
-
*
|
|
4
|
+
* Reason phrases attached by the NestJS default exception filter, keyed by HTTP status code.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Mirrors the `error` field values NestJS produces for common client-error statuses, so a Hono app can
|
|
8
|
+
* return byte-identical error bodies. Used as the default `reasonPhrases` map by {@link createNestErrorHandler}.
|
|
6
9
|
*/
|
|
7
10
|
export declare const NEST_REASON_PHRASES: Record<number, string>;
|
|
8
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Contextual metadata passed to an {@link ErrorReporter} when reporting an unexpected error.
|
|
13
|
+
*/
|
|
9
14
|
export interface ErrorReportContext {
|
|
15
|
+
/** Correlation id for the failing request, if one is tracked. */
|
|
10
16
|
requestId?: string;
|
|
11
17
|
}
|
|
12
18
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
19
|
+
* Signature of a function that reports an unexpected (non-HTTP) error to an external sink such as Sentry.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* Wire it into {@link createNestErrorHandler} via `onUnhandledError`, e.g.
|
|
23
|
+
* `(err, c) => reporter(err, { requestId: c.get('requestId') })`. The reporting client itself is
|
|
24
|
+
* intentionally kept out of this kit; the consumer supplies the implementation.
|
|
25
|
+
*
|
|
26
|
+
* @param error - The thrown value being reported.
|
|
27
|
+
* @param context - Optional correlation context for the failing request.
|
|
16
28
|
*/
|
|
17
29
|
export type ErrorReporter = (error: unknown, context?: ErrorReportContext) => void;
|
|
18
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Minimal shape read from a value treated as an HTTP error: its status, message, and optional body.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
19
35
|
interface HttpErrorLike {
|
|
36
|
+
/** HTTP status code to respond with. */
|
|
20
37
|
status: ContentfulStatusCode;
|
|
38
|
+
/** Human-readable error message placed in the response body. */
|
|
21
39
|
message: string;
|
|
22
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Escape hatch for a fully custom response body. When present, it is rendered verbatim instead of
|
|
42
|
+
* the NestJS-shaped body.
|
|
43
|
+
*/
|
|
23
44
|
body?: unknown;
|
|
24
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Options controlling how {@link createNestErrorHandler} shapes error responses.
|
|
48
|
+
*
|
|
49
|
+
* @typeParam E - The Hono environment type, so `onUnhandledError` receives a correctly typed context.
|
|
50
|
+
*/
|
|
25
51
|
export interface NestErrorHandlerOptions<E extends Env = Env> {
|
|
26
|
-
/** reason
|
|
52
|
+
/** Status-to-reason-phrase map for the `error` field. Defaults to {@link NEST_REASON_PHRASES}. */
|
|
27
53
|
reasonPhrases?: Record<number, string>;
|
|
28
54
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
55
|
+
* Statuses that return only `{ statusCode, message }`, omitting the `error` field. Defaults to `[401]`,
|
|
56
|
+
* matching NestJS where a generic `HttpException(msg, 401)` carries no `error`.
|
|
31
57
|
*/
|
|
32
58
|
bareStatuses?: readonly number[];
|
|
33
59
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
60
|
+
* Field order of the non-bare error body. Defaults to `'statusCode-first'` (the NestJS canonical order).
|
|
61
|
+
* Use `'message-first'` to emit `{ message, error, statusCode }` when byte parity requires it.
|
|
36
62
|
*/
|
|
37
63
|
fieldOrder?: 'statusCode-first' | 'message-first';
|
|
38
64
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
65
|
+
* Fallback `error` value for statuses that are neither bare nor present in `reasonPhrases`. Defaults to
|
|
66
|
+
* `undefined`, meaning the `error` field is omitted when no reason phrase is known. Set to a string such
|
|
67
|
+
* as `'Error'` to always include an `error` field, faithfully reproducing the NestJS default exception
|
|
68
|
+
* filter behavior where `error` is always present.
|
|
42
69
|
*/
|
|
43
70
|
fallbackReason?: string;
|
|
44
71
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
72
|
+
* Predicate identifying which thrown values are HTTP errors. Defaults to detecting Hono's `HTTPException`.
|
|
73
|
+
* Override it (e.g. `(e) => e instanceof MyHttpError`) when the app throws a custom HTTP error type.
|
|
47
74
|
*/
|
|
48
75
|
isHttpError?: (err: unknown) => err is HttpErrorLike;
|
|
49
76
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
77
|
+
* Hook invoked before an unexpected (non-HTTP) error is returned as a 500, typically used to report the
|
|
78
|
+
* error (e.g. to Sentry). Any exception thrown by this hook is swallowed so reporting cannot alter the
|
|
79
|
+
* error response.
|
|
52
80
|
*/
|
|
53
81
|
onUnhandledError?: (err: unknown, c: Context<E>) => void;
|
|
54
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Response body for unexpected errors returned as 500. Defaults to
|
|
84
|
+
* `{ statusCode: 500, message: 'Internal server error' }`.
|
|
85
|
+
*/
|
|
55
86
|
internalServerErrorBody?: unknown;
|
|
56
87
|
}
|
|
57
88
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
89
|
+
* Create a Hono `onError` handler that maps thrown errors to NestJS-shaped error JSON.
|
|
90
|
+
*
|
|
91
|
+
* @remarks
|
|
92
|
+
* Reproduces the NestJS default exception filter so a Hono app returns byte-identical error bodies:
|
|
93
|
+
* - HTTP errors (by default `HTTPException`) are mapped to a NestJS-shaped body; if the error carries a
|
|
94
|
+
* custom `body`, that body is returned verbatim.
|
|
95
|
+
* - Statuses listed in `bareStatuses` (default `[401]`) omit the `error` field.
|
|
96
|
+
* - Any other (unexpected) error triggers `onUnhandledError`, is logged via `console.error`, and returns 500.
|
|
62
97
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
98
|
+
* Per-app differences in body field order, HTTP error type, and reporting hook are absorbed through
|
|
99
|
+
* {@link NestErrorHandlerOptions}, while the branching logic stays shared.
|
|
100
|
+
*
|
|
101
|
+
* @typeParam E - The Hono environment type propagated to `onUnhandledError`.
|
|
102
|
+
* @param options - Overrides for reason phrases, bare statuses, field order, error detection, and reporting.
|
|
103
|
+
* @returns A handler suitable for `app.onError(...)`.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* app.onError(
|
|
108
|
+
* createNestErrorHandler({
|
|
109
|
+
* fieldOrder: 'message-first',
|
|
110
|
+
* fallbackReason: 'Error',
|
|
111
|
+
* onUnhandledError: (err, c) => reportError(err, { requestId: c.get('requestId') }),
|
|
112
|
+
* }),
|
|
113
|
+
* );
|
|
114
|
+
* ```
|
|
65
115
|
*/
|
|
66
116
|
export declare function createNestErrorHandler<E extends Env = Env>(options?: NestErrorHandlerOptions<E>): (err: Error, c: Context<E>) => Response;
|
|
67
117
|
/**
|
|
68
|
-
* Express/
|
|
69
|
-
*
|
|
118
|
+
* Hono `notFound` handler that returns the canonical Express/NestJS unmatched-route 404 body.
|
|
119
|
+
*
|
|
120
|
+
* @remarks
|
|
121
|
+
* Produces `{ message: "Cannot <METHOD> <path>", error: 'Not Found', statusCode: 404 }`, matching the
|
|
122
|
+
* NestJS default 404 response so unmatched routes stay byte-identical.
|
|
123
|
+
*
|
|
124
|
+
* @param c - The Hono request context for the unmatched route.
|
|
125
|
+
* @returns A 404 JSON response.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* app.notFound(nestNotFoundHandler);
|
|
130
|
+
* ```
|
|
70
131
|
*/
|
|
71
132
|
export declare function nestNotFoundHandler(c: Context): Response;
|
|
72
133
|
export {};
|
package/dist/http/nest-error.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* NestJS
|
|
3
|
-
*
|
|
2
|
+
* Reason phrases attached by the NestJS default exception filter, keyed by HTTP status code.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Mirrors the `error` field values NestJS produces for common client-error statuses, so a Hono app can
|
|
6
|
+
* return byte-identical error bodies. Used as the default `reasonPhrases` map by {@link createNestErrorHandler}.
|
|
4
7
|
*/
|
|
5
8
|
export const NEST_REASON_PHRASES = {
|
|
6
9
|
400: 'Bad Request',
|
|
@@ -9,33 +12,60 @@ export const NEST_REASON_PHRASES = {
|
|
|
9
12
|
404: 'Not Found',
|
|
10
13
|
};
|
|
11
14
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
15
|
+
* Structurally detect Hono's `HTTPException` without relying on `instanceof`.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* When this kit is symlinked into a consumer, the `hono` instance it resolves can differ from the
|
|
19
|
+
* consumer's `hono`, so an `HTTPException` from one copy fails an `instanceof` check against the other.
|
|
20
|
+
* Detecting the presence of a `getResponse()` method and a numeric `status` is stable across module
|
|
21
|
+
* boundaries and production bundles.
|
|
22
|
+
*
|
|
23
|
+
* @param err - The thrown value to test.
|
|
24
|
+
* @returns `true` when `err` looks like a Hono `HTTPException`.
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
16
27
|
*/
|
|
17
28
|
const isHTTPException = (err) => err instanceof Error &&
|
|
18
29
|
typeof err.getResponse === 'function' &&
|
|
19
30
|
typeof err.status === 'number';
|
|
20
31
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
32
|
+
* Create a Hono `onError` handler that maps thrown errors to NestJS-shaped error JSON.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* Reproduces the NestJS default exception filter so a Hono app returns byte-identical error bodies:
|
|
36
|
+
* - HTTP errors (by default `HTTPException`) are mapped to a NestJS-shaped body; if the error carries a
|
|
37
|
+
* custom `body`, that body is returned verbatim.
|
|
38
|
+
* - Statuses listed in `bareStatuses` (default `[401]`) omit the `error` field.
|
|
39
|
+
* - Any other (unexpected) error triggers `onUnhandledError`, is logged via `console.error`, and returns 500.
|
|
25
40
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
41
|
+
* Per-app differences in body field order, HTTP error type, and reporting hook are absorbed through
|
|
42
|
+
* {@link NestErrorHandlerOptions}, while the branching logic stays shared.
|
|
43
|
+
*
|
|
44
|
+
* @typeParam E - The Hono environment type propagated to `onUnhandledError`.
|
|
45
|
+
* @param options - Overrides for reason phrases, bare statuses, field order, error detection, and reporting.
|
|
46
|
+
* @returns A handler suitable for `app.onError(...)`.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* app.onError(
|
|
51
|
+
* createNestErrorHandler({
|
|
52
|
+
* fieldOrder: 'message-first',
|
|
53
|
+
* fallbackReason: 'Error',
|
|
54
|
+
* onUnhandledError: (err, c) => reportError(err, { requestId: c.get('requestId') }),
|
|
55
|
+
* }),
|
|
56
|
+
* );
|
|
57
|
+
* ```
|
|
28
58
|
*/
|
|
29
59
|
export function createNestErrorHandler(options = {}) {
|
|
30
60
|
const { reasonPhrases = NEST_REASON_PHRASES, bareStatuses = [401], fieldOrder = 'statusCode-first', isHttpError = isHTTPException, onUnhandledError, internalServerErrorBody = { statusCode: 500, message: 'Internal server error' }, fallbackReason, } = options;
|
|
31
61
|
return (err, c) => {
|
|
32
62
|
if (isHttpError(err)) {
|
|
33
|
-
//
|
|
63
|
+
// Escape hatch for a custom error body: render it verbatim.
|
|
34
64
|
if (err.body !== undefined) {
|
|
35
65
|
return c.json(err.body, err.status);
|
|
36
66
|
}
|
|
37
|
-
// reasonPhrases[status]
|
|
38
|
-
//
|
|
67
|
+
// reasonPhrases[status] is typed as string, but with noUncheckedIndexedAccess disabled it can be
|
|
68
|
+
// undefined at runtime. The fallbackReason fallback for unregistered statuses is intentional.
|
|
39
69
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
40
70
|
const reason = bareStatuses.includes(err.status) ? undefined : (reasonPhrases[err.status] ?? fallbackReason);
|
|
41
71
|
if (reason === undefined) {
|
|
@@ -50,15 +80,26 @@ export function createNestErrorHandler(options = {}) {
|
|
|
50
80
|
onUnhandledError?.(err, c);
|
|
51
81
|
}
|
|
52
82
|
catch {
|
|
53
|
-
//
|
|
83
|
+
// Reporting must never change the behavior of the error response.
|
|
54
84
|
}
|
|
55
85
|
console.error(err);
|
|
56
86
|
return c.json(internalServerErrorBody, 500);
|
|
57
87
|
};
|
|
58
88
|
}
|
|
59
89
|
/**
|
|
60
|
-
* Express/
|
|
61
|
-
*
|
|
90
|
+
* Hono `notFound` handler that returns the canonical Express/NestJS unmatched-route 404 body.
|
|
91
|
+
*
|
|
92
|
+
* @remarks
|
|
93
|
+
* Produces `{ message: "Cannot <METHOD> <path>", error: 'Not Found', statusCode: 404 }`, matching the
|
|
94
|
+
* NestJS default 404 response so unmatched routes stay byte-identical.
|
|
95
|
+
*
|
|
96
|
+
* @param c - The Hono request context for the unmatched route.
|
|
97
|
+
* @returns A 404 JSON response.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* app.notFound(nestNotFoundHandler);
|
|
102
|
+
* ```
|
|
62
103
|
*/
|
|
63
104
|
export function nestNotFoundHandler(c) {
|
|
64
105
|
return c.json({ message: `Cannot ${c.req.method} ${new URL(c.req.url).pathname}`, error: 'Not Found', statusCode: 404 }, 404);
|
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
import type { Context } from 'hono';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* The client's network identity: IP address and user agent.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Equivalent to the data a NestJS `@UserProtocol` decorator would expose, so a Hono app can persist the
|
|
7
|
+
* same client metadata. Both fields are nullable to map directly onto nullable database columns.
|
|
8
|
+
*/
|
|
3
9
|
export interface IUserProtocol {
|
|
10
|
+
/** Client IP address, or `null` when no source header is present. */
|
|
4
11
|
ipAddress: string | null;
|
|
12
|
+
/** Client user-agent string, or `null` when the `User-Agent` header is absent. */
|
|
5
13
|
userAgent: string | null;
|
|
6
14
|
}
|
|
7
15
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
16
|
+
* Read the client's IP address and user agent from the Hono request context.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* On Cloudflare the real client IP is provided in `CF-Connecting-IP`, with `X-Forwarded-For` used as a
|
|
20
|
+
* fallback. Missing values resolve to `null` so they map cleanly onto nullable storage.
|
|
21
|
+
*
|
|
22
|
+
* @param c - The Hono request context to read headers from.
|
|
23
|
+
* @returns The client's IP address and user agent for the current request.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const { ipAddress, userAgent } = getUserProtocol(c);
|
|
28
|
+
* await auditLog.insert({ ipAddress, userAgent });
|
|
29
|
+
* ```
|
|
10
30
|
*/
|
|
11
31
|
export declare const getUserProtocol: (c: Context) => IUserProtocol;
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Read the client's IP address and user agent from the Hono request context.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* On Cloudflare the real client IP is provided in `CF-Connecting-IP`, with `X-Forwarded-For` used as a
|
|
6
|
+
* fallback. Missing values resolve to `null` so they map cleanly onto nullable storage.
|
|
7
|
+
*
|
|
8
|
+
* @param c - The Hono request context to read headers from.
|
|
9
|
+
* @returns The client's IP address and user agent for the current request.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const { ipAddress, userAgent } = getUserProtocol(c);
|
|
14
|
+
* await auditLog.insert({ ipAddress, userAgent });
|
|
15
|
+
* ```
|
|
4
16
|
*/
|
|
5
17
|
export const getUserProtocol = (c) => ({
|
|
6
18
|
ipAddress: c.req.header('cf-connecting-ip') ?? c.req.header('x-forwarded-for') ?? null,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
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
|
+
*/
|
|
1
12
|
export { finalizeResponse } from './middleware/finalize-response.js';
|
|
2
13
|
export { validate, createSentryValidate } from './middleware/validation.js';
|
|
3
14
|
export type { ValidateOptions, ValidationTarget, ZodErrorLike, SentryLike, SentryScopeLike, } from './middleware/validation.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
// middleware
|
|
5
13
|
export { finalizeResponse } from './middleware/finalize-response.js';
|
|
6
14
|
export { validate, createSentryValidate } from './middleware/validation.js';
|
|
@@ -1,36 +1,97 @@
|
|
|
1
1
|
import type { Context, Env, MiddlewareHandler } from 'hono';
|
|
2
2
|
import type { ContentfulStatusCode } from 'hono/utils/http-status';
|
|
3
3
|
import type { AppInfo } from '../http/app-info.js';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for {@link createAuthMiddleware}.
|
|
6
|
+
*
|
|
7
|
+
* @typeParam E - The Hono `Env` (bindings/variables) of the application.
|
|
8
|
+
* @typeParam Verified - The value produced by {@link AuthMiddlewareOptions.verify} (e.g. a decoded token or user record).
|
|
9
|
+
* @typeParam Id - The resolved user identifier type.
|
|
10
|
+
*/
|
|
4
11
|
export interface AuthMiddlewareOptions<E extends Env, Verified, Id = unknown> {
|
|
5
|
-
/** ID
|
|
12
|
+
/** Header carrying the ID token. Defaults to `'x-amz-security-token'`. */
|
|
6
13
|
tokenHeader?: string;
|
|
7
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Verify the raw token and return the decoded value or user record.
|
|
16
|
+
*
|
|
17
|
+
* @param token - The raw token read from {@link AuthMiddlewareOptions.tokenHeader} (empty string if absent).
|
|
18
|
+
* @param c - The current Hono context.
|
|
19
|
+
* @returns The verified value passed to {@link AuthMiddlewareOptions.resolveUserId}/{@link AuthMiddlewareOptions.setContext}.
|
|
20
|
+
* @throws If the token is invalid; rejecting/throwing triggers the failure path.
|
|
21
|
+
*/
|
|
8
22
|
verify: (token: string, c: Context<E>) => Promise<Verified>;
|
|
9
23
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
24
|
+
* Resolve the database user id (creating the user if necessary).
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* Omit this to run in **token-only** mode (verification only, e.g. for login). Create-on-miss
|
|
28
|
+
* behavior (such as `getUserId(...).catch(() => createUser(...))`) should be composed here by the
|
|
29
|
+
* caller.
|
|
30
|
+
*
|
|
31
|
+
* @param verified - The value returned by {@link AuthMiddlewareOptions.verify}.
|
|
32
|
+
* @param c - The current Hono context.
|
|
33
|
+
* @param appInfo - The resolved application info for the request.
|
|
34
|
+
* @returns The resolved user id.
|
|
12
35
|
*/
|
|
13
36
|
resolveUserId?: (verified: Verified, c: Context<E>, appInfo: AppInfo) => Promise<Id>;
|
|
14
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Store the verification result on the context variables.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* Inject the application-specific variable names here (e.g. `decodedToken`, `userRecord`, `userProtocol`).
|
|
42
|
+
*
|
|
43
|
+
* @param c - The current Hono context.
|
|
44
|
+
* @param data - The verified value, resolved app info, and (when available) the user id.
|
|
45
|
+
*/
|
|
15
46
|
setContext: (c: Context<E>, data: {
|
|
16
47
|
verified: Verified;
|
|
17
48
|
appInfo: AppInfo;
|
|
18
49
|
userId?: Id;
|
|
19
50
|
}) => void;
|
|
20
51
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
52
|
+
* Override the failure behavior.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* Defaults to `throw new HTTPException(failureStatus, { message: failureMessage })`. Provide this to
|
|
56
|
+
* return a custom `Response` instead (e.g. `c.json(body, status)`).
|
|
57
|
+
*
|
|
58
|
+
* @param err - The error thrown during verification/resolution.
|
|
59
|
+
* @param c - The current Hono context.
|
|
60
|
+
* @returns The failure response to send.
|
|
23
61
|
*/
|
|
24
62
|
onFailure?: (err: unknown, c: Context<E>) => Response;
|
|
25
|
-
/**
|
|
63
|
+
/** Status used by the default `onFailure`. Defaults to `403`. */
|
|
26
64
|
failureStatus?: ContentfulStatusCode;
|
|
27
|
-
/**
|
|
65
|
+
/** Message used by the default `onFailure`. Defaults to `'Forbidden resource'`. */
|
|
28
66
|
failureMessage?: string;
|
|
29
67
|
}
|
|
30
68
|
/**
|
|
31
|
-
* NestJS
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* `resolveUserId`
|
|
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 declare function createAuthMiddleware<E extends Env = Env, Verified = unknown, Id = unknown>(options: AuthMiddlewareOptions<E, Verified, Id>): MiddlewareHandler<E>;
|
package/dist/middleware/auth.js
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
import { HTTPException } from 'hono/http-exception';
|
|
2
2
|
import { getAppInfo } from '../http/app-info.js';
|
|
3
3
|
/**
|
|
4
|
-
* NestJS
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `resolveUserId`
|
|
4
|
+
* Create an authentication middleware equivalent to a NestJS `AuthGuard` / `TokenGuard`.
|
|
5
|
+
*
|
|
6
|
+
* The middleware runs a fixed skeleton — read the token header, `verify`, `getAppInfo`,
|
|
7
|
+
* `resolveUserId`, `setContext`, and on error `console.error` then `onFailure` — while the
|
|
8
|
+
* application injects the variable parts (token verification, user-id resolution, context variable
|
|
9
|
+
* names, and the failure response). Omitting {@link AuthMiddlewareOptions.resolveUserId} yields a
|
|
10
|
+
* token-only middleware.
|
|
11
|
+
*
|
|
12
|
+
* @typeParam E - The Hono `Env` of the application.
|
|
13
|
+
* @typeParam Verified - The value produced by `verify`.
|
|
14
|
+
* @typeParam Id - The resolved user identifier type.
|
|
15
|
+
* @param options - The verification, resolution, and failure hooks; see {@link AuthMiddlewareOptions}.
|
|
16
|
+
* @returns A {@link MiddlewareHandler} that authenticates the request and populates the context.
|
|
17
|
+
* @throws HTTPException From the default failure handler when `onFailure` is not supplied.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const auth = createAuthMiddleware({
|
|
22
|
+
* verify: (token, c) => verifier.verifyIdToken(token),
|
|
23
|
+
* resolveUserId: (decoded) => findUserId(decoded.uid),
|
|
24
|
+
* setContext: (c, { verified, userId }) => {
|
|
25
|
+
* c.set('decodedToken', verified);
|
|
26
|
+
* c.set('userId', userId);
|
|
27
|
+
* },
|
|
28
|
+
* });
|
|
29
|
+
* app.use('/api/*', auth);
|
|
30
|
+
* ```
|
|
8
31
|
*/
|
|
9
32
|
export function createAuthMiddleware(options) {
|
|
10
33
|
const { tokenHeader = 'x-amz-security-token', verify, resolveUserId, setContext, onFailure, failureStatus = 403, failureMessage = 'Forbidden resource', } = options;
|
|
@@ -17,8 +40,9 @@ export function createAuthMiddleware(options) {
|
|
|
17
40
|
setContext(c, { verified, appInfo, userId });
|
|
18
41
|
}
|
|
19
42
|
catch (e) {
|
|
20
|
-
//
|
|
21
|
-
//
|
|
43
|
+
// Equivalent to a guard returning false → ForbiddenException('Forbidden resource'). Log the
|
|
44
|
+
// cause and, by default, throw so the app's onError renders the error body (callers can
|
|
45
|
+
// override with onFailure to return a custom response instead).
|
|
22
46
|
console.error(e);
|
|
23
47
|
if (onFailure) {
|
|
24
48
|
return onFailure(e, c);
|
|
@@ -1,2 +1,32 @@
|
|
|
1
1
|
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
/**
|
|
3
|
+
* Create a Hono middleware that finalizes responses for byte-parity with an Express/Nest backend.
|
|
4
|
+
*
|
|
5
|
+
* After the downstream handler runs, it performs two adjustments:
|
|
6
|
+
*
|
|
7
|
+
* 1. **JSON charset**: Express's `res.json` emits `application/json; charset=utf-8`, whereas Hono's
|
|
8
|
+
* `c.json` emits a bare `application/json`. A bare `application/json` content type is rewritten
|
|
9
|
+
* to include `; charset=utf-8`.
|
|
10
|
+
* 2. **Weak ETag**: An Express `etag`-package compatible weak ETag is added, matching the format an
|
|
11
|
+
* Express/Nest backend applies to responses by default. See {@link weakEtag} for the exact format.
|
|
12
|
+
*
|
|
13
|
+
* Server-Sent Events (`text/event-stream`) are skipped entirely because the stream cannot be
|
|
14
|
+
* buffered. ETag generation is also skipped for `204`/`304` responses, responses that already carry
|
|
15
|
+
* an `etag` header, and responses without a body.
|
|
16
|
+
*
|
|
17
|
+
* @returns A {@link MiddlewareHandler} that rewrites the response headers (and body, when an ETag
|
|
18
|
+
* must be computed) in place.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { Hono } from 'hono';
|
|
23
|
+
* import { finalizeResponse } from '@rdlabo/workers-hono-kit';
|
|
24
|
+
*
|
|
25
|
+
* const app = new Hono();
|
|
26
|
+
* app.use('*', finalizeResponse());
|
|
27
|
+
* app.get('/users', (c) => c.json({ ok: true }));
|
|
28
|
+
* // → Content-Type: application/json; charset=utf-8
|
|
29
|
+
* // → ETag: W/"b-..." (b = 0xb = 11 bytes, the length of `{"ok":true}`)
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
2
32
|
export declare function finalizeResponse(): MiddlewareHandler;
|