@kotodayori/fetch 0.1.1 → 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 CHANGED
@@ -87,6 +87,7 @@ function fetchAdapter<TEvent extends WebhookEvent>(
87
87
  verifier: Verifier<TEvent>;
88
88
  onError?: (error: Error, event: TEvent) => Promise<void> | void;
89
89
  logger?: Logger; // defaults to consoleLogger
90
+ createContext?: (request: Request) => TContext | Promise<TContext>; // defaults to the Request
90
91
  }
91
92
  ): (request: Request) => Promise<Response>
92
93
  ```
@@ -94,6 +95,7 @@ function fetchAdapter<TEvent extends WebhookEvent>(
94
95
  - **verifier** — Required. Verifies the request and returns the event (e.g. `createStripeVerifier`).
95
96
  - **onError** — Optional. Called when a handler throws; errors thrown by `onError` itself are ignored and the response stays 500.
96
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.
97
99
 
98
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.
99
101
 
@@ -105,6 +107,28 @@ The raw body is read with `request.text()` and passed to the verifier untouched,
105
107
  | Handler threw | 500 | `{ "error": "Internal server error" }` |
106
108
  | Success | 200 | `{ "received": true }` |
107
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
+
108
132
  ### Custom logger
109
133
 
110
134
  Route verification failures to your own logger, or silence them:
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)(router, options, rawBody, headers);
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
  }
@@ -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: FetchAdapterOptions<TEvent>\n): (request: Request) => Promise<Response> {\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(router, options, rawBody, headers);\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,kBAKO;AAwEP,IAAAA,eAA+I;AAjE/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;AAmCO,SAAS,aAGd,QACA,SACyC;AACzC,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,2BAAc,QAAQ,SAAS,SAAS,OAAO;AAC9E,WAAO,aAAa,MAAM,MAAM;AAAA,EAClC;AACF;","names":["import_core"]}
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: FetchAdapterOptions<TEvent>): (request: Request) => Promise<Response>;
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: FetchAdapterOptions<TEvent>): (request: Request) => Promise<Response>;
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(router, options, rawBody, headers);
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: FetchAdapterOptions<TEvent>\n): (request: Request) => Promise<Response> {\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(router, options, rawBody, headers);\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,OAIK;AAwEP,SAAS,qBAAsI;AAjE/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;AAmCO,SAAS,aAGd,QACA,SACyC;AACzC,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,cAAc,QAAQ,SAAS,SAAS,OAAO;AAC9E,WAAO,aAAa,MAAM,MAAM;AAAA,EAClC;AACF;","names":[]}
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.1.1",
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.4.0"
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.5.0"
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
  }