@firtoz/hono-fetcher 2.6.0 → 2.7.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
@@ -4,7 +4,11 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/%40firtoz%2Fhono-fetcher.svg)](https://www.npmjs.com/package/@firtoz/hono-fetcher)
5
5
  [![license](https://img.shields.io/npm/l/%40firtoz%2Fhono-fetcher.svg)](https://github.com/firtoz/fullstack-toolkit/blob/main/LICENSE)
6
6
 
7
- Type-safe Hono API client with full TypeScript inference for routes, params, and payloads.
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
8
+ [![Hono](https://img.shields.io/badge/Hono-E36002?logo=hono&logoColor=white)](https://hono.dev)
9
+ [![Cloudflare Workers](https://img.shields.io/badge/Cloudflare-Workers-F38020?logo=cloudflare&logoColor=white)](https://developers.cloudflare.com/workers/)
10
+
11
+ **Typed Hono fetcher for Workers and Durable Objects** — infer `AppType` routes, params, and JSON bodies end to end.
8
12
 
9
13
  > **⚠️ Early WIP Notice:** This package is in very early development and is **not production-ready**. It is TypeScript-only and may have breaking changes. While I (the maintainer) have limited time, I'm open to PRs for features, bug fixes, or additional support (like JS builds). Please feel free to try it out and contribute! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.
10
14
 
@@ -371,23 +375,23 @@ const wsResp = await api.websocket({
371
375
  });
372
376
  ```
373
377
 
374
- ### Integration with ZodWebSocketClient
378
+ ### Integration with StandardSchemaWebSocketClient
375
379
 
376
- For even better type safety, combine with `@firtoz/websocket-do`'s `ZodWebSocketClient`:
380
+ For even better type safety, combine with `@firtoz/websocket-do`'s `StandardSchemaWebSocketClient`:
377
381
 
378
382
  ```typescript
379
- import { ZodWebSocketClient } from '@firtoz/websocket-do';
383
+ import { StandardSchemaWebSocketClient } from '@firtoz/websocket-do';
380
384
  import { honoDoFetcherWithName } from '@firtoz/hono-fetcher';
381
385
 
382
386
  // 1. Connect to DO WebSocket
383
387
  const api = honoDoFetcherWithName(env.CHAT_ROOM, 'room-1');
384
388
  const wsResp = await api.websocket({
385
389
  url: '/websocket',
386
- config: { autoAccept: false }, // Let ZodWebSocketClient handle acceptance
390
+ config: { autoAccept: false }, // Let StandardSchemaWebSocketClient handle acceptance
387
391
  });
388
392
 
389
393
  // 2. Wrap with type-safe client
390
- const client = new ZodWebSocketClient({
394
+ const client = new StandardSchemaWebSocketClient({
391
395
  webSocket: wsResp.webSocket,
392
396
  clientSchema: ClientMessageSchema,
393
397
  serverSchema: ServerMessageSchema,
@@ -401,10 +405,10 @@ const client = new ZodWebSocketClient({
401
405
  wsResp.webSocket?.accept();
402
406
 
403
407
  // 4. Send type-safe messages
404
- client.send({ type: 'chat', text: 'Hello!' }); // Validated with Zod!
408
+ await client.send({ type: 'chat', text: 'Hello!' }); // Validated with Standard Schema
405
409
  ```
406
410
 
407
- See the [ZodWebSocketClient documentation](#) for more details on type-safe WebSocket communication.
411
+ See the `@firtoz/websocket-do` README for more details on type-safe WebSocket communication.
408
412
 
409
413
  ## Durable Objects API
410
414
 
@@ -513,7 +517,7 @@ await api.websocket({ url: '/ws', config });
513
517
  ```
514
518
 
515
519
  **Options:**
516
- - `autoAccept?: boolean` - Whether to automatically call `accept()` on the WebSocket. Defaults to `true` for convenience. Set to `false` if you need manual control over when the WebSocket is accepted (e.g., when using with `ZodWebSocketClient`).
520
+ - `autoAccept?: boolean` - Whether to automatically call `accept()` on the WebSocket. Defaults to `true` for convenience. Set to `false` if you need manual control over when the WebSocket is accepted (e.g., when using with `StandardSchemaWebSocketClient`).
517
521
 
518
522
  ### `ParsePathParams<T>`
519
523
 
@@ -0,0 +1,43 @@
1
+ import { honoFetcher } from './chunk-ULIZJ5OD.js';
2
+
3
+ // src/honoDoFetcher.ts
4
+ var DUMMY_URL = "http://dummy-url";
5
+ function withStubDispose(stub, api) {
6
+ return Object.assign(api, {
7
+ [Symbol.dispose]() {
8
+ const disposeFn = Reflect.get(stub, Symbol.dispose);
9
+ if (typeof disposeFn !== "function") {
10
+ return;
11
+ }
12
+ try {
13
+ disposeFn.call(stub);
14
+ } catch (e) {
15
+ console.error(
16
+ "[@firtoz/hono-fetcher] Durable Object stub dispose failed",
17
+ e
18
+ );
19
+ }
20
+ }
21
+ });
22
+ }
23
+ var honoDoFetcher = (durableObject) => {
24
+ const api = honoFetcher((url, init) => {
25
+ return durableObject.fetch(`${DUMMY_URL}${url}`, init);
26
+ });
27
+ return withStubDispose(
28
+ durableObject,
29
+ api
30
+ );
31
+ };
32
+ var honoDoFetcherWithName = (namespace, name) => {
33
+ return honoDoFetcher(namespace.getByName(name));
34
+ };
35
+ var honoDoFetcherWithId = (namespace, id) => {
36
+ return honoDoFetcher(
37
+ namespace.get(namespace.idFromString(id))
38
+ );
39
+ };
40
+
41
+ export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName };
42
+ //# sourceMappingURL=chunk-EXAM5VFZ.js.map
43
+ //# sourceMappingURL=chunk-EXAM5VFZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/honoDoFetcher.ts"],"names":[],"mappings":";;;AAQA,IAAM,SAAA,GAAY,kBAAA;AAiDlB,SAAS,eAAA,CAIR,MAEA,GAAA,EAE+C;AAC/C,EAAA,OAAO,MAAA,CAAO,OAAO,GAAA,EAAK;AAAA,IACzB,CAAC,MAAA,CAAO,OAAO,CAAA,GAAI;AAElB,MAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,IAAA,EAAM,OAAO,OAAO,CAAA;AAClD,MAAA,IAAI,OAAO,cAAc,UAAA,EAAY;AACpC,QAAA;AAAA,MACD;AACA,MAAA,IAAI;AACH,QAAA,SAAA,CAAU,KAAK,IAAI,CAAA;AAAA,MACpB,SAAS,CAAA,EAAG;AACX,QAAA,OAAA,CAAQ,KAAA;AAAA,UACP,2DAAA;AAAA,UACA;AAAA,SACD;AAAA,MACD;AAAA,IACD;AAAA,GACA,CAAA;AACF;AAkBO,IAAM,aAAA,GAAgB,CAC5B,aAAA,KAGyC;AAIzC,EAAA,MAAM,GAAA,GAAM,WAAA,CAAkC,CAAC,GAAA,EAAK,IAAA,KAAS;AAC5D,IAAA,OAAO,cAAc,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,EAAG,GAAG,IAAI,IAAI,CAAA;AAAA,EACtD,CAAC,CAAA;AACD,EAAA,OAAO,eAAA;AAAA,IACN,aAAA;AAAA,IACA;AAAA,GACD;AAGD;AAEO,IAAM,qBAAA,GAAwB,CAGpC,SAAA,EACA,IAAA,KACuD;AACvD,EAAA,OAAO,aAAA,CAAc,SAAA,CAAU,SAAA,CAAU,IAAI,CAAC,CAAA;AAI/C;AAEO,IAAM,mBAAA,GAAsB,CAGlC,SAAA,EACA,EAAA,KACuD;AACvD,EAAA,OAAO,aAAA;AAAA,IACN,SAAA,CAAU,GAAA,CAAI,SAAA,CAAU,YAAA,CAAa,EAAE,CAAC;AAAA,GACzC;AACD","file":"chunk-EXAM5VFZ.js","sourcesContent":["import type { Hono, Schema } from \"hono\";\nimport type { ExtractSchema } from \"hono/types\";\nimport {\n\thonoFetcher,\n\ttype BaseDisposableTypedHonoFetcher,\n\ttype TypedHonoFetcher,\n} from \"./honoFetcher\";\n\nconst DUMMY_URL = \"http://dummy-url\";\n\nexport type DOWithHonoApp<S extends Schema = Schema> =\n\tRpc.DurableObjectBranded & {\n\t\t// biome-ignore lint/suspicious/noExplicitAny: We need to be able to pass in any schema\n\t\tapp: Hono<any, S>;\n\t};\n\nexport type DOSchemaMap<T extends DOWithHonoApp> = T extends DOWithHonoApp\n\t? ExtractSchema<T[\"app\"]>\n\t: never;\n\nexport type DOSchemaKeys<T extends DOWithHonoApp> = string &\n\tkeyof DOSchemaMap<T>;\n\nexport type DOStubSchema<T extends DurableObjectStub> =\n\tT extends DurableObjectStub<infer S>\n\t\t? S extends DOWithHonoApp\n\t\t\t? ExtractSchema<S[\"app\"]>\n\t\t\t: never\n\t\t: never;\n\n/**\n * Fetcher for a **real** `DurableObjectStub`: HTTP results are {@link RpcDisposableJsonResponse}\n * and `websocket` returns `Response & Disposable`, matching Workers RPC when the runtime attaches\n * disposers. Use with {@link honoDoFetcher} / {@link honoDoFetcherWithName} / {@link honoDoFetcherWithId}\n * when `T` is a full stub—not with a minimal `Pick<stub, \"fetch\">` mock (that path uses plain\n * {@link TypedHonoFetcher} responses instead).\n *\n * @see https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/\n */\nexport type TypedDoFetcher<T extends DurableObjectStub> =\n\tBaseDisposableTypedHonoFetcher<\n\t\t// biome-ignore lint/suspicious/noExplicitAny: Generic parameter needs flexibility\n\t\tHono<any, DOStubSchema<T>>\n\t>;\n\n/**\n * Argument to {@link honoDoFetcher}: a **full** {@link DurableObjectStub} (production) or a minimal\n * **`{ fetch }`** mock. Only the full stub is typed as {@link TypedDoFetcher} with disposable RPC\n * responses; **`Pick<stub, \"fetch\">`** is typed as {@link TypedHonoFetcher} for `Hono` with ordinary\n * `JsonResponse` / `Response` (no `Disposable` on results—matches mocks without `Symbol.dispose`).\n *\n * @see https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/\n */\nexport type HonoDoFetcherStubInput =\n\t| DurableObjectStub<DOWithHonoApp>\n\t| Pick<DurableObjectStub<DOWithHonoApp>, \"fetch\">;\n\nfunction withStubDispose<\n\tTStub extends Pick<DurableObjectStub<DOWithHonoApp>, \"fetch\">,\n\tTS extends Schema,\n>(\n\tstub: TStub,\n\t// biome-ignore lint/suspicious/noExplicitAny: Matches honoFetcher generic pattern for schema-driven apps\n\tapi: TypedHonoFetcher<Hono<any, TS>>,\n\t// biome-ignore lint/suspicious/noExplicitAny: Matches honoFetcher generic pattern for schema-driven apps\n): TypedHonoFetcher<Hono<any, TS>> & Disposable {\n\treturn Object.assign(api, {\n\t\t[Symbol.dispose]() {\n\t\t\t// Stubs may omit Symbol.dispose (e.g. Vite mocks); DurableObjectStub types may not list it.\n\t\t\tconst disposeFn = Reflect.get(stub, Symbol.dispose);\n\t\t\tif (typeof disposeFn !== \"function\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tdisposeFn.call(stub);\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"[@firtoz/hono-fetcher] Durable Object stub dispose failed\",\n\t\t\t\t\te,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t});\n}\n\n/**\n * Typed fetcher for a Durable Object stub.\n *\n * - **Full `DurableObjectStub`:** return type is {@link TypedDoFetcher} **`& Disposable`** — each\n * HTTP/WebSocket result is typed as disposable (`RpcDisposableJsonResponse` / `Response & Disposable`)\n * so **`using res = await …`** type-checks when `\"ESNext.Disposable\"` is in `lib`, matching Workers RPC\n * when the runtime attaches `[Symbol.dispose]` (see `@see` below).\n * - **`Pick<stub, \"fetch\">` only (e.g. tests):** return type is **`TypedHonoFetcher<Hono> & Disposable`**\n * — same **`JsonResponse` / `Response`** shapes as {@link honoFetcher}; results are **not** typed as\n * `Disposable` so typings are not faked for mocks that lack RPC disposers.\n *\n * Disposing only the fetcher (`using api = …`) releases the **stub**; RPC **`Response`** disposal\n * (when applicable) is separate — prefer **`using res`**, **`res[Symbol.dispose]()`**, or **`DisposableStack`**.\n *\n * @see https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/\n */\nexport const honoDoFetcher = <const T extends HonoDoFetcherStubInput>(\n\tdurableObject: T,\n): T extends DurableObjectStub<DOWithHonoApp>\n\t? TypedDoFetcher<T> & Disposable\n\t: TypedHonoFetcher<Hono> & Disposable => {\n\ttype OutSchema =\n\t\tT extends DurableObjectStub<DOWithHonoApp> ? DOStubSchema<T> : Schema;\n\t// biome-ignore lint/suspicious/noExplicitAny: Generic parameter needs flexibility\n\tconst api = honoFetcher<Hono<any, OutSchema>>((url, init) => {\n\t\treturn durableObject.fetch(`${DUMMY_URL}${url}`, init);\n\t});\n\treturn withStubDispose(\n\t\tdurableObject,\n\t\tapi,\n\t) as T extends DurableObjectStub<DOWithHonoApp>\n\t\t? TypedDoFetcher<T> & Disposable\n\t\t: TypedHonoFetcher<Hono> & Disposable;\n};\n\nexport const honoDoFetcherWithName = <\n\tconst T extends Rpc.DurableObjectBranded & DOWithHonoApp,\n>(\n\tnamespace: DurableObjectNamespace<T>,\n\tname: string,\n): TypedDoFetcher<DurableObjectStub<T>> & Disposable => {\n\treturn honoDoFetcher(namespace.getByName(name)) as TypedDoFetcher<\n\t\tDurableObjectStub<T>\n\t> &\n\t\tDisposable;\n};\n\nexport const honoDoFetcherWithId = <\n\tconst T extends Rpc.DurableObjectBranded & DOWithHonoApp,\n>(\n\tnamespace: DurableObjectNamespace<T>,\n\tid: string,\n): TypedDoFetcher<DurableObjectStub<T>> & Disposable => {\n\treturn honoDoFetcher(\n\t\tnamespace.get(namespace.idFromString(id)),\n\t) as TypedDoFetcher<DurableObjectStub<T>> & Disposable;\n};\n"]}
@@ -0,0 +1,112 @@
1
+ // src/honoFetcher.ts
2
+ function appendQueryString(url, query) {
3
+ if (!query) {
4
+ return url;
5
+ }
6
+ const searchParams = new URLSearchParams();
7
+ for (const [key, value] of Object.entries(query)) {
8
+ if (value === void 0 || value === null) {
9
+ continue;
10
+ }
11
+ searchParams.append(key, String(value));
12
+ }
13
+ const serialized = searchParams.toString();
14
+ if (!serialized) {
15
+ return url;
16
+ }
17
+ const separator = url.includes("?") ? "&" : "?";
18
+ return `${url}${separator}${serialized}`;
19
+ }
20
+ function restOfRequestInit(init) {
21
+ const { headers: _h, body: _b, method: _m, ...rest } = init;
22
+ return rest;
23
+ }
24
+ var createMethodFetcher = (fetcher, method) => {
25
+ return (async (request) => {
26
+ let finalUrl = request.url;
27
+ const { init = {}, params, query } = request;
28
+ if (params && typeof params === "object") {
29
+ finalUrl = Object.entries(params).reduce((acc, [key, value]) => {
30
+ return acc.replace(`:${key}`, value);
31
+ }, finalUrl);
32
+ }
33
+ finalUrl = appendQueryString(finalUrl, query);
34
+ const requestAsOptionalFormBody = request;
35
+ let body;
36
+ if (requestAsOptionalFormBody.form) {
37
+ const formData = new FormData();
38
+ for (const [key, value] of Object.entries(
39
+ requestAsOptionalFormBody.form
40
+ )) {
41
+ formData.append(key, value);
42
+ }
43
+ body = formData;
44
+ } else if (requestAsOptionalFormBody.body) {
45
+ body = JSON.stringify(requestAsOptionalFormBody.body);
46
+ }
47
+ const newHeaders = new Headers(
48
+ init.headers
49
+ );
50
+ if (body && !requestAsOptionalFormBody.form) {
51
+ newHeaders.set("Content-Type", "application/json");
52
+ }
53
+ try {
54
+ return await fetcher(finalUrl, {
55
+ ...restOfRequestInit(init),
56
+ method: method.toUpperCase(),
57
+ headers: newHeaders,
58
+ ...body ? { body } : {}
59
+ });
60
+ } catch (error) {
61
+ console.error(`Error ${method}ing`, error);
62
+ throw new Error(`Failed to ${method} ${finalUrl}: ${error}`);
63
+ }
64
+ });
65
+ };
66
+ var createWebSocketFetcher = (fetcher) => {
67
+ return (async (request) => {
68
+ let finalUrl = request.url;
69
+ const { init = {}, params, query, config } = request;
70
+ const autoAccept = config?.autoAccept ?? true;
71
+ if (params && typeof params === "object") {
72
+ finalUrl = Object.entries(params).reduce((acc, [key, value]) => {
73
+ return acc.replace(`:${key}`, value);
74
+ }, finalUrl);
75
+ }
76
+ finalUrl = appendQueryString(finalUrl, query);
77
+ const newHeaders = new Headers(
78
+ init.headers
79
+ );
80
+ newHeaders.set("Upgrade", "websocket");
81
+ try {
82
+ const response = await fetcher(finalUrl, {
83
+ ...restOfRequestInit(init),
84
+ method: "GET",
85
+ headers: newHeaders
86
+ });
87
+ if (autoAccept && response.webSocket) {
88
+ response.webSocket.accept();
89
+ }
90
+ return response;
91
+ } catch (error) {
92
+ console.error("Error upgrading to WebSocket", error);
93
+ throw new Error(`Failed to upgrade WebSocket at ${finalUrl}: ${error}`);
94
+ }
95
+ });
96
+ };
97
+ var honoFetcher = (fetcher) => {
98
+ const methods = ["get", "post", "put", "delete", "patch"];
99
+ const result = methods.reduce(
100
+ (acc, method) => {
101
+ acc[method] = createMethodFetcher(fetcher, method);
102
+ return acc;
103
+ },
104
+ {}
105
+ );
106
+ result.websocket = createWebSocketFetcher(fetcher);
107
+ return result;
108
+ };
109
+
110
+ export { honoFetcher };
111
+ //# sourceMappingURL=chunk-ULIZJ5OD.js.map
112
+ //# sourceMappingURL=chunk-ULIZJ5OD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/honoFetcher.ts"],"names":[],"mappings":"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","file":"chunk-ULIZJ5OD.js","sourcesContent":["import type { Hono } from \"hono\";\nimport type { ExtractSchema } from \"hono/types\";\n\nexport type ParsePathParams<T extends string> =\n\tT extends `${infer _Start}/:${infer Param}/${infer Rest}`\n\t\t? { [K in Param | keyof ParsePathParams<`/${Rest}`>]: string }\n\t\t: T extends `${infer _Start}/:${infer Param}`\n\t\t\t? { [K in Param]: string }\n\t\t\t: never;\n\nexport type HttpMethod = \"get\" | \"post\" | \"put\" | \"delete\" | \"patch\";\n\nexport type HonoSchemaKeys<T extends Hono> = string & keyof ExtractSchema<T>;\n\ntype FilterKeysByMethod<\n\tTApp extends ExtractSchema<unknown>,\n\tTMethod extends HttpMethod,\n> = {\n\t[K in keyof TApp as TApp[K] extends { [key in `$${TMethod}`]: unknown }\n\t\t? K\n\t\t: never]: TApp[K];\n};\n\ntype HonoSchema<TApp extends Hono> = {\n\t[M in HttpMethod]: FilterKeysByMethod<ExtractSchema<TApp>, M>;\n};\n\nexport type JsonResponse<T> = Omit<Response, \"json\"> & {\n\tjson: () => Promise<T>;\n};\n\n/**\n * {@link JsonResponse} intersected with `Disposable` for Workers RPC: `Response`\n * values from `DurableObjectStub#fetch()` may implement `[Symbol.dispose]` even\n * though `Fetcher.fetch` is still typed as `Promise<Response>`. Use with\n * {@link BaseDisposableTypedHonoFetcher} (and `TypedDoFetcher` from `./honoDoFetcher`) so\n * `using resp = await api.get(...)` type-checks when `\"ESNext.Disposable\"` is in `lib`.\n *\n * @see https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/\n */\nexport type RpcDisposableJsonResponse<T> = JsonResponse<T> & Disposable;\n\ntype HasPathParams<T extends string> = T extends `${string}:${string}`\n\t? true\n\t: false;\n\n/**\n * Values allowed in the optional `query` object on fetcher requests.\n * `null` and `undefined` entries are omitted from the serialized query string.\n */\nexport type HonoFetcherQueryParamValue = string | number | boolean;\n\nexport type HonoFetcherQueryParams = Record<\n\tstring,\n\tHonoFetcherQueryParamValue | null | undefined\n>;\n\nfunction appendQueryString(\n\turl: string,\n\tquery?: HonoFetcherQueryParams,\n): string {\n\tif (!query) {\n\t\treturn url;\n\t}\n\tconst searchParams = new URLSearchParams();\n\tfor (const [key, value] of Object.entries(query)) {\n\t\tif (value === undefined || value === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tsearchParams.append(key, String(value));\n\t}\n\tconst serialized = searchParams.toString();\n\tif (!serialized) {\n\t\treturn url;\n\t}\n\tconst separator = url.includes(\"?\") ? \"&\" : \"?\";\n\treturn `${url}${separator}${serialized}`;\n}\n\n/**\n * `RequestInit` fields that honoFetcher sets must not be overwritten by spreading `...init` last.\n */\nfunction restOfRequestInit(\n\tinit: RequestInit,\n): Omit<RequestInit, \"headers\" | \"body\" | \"method\"> {\n\tconst { headers: _h, body: _b, method: _m, ...rest } = init;\n\treturn rest;\n}\n\ntype FetcherParams<SchemaPath extends string> =\n\tHasPathParams<SchemaPath> extends true\n\t\t? {\n\t\t\t\tparams: ParsePathParams<SchemaPath>;\n\t\t\t\tquery?: HonoFetcherQueryParams;\n\t\t\t\tinit?: RequestInit;\n\t\t\t}\n\t\t: {\n\t\t\t\tparams?: never;\n\t\t\t\tquery?: HonoFetcherQueryParams;\n\t\t\t\tinit?: RequestInit;\n\t\t\t};\n\n// biome-ignore lint/complexity/noBannedTypes: We need an empty object to remove the body and form keys from the request object\ntype EmptyObject = {};\n\ntype TypedMethodFetcher<T extends Hono, M extends HttpMethod> = <\n\tSchemaPath extends string & keyof HonoSchema<T>[M],\n>(\n\trequest: {\n\t\turl: SchemaPath;\n\t} & FetcherParams<SchemaPath> &\n\t\t(M extends \"get\" | \"delete\" ? EmptyObject : BodyParams<T, M, SchemaPath>),\n) => Promise<SchemaOutput<T, M, SchemaPath>>;\n\ntype SchemaOutput<\n\tT extends Hono,\n\tM extends HttpMethod,\n\tSchemaPath extends string & keyof HonoSchema<T>[M],\n\tDollarM extends `$${M}` & keyof HonoSchema<T>[M][SchemaPath] = `$${M}` &\n\t\tkeyof HonoSchema<T>[M][SchemaPath],\n> = \"output\" extends keyof HonoSchema<T>[M][SchemaPath][DollarM]\n\t? JsonResponse<HonoSchema<T>[M][SchemaPath][DollarM][\"output\"]>\n\t: never;\n\ntype DoSchemaOutput<\n\tT extends Hono,\n\tM extends HttpMethod,\n\tSchemaPath extends string & keyof HonoSchema<T>[M],\n\tDollarM extends `$${M}` & keyof HonoSchema<T>[M][SchemaPath] = `$${M}` &\n\t\tkeyof HonoSchema<T>[M][SchemaPath],\n> = \"output\" extends keyof HonoSchema<T>[M][SchemaPath][DollarM]\n\t? RpcDisposableJsonResponse<HonoSchema<T>[M][SchemaPath][DollarM][\"output\"]>\n\t: never;\n\ntype BodyParams<\n\tTApp extends Hono,\n\tTMethod extends HttpMethod,\n\tSchemaPath extends string & keyof HonoSchema<TApp>[TMethod],\n\tDollarMethod extends `$${TMethod}` &\n\t\tkeyof HonoSchema<TApp>[TMethod][SchemaPath] = `$${TMethod}` &\n\t\tkeyof HonoSchema<TApp>[TMethod][SchemaPath],\n> = \"input\" extends keyof HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod]\n\t? \"json\" extends keyof HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod][\"input\"]\n\t\t? \"form\" extends keyof HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod][\"input\"]\n\t\t\t?\n\t\t\t\t\t| {\n\t\t\t\t\t\t\tbody: HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod][\"input\"][\"json\"];\n\t\t\t\t\t }\n\t\t\t\t\t| {\n\t\t\t\t\t\t\tform: HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod][\"input\"][\"form\"];\n\t\t\t\t\t }\n\t\t\t: {\n\t\t\t\t\tbody: HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod][\"input\"][\"json\"];\n\t\t\t\t}\n\t\t: \"form\" extends keyof HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod][\"input\"]\n\t\t\t? {\n\t\t\t\t\tform: HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod][\"input\"][\"form\"];\n\t\t\t\t}\n\t\t\t: { body?: unknown } | { form?: unknown }\n\t: EmptyObject;\n\ntype AvailableMethods<T extends Hono> = {\n\t[M in HttpMethod]: keyof HonoSchema<T>[M] extends never ? never : M;\n}[HttpMethod];\n\nexport interface WebSocketConfig {\n\t/**\n\t * Whether to automatically call accept() on the WebSocket before returning.\n\t * Defaults to true for convenience.\n\t *\n\t * In Cloudflare Workers, you must call accept() before using a WebSocket.\n\t * Setting this to false allows you to call accept() manually if needed.\n\t *\n\t * @default true\n\t */\n\tautoAccept?: boolean;\n}\n\nexport type TypedWebSocketFetcher<T extends Hono> = <\n\tSchemaPath extends string & keyof HonoSchema<T>[\"get\"],\n>(\n\trequest: {\n\t\turl: SchemaPath;\n\t\tconfig?: WebSocketConfig;\n\t} & FetcherParams<SchemaPath>,\n) => Promise<Response>;\n\nexport type BaseTypedHonoFetcher<T extends Hono> = {\n\t[M in AvailableMethods<T>]: TypedMethodFetcher<T, M>;\n} & (keyof HonoSchema<T>[\"get\"] extends never\n\t? // biome-ignore lint/complexity/noBannedTypes: We really do want an empty object if the get method is not available\n\t\t{}\n\t: { websocket: TypedWebSocketFetcher<T> });\n\ntype TypedDisposableMethodFetcher<T extends Hono, M extends HttpMethod> = <\n\tSchemaPath extends string & keyof HonoSchema<T>[M],\n>(\n\trequest: {\n\t\turl: SchemaPath;\n\t} & FetcherParams<SchemaPath> &\n\t\t(M extends \"get\" | \"delete\" ? EmptyObject : BodyParams<T, M, SchemaPath>),\n) => Promise<DoSchemaOutput<T, M, SchemaPath>>;\n\nexport type TypedDisposableWebSocketFetcher<T extends Hono> = <\n\tSchemaPath extends string & keyof HonoSchema<T>[\"get\"],\n>(\n\trequest: {\n\t\turl: SchemaPath;\n\t\tconfig?: WebSocketConfig;\n\t} & FetcherParams<SchemaPath>,\n) => Promise<Response & Disposable>;\n\n/**\n * Same shape as {@link BaseTypedHonoFetcher} but HTTP methods return\n * {@link RpcDisposableJsonResponse} and `websocket` returns `Response & Disposable`\n * so `using` on RPC results type-checks for Durable Object clients.\n *\n * @see https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/\n */\nexport type BaseDisposableTypedHonoFetcher<T extends Hono> = {\n\t[M in AvailableMethods<T>]: TypedDisposableMethodFetcher<T, M>;\n} & (keyof HonoSchema<T>[\"get\"] extends never\n\t? // biome-ignore lint/complexity/noBannedTypes: We really do want an empty object if the get method is not available\n\t\t{}\n\t: { websocket: TypedDisposableWebSocketFetcher<T> });\n\nconst createMethodFetcher = <T extends Hono, M extends HttpMethod>(\n\tfetcher: (\n\t\trequest: string,\n\t\tinit?: RequestInit,\n\t) => ReturnType<T[\"request\"]> | Promise<ReturnType<T[\"request\"]>>,\n\tmethod: M,\n): TypedMethodFetcher<T, M> => {\n\treturn (async (request) => {\n\t\tlet finalUrl: string = request.url;\n\n\t\tconst { init = {}, params, query } = request;\n\n\t\tif (params && typeof params === \"object\") {\n\t\t\tfinalUrl = Object.entries(params).reduce((acc, [key, value]) => {\n\t\t\t\treturn acc.replace(`:${key}`, value as string);\n\t\t\t}, finalUrl);\n\t\t}\n\n\t\tfinalUrl = appendQueryString(finalUrl, query);\n\n\t\tconst requestAsOptionalFormBody = request as {\n\t\t\tform?: unknown;\n\t\t\tbody?: unknown;\n\t\t};\n\n\t\tlet body: BodyInit | undefined;\n\t\tif (requestAsOptionalFormBody.form) {\n\t\t\tconst formData = new FormData();\n\t\t\tfor (const [key, value] of Object.entries(\n\t\t\t\trequestAsOptionalFormBody.form,\n\t\t\t)) {\n\t\t\t\tformData.append(key, value as string);\n\t\t\t}\n\t\t\tbody = formData;\n\t\t} else if (requestAsOptionalFormBody.body) {\n\t\t\tbody = JSON.stringify(requestAsOptionalFormBody.body) as BodyInit;\n\t\t}\n\n\t\tconst newHeaders = new Headers(\n\t\t\tinit.headers as unknown as ConstructorParameters<typeof Headers>[0],\n\t\t);\n\n\t\tif (body && !requestAsOptionalFormBody.form) {\n\t\t\tnewHeaders.set(\"Content-Type\", \"application/json\");\n\t\t}\n\n\t\ttry {\n\t\t\treturn await fetcher(finalUrl, {\n\t\t\t\t...restOfRequestInit(init),\n\t\t\t\tmethod: method.toUpperCase(),\n\t\t\t\theaders: newHeaders,\n\t\t\t\t...(body ? { body } : {}),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error ${method}ing`, error);\n\t\t\tthrow new Error(`Failed to ${method} ${finalUrl}: ${error}`);\n\t\t}\n\t}) as TypedMethodFetcher<T, M>;\n};\n\nconst createWebSocketFetcher = <T extends Hono>(\n\tfetcher: (\n\t\trequest: string,\n\t\tinit?: RequestInit,\n\t) => ReturnType<T[\"request\"]> | Promise<ReturnType<T[\"request\"]>>,\n): TypedWebSocketFetcher<T> => {\n\treturn (async (request) => {\n\t\tlet finalUrl: string = request.url;\n\n\t\tconst { init = {}, params, query, config } = request;\n\t\tconst autoAccept = config?.autoAccept ?? true; // Default to true\n\n\t\tif (params && typeof params === \"object\") {\n\t\t\tfinalUrl = Object.entries(params).reduce((acc, [key, value]) => {\n\t\t\t\treturn acc.replace(`:${key}`, value as string);\n\t\t\t}, finalUrl);\n\t\t}\n\n\t\tfinalUrl = appendQueryString(finalUrl, query);\n\n\t\tconst newHeaders = new Headers(\n\t\t\tinit.headers as unknown as ConstructorParameters<typeof Headers>[0],\n\t\t);\n\t\tnewHeaders.set(\"Upgrade\", \"websocket\");\n\n\t\ttry {\n\t\t\tconst response = await fetcher(finalUrl, {\n\t\t\t\t...restOfRequestInit(init),\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: newHeaders,\n\t\t\t});\n\n\t\t\t// Auto-accept the WebSocket if configured (default: true)\n\t\t\tif (autoAccept && response.webSocket) {\n\t\t\t\tresponse.webSocket.accept();\n\t\t\t}\n\n\t\t\treturn response;\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Error upgrading to WebSocket\", error);\n\t\t\tthrow new Error(`Failed to upgrade WebSocket at ${finalUrl}: ${error}`);\n\t\t}\n\t}) as TypedWebSocketFetcher<T>;\n};\n\nexport type TypedHonoFetcher<T extends Hono> = BaseTypedHonoFetcher<T>;\n\nexport const honoFetcher = <T extends Hono>(\n\tfetcher: (\n\t\trequest: string,\n\t\tinit?: RequestInit,\n\t) => ReturnType<T[\"request\"]> | Promise<ReturnType<T[\"request\"]>>,\n): TypedHonoFetcher<T> => {\n\tconst methods = [\"get\", \"post\", \"put\", \"delete\", \"patch\"] as const;\n\n\tconst result = methods.reduce(\n\t\t(acc, method) => {\n\t\t\t(\n\t\t\t\tacc as TypedHonoFetcher<T> & {\n\t\t\t\t\t[M in typeof method]: TypedMethodFetcher<T, M>;\n\t\t\t\t}\n\t\t\t)[method] = createMethodFetcher(fetcher, method) as TypedMethodFetcher<\n\t\t\t\tT,\n\t\t\t\ttypeof method\n\t\t\t>;\n\t\t\treturn acc;\n\t\t},\n\t\t{} as TypedHonoFetcher<T>,\n\t);\n\n\t// Add websocket method\n\t(\n\t\tresult as TypedHonoFetcher<T> & { websocket?: TypedWebSocketFetcher<T> }\n\t).websocket = createWebSocketFetcher(fetcher);\n\n\treturn result;\n};\n"]}
@@ -0,0 +1,12 @@
1
+ import { honoFetcher } from './chunk-ULIZJ5OD.js';
2
+
3
+ // src/honoDirectFetcher.ts
4
+ var honoDirectFetcher = (baseUrl) => {
5
+ return honoFetcher((request, init) => {
6
+ return fetch(`${baseUrl}${request}`, init);
7
+ });
8
+ };
9
+
10
+ export { honoDirectFetcher };
11
+ //# sourceMappingURL=chunk-YCTPXFUH.js.map
12
+ //# sourceMappingURL=chunk-YCTPXFUH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/honoDirectFetcher.ts"],"names":[],"mappings":";;;AAGO,IAAM,iBAAA,GAAoB,CAChC,OAAA,KACyB;AACzB,EAAA,OAAO,WAAA,CAAe,CAAC,OAAA,EAAS,IAAA,KAAS;AACxC,IAAA,OAAO,MAAM,CAAA,EAAG,OAAO,CAAA,EAAG,OAAO,IAAI,IAAI,CAAA;AAAA,EAC1C,CAAC,CAAA;AACF","file":"chunk-YCTPXFUH.js","sourcesContent":["import type { Hono } from \"hono\";\nimport { honoFetcher, type TypedHonoFetcher } from \"./honoFetcher\";\n\nexport const honoDirectFetcher = <T extends Hono>(\n\tbaseUrl: string,\n): TypedHonoFetcher<T> => {\n\treturn honoFetcher<T>((request, init) => {\n\t\treturn fetch(`${baseUrl}${request}`, init) as ReturnType<T[\"request\"]>;\n\t});\n};\n"]}
@@ -0,0 +1,7 @@
1
+ import { Hono } from 'hono';
2
+ import { TypedHonoFetcher } from './honoFetcher.js';
3
+ import 'hono/types';
4
+
5
+ declare const honoDirectFetcher: <T extends Hono>(baseUrl: string) => TypedHonoFetcher<T>;
6
+
7
+ export { honoDirectFetcher };
@@ -0,0 +1,4 @@
1
+ export { honoDirectFetcher } from './chunk-YCTPXFUH.js';
2
+ import './chunk-ULIZJ5OD.js';
3
+ //# sourceMappingURL=honoDirectFetcher.js.map
4
+ //# sourceMappingURL=honoDirectFetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"honoDirectFetcher.js"}
@@ -0,0 +1,50 @@
1
+ import { Schema, Hono } from 'hono';
2
+ import { ExtractSchema } from 'hono/types';
3
+ import { BaseDisposableTypedHonoFetcher, TypedHonoFetcher } from './honoFetcher.js';
4
+
5
+ type DOWithHonoApp<S extends Schema = Schema> = Rpc.DurableObjectBranded & {
6
+ app: Hono<any, S>;
7
+ };
8
+ type DOSchemaMap<T extends DOWithHonoApp> = T extends DOWithHonoApp ? ExtractSchema<T["app"]> : never;
9
+ type DOSchemaKeys<T extends DOWithHonoApp> = string & keyof DOSchemaMap<T>;
10
+ type DOStubSchema<T extends DurableObjectStub> = T extends DurableObjectStub<infer S> ? S extends DOWithHonoApp ? ExtractSchema<S["app"]> : never : never;
11
+ /**
12
+ * Fetcher for a **real** `DurableObjectStub`: HTTP results are {@link RpcDisposableJsonResponse}
13
+ * and `websocket` returns `Response & Disposable`, matching Workers RPC when the runtime attaches
14
+ * disposers. Use with {@link honoDoFetcher} / {@link honoDoFetcherWithName} / {@link honoDoFetcherWithId}
15
+ * when `T` is a full stub—not with a minimal `Pick<stub, "fetch">` mock (that path uses plain
16
+ * {@link TypedHonoFetcher} responses instead).
17
+ *
18
+ * @see https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/
19
+ */
20
+ type TypedDoFetcher<T extends DurableObjectStub> = BaseDisposableTypedHonoFetcher<Hono<any, DOStubSchema<T>>>;
21
+ /**
22
+ * Argument to {@link honoDoFetcher}: a **full** {@link DurableObjectStub} (production) or a minimal
23
+ * **`{ fetch }`** mock. Only the full stub is typed as {@link TypedDoFetcher} with disposable RPC
24
+ * responses; **`Pick<stub, "fetch">`** is typed as {@link TypedHonoFetcher} for `Hono` with ordinary
25
+ * `JsonResponse` / `Response` (no `Disposable` on results—matches mocks without `Symbol.dispose`).
26
+ *
27
+ * @see https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/
28
+ */
29
+ type HonoDoFetcherStubInput = DurableObjectStub<DOWithHonoApp> | Pick<DurableObjectStub<DOWithHonoApp>, "fetch">;
30
+ /**
31
+ * Typed fetcher for a Durable Object stub.
32
+ *
33
+ * - **Full `DurableObjectStub`:** return type is {@link TypedDoFetcher} **`& Disposable`** — each
34
+ * HTTP/WebSocket result is typed as disposable (`RpcDisposableJsonResponse` / `Response & Disposable`)
35
+ * so **`using res = await …`** type-checks when `"ESNext.Disposable"` is in `lib`, matching Workers RPC
36
+ * when the runtime attaches `[Symbol.dispose]` (see `@see` below).
37
+ * - **`Pick<stub, "fetch">` only (e.g. tests):** return type is **`TypedHonoFetcher<Hono> & Disposable`**
38
+ * — same **`JsonResponse` / `Response`** shapes as {@link honoFetcher}; results are **not** typed as
39
+ * `Disposable` so typings are not faked for mocks that lack RPC disposers.
40
+ *
41
+ * Disposing only the fetcher (`using api = …`) releases the **stub**; RPC **`Response`** disposal
42
+ * (when applicable) is separate — prefer **`using res`**, **`res[Symbol.dispose]()`**, or **`DisposableStack`**.
43
+ *
44
+ * @see https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/
45
+ */
46
+ declare const honoDoFetcher: <const T extends HonoDoFetcherStubInput>(durableObject: T) => T extends DurableObjectStub<DOWithHonoApp> ? TypedDoFetcher<T> & Disposable : TypedHonoFetcher<Hono> & Disposable;
47
+ declare const honoDoFetcherWithName: <const T extends Rpc.DurableObjectBranded & DOWithHonoApp>(namespace: DurableObjectNamespace<T>, name: string) => TypedDoFetcher<DurableObjectStub<T>> & Disposable;
48
+ declare const honoDoFetcherWithId: <const T extends Rpc.DurableObjectBranded & DOWithHonoApp>(namespace: DurableObjectNamespace<T>, id: string) => TypedDoFetcher<DurableObjectStub<T>> & Disposable;
49
+
50
+ export { type DOSchemaKeys, type DOSchemaMap, type DOStubSchema, type DOWithHonoApp, type HonoDoFetcherStubInput, type TypedDoFetcher, honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName };
@@ -0,0 +1,4 @@
1
+ export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName } from './chunk-EXAM5VFZ.js';
2
+ import './chunk-ULIZJ5OD.js';
3
+ //# sourceMappingURL=honoDoFetcher.js.map
4
+ //# sourceMappingURL=honoDoFetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"honoDoFetcher.js"}
@@ -0,0 +1,113 @@
1
+ import { Hono } from 'hono';
2
+ import { ExtractSchema } from 'hono/types';
3
+
4
+ type ParsePathParams<T extends string> = T extends `${infer _Start}/:${infer Param}/${infer Rest}` ? {
5
+ [K in Param | keyof ParsePathParams<`/${Rest}`>]: string;
6
+ } : T extends `${infer _Start}/:${infer Param}` ? {
7
+ [K in Param]: string;
8
+ } : never;
9
+ type HttpMethod = "get" | "post" | "put" | "delete" | "patch";
10
+ type HonoSchemaKeys<T extends Hono> = string & keyof ExtractSchema<T>;
11
+ type FilterKeysByMethod<TApp extends ExtractSchema<unknown>, TMethod extends HttpMethod> = {
12
+ [K in keyof TApp as TApp[K] extends {
13
+ [key in `$${TMethod}`]: unknown;
14
+ } ? K : never]: TApp[K];
15
+ };
16
+ type HonoSchema<TApp extends Hono> = {
17
+ [M in HttpMethod]: FilterKeysByMethod<ExtractSchema<TApp>, M>;
18
+ };
19
+ type JsonResponse<T> = Omit<Response, "json"> & {
20
+ json: () => Promise<T>;
21
+ };
22
+ /**
23
+ * {@link JsonResponse} intersected with `Disposable` for Workers RPC: `Response`
24
+ * values from `DurableObjectStub#fetch()` may implement `[Symbol.dispose]` even
25
+ * though `Fetcher.fetch` is still typed as `Promise<Response>`. Use with
26
+ * {@link BaseDisposableTypedHonoFetcher} (and `TypedDoFetcher` from `./honoDoFetcher`) so
27
+ * `using resp = await api.get(...)` type-checks when `"ESNext.Disposable"` is in `lib`.
28
+ *
29
+ * @see https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/
30
+ */
31
+ type RpcDisposableJsonResponse<T> = JsonResponse<T> & Disposable;
32
+ type HasPathParams<T extends string> = T extends `${string}:${string}` ? true : false;
33
+ /**
34
+ * Values allowed in the optional `query` object on fetcher requests.
35
+ * `null` and `undefined` entries are omitted from the serialized query string.
36
+ */
37
+ type HonoFetcherQueryParamValue = string | number | boolean;
38
+ type HonoFetcherQueryParams = Record<string, HonoFetcherQueryParamValue | null | undefined>;
39
+ type FetcherParams<SchemaPath extends string> = HasPathParams<SchemaPath> extends true ? {
40
+ params: ParsePathParams<SchemaPath>;
41
+ query?: HonoFetcherQueryParams;
42
+ init?: RequestInit;
43
+ } : {
44
+ params?: never;
45
+ query?: HonoFetcherQueryParams;
46
+ init?: RequestInit;
47
+ };
48
+ type EmptyObject = {};
49
+ type TypedMethodFetcher<T extends Hono, M extends HttpMethod> = <SchemaPath extends string & keyof HonoSchema<T>[M]>(request: {
50
+ url: SchemaPath;
51
+ } & FetcherParams<SchemaPath> & (M extends "get" | "delete" ? EmptyObject : BodyParams<T, M, SchemaPath>)) => Promise<SchemaOutput<T, M, SchemaPath>>;
52
+ type SchemaOutput<T extends Hono, M extends HttpMethod, SchemaPath extends string & keyof HonoSchema<T>[M], DollarM extends `$${M}` & keyof HonoSchema<T>[M][SchemaPath] = `$${M}` & keyof HonoSchema<T>[M][SchemaPath]> = "output" extends keyof HonoSchema<T>[M][SchemaPath][DollarM] ? JsonResponse<HonoSchema<T>[M][SchemaPath][DollarM]["output"]> : never;
53
+ type DoSchemaOutput<T extends Hono, M extends HttpMethod, SchemaPath extends string & keyof HonoSchema<T>[M], DollarM extends `$${M}` & keyof HonoSchema<T>[M][SchemaPath] = `$${M}` & keyof HonoSchema<T>[M][SchemaPath]> = "output" extends keyof HonoSchema<T>[M][SchemaPath][DollarM] ? RpcDisposableJsonResponse<HonoSchema<T>[M][SchemaPath][DollarM]["output"]> : never;
54
+ type BodyParams<TApp extends Hono, TMethod extends HttpMethod, SchemaPath extends string & keyof HonoSchema<TApp>[TMethod], DollarMethod extends `$${TMethod}` & keyof HonoSchema<TApp>[TMethod][SchemaPath] = `$${TMethod}` & keyof HonoSchema<TApp>[TMethod][SchemaPath]> = "input" extends keyof HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod] ? "json" extends keyof HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod]["input"] ? "form" extends keyof HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod]["input"] ? {
55
+ body: HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod]["input"]["json"];
56
+ } | {
57
+ form: HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod]["input"]["form"];
58
+ } : {
59
+ body: HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod]["input"]["json"];
60
+ } : "form" extends keyof HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod]["input"] ? {
61
+ form: HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod]["input"]["form"];
62
+ } : {
63
+ body?: unknown;
64
+ } | {
65
+ form?: unknown;
66
+ } : EmptyObject;
67
+ type AvailableMethods<T extends Hono> = {
68
+ [M in HttpMethod]: keyof HonoSchema<T>[M] extends never ? never : M;
69
+ }[HttpMethod];
70
+ interface WebSocketConfig {
71
+ /**
72
+ * Whether to automatically call accept() on the WebSocket before returning.
73
+ * Defaults to true for convenience.
74
+ *
75
+ * In Cloudflare Workers, you must call accept() before using a WebSocket.
76
+ * Setting this to false allows you to call accept() manually if needed.
77
+ *
78
+ * @default true
79
+ */
80
+ autoAccept?: boolean;
81
+ }
82
+ type TypedWebSocketFetcher<T extends Hono> = <SchemaPath extends string & keyof HonoSchema<T>["get"]>(request: {
83
+ url: SchemaPath;
84
+ config?: WebSocketConfig;
85
+ } & FetcherParams<SchemaPath>) => Promise<Response>;
86
+ type BaseTypedHonoFetcher<T extends Hono> = {
87
+ [M in AvailableMethods<T>]: TypedMethodFetcher<T, M>;
88
+ } & (keyof HonoSchema<T>["get"] extends never ? {} : {
89
+ websocket: TypedWebSocketFetcher<T>;
90
+ });
91
+ type TypedDisposableMethodFetcher<T extends Hono, M extends HttpMethod> = <SchemaPath extends string & keyof HonoSchema<T>[M]>(request: {
92
+ url: SchemaPath;
93
+ } & FetcherParams<SchemaPath> & (M extends "get" | "delete" ? EmptyObject : BodyParams<T, M, SchemaPath>)) => Promise<DoSchemaOutput<T, M, SchemaPath>>;
94
+ type TypedDisposableWebSocketFetcher<T extends Hono> = <SchemaPath extends string & keyof HonoSchema<T>["get"]>(request: {
95
+ url: SchemaPath;
96
+ config?: WebSocketConfig;
97
+ } & FetcherParams<SchemaPath>) => Promise<Response & Disposable>;
98
+ /**
99
+ * Same shape as {@link BaseTypedHonoFetcher} but HTTP methods return
100
+ * {@link RpcDisposableJsonResponse} and `websocket` returns `Response & Disposable`
101
+ * so `using` on RPC results type-checks for Durable Object clients.
102
+ *
103
+ * @see https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/
104
+ */
105
+ type BaseDisposableTypedHonoFetcher<T extends Hono> = {
106
+ [M in AvailableMethods<T>]: TypedDisposableMethodFetcher<T, M>;
107
+ } & (keyof HonoSchema<T>["get"] extends never ? {} : {
108
+ websocket: TypedDisposableWebSocketFetcher<T>;
109
+ });
110
+ type TypedHonoFetcher<T extends Hono> = BaseTypedHonoFetcher<T>;
111
+ declare const honoFetcher: <T extends Hono>(fetcher: (request: string, init?: RequestInit) => ReturnType<T["request"]> | Promise<ReturnType<T["request"]>>) => TypedHonoFetcher<T>;
112
+
113
+ export { type BaseDisposableTypedHonoFetcher, type BaseTypedHonoFetcher, type HonoFetcherQueryParamValue, type HonoFetcherQueryParams, type HonoSchemaKeys, type HttpMethod, type JsonResponse, type ParsePathParams, type RpcDisposableJsonResponse, type TypedDisposableWebSocketFetcher, type TypedHonoFetcher, type TypedWebSocketFetcher, type WebSocketConfig, honoFetcher };
@@ -0,0 +1,3 @@
1
+ export { honoFetcher } from './chunk-ULIZJ5OD.js';
2
+ //# sourceMappingURL=honoFetcher.js.map
3
+ //# sourceMappingURL=honoFetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"honoFetcher.js"}
@@ -0,0 +1,5 @@
1
+ export { honoDirectFetcher } from './honoDirectFetcher.js';
2
+ export { DOSchemaKeys, DOSchemaMap, DOStubSchema, DOWithHonoApp, HonoDoFetcherStubInput, TypedDoFetcher, honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName } from './honoDoFetcher.js';
3
+ export { BaseDisposableTypedHonoFetcher, BaseTypedHonoFetcher, HonoFetcherQueryParamValue, HonoFetcherQueryParams, HonoSchemaKeys, HttpMethod, JsonResponse, ParsePathParams, RpcDisposableJsonResponse, TypedDisposableWebSocketFetcher, TypedHonoFetcher, TypedWebSocketFetcher, WebSocketConfig, honoFetcher } from './honoFetcher.js';
4
+ import 'hono';
5
+ import 'hono/types';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { honoDirectFetcher } from './chunk-YCTPXFUH.js';
2
+ export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName } from './chunk-EXAM5VFZ.js';
3
+ export { honoFetcher } from './chunk-ULIZJ5OD.js';
4
+ //# sourceMappingURL=index.js.map
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
package/package.json CHANGED
@@ -1,28 +1,32 @@
1
1
  {
2
2
  "name": "@firtoz/hono-fetcher",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "Type-safe Hono API client with full TypeScript inference for routes, params, and payloads",
5
- "main": "./src/index.ts",
6
- "module": "./src/index.ts",
7
- "types": "./src/index.ts",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
8
9
  "exports": {
9
10
  ".": {
10
- "types": "./src/index.ts",
11
- "import": "./src/index.ts",
12
- "require": "./src/index.ts"
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
13
  },
14
14
  "./honoDoFetcher": {
15
- "types": "./src/honoDoFetcher.ts",
16
- "import": "./src/honoDoFetcher.ts",
17
- "require": "./src/honoDoFetcher.ts"
15
+ "types": "./dist/honoDoFetcher.d.ts",
16
+ "import": "./dist/honoDoFetcher.js"
18
17
  }
19
18
  },
20
19
  "files": [
20
+ "dist/**/*.js",
21
+ "dist/**/*.js.map",
22
+ "dist/**/*.d.ts",
21
23
  "src/**/*.ts",
22
24
  "!src/**/*.test.ts",
23
25
  "README.md"
24
26
  ],
25
27
  "scripts": {
28
+ "build": "tsup",
29
+ "prepack": "bun run build",
26
30
  "typecheck": "tsgo --noEmit -p ./tsconfig.json",
27
31
  "lint": "biome check --write src",
28
32
  "lint:ci": "biome ci src",