@kotodayori/fetch 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/dist/index.cjs +8 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -4
- package/dist/index.d.ts +31 -4
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -86,10 +86,17 @@ function fetchAdapter<TEvent extends WebhookEvent>(
|
|
|
86
86
|
options: {
|
|
87
87
|
verifier: Verifier<TEvent>;
|
|
88
88
|
onError?: (error: Error, event: TEvent) => Promise<void> | void;
|
|
89
|
+
logger?: Logger; // defaults to consoleLogger
|
|
90
|
+
createContext?: (request: Request) => TContext | Promise<TContext>; // defaults to the Request
|
|
89
91
|
}
|
|
90
92
|
): (request: Request) => Promise<Response>
|
|
91
93
|
```
|
|
92
94
|
|
|
95
|
+
- **verifier** — Required. Verifies the request and returns the event (e.g. `createStripeVerifier`).
|
|
96
|
+
- **onError** — Optional. Called when a handler throws; errors thrown by `onError` itself are ignored and the response stays 500.
|
|
97
|
+
- **logger** — Optional. Receives adapter diagnostics, currently verification failures (`logger.error('Webhook verification failed:', err)`). Defaults to `consoleLogger`, which writes to `console`.
|
|
98
|
+
- **createContext** — Optional. Builds the per-request context passed to handlers as their second argument. Defaults to the `Request` itself. The types require it when the router's context is not something `Request` satisfies.
|
|
99
|
+
|
|
93
100
|
The raw body is read with `request.text()` and passed to the verifier untouched, so signature verification sees the exact bytes that were signed.
|
|
94
101
|
|
|
95
102
|
| Situation | Status | Body |
|
|
@@ -100,6 +107,41 @@ The raw body is read with `request.text()` and passed to the verifier untouched,
|
|
|
100
107
|
| Handler threw | 500 | `{ "error": "Internal server error" }` |
|
|
101
108
|
| Success | 200 | `{ "received": true }` |
|
|
102
109
|
|
|
110
|
+
### Per-request context (Workers bindings)
|
|
111
|
+
|
|
112
|
+
Handlers receive a per-request context as their second argument, so a router defined once at module scope can still reach request-scoped values such as Cloudflare Workers bindings. Create the handler inside `fetch(request, env)` and build the context from `env`:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
const router = new StripeWebhookRouter<{ db: D1Database }>();
|
|
116
|
+
|
|
117
|
+
router.on('checkout.session.completed', async (event, { db }) => {
|
|
118
|
+
await db.prepare('INSERT INTO orders (id) VALUES (?)').bind(event.data.object.id).run();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export default {
|
|
122
|
+
fetch: (request: Request, env: Env) =>
|
|
123
|
+
fetchAdapter(router, {
|
|
124
|
+
verifier: createStripeVerifier(stripe, env.STRIPE_WEBHOOK_SECRET),
|
|
125
|
+
createContext: () => ({ db: env.DB }),
|
|
126
|
+
})(request),
|
|
127
|
+
};
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
If `createContext` throws, the adapter responds 500 and calls `onError`, like a handler error.
|
|
131
|
+
|
|
132
|
+
### Custom logger
|
|
133
|
+
|
|
134
|
+
Route verification failures to your own logger, or silence them:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { silentLogger } from '@kotodayori/core';
|
|
138
|
+
|
|
139
|
+
const handle = fetchAdapter(router, {
|
|
140
|
+
verifier,
|
|
141
|
+
logger: silentLogger, // or any object with `warn` and `error` methods
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
103
145
|
Unlike the Hono adapter, non-POST requests are rejected with 405 because the handler may be mounted directly (e.g. `export default { fetch: handle }`) with no router in front of it.
|
|
104
146
|
|
|
105
147
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,7 @@ function jsonResponse(body, status, headers = {}) {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
function fetchAdapter(router, options) {
|
|
36
|
+
const { createContext } = options;
|
|
36
37
|
return async (request) => {
|
|
37
38
|
if (request.method !== "POST") {
|
|
38
39
|
return jsonResponse({ error: "Method not allowed" }, 405, { allow: "POST" });
|
|
@@ -42,7 +43,13 @@ function fetchAdapter(router, options) {
|
|
|
42
43
|
request.headers.forEach((value, key) => {
|
|
43
44
|
headers[key] = value;
|
|
44
45
|
});
|
|
45
|
-
const { status, body } = await (0, import_core.handleWebhook)(
|
|
46
|
+
const { status, body } = await (0, import_core.handleWebhook)(
|
|
47
|
+
router,
|
|
48
|
+
options,
|
|
49
|
+
rawBody,
|
|
50
|
+
headers,
|
|
51
|
+
(0, import_core.contextProvider)(createContext, [request], request)
|
|
52
|
+
);
|
|
46
53
|
return jsonResponse(body, status);
|
|
47
54
|
};
|
|
48
55
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n handleWebhook,\n type WebhookDispatcher,\n type WebhookEvent,\n type WebhookHandlerOptions,\n} from '@kotodayori/core';\n\n/**\n * Options for the fetch adapter\n */\nexport type FetchAdapterOptions<T extends WebhookEvent = WebhookEvent> = WebhookHandlerOptions<T>;\n\nfunction jsonResponse(body: unknown, status: number, headers: Record<string, string> = {}): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json', ...headers },\n });\n}\n\n/**\n * Creates a Web-standard `(request: Request) => Promise<Response>` webhook handler\n *\n * Works anywhere the runtime hands you a standard `Request`: Cloudflare Workers,\n * Bun, Deno, Next.js App Router route handlers, React Router / Remix actions, etc.\n * The raw body is read with `request.text()` and passed to the verifier untouched.\n * Non-POST requests get `405 Method Not Allowed`, since the handler may be mounted\n * without a router in front of it.\n *\n * @example\n * ```typescript\n * import { fetchAdapter } from '@kotodayori/fetch';\n * import { createStripeVerifier, StripeWebhookRouter } from '@kotodayori/stripe';\n *\n * const router = new StripeWebhookRouter();\n * const handle = fetchAdapter(router, {\n * verifier: createStripeVerifier(stripe, env.STRIPE_WEBHOOK_SECRET),\n * });\n *\n * // Cloudflare Workers / Bun / Deno\n * export default { fetch: handle };\n *\n * // Next.js App Router (app/webhook/route.ts)\n * export const POST = handle;\n *\n * // React Router v7 / Remix\n * export const action = ({ request }) => handle(request);\n * ```\n *\n * @param router - A WebhookDispatcher (any object with a `dispatch(event)` method, e.g. WebhookRouter)\n * @param options - Adapter options including a verifier function\n * @returns Fetch handler function\n */\nexport function fetchAdapter<\n TEvent extends WebhookEvent = WebhookEvent,\n>(\n router: WebhookDispatcher<TEvent>,\n options:
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n contextProvider,\n handleWebhook,\n type ContextFactory,\n type ContextFactoryOption,\n type WebhookDispatcher,\n type WebhookEvent,\n type WebhookHandlerOptions,\n} from '@kotodayori/core';\n\n/**\n * Options for the fetch adapter\n */\nexport type FetchAdapterOptions<T extends WebhookEvent = WebhookEvent, TContext = Request> =\n WebhookHandlerOptions<T> & {\n /**\n * Builds the per-request context passed to handlers as their second argument.\n * Defaults to passing the `Request` itself. Required when the router's context type\n * is not satisfied by `Request`. On Cloudflare Workers, create the handler inside\n * `fetch(request, env)` and read bindings from `env`. Errors are handled like handler\n * errors.\n */\n createContext?: ContextFactory<[request: Request], TContext>;\n };\n\n/**\n * Fetch adapter options for a router with context `TContext`: `createContext` is optional\n * when `Request` already satisfies `TContext`, and required otherwise.\n */\nexport type FetchAdapterOptionsFor<T extends WebhookEvent, TContext> = WebhookHandlerOptions<T> &\n ContextFactoryOption<Request, TContext, [request: Request]>;\n\nfunction jsonResponse(body: unknown, status: number, headers: Record<string, string> = {}): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json', ...headers },\n });\n}\n\n/**\n * Creates a Web-standard `(request: Request) => Promise<Response>` webhook handler\n *\n * Works anywhere the runtime hands you a standard `Request`: Cloudflare Workers,\n * Bun, Deno, Next.js App Router route handlers, React Router / Remix actions, etc.\n * The raw body is read with `request.text()` and passed to the verifier untouched.\n * Non-POST requests get `405 Method Not Allowed`, since the handler may be mounted\n * without a router in front of it.\n *\n * @example\n * ```typescript\n * import { fetchAdapter } from '@kotodayori/fetch';\n * import { createStripeVerifier, StripeWebhookRouter } from '@kotodayori/stripe';\n *\n * const router = new StripeWebhookRouter();\n * const handle = fetchAdapter(router, {\n * verifier: createStripeVerifier(stripe, env.STRIPE_WEBHOOK_SECRET),\n * });\n *\n * // Cloudflare Workers / Bun / Deno\n * export default { fetch: handle };\n *\n * // Next.js App Router (app/webhook/route.ts)\n * export const POST = handle;\n *\n * // React Router v7 / Remix\n * export const action = ({ request }) => handle(request);\n * ```\n *\n * @example\n * ```typescript\n * // Cloudflare Workers: handlers reach bindings through the per-request context\n * const router = new StripeWebhookRouter<{ db: D1Database }>();\n * export default {\n * fetch: (request: Request, env: Env) =>\n * fetchAdapter(router, {\n * verifier: createStripeVerifier(stripe, env.STRIPE_WEBHOOK_SECRET),\n * createContext: () => ({ db: env.DB }),\n * })(request),\n * };\n * ```\n *\n * @param router - A WebhookDispatcher (any object with a `dispatch(event)` method, e.g. WebhookRouter)\n * @param options - Adapter options including a verifier function\n * @returns Fetch handler function\n */\nexport function fetchAdapter<\n TEvent extends WebhookEvent = WebhookEvent,\n TContext = unknown,\n>(\n router: WebhookDispatcher<NoInfer<TEvent>, TContext>,\n options: FetchAdapterOptionsFor<TEvent, NoInfer<TContext>>\n): (request: Request) => Promise<Response> {\n const { createContext } = options as FetchAdapterOptions<TEvent, TContext>;\n\n return async (request: Request): Promise<Response> => {\n if (request.method !== 'POST') {\n return jsonResponse({ error: 'Method not allowed' }, 405, { allow: 'POST' });\n }\n\n // Get raw body text for signature verification\n const rawBody = await request.text();\n\n const headers: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n const { status, body } = await handleWebhook(\n router,\n options,\n rawBody,\n headers,\n contextProvider(createContext, [request], request)\n );\n return jsonResponse(body, status);\n };\n}\n\n// Re-export core types\nexport { WebhookRouter, type WebhookDispatcher, type WebhookEvent, type EventHandler, type Middleware, type Verifier, type VerifyResult } from '@kotodayori/core';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAQO;AA+GP,IAAAA,eAA+I;AAvF/I,SAAS,aAAa,MAAe,QAAgB,UAAkC,CAAC,GAAa;AACnG,SAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC;AAAA,IACA,SAAS,EAAE,gBAAgB,oBAAoB,GAAG,QAAQ;AAAA,EAC5D,CAAC;AACH;AAgDO,SAAS,aAId,QACA,SACyC;AACzC,QAAM,EAAE,cAAc,IAAI;AAE1B,SAAO,OAAO,YAAwC;AACpD,QAAI,QAAQ,WAAW,QAAQ;AAC7B,aAAO,aAAa,EAAE,OAAO,qBAAqB,GAAG,KAAK,EAAE,OAAO,OAAO,CAAC;AAAA,IAC7E;AAGA,UAAM,UAAU,MAAM,QAAQ,KAAK;AAEnC,UAAM,UAAkC,CAAC;AACzC,YAAQ,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACtC,cAAQ,GAAG,IAAI;AAAA,IACjB,CAAC;AAED,UAAM,EAAE,QAAQ,KAAK,IAAI,UAAM;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,UACA,6BAAgB,eAAe,CAAC,OAAO,GAAG,OAAO;AAAA,IACnD;AACA,WAAO,aAAa,MAAM,MAAM;AAAA,EAClC;AACF;","names":["import_core"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import { WebhookEvent, WebhookHandlerOptions, WebhookDispatcher } from '@kotodayori/core';
|
|
1
|
+
import { WebhookEvent, WebhookHandlerOptions, ContextFactory, ContextFactoryOption, WebhookDispatcher } from '@kotodayori/core';
|
|
2
2
|
export { EventHandler, Middleware, Verifier, VerifyResult, WebhookDispatcher, WebhookEvent, WebhookRouter } from '@kotodayori/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Options for the fetch adapter
|
|
6
6
|
*/
|
|
7
|
-
type FetchAdapterOptions<T extends WebhookEvent = WebhookEvent> = WebhookHandlerOptions<T
|
|
7
|
+
type FetchAdapterOptions<T extends WebhookEvent = WebhookEvent, TContext = Request> = WebhookHandlerOptions<T> & {
|
|
8
|
+
/**
|
|
9
|
+
* Builds the per-request context passed to handlers as their second argument.
|
|
10
|
+
* Defaults to passing the `Request` itself. Required when the router's context type
|
|
11
|
+
* is not satisfied by `Request`. On Cloudflare Workers, create the handler inside
|
|
12
|
+
* `fetch(request, env)` and read bindings from `env`. Errors are handled like handler
|
|
13
|
+
* errors.
|
|
14
|
+
*/
|
|
15
|
+
createContext?: ContextFactory<[request: Request], TContext>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Fetch adapter options for a router with context `TContext`: `createContext` is optional
|
|
19
|
+
* when `Request` already satisfies `TContext`, and required otherwise.
|
|
20
|
+
*/
|
|
21
|
+
type FetchAdapterOptionsFor<T extends WebhookEvent, TContext> = WebhookHandlerOptions<T> & ContextFactoryOption<Request, TContext, [request: Request]>;
|
|
8
22
|
/**
|
|
9
23
|
* Creates a Web-standard `(request: Request) => Promise<Response>` webhook handler
|
|
10
24
|
*
|
|
@@ -34,10 +48,23 @@ type FetchAdapterOptions<T extends WebhookEvent = WebhookEvent> = WebhookHandler
|
|
|
34
48
|
* export const action = ({ request }) => handle(request);
|
|
35
49
|
* ```
|
|
36
50
|
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // Cloudflare Workers: handlers reach bindings through the per-request context
|
|
54
|
+
* const router = new StripeWebhookRouter<{ db: D1Database }>();
|
|
55
|
+
* export default {
|
|
56
|
+
* fetch: (request: Request, env: Env) =>
|
|
57
|
+
* fetchAdapter(router, {
|
|
58
|
+
* verifier: createStripeVerifier(stripe, env.STRIPE_WEBHOOK_SECRET),
|
|
59
|
+
* createContext: () => ({ db: env.DB }),
|
|
60
|
+
* })(request),
|
|
61
|
+
* };
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
37
64
|
* @param router - A WebhookDispatcher (any object with a `dispatch(event)` method, e.g. WebhookRouter)
|
|
38
65
|
* @param options - Adapter options including a verifier function
|
|
39
66
|
* @returns Fetch handler function
|
|
40
67
|
*/
|
|
41
|
-
declare function fetchAdapter<TEvent extends WebhookEvent = WebhookEvent>(router: WebhookDispatcher<TEvent>, options:
|
|
68
|
+
declare function fetchAdapter<TEvent extends WebhookEvent = WebhookEvent, TContext = unknown>(router: WebhookDispatcher<NoInfer<TEvent>, TContext>, options: FetchAdapterOptionsFor<TEvent, NoInfer<TContext>>): (request: Request) => Promise<Response>;
|
|
42
69
|
|
|
43
|
-
export { type FetchAdapterOptions, fetchAdapter };
|
|
70
|
+
export { type FetchAdapterOptions, type FetchAdapterOptionsFor, fetchAdapter };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import { WebhookEvent, WebhookHandlerOptions, WebhookDispatcher } from '@kotodayori/core';
|
|
1
|
+
import { WebhookEvent, WebhookHandlerOptions, ContextFactory, ContextFactoryOption, WebhookDispatcher } from '@kotodayori/core';
|
|
2
2
|
export { EventHandler, Middleware, Verifier, VerifyResult, WebhookDispatcher, WebhookEvent, WebhookRouter } from '@kotodayori/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Options for the fetch adapter
|
|
6
6
|
*/
|
|
7
|
-
type FetchAdapterOptions<T extends WebhookEvent = WebhookEvent> = WebhookHandlerOptions<T
|
|
7
|
+
type FetchAdapterOptions<T extends WebhookEvent = WebhookEvent, TContext = Request> = WebhookHandlerOptions<T> & {
|
|
8
|
+
/**
|
|
9
|
+
* Builds the per-request context passed to handlers as their second argument.
|
|
10
|
+
* Defaults to passing the `Request` itself. Required when the router's context type
|
|
11
|
+
* is not satisfied by `Request`. On Cloudflare Workers, create the handler inside
|
|
12
|
+
* `fetch(request, env)` and read bindings from `env`. Errors are handled like handler
|
|
13
|
+
* errors.
|
|
14
|
+
*/
|
|
15
|
+
createContext?: ContextFactory<[request: Request], TContext>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Fetch adapter options for a router with context `TContext`: `createContext` is optional
|
|
19
|
+
* when `Request` already satisfies `TContext`, and required otherwise.
|
|
20
|
+
*/
|
|
21
|
+
type FetchAdapterOptionsFor<T extends WebhookEvent, TContext> = WebhookHandlerOptions<T> & ContextFactoryOption<Request, TContext, [request: Request]>;
|
|
8
22
|
/**
|
|
9
23
|
* Creates a Web-standard `(request: Request) => Promise<Response>` webhook handler
|
|
10
24
|
*
|
|
@@ -34,10 +48,23 @@ type FetchAdapterOptions<T extends WebhookEvent = WebhookEvent> = WebhookHandler
|
|
|
34
48
|
* export const action = ({ request }) => handle(request);
|
|
35
49
|
* ```
|
|
36
50
|
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // Cloudflare Workers: handlers reach bindings through the per-request context
|
|
54
|
+
* const router = new StripeWebhookRouter<{ db: D1Database }>();
|
|
55
|
+
* export default {
|
|
56
|
+
* fetch: (request: Request, env: Env) =>
|
|
57
|
+
* fetchAdapter(router, {
|
|
58
|
+
* verifier: createStripeVerifier(stripe, env.STRIPE_WEBHOOK_SECRET),
|
|
59
|
+
* createContext: () => ({ db: env.DB }),
|
|
60
|
+
* })(request),
|
|
61
|
+
* };
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
37
64
|
* @param router - A WebhookDispatcher (any object with a `dispatch(event)` method, e.g. WebhookRouter)
|
|
38
65
|
* @param options - Adapter options including a verifier function
|
|
39
66
|
* @returns Fetch handler function
|
|
40
67
|
*/
|
|
41
|
-
declare function fetchAdapter<TEvent extends WebhookEvent = WebhookEvent>(router: WebhookDispatcher<TEvent>, options:
|
|
68
|
+
declare function fetchAdapter<TEvent extends WebhookEvent = WebhookEvent, TContext = unknown>(router: WebhookDispatcher<NoInfer<TEvent>, TContext>, options: FetchAdapterOptionsFor<TEvent, NoInfer<TContext>>): (request: Request) => Promise<Response>;
|
|
42
69
|
|
|
43
|
-
export { type FetchAdapterOptions, fetchAdapter };
|
|
70
|
+
export { type FetchAdapterOptions, type FetchAdapterOptionsFor, fetchAdapter };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
|
+
contextProvider,
|
|
3
4
|
handleWebhook
|
|
4
5
|
} from "@kotodayori/core";
|
|
5
6
|
import { WebhookRouter } from "@kotodayori/core";
|
|
@@ -10,6 +11,7 @@ function jsonResponse(body, status, headers = {}) {
|
|
|
10
11
|
});
|
|
11
12
|
}
|
|
12
13
|
function fetchAdapter(router, options) {
|
|
14
|
+
const { createContext } = options;
|
|
13
15
|
return async (request) => {
|
|
14
16
|
if (request.method !== "POST") {
|
|
15
17
|
return jsonResponse({ error: "Method not allowed" }, 405, { allow: "POST" });
|
|
@@ -19,7 +21,13 @@ function fetchAdapter(router, options) {
|
|
|
19
21
|
request.headers.forEach((value, key) => {
|
|
20
22
|
headers[key] = value;
|
|
21
23
|
});
|
|
22
|
-
const { status, body } = await handleWebhook(
|
|
24
|
+
const { status, body } = await handleWebhook(
|
|
25
|
+
router,
|
|
26
|
+
options,
|
|
27
|
+
rawBody,
|
|
28
|
+
headers,
|
|
29
|
+
contextProvider(createContext, [request], request)
|
|
30
|
+
);
|
|
23
31
|
return jsonResponse(body, status);
|
|
24
32
|
};
|
|
25
33
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n handleWebhook,\n type WebhookDispatcher,\n type WebhookEvent,\n type WebhookHandlerOptions,\n} from '@kotodayori/core';\n\n/**\n * Options for the fetch adapter\n */\nexport type FetchAdapterOptions<T extends WebhookEvent = WebhookEvent> = WebhookHandlerOptions<T>;\n\nfunction jsonResponse(body: unknown, status: number, headers: Record<string, string> = {}): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json', ...headers },\n });\n}\n\n/**\n * Creates a Web-standard `(request: Request) => Promise<Response>` webhook handler\n *\n * Works anywhere the runtime hands you a standard `Request`: Cloudflare Workers,\n * Bun, Deno, Next.js App Router route handlers, React Router / Remix actions, etc.\n * The raw body is read with `request.text()` and passed to the verifier untouched.\n * Non-POST requests get `405 Method Not Allowed`, since the handler may be mounted\n * without a router in front of it.\n *\n * @example\n * ```typescript\n * import { fetchAdapter } from '@kotodayori/fetch';\n * import { createStripeVerifier, StripeWebhookRouter } from '@kotodayori/stripe';\n *\n * const router = new StripeWebhookRouter();\n * const handle = fetchAdapter(router, {\n * verifier: createStripeVerifier(stripe, env.STRIPE_WEBHOOK_SECRET),\n * });\n *\n * // Cloudflare Workers / Bun / Deno\n * export default { fetch: handle };\n *\n * // Next.js App Router (app/webhook/route.ts)\n * export const POST = handle;\n *\n * // React Router v7 / Remix\n * export const action = ({ request }) => handle(request);\n * ```\n *\n * @param router - A WebhookDispatcher (any object with a `dispatch(event)` method, e.g. WebhookRouter)\n * @param options - Adapter options including a verifier function\n * @returns Fetch handler function\n */\nexport function fetchAdapter<\n TEvent extends WebhookEvent = WebhookEvent,\n>(\n router: WebhookDispatcher<TEvent>,\n options:
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n contextProvider,\n handleWebhook,\n type ContextFactory,\n type ContextFactoryOption,\n type WebhookDispatcher,\n type WebhookEvent,\n type WebhookHandlerOptions,\n} from '@kotodayori/core';\n\n/**\n * Options for the fetch adapter\n */\nexport type FetchAdapterOptions<T extends WebhookEvent = WebhookEvent, TContext = Request> =\n WebhookHandlerOptions<T> & {\n /**\n * Builds the per-request context passed to handlers as their second argument.\n * Defaults to passing the `Request` itself. Required when the router's context type\n * is not satisfied by `Request`. On Cloudflare Workers, create the handler inside\n * `fetch(request, env)` and read bindings from `env`. Errors are handled like handler\n * errors.\n */\n createContext?: ContextFactory<[request: Request], TContext>;\n };\n\n/**\n * Fetch adapter options for a router with context `TContext`: `createContext` is optional\n * when `Request` already satisfies `TContext`, and required otherwise.\n */\nexport type FetchAdapterOptionsFor<T extends WebhookEvent, TContext> = WebhookHandlerOptions<T> &\n ContextFactoryOption<Request, TContext, [request: Request]>;\n\nfunction jsonResponse(body: unknown, status: number, headers: Record<string, string> = {}): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json', ...headers },\n });\n}\n\n/**\n * Creates a Web-standard `(request: Request) => Promise<Response>` webhook handler\n *\n * Works anywhere the runtime hands you a standard `Request`: Cloudflare Workers,\n * Bun, Deno, Next.js App Router route handlers, React Router / Remix actions, etc.\n * The raw body is read with `request.text()` and passed to the verifier untouched.\n * Non-POST requests get `405 Method Not Allowed`, since the handler may be mounted\n * without a router in front of it.\n *\n * @example\n * ```typescript\n * import { fetchAdapter } from '@kotodayori/fetch';\n * import { createStripeVerifier, StripeWebhookRouter } from '@kotodayori/stripe';\n *\n * const router = new StripeWebhookRouter();\n * const handle = fetchAdapter(router, {\n * verifier: createStripeVerifier(stripe, env.STRIPE_WEBHOOK_SECRET),\n * });\n *\n * // Cloudflare Workers / Bun / Deno\n * export default { fetch: handle };\n *\n * // Next.js App Router (app/webhook/route.ts)\n * export const POST = handle;\n *\n * // React Router v7 / Remix\n * export const action = ({ request }) => handle(request);\n * ```\n *\n * @example\n * ```typescript\n * // Cloudflare Workers: handlers reach bindings through the per-request context\n * const router = new StripeWebhookRouter<{ db: D1Database }>();\n * export default {\n * fetch: (request: Request, env: Env) =>\n * fetchAdapter(router, {\n * verifier: createStripeVerifier(stripe, env.STRIPE_WEBHOOK_SECRET),\n * createContext: () => ({ db: env.DB }),\n * })(request),\n * };\n * ```\n *\n * @param router - A WebhookDispatcher (any object with a `dispatch(event)` method, e.g. WebhookRouter)\n * @param options - Adapter options including a verifier function\n * @returns Fetch handler function\n */\nexport function fetchAdapter<\n TEvent extends WebhookEvent = WebhookEvent,\n TContext = unknown,\n>(\n router: WebhookDispatcher<NoInfer<TEvent>, TContext>,\n options: FetchAdapterOptionsFor<TEvent, NoInfer<TContext>>\n): (request: Request) => Promise<Response> {\n const { createContext } = options as FetchAdapterOptions<TEvent, TContext>;\n\n return async (request: Request): Promise<Response> => {\n if (request.method !== 'POST') {\n return jsonResponse({ error: 'Method not allowed' }, 405, { allow: 'POST' });\n }\n\n // Get raw body text for signature verification\n const rawBody = await request.text();\n\n const headers: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n const { status, body } = await handleWebhook(\n router,\n options,\n rawBody,\n headers,\n contextProvider(createContext, [request], request)\n );\n return jsonResponse(body, status);\n };\n}\n\n// Re-export core types\nexport { WebhookRouter, type WebhookDispatcher, type WebhookEvent, type EventHandler, type Middleware, type Verifier, type VerifyResult } from '@kotodayori/core';\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAMK;AA+GP,SAAS,qBAAsI;AAvF/I,SAAS,aAAa,MAAe,QAAgB,UAAkC,CAAC,GAAa;AACnG,SAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC;AAAA,IACA,SAAS,EAAE,gBAAgB,oBAAoB,GAAG,QAAQ;AAAA,EAC5D,CAAC;AACH;AAgDO,SAAS,aAId,QACA,SACyC;AACzC,QAAM,EAAE,cAAc,IAAI;AAE1B,SAAO,OAAO,YAAwC;AACpD,QAAI,QAAQ,WAAW,QAAQ;AAC7B,aAAO,aAAa,EAAE,OAAO,qBAAqB,GAAG,KAAK,EAAE,OAAO,OAAO,CAAC;AAAA,IAC7E;AAGA,UAAM,UAAU,MAAM,QAAQ,KAAK;AAEnC,UAAM,UAAkC,CAAC;AACzC,YAAQ,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACtC,cAAQ,GAAG,IAAI;AAAA,IACjB,CAAC;AAED,UAAM,EAAE,QAAQ,KAAK,IAAI,MAAM;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,gBAAgB,eAAe,CAAC,OAAO,GAAG,OAAO;AAAA,IACnD;AACA,WAAO,aAAa,MAAM,MAAM;AAAA,EAClC;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kotodayori/fetch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Web-standard fetch (Request/Response) adapter for Kotodayori Webhook Router",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@kotodayori/core": "^1.
|
|
25
|
+
"@kotodayori/core": "^1.5.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"stripe": "^23.0.0",
|
|
29
29
|
"tsup": "^8.5.1",
|
|
30
30
|
"typescript": "^6.0.0",
|
|
31
31
|
"vitest": "^4.1.8",
|
|
32
|
-
"@kotodayori/stripe": "^1.
|
|
32
|
+
"@kotodayori/stripe": "^1.6.0"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|
|
35
35
|
"webhook",
|
|
@@ -60,6 +60,6 @@
|
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsup",
|
|
62
62
|
"test": "vitest run",
|
|
63
|
-
"typecheck": "tsc --noEmit"
|
|
63
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json"
|
|
64
64
|
}
|
|
65
65
|
}
|