@kotodayori/fetch 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +124 -2
- package/dist/index.cjs +54 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +43 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Hidetaka Okamoto
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,125 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @kotodayori/fetch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@kotodayori/fetch)
|
|
4
|
+
[](https://www.npmjs.com/package/@kotodayori/fetch)
|
|
5
|
+
|
|
6
|
+
Web-standard fetch adapter for Kotodayori webhook router.
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
`@kotodayori/fetch` turns a Kotodayori router into a `(request: Request) => Promise<Response>` handler. Any runtime or framework that hands you a standard `Request` can use it without a dedicated adapter:
|
|
11
|
+
|
|
12
|
+
- Cloudflare Workers (without Hono)
|
|
13
|
+
- Bun / Deno
|
|
14
|
+
- Next.js App Router route handlers
|
|
15
|
+
- React Router v7 / Remix actions
|
|
16
|
+
- Netlify Functions (`export default async (req: Request, context: Context) => ...` style)
|
|
17
|
+
|
|
18
|
+
Handlers that receive Node-style `(req, res)` objects (Express, Next.js Pages Router `pages/api`, Netlify's legacy `handler(event, context)` signature) are **not** supported — use `@kotodayori/express` or `@kotodayori/lambda` instead.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm add @kotodayori/fetch @kotodayori/core
|
|
24
|
+
# The Stripe example below also needs:
|
|
25
|
+
pnpm add @kotodayori/stripe stripe
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
Build the handler from the secrets your runtime provides. Cloudflare Workers passes secrets to `fetch` as the `env` binding, so the handler is created per request there; elsewhere `process.env` works.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import Stripe from 'stripe';
|
|
34
|
+
import { StripeWebhookRouter, createStripeVerifier } from '@kotodayori/stripe';
|
|
35
|
+
import { fetchAdapter } from '@kotodayori/fetch';
|
|
36
|
+
|
|
37
|
+
const router = new StripeWebhookRouter();
|
|
38
|
+
|
|
39
|
+
router.on('payment_intent.succeeded', async (event) => {
|
|
40
|
+
console.log('Payment succeeded:', event.data.object.id);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const createHandler = (apiKey: string, webhookSecret: string) =>
|
|
44
|
+
fetchAdapter(router, {
|
|
45
|
+
verifier: createStripeVerifier(new Stripe(apiKey), webhookSecret),
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Cloudflare Workers
|
|
50
|
+
|
|
51
|
+
Secrets are read from the `env` binding (set with `wrangler secret put STRIPE_API_KEY` / `STRIPE_WEBHOOK_SECRET`), not from `process.env`:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
export default {
|
|
55
|
+
fetch: (request: Request, env: { STRIPE_API_KEY: string; STRIPE_WEBHOOK_SECRET: string }) =>
|
|
56
|
+
createHandler(env.STRIPE_API_KEY, env.STRIPE_WEBHOOK_SECRET)(request),
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Bun / Deno
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const handle = createHandler(process.env.STRIPE_API_KEY!, process.env.STRIPE_WEBHOOK_SECRET!);
|
|
64
|
+
|
|
65
|
+
export default { fetch: handle };
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Next.js App Router (`app/webhook/route.ts`)
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
export const POST = createHandler(process.env.STRIPE_API_KEY!, process.env.STRIPE_WEBHOOK_SECRET!);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### React Router v7 / Remix
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
export const action = ({ request }: { request: Request }) =>
|
|
78
|
+
createHandler(process.env.STRIPE_API_KEY!, process.env.STRIPE_WEBHOOK_SECRET!)(request);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
function fetchAdapter<TEvent extends WebhookEvent>(
|
|
85
|
+
router: WebhookDispatcher<TEvent>,
|
|
86
|
+
options: {
|
|
87
|
+
verifier: Verifier<TEvent>;
|
|
88
|
+
onError?: (error: Error, event: TEvent) => Promise<void> | void;
|
|
89
|
+
logger?: Logger; // defaults to consoleLogger
|
|
90
|
+
}
|
|
91
|
+
): (request: Request) => Promise<Response>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- **verifier** — Required. Verifies the request and returns the event (e.g. `createStripeVerifier`).
|
|
95
|
+
- **onError** — Optional. Called when a handler throws; errors thrown by `onError` itself are ignored and the response stays 500.
|
|
96
|
+
- **logger** — Optional. Receives adapter diagnostics, currently verification failures (`logger.error('Webhook verification failed:', err)`). Defaults to `consoleLogger`, which writes to `console`.
|
|
97
|
+
|
|
98
|
+
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
|
+
|
|
100
|
+
| Situation | Status | Body |
|
|
101
|
+
|-----------|--------|------|
|
|
102
|
+
| Non-POST method | 405 (`Allow: POST`) | `{ "error": "Method not allowed" }` |
|
|
103
|
+
| Empty or whitespace-only body | 400 | `{ "error": "Request body cannot be empty" }` |
|
|
104
|
+
| Verification failed | 400 | `{ "error": "Verification failed" }` |
|
|
105
|
+
| Handler threw | 500 | `{ "error": "Internal server error" }` |
|
|
106
|
+
| Success | 200 | `{ "received": true }` |
|
|
107
|
+
|
|
108
|
+
### Custom logger
|
|
109
|
+
|
|
110
|
+
Route verification failures to your own logger, or silence them:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { silentLogger } from '@kotodayori/core';
|
|
114
|
+
|
|
115
|
+
const handle = fetchAdapter(router, {
|
|
116
|
+
verifier,
|
|
117
|
+
logger: silentLogger, // or any object with `warn` and `error` methods
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
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.
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
WebhookRouter: () => import_core2.WebhookRouter,
|
|
24
|
+
fetchAdapter: () => fetchAdapter
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
var import_core = require("@kotodayori/core");
|
|
28
|
+
var import_core2 = require("@kotodayori/core");
|
|
29
|
+
function jsonResponse(body, status, headers = {}) {
|
|
30
|
+
return new Response(JSON.stringify(body), {
|
|
31
|
+
status,
|
|
32
|
+
headers: { "content-type": "application/json", ...headers }
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function fetchAdapter(router, options) {
|
|
36
|
+
return async (request) => {
|
|
37
|
+
if (request.method !== "POST") {
|
|
38
|
+
return jsonResponse({ error: "Method not allowed" }, 405, { allow: "POST" });
|
|
39
|
+
}
|
|
40
|
+
const rawBody = await request.text();
|
|
41
|
+
const headers = {};
|
|
42
|
+
request.headers.forEach((value, key) => {
|
|
43
|
+
headers[key] = value;
|
|
44
|
+
});
|
|
45
|
+
const { status, body } = await (0, import_core.handleWebhook)(router, options, rawBody, headers);
|
|
46
|
+
return jsonResponse(body, status);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
50
|
+
0 && (module.exports = {
|
|
51
|
+
WebhookRouter,
|
|
52
|
+
fetchAdapter
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { WebhookEvent, WebhookHandlerOptions, WebhookDispatcher } from '@kotodayori/core';
|
|
2
|
+
export { EventHandler, Middleware, Verifier, VerifyResult, WebhookDispatcher, WebhookEvent, WebhookRouter } from '@kotodayori/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for the fetch adapter
|
|
6
|
+
*/
|
|
7
|
+
type FetchAdapterOptions<T extends WebhookEvent = WebhookEvent> = WebhookHandlerOptions<T>;
|
|
8
|
+
/**
|
|
9
|
+
* Creates a Web-standard `(request: Request) => Promise<Response>` webhook handler
|
|
10
|
+
*
|
|
11
|
+
* Works anywhere the runtime hands you a standard `Request`: Cloudflare Workers,
|
|
12
|
+
* Bun, Deno, Next.js App Router route handlers, React Router / Remix actions, etc.
|
|
13
|
+
* The raw body is read with `request.text()` and passed to the verifier untouched.
|
|
14
|
+
* Non-POST requests get `405 Method Not Allowed`, since the handler may be mounted
|
|
15
|
+
* without a router in front of it.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { fetchAdapter } from '@kotodayori/fetch';
|
|
20
|
+
* import { createStripeVerifier, StripeWebhookRouter } from '@kotodayori/stripe';
|
|
21
|
+
*
|
|
22
|
+
* const router = new StripeWebhookRouter();
|
|
23
|
+
* const handle = fetchAdapter(router, {
|
|
24
|
+
* verifier: createStripeVerifier(stripe, env.STRIPE_WEBHOOK_SECRET),
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Cloudflare Workers / Bun / Deno
|
|
28
|
+
* export default { fetch: handle };
|
|
29
|
+
*
|
|
30
|
+
* // Next.js App Router (app/webhook/route.ts)
|
|
31
|
+
* export const POST = handle;
|
|
32
|
+
*
|
|
33
|
+
* // React Router v7 / Remix
|
|
34
|
+
* export const action = ({ request }) => handle(request);
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @param router - A WebhookDispatcher (any object with a `dispatch(event)` method, e.g. WebhookRouter)
|
|
38
|
+
* @param options - Adapter options including a verifier function
|
|
39
|
+
* @returns Fetch handler function
|
|
40
|
+
*/
|
|
41
|
+
declare function fetchAdapter<TEvent extends WebhookEvent = WebhookEvent>(router: WebhookDispatcher<TEvent>, options: FetchAdapterOptions<TEvent>): (request: Request) => Promise<Response>;
|
|
42
|
+
|
|
43
|
+
export { type FetchAdapterOptions, fetchAdapter };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { WebhookEvent, WebhookHandlerOptions, WebhookDispatcher } from '@kotodayori/core';
|
|
2
|
+
export { EventHandler, Middleware, Verifier, VerifyResult, WebhookDispatcher, WebhookEvent, WebhookRouter } from '@kotodayori/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for the fetch adapter
|
|
6
|
+
*/
|
|
7
|
+
type FetchAdapterOptions<T extends WebhookEvent = WebhookEvent> = WebhookHandlerOptions<T>;
|
|
8
|
+
/**
|
|
9
|
+
* Creates a Web-standard `(request: Request) => Promise<Response>` webhook handler
|
|
10
|
+
*
|
|
11
|
+
* Works anywhere the runtime hands you a standard `Request`: Cloudflare Workers,
|
|
12
|
+
* Bun, Deno, Next.js App Router route handlers, React Router / Remix actions, etc.
|
|
13
|
+
* The raw body is read with `request.text()` and passed to the verifier untouched.
|
|
14
|
+
* Non-POST requests get `405 Method Not Allowed`, since the handler may be mounted
|
|
15
|
+
* without a router in front of it.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { fetchAdapter } from '@kotodayori/fetch';
|
|
20
|
+
* import { createStripeVerifier, StripeWebhookRouter } from '@kotodayori/stripe';
|
|
21
|
+
*
|
|
22
|
+
* const router = new StripeWebhookRouter();
|
|
23
|
+
* const handle = fetchAdapter(router, {
|
|
24
|
+
* verifier: createStripeVerifier(stripe, env.STRIPE_WEBHOOK_SECRET),
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Cloudflare Workers / Bun / Deno
|
|
28
|
+
* export default { fetch: handle };
|
|
29
|
+
*
|
|
30
|
+
* // Next.js App Router (app/webhook/route.ts)
|
|
31
|
+
* export const POST = handle;
|
|
32
|
+
*
|
|
33
|
+
* // React Router v7 / Remix
|
|
34
|
+
* export const action = ({ request }) => handle(request);
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @param router - A WebhookDispatcher (any object with a `dispatch(event)` method, e.g. WebhookRouter)
|
|
38
|
+
* @param options - Adapter options including a verifier function
|
|
39
|
+
* @returns Fetch handler function
|
|
40
|
+
*/
|
|
41
|
+
declare function fetchAdapter<TEvent extends WebhookEvent = WebhookEvent>(router: WebhookDispatcher<TEvent>, options: FetchAdapterOptions<TEvent>): (request: Request) => Promise<Response>;
|
|
42
|
+
|
|
43
|
+
export { type FetchAdapterOptions, fetchAdapter };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
handleWebhook
|
|
4
|
+
} from "@kotodayori/core";
|
|
5
|
+
import { WebhookRouter } from "@kotodayori/core";
|
|
6
|
+
function jsonResponse(body, status, headers = {}) {
|
|
7
|
+
return new Response(JSON.stringify(body), {
|
|
8
|
+
status,
|
|
9
|
+
headers: { "content-type": "application/json", ...headers }
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function fetchAdapter(router, options) {
|
|
13
|
+
return async (request) => {
|
|
14
|
+
if (request.method !== "POST") {
|
|
15
|
+
return jsonResponse({ error: "Method not allowed" }, 405, { allow: "POST" });
|
|
16
|
+
}
|
|
17
|
+
const rawBody = await request.text();
|
|
18
|
+
const headers = {};
|
|
19
|
+
request.headers.forEach((value, key) => {
|
|
20
|
+
headers[key] = value;
|
|
21
|
+
});
|
|
22
|
+
const { status, body } = await handleWebhook(router, options, rawBody, headers);
|
|
23
|
+
return jsonResponse(body, status);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
WebhookRouter,
|
|
28
|
+
fetchAdapter
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kotodayori/fetch",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Web-standard fetch (Request/Response) adapter for Kotodayori Webhook Router",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@kotodayori/core": "^1.4.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"stripe": "^23.0.0",
|
|
29
|
+
"tsup": "^8.5.1",
|
|
30
|
+
"typescript": "^6.0.0",
|
|
31
|
+
"vitest": "^4.1.8",
|
|
32
|
+
"@kotodayori/stripe": "^1.5.0"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"webhook",
|
|
36
|
+
"router",
|
|
37
|
+
"fetch",
|
|
38
|
+
"cloudflare-workers",
|
|
39
|
+
"bun",
|
|
40
|
+
"deno",
|
|
41
|
+
"nextjs",
|
|
42
|
+
"react-router",
|
|
43
|
+
"typescript"
|
|
44
|
+
],
|
|
45
|
+
"author": {
|
|
46
|
+
"name": "Hidetaka Okamoto",
|
|
47
|
+
"url": "https://github.com/hideokamoto"
|
|
48
|
+
},
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/hideokamoto/kotodayori.git",
|
|
56
|
+
"directory": "packages/fetch"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/hideokamoto/kotodayori#readme",
|
|
59
|
+
"bugs": "https://github.com/hideokamoto/kotodayori/issues",
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsup",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"typecheck": "tsc --noEmit"
|
|
64
|
+
}
|
|
6
65
|
}
|