@firtoz/hono-fetcher 2.7.2 → 2.8.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
@@ -136,7 +136,7 @@ export class ChatRoomDO extends DurableObject {
136
136
  }
137
137
 
138
138
  // `using api` disposes the stub. Returning the DO `Response` directly is a common pass-through pattern;
139
- // if you consume the body in this worker instead, also dispose the RPC result (e.g. `using res`).
139
+ // do not `using res` when you return the value to the client (see disposal section below).
140
140
  // See https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/
141
141
  export default {
142
142
  async fetch(request: Request, env: Env): Promise<Response> {
@@ -468,7 +468,7 @@ Cloudflare Workers RPC gives **separate disposers** for the **Durable Object stu
468
468
 
469
469
  - **Stub (`using api = honoDoFetcherWithName(...)`):** Releases the **stub** when the block ends. This does **not** release RPC **`Response`** objects from individual requests.
470
470
  - **Response (HTTP / WebSocket upgrade):** On a **real stub** (`TypedDoFetcher`), TypeScript types those results as **`Disposable`** so **`using res`** / **`using wsRes`** is valid. At runtime, **`[Symbol.dispose]`** is present when Workers RPC attaches it (often in production); **minimal `fetch`-only mocks** are typed as **non-**`Disposable` **`Response`**s because disposers are usually absent. Prefer **`using res`** when types allow it; otherwise call **`res[Symbol.dispose]()`** or use a **`DisposableStack`** as described in Cloudflare’s **[Workers RPC lifecycle](https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/)** documentation. Reading the body does **not** implicitly dispose the RPC result in this library.
471
- - **Returning `Response` to the client:** **Do not** use **`using res`** on a response you **`return`** from your Worker—disposal can run on scope exit before the runtime finishes serving it. Return the value directly; see **[Workers RPC lifecycle](https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/)** for pass-through patterns.
471
+ - **Returning `Response` to the client:** **Do not** use **`using res`** on a response you **`return`** from your Worker—disposal can run on scope exit before the runtime finishes serving it. Return the value directly (`return api.get(...)`). In **local dev** (Miniflare, Alchemy), RPC responses may **not** implement **`Symbol.dispose`** even when TypeScript types them as **`Disposable`** — **`using res = await …`** can throw **`TypeError: Object not disposable`**. Prefer **`using api`** on the stub only and **`const res`** / direct **`return`** for individual responses. **`TypedDoFetcher`** HTTP results are **`RpcDisposableJsonResponse`** (not Hono’s **`HandlerResponse`** union); that typing mismatch for proxy routes is a known gap—track fixing return types rather than papering over with casts.
472
472
  - **Vite SSR, some Miniflare setups, or test mocks** may expose stubs or **`Response`** objects without **`Symbol.dispose`**; there is nothing to call in that case.
473
473
  - **Errors from `Symbol.dispose`:** If the runtime’s dispose implementation throws (for example during unwind after your code threw), the library catches the error and logs it with **`console.error`**. It does **not** rethrow, so your original error is not masked by a `SuppressedError`.
474
474
  - **TypeScript `using`:** Add **`"ESNext.Disposable"`** to the `compilerOptions.lib` array in your **`tsconfig.json`** (alongside your existing libs) so `using` and `Disposable` type-check. TypeScript 5.2+ is required for `using`. That applies to **`TypedDoFetcher`** (full **`DurableObjectStub`** path): **`RpcDisposableJsonResponse`** / **`Response & Disposable`** on **`websocket`**. **`Pick<stub, "fetch">`** clients get ordinary **`TypedHonoFetcher`** return types without **`Disposable`** on responses. Cloudflare’s runtime rules for RPC disposal are documented under **[Workers RPC lifecycle](https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/)**.
@@ -543,6 +543,97 @@ export class MyDO extends DurableObject implements DOWithHonoApp {
543
543
  }
544
544
  ```
545
545
 
546
+ ### `DoRpcWithApp<T>`
547
+
548
+ Nameable RPC surface for `Env` bindings and generated worker types (avoids Alchemy **`TS2883`** when the full DO class cannot be named in exports):
549
+
550
+ ```typescript
551
+ import type { DoRpcWithApp } from '@firtoz/hono-fetcher';
552
+
553
+ export class ChatroomDo extends DurableObject {
554
+ app = new Hono().get('/messages', (c) => c.json({ messages: [] }));
555
+ }
556
+
557
+ export type ChatroomDoRpc = DoRpcWithApp<ChatroomDo>;
558
+
559
+ type Env = {
560
+ CHATROOM: DurableObjectNamespace<ChatroomDoRpc>;
561
+ };
562
+ ```
563
+
564
+ ### Query parameters
565
+
566
+ All HTTP methods accept an optional **`query`** object. **`null`** and **`undefined`** values are omitted from the serialized string:
567
+
568
+ ```typescript
569
+ await api.get({
570
+ url: '/items',
571
+ query: { sort: 'name', limit: 10, active: undefined },
572
+ });
573
+
574
+ // With exactOptionalPropertyTypes, optional query may be omitted or set — not `{ query: undefined }`:
575
+ const limit: number | undefined = undefined;
576
+ await api.get({
577
+ url: '/items',
578
+ ...(limit === undefined ? {} : { query: { limit } }),
579
+ });
580
+ ```
581
+
582
+ ### `honoFetcherMounted(appOrFetcher, mountPath)`
583
+
584
+ Typed client for routes under a mount prefix (auth worker `/admin`, service binding sub-app, etc.).
585
+
586
+ **Local / in-process** — pass the Hono app; types and transport (`app.request`) are inferred:
587
+
588
+ ```typescript
589
+ const admin = honoFetcherMounted(workerApp, '/admin');
590
+ await admin.get({ url: '/users' }); // GET /admin/users, admin response type
591
+ ```
592
+
593
+ **Production** — pass a fetcher when transport is not `app.request` (service bindings, DO stubs, browser `fetch`):
594
+
595
+ ```typescript
596
+ const admin = honoFetcherMounted<typeof workerApp, '/admin'>(
597
+ (url, init) => env.AUTH.fetch(new Request(`https://auth${url}`, init)),
598
+ '/admin',
599
+ );
600
+ ```
601
+
602
+ When you pass a fetcher, you must supply the app type (`typeof workerApp`) because the callback carries no route schema. The second type arg (`'/admin'`) is only needed if you also wrote an explicit first type arg — otherwise `'/admin'` is inferred from the argument.
603
+
604
+ **Sub-app type** — routes on the type omit the mount (`/users`); pass the worker mount string:
605
+
606
+ ```typescript
607
+ const admin = honoFetcherMounted<typeof adminRoutes>(
608
+ (url, init) => env.AUTH.fetch(new Request(`https://auth${url}`, init)),
609
+ '/admin',
610
+ );
611
+
612
+ await admin.get({ url: '/users' }); // GET …/admin/users
613
+ ```
614
+
615
+ **Worker app type** — schema keys are full paths (`/admin/users`, `/admin/users/:id`). `mountPath` must be a {@link ValidMountPrefix} (derived from routes like `/admin/...`). Client `url` values are **after** the mount:
616
+
617
+ ```typescript
618
+ import { honoFetcherMounted } from '@firtoz/hono-fetcher';
619
+
620
+ // Routes: /users, /admin/users, /admin/users/:id, /a/b, /a/c, /a/:d
621
+
622
+ const admin = honoFetcherMounted(workerApp, '/admin');
623
+ await admin.get({ url: '/users' }); // GET …/admin/users — not url: '/admin/users'
624
+
625
+ const aRoutes = honoFetcherMounted(workerApp, '/a');
626
+ await aRoutes.get({ url: '/b' }); // GET …/a/b
627
+ await aRoutes.get({ url: '/:d', params: { d: 'room-1' } }); // GET …/a/room-1
628
+
629
+ const deep = honoFetcherMounted(workerApp, '/nested/deep');
630
+ await deep.get({ url: '/foo' }); // GET …/nested/deep/foo
631
+
632
+ const level1 = honoFetcherMounted(workerApp, '/level1/:param', { param: 'room-1' });
633
+ await level1.get({ url: '/foo' }); // GET …/level1/room-1/foo
634
+ await level1.get({ url: '/bar' }); // GET …/level1/room-1/bar
635
+ ```
636
+
546
637
  ## Advanced Usage
547
638
 
548
639
  ### Sharing Types Between Frontend and Backend
@@ -1,4 +1,4 @@
1
- import { honoFetcher } from './chunk-5KDMHM65.js';
1
+ import { honoFetcher } from './chunk-ECAZNFX4.js';
2
2
 
3
3
  // src/honoDoFetcher.ts
4
4
  var DUMMY_URL = "http://dummy-url";
@@ -39,5 +39,5 @@ var honoDoFetcherWithId = (namespace, id) => {
39
39
  };
40
40
 
41
41
  export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName };
42
- //# sourceMappingURL=chunk-WN4MD3WJ.js.map
43
- //# sourceMappingURL=chunk-WN4MD3WJ.js.map
42
+ //# sourceMappingURL=chunk-ADIG352H.js.map
43
+ //# sourceMappingURL=chunk-ADIG352H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/honoDoFetcher.ts"],"names":[],"mappings":";;;AAQA,IAAM,SAAA,GAAY,kBAAA;AA+DlB,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-ADIG352H.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\n/**\n * Nameable RPC surface for a Durable Object class that exposes a Hono `app`.\n * Use when exporting types for `Env` bindings (avoids Alchemy `TS2883` when the\n * full DO class type cannot be named in generated declarations).\n *\n * @example\n * ```ts\n * export type ChatroomDoRpc = DoRpcWithApp<ChatroomDo>;\n * // Env: { CHATROOM: DurableObjectNamespace<ChatroomDoRpc> }\n * ```\n */\nexport type DoRpcWithApp<T extends { app: Hono }> = Rpc.DurableObjectBranded &\n\tPick<T, \"app\">;\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"]}
@@ -109,5 +109,5 @@ var honoFetcher = (fetcher) => {
109
109
  };
110
110
 
111
111
  export { honoFetcher };
112
- //# sourceMappingURL=chunk-5KDMHM65.js.map
113
- //# sourceMappingURL=chunk-5KDMHM65.js.map
112
+ //# sourceMappingURL=chunk-ECAZNFX4.js.map
113
+ //# sourceMappingURL=chunk-ECAZNFX4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/honoFetcher.ts"],"names":[],"mappings":"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","file":"chunk-ECAZNFX4.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 | undefined;\n\t\t\t\tinit?: RequestInit | undefined;\n\t\t\t}\n\t\t: {\n\t\t\t\tparams?: never;\n\t\t\t\tquery?: HonoFetcherQueryParams | undefined;\n\t\t\t\tinit?: RequestInit | undefined;\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 | undefined;\n\t/**\n\t * Arguments for Cloudflare’s `WebSocket#accept` (e.g. `{ allowHalfOpen: true }` when\n\t * [proxying](https://developers.cloudflare.com/workers/runtime-apis/websockets/#close-behavior)\n\t * and coordinating close on both sides). Used only when `autoAccept` is true.\n\t */\n\tacceptOptions?: WebSocketAcceptOptions | undefined;\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;\n\t\tconst acceptOptions = config?.acceptOptions;\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\tif (autoAccept && response.webSocket) {\n\t\t\t\tresponse.webSocket.accept(acceptOptions);\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"]}
@@ -1,4 +1,4 @@
1
- import { honoFetcher } from './chunk-5KDMHM65.js';
1
+ import { honoFetcher } from './chunk-ECAZNFX4.js';
2
2
 
3
3
  // src/honoDirectFetcher.ts
4
4
  var honoDirectFetcher = (baseUrl) => {
@@ -8,5 +8,5 @@ var honoDirectFetcher = (baseUrl) => {
8
8
  };
9
9
 
10
10
  export { honoDirectFetcher };
11
- //# sourceMappingURL=chunk-MZBZTJN5.js.map
12
- //# sourceMappingURL=chunk-MZBZTJN5.js.map
11
+ //# sourceMappingURL=chunk-OMZLJ6SZ.js.map
12
+ //# sourceMappingURL=chunk-OMZLJ6SZ.js.map
@@ -1 +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-MZBZTJN5.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"]}
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-OMZLJ6SZ.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,41 @@
1
+ import { honoFetcher } from './chunk-ECAZNFX4.js';
2
+ import { Hono } from 'hono';
3
+
4
+ function normalizeMountPath(mountPath) {
5
+ const trimmed = mountPath.trim();
6
+ if (trimmed === "" || trimmed === "/") {
7
+ return "";
8
+ }
9
+ const withLeading = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
10
+ return withLeading.endsWith("/") ? withLeading.slice(0, -1) : withLeading;
11
+ }
12
+ function substitutePathParams(path, params) {
13
+ return Object.entries(params).reduce(
14
+ (acc, [key, value]) => acc.replace(`:${key}`, value),
15
+ path
16
+ );
17
+ }
18
+ function createMountedFetcher(parentFetcher, mountPath, mountParams) {
19
+ const normalized = normalizeMountPath(mountPath);
20
+ const prefix = mountParams === void 0 ? normalized : substitutePathParams(normalized, mountParams);
21
+ return honoFetcher((request, init) => {
22
+ const path = request.startsWith("/") ? request : `/${request}`;
23
+ const url = prefix === "" ? path : `${prefix}${path}`;
24
+ return parentFetcher(url, init);
25
+ });
26
+ }
27
+ function isHonoApp(value) {
28
+ return value instanceof Hono;
29
+ }
30
+ function honoFetcherMounted(appOrFetcher, mountPath, mountParams) {
31
+ const parentFetcher = isHonoApp(appOrFetcher) ? (url, init) => appOrFetcher.request(url, init) : appOrFetcher;
32
+ return createMountedFetcher(
33
+ parentFetcher,
34
+ mountPath,
35
+ mountParams
36
+ );
37
+ }
38
+
39
+ export { honoFetcherMounted };
40
+ //# sourceMappingURL=chunk-VOOAFLGL.js.map
41
+ //# sourceMappingURL=chunk-VOOAFLGL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/honoFetcherMounted.ts"],"names":["HonoClass"],"mappings":";;;AAmGA,SAAS,mBAAmB,SAAA,EAA2B;AACtD,EAAA,MAAM,OAAA,GAAU,UAAU,IAAA,EAAK;AAC/B,EAAA,IAAI,OAAA,KAAY,EAAA,IAAM,OAAA,KAAY,GAAA,EAAK;AACtC,IAAA,OAAO,EAAA;AAAA,EACR;AACA,EAAA,MAAM,cAAc,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,GAAI,OAAA,GAAU,IAAI,OAAO,CAAA,CAAA;AACnE,EAAA,OAAO,WAAA,CAAY,SAAS,GAAG,CAAA,GAAI,YAAY,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,GAAI,WAAA;AAC/D;AAEA,SAAS,oBAAA,CACR,MACA,MAAA,EACS;AACT,EAAA,OAAO,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,MAAA;AAAA,IAC7B,CAAC,GAAA,EAAK,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM,GAAA,CAAI,OAAA,CAAQ,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA,EAAI,KAAK,CAAA;AAAA,IACnD;AAAA,GACD;AACD;AAOA,SAAS,oBAAA,CAIR,aAAA,EACA,SAAA,EACA,WAAA,EAGC;AACD,EAAA,MAAM,UAAA,GAAa,mBAAmB,SAAS,CAAA;AAC/C,EAAA,MAAM,SACL,WAAA,KAAgB,MAAA,GACb,UAAA,GACA,oBAAA,CAAqB,YAAY,WAAqC,CAAA;AAC1E,EAAA,OAAO,WAAA,CAAqC,CAAC,OAAA,EAAS,IAAA,KAAS;AAC9D,IAAA,MAAM,OAAO,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,GAAI,OAAA,GAAU,IAAI,OAAO,CAAA,CAAA;AAC5D,IAAA,MAAM,MAAM,MAAA,KAAW,EAAA,GAAK,OAAO,CAAA,EAAG,MAAM,GAAG,IAAI,CAAA,CAAA;AACnD,IAAA,OAAO,aAAA,CAAc,KAAK,IAAI,CAAA;AAAA,EAG/B,CAAC,CAAA;AAGF;AAEA,SAAS,UAAU,KAAA,EAA4C;AAC9D,EAAA,OAAO,KAAA,YAAiBA,IAAA;AACzB;AA2DO,SAAS,kBAAA,CAIf,YAAA,EACA,SAAA,EACA,WAAA,EAC8D;AAC9D,EAAA,MAAM,aAAA,GAAgB,SAAA,CAAU,YAAY,CAAA,GACzC,CAAC,GAAA,EAAa,IAAA,KAAuB,YAAA,CAAa,OAAA,CAAQ,GAAA,EAAK,IAAI,CAAA,GACnE,YAAA;AACH,EAAA,OAAO,oBAAA;AAAA,IACN,aAAA;AAAA,IACA,SAAA;AAAA,IACA;AAAA,GACD;AACD","file":"chunk-VOOAFLGL.js","sourcesContent":["import type { Hono } from \"hono\";\nimport { Hono as HonoClass } from \"hono\";\nimport type { Env, Schema } from \"hono/types\";\nimport type { ExtractSchema } from \"hono/types\";\nimport {\n\thonoFetcher,\n\ttype ParsePathParams,\n\ttype TypedHonoFetcher,\n} from \"./honoFetcher\";\n\ntype SchemaRouteKeys<T extends Hono> = string & keyof ExtractSchema<T>;\n\n/**\n * Client-side view of a Hono app: same routes/schema as the server app but without\n * server `Bindings` (service-binding and browser clients do not send Worker env).\n *\n * Use with a **sub-app** type when the mount path is only on the worker (e.g. mount\n * `/admin` but routes on the type are `/users`). For typed mount prefixes derived from\n * full worker paths, pass the **worker app** type and use {@link MountedClientApp}.\n */\nexport type HonoClientApp<T extends Hono> =\n\tT extends Hono<\n\t\tinfer E extends Env,\n\t\tinfer S extends Schema,\n\t\tinfer BasePath extends string\n\t>\n\t\t? Hono<Omit<E, \"Bindings\">, S, BasePath>\n\t\t: never;\n\ntype HasMorePathSegments<Rest extends string> = Rest extends\n\t| `${string}/${string}`\n\t| `:${string}/${string}`\n\t? true\n\t: false;\n\ntype MountPrefixesWithPrefix<\n\tPrefix extends string,\n\tRoute extends string,\n> = Route extends `/${infer Seg}/${infer Rest}`\n\t? HasMorePathSegments<Rest> extends true\n\t\t?\n\t\t\t\t| `${Prefix}/${Seg}`\n\t\t\t\t| MountPrefixesWithPrefix<`${Prefix}/${Seg}`, `/${Rest}`>\n\t\t: `${Prefix}/${Seg}`\n\t: never;\n\n/** Every mount prefix along a route (`/a/b/c` → `/a` | `/a/b`). */\ntype MountPrefixesOfRoute<Route extends string> =\n\tRoute extends `/${infer Seg}/${infer Rest}`\n\t\t? HasMorePathSegments<Rest> extends true\n\t\t\t? `/${Seg}` | MountPrefixesWithPrefix<`/${Seg}`, `/${Rest}`>\n\t\t\t: `/${Seg}`\n\t\t: never;\n\n/**\n * Mount prefix valid when the worker app schema has routes under `${M}/…`.\n * Includes multi-segment prefixes (`/nested/deep`) and param segments (`/level1/:param`).\n * Excludes top-level-only routes (`/users`, `/x`) — use {@link honoFetcher} for those.\n */\nexport type ValidMountPrefix<T extends Hono> = MountPrefixesOfRoute<\n\tSchemaRouteKeys<T>\n>;\n\n/** Path params required when the mount path contains `:param` segments. */\nexport type MountPathParams<M extends string> = ParsePathParams<M>;\n\ntype StripMountRoute<\n\tRoute extends string,\n\tMount extends string,\n> = Route extends `${Mount}/${infer Rest}` ? `/${Rest}` : never;\n\ntype MountedSchema<T extends Hono, Mount extends string> = {\n\t[Route in SchemaRouteKeys<T> as StripMountRoute<\n\t\tRoute,\n\t\tMount\n\t> extends infer Stripped extends string\n\t\t? Stripped\n\t\t: never]: ExtractSchema<T>[Route];\n} & Schema;\n\n/**\n * Client view of routes under `Mount` on a **worker app** whose schema keys are full\n * paths (e.g. `/admin/users` → client `url: \"/users\"` when `Mount` is `\"/admin\"`).\n */\nexport type MountedClientApp<T extends Hono, Mount extends string> =\n\tT extends Hono<\n\t\tinfer E extends Env,\n\t\tinfer _S extends Schema,\n\t\tinfer BasePath extends string\n\t>\n\t\t? Hono<Omit<E, \"Bindings\">, MountedSchema<T, Mount>, BasePath>\n\t\t: never;\n\ntype ClientAppForMount<T extends Hono, M extends string> =\n\tM extends ValidMountPrefix<T> ? MountedClientApp<T, M> : HonoClientApp<T>;\n\ntype MountParamsArg<M extends string> =\n\tParsePathParams<M> extends never ? undefined : ParsePathParams<M>;\n\nfunction normalizeMountPath(mountPath: string): string {\n\tconst trimmed = mountPath.trim();\n\tif (trimmed === \"\" || trimmed === \"/\") {\n\t\treturn \"\";\n\t}\n\tconst withLeading = trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n\treturn withLeading.endsWith(\"/\") ? withLeading.slice(0, -1) : withLeading;\n}\n\nfunction substitutePathParams(\n\tpath: string,\n\tparams: Record<string, string>,\n): string {\n\treturn Object.entries(params).reduce(\n\t\t(acc, [key, value]) => acc.replace(`:${key}`, value),\n\t\tpath,\n\t);\n}\n\ntype ParentFetcher = (\n\trequest: string,\n\tinit?: RequestInit,\n) => Response | Promise<Response>;\n\nfunction createMountedFetcher<\n\tT extends Hono,\n\tconst M extends ValidMountPrefix<T> | string,\n>(\n\tparentFetcher: ParentFetcher,\n\tmountPath: M,\n\tmountParams?: MountParamsArg<M>,\n): TypedHonoFetcher<\n\tM extends ValidMountPrefix<T> ? MountedClientApp<T, M> : HonoClientApp<T>\n> {\n\tconst normalized = normalizeMountPath(mountPath);\n\tconst prefix =\n\t\tmountParams === undefined\n\t\t\t? normalized\n\t\t\t: substitutePathParams(normalized, mountParams as Record<string, string>);\n\treturn honoFetcher<ClientAppForMount<T, M>>((request, init) => {\n\t\tconst path = request.startsWith(\"/\") ? request : `/${request}`;\n\t\tconst url = prefix === \"\" ? path : `${prefix}${path}`;\n\t\treturn parentFetcher(url, init) as ReturnType<\n\t\t\tClientAppForMount<T, M>[\"request\"]\n\t\t>;\n\t}) as TypedHonoFetcher<\n\t\tM extends ValidMountPrefix<T> ? MountedClientApp<T, M> : HonoClientApp<T>\n\t>;\n}\n\nfunction isHonoApp(value: ParentFetcher | Hono): value is Hono {\n\treturn value instanceof HonoClass;\n}\n\n/**\n * Typed client for routes under `mountPath`, using `app.request` as transport.\n * Infers both the app schema and (when valid) stripped mount paths — no type args needed.\n *\n * When `mountPath` contains `:param` segments, pass `mountParams` (same shape as route\n * `params` on {@link honoFetcher}).\n */\nexport function honoFetcherMounted<\n\tT extends Hono,\n\tconst M extends ValidMountPrefix<T>,\n>(\n\tapp: T,\n\tmountPath: M,\n\tmountParams: MountPathParams<M>,\n): TypedHonoFetcher<MountedClientApp<T, M>>;\n\nexport function honoFetcherMounted<\n\tT extends Hono,\n\tconst M extends ValidMountPrefix<T>,\n>(app: T, mountPath: M): TypedHonoFetcher<MountedClientApp<T, M>>;\n\n/**\n * Typed client for routes under `mountPath` on a parent fetcher.\n *\n * Use when transport is not `app.request` (service bindings, DO stubs, browser `fetch`, etc.).\n *\n * - **Worker app type** (schema keys include the mount, e.g. `/admin/users`): pass\n * `typeof workerApp` and a {@link ValidMountPrefix}; client `url` values are paths\n * **after** the mount (`/users`, not `/admin/users`). Prefer {@link honoFetcherMounted}\n * with the app instance when using `app.request`.\n * - **Sub-app type** (routes are `/users` on the sub-app, mount is only on the worker):\n * pass `typeof adminRoutes` and the worker mount string (`\"/admin\"`); client URLs\n * match the sub-app schema.\n */\nexport function honoFetcherMounted<T extends Hono>(\n\tparentFetcher: ParentFetcher,\n\tmountPath: string,\n\tmountParams?: Record<string, string>,\n): TypedHonoFetcher<HonoClientApp<T>>;\n\nexport function honoFetcherMounted<\n\tT extends Hono,\n\tconst M extends ValidMountPrefix<T>,\n>(\n\tparentFetcher: ParentFetcher,\n\tmountPath: M,\n\tmountParams: MountPathParams<M>,\n): TypedHonoFetcher<MountedClientApp<T, M>>;\n\nexport function honoFetcherMounted<\n\tT extends Hono,\n\tconst M extends ValidMountPrefix<T>,\n>(\n\tparentFetcher: ParentFetcher,\n\tmountPath: M,\n): TypedHonoFetcher<MountedClientApp<T, M>>;\n\nexport function honoFetcherMounted<\n\tT extends Hono,\n\tconst M extends ValidMountPrefix<T> | string,\n>(\n\tappOrFetcher: T | ParentFetcher,\n\tmountPath: M,\n\tmountParams?: MountParamsArg<M>,\n): TypedHonoFetcher<HonoClientApp<T> | MountedClientApp<T, M>> {\n\tconst parentFetcher = isHonoApp(appOrFetcher)\n\t\t? (url: string, init?: RequestInit) => appOrFetcher.request(url, init)\n\t\t: appOrFetcher;\n\treturn createMountedFetcher<T, M>(\n\t\tparentFetcher,\n\t\tmountPath,\n\t\tmountParams,\n\t) as TypedHonoFetcher<HonoClientApp<T> | MountedClientApp<T, M>>;\n}\n"]}
@@ -1,4 +1,4 @@
1
- export { honoDirectFetcher } from './chunk-MZBZTJN5.js';
2
- import './chunk-5KDMHM65.js';
1
+ export { honoDirectFetcher } from './chunk-OMZLJ6SZ.js';
2
+ import './chunk-ECAZNFX4.js';
3
3
  //# sourceMappingURL=honoDirectFetcher.js.map
4
4
  //# sourceMappingURL=honoDirectFetcher.js.map
@@ -4,6 +4,20 @@ import { type BaseDisposableTypedHonoFetcher, type TypedHonoFetcher } from "./ho
4
4
  export type DOWithHonoApp<S extends Schema = Schema> = Rpc.DurableObjectBranded & {
5
5
  app: Hono<any, S>;
6
6
  };
7
+ /**
8
+ * Nameable RPC surface for a Durable Object class that exposes a Hono `app`.
9
+ * Use when exporting types for `Env` bindings (avoids Alchemy `TS2883` when the
10
+ * full DO class type cannot be named in generated declarations).
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * export type ChatroomDoRpc = DoRpcWithApp<ChatroomDo>;
15
+ * // Env: { CHATROOM: DurableObjectNamespace<ChatroomDoRpc> }
16
+ * ```
17
+ */
18
+ export type DoRpcWithApp<T extends {
19
+ app: Hono;
20
+ }> = Rpc.DurableObjectBranded & Pick<T, "app">;
7
21
  export type DOSchemaMap<T extends DOWithHonoApp> = T extends DOWithHonoApp ? ExtractSchema<T["app"]> : never;
8
22
  export type DOSchemaKeys<T extends DOWithHonoApp> = string & keyof DOSchemaMap<T>;
9
23
  export type DOStubSchema<T extends DurableObjectStub> = T extends DurableObjectStub<infer S> ? S extends DOWithHonoApp ? ExtractSchema<S["app"]> : never : never;
@@ -1,4 +1,4 @@
1
- export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName } from './chunk-WN4MD3WJ.js';
2
- import './chunk-5KDMHM65.js';
1
+ export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName } from './chunk-ADIG352H.js';
2
+ import './chunk-ECAZNFX4.js';
3
3
  //# sourceMappingURL=honoDoFetcher.js.map
4
4
  //# sourceMappingURL=honoDoFetcher.js.map
@@ -37,12 +37,12 @@ export type HonoFetcherQueryParamValue = string | number | boolean;
37
37
  export type HonoFetcherQueryParams = Record<string, HonoFetcherQueryParamValue | null | undefined>;
38
38
  type FetcherParams<SchemaPath extends string> = HasPathParams<SchemaPath> extends true ? {
39
39
  params: ParsePathParams<SchemaPath>;
40
- query?: HonoFetcherQueryParams;
41
- init?: RequestInit;
40
+ query?: HonoFetcherQueryParams | undefined;
41
+ init?: RequestInit | undefined;
42
42
  } : {
43
43
  params?: never;
44
- query?: HonoFetcherQueryParams;
45
- init?: RequestInit;
44
+ query?: HonoFetcherQueryParams | undefined;
45
+ init?: RequestInit | undefined;
46
46
  };
47
47
  type EmptyObject = {};
48
48
  type TypedMethodFetcher<T extends Hono, M extends HttpMethod> = <SchemaPath extends string & keyof HonoSchema<T>[M]>(request: {
@@ -76,13 +76,13 @@ export interface WebSocketConfig {
76
76
  *
77
77
  * @default true
78
78
  */
79
- autoAccept?: boolean;
79
+ autoAccept?: boolean | undefined;
80
80
  /**
81
81
  * Arguments for Cloudflare’s `WebSocket#accept` (e.g. `{ allowHalfOpen: true }` when
82
82
  * [proxying](https://developers.cloudflare.com/workers/runtime-apis/websockets/#close-behavior)
83
83
  * and coordinating close on both sides). Used only when `autoAccept` is true.
84
84
  */
85
- acceptOptions?: WebSocketAcceptOptions;
85
+ acceptOptions?: WebSocketAcceptOptions | undefined;
86
86
  }
87
87
  export type TypedWebSocketFetcher<T extends Hono> = <SchemaPath extends string & keyof HonoSchema<T>["get"]>(request: {
88
88
  url: SchemaPath;
@@ -1,3 +1,3 @@
1
- export { honoFetcher } from './chunk-5KDMHM65.js';
1
+ export { honoFetcher } from './chunk-ECAZNFX4.js';
2
2
  //# sourceMappingURL=honoFetcher.js.map
3
3
  //# sourceMappingURL=honoFetcher.js.map
@@ -0,0 +1,62 @@
1
+ import type { Hono } from "hono";
2
+ import type { Env, Schema } from "hono/types";
3
+ import type { ExtractSchema } from "hono/types";
4
+ import { type ParsePathParams, type TypedHonoFetcher } from "./honoFetcher";
5
+ type SchemaRouteKeys<T extends Hono> = string & keyof ExtractSchema<T>;
6
+ /**
7
+ * Client-side view of a Hono app: same routes/schema as the server app but without
8
+ * server `Bindings` (service-binding and browser clients do not send Worker env).
9
+ *
10
+ * Use with a **sub-app** type when the mount path is only on the worker (e.g. mount
11
+ * `/admin` but routes on the type are `/users`). For typed mount prefixes derived from
12
+ * full worker paths, pass the **worker app** type and use {@link MountedClientApp}.
13
+ */
14
+ export type HonoClientApp<T extends Hono> = T extends Hono<infer E extends Env, infer S extends Schema, infer BasePath extends string> ? Hono<Omit<E, "Bindings">, S, BasePath> : never;
15
+ type HasMorePathSegments<Rest extends string> = Rest extends `${string}/${string}` | `:${string}/${string}` ? true : false;
16
+ type MountPrefixesWithPrefix<Prefix extends string, Route extends string> = Route extends `/${infer Seg}/${infer Rest}` ? HasMorePathSegments<Rest> extends true ? `${Prefix}/${Seg}` | MountPrefixesWithPrefix<`${Prefix}/${Seg}`, `/${Rest}`> : `${Prefix}/${Seg}` : never;
17
+ /** Every mount prefix along a route (`/a/b/c` → `/a` | `/a/b`). */
18
+ type MountPrefixesOfRoute<Route extends string> = Route extends `/${infer Seg}/${infer Rest}` ? HasMorePathSegments<Rest> extends true ? `/${Seg}` | MountPrefixesWithPrefix<`/${Seg}`, `/${Rest}`> : `/${Seg}` : never;
19
+ /**
20
+ * Mount prefix valid when the worker app schema has routes under `${M}/…`.
21
+ * Includes multi-segment prefixes (`/nested/deep`) and param segments (`/level1/:param`).
22
+ * Excludes top-level-only routes (`/users`, `/x`) — use {@link honoFetcher} for those.
23
+ */
24
+ export type ValidMountPrefix<T extends Hono> = MountPrefixesOfRoute<SchemaRouteKeys<T>>;
25
+ /** Path params required when the mount path contains `:param` segments. */
26
+ export type MountPathParams<M extends string> = ParsePathParams<M>;
27
+ type StripMountRoute<Route extends string, Mount extends string> = Route extends `${Mount}/${infer Rest}` ? `/${Rest}` : never;
28
+ type MountedSchema<T extends Hono, Mount extends string> = {
29
+ [Route in SchemaRouteKeys<T> as StripMountRoute<Route, Mount> extends infer Stripped extends string ? Stripped : never]: ExtractSchema<T>[Route];
30
+ } & Schema;
31
+ /**
32
+ * Client view of routes under `Mount` on a **worker app** whose schema keys are full
33
+ * paths (e.g. `/admin/users` → client `url: "/users"` when `Mount` is `"/admin"`).
34
+ */
35
+ export type MountedClientApp<T extends Hono, Mount extends string> = T extends Hono<infer E extends Env, infer _S extends Schema, infer BasePath extends string> ? Hono<Omit<E, "Bindings">, MountedSchema<T, Mount>, BasePath> : never;
36
+ type ParentFetcher = (request: string, init?: RequestInit) => Response | Promise<Response>;
37
+ /**
38
+ * Typed client for routes under `mountPath`, using `app.request` as transport.
39
+ * Infers both the app schema and (when valid) stripped mount paths — no type args needed.
40
+ *
41
+ * When `mountPath` contains `:param` segments, pass `mountParams` (same shape as route
42
+ * `params` on {@link honoFetcher}).
43
+ */
44
+ export declare function honoFetcherMounted<T extends Hono, const M extends ValidMountPrefix<T>>(app: T, mountPath: M, mountParams: MountPathParams<M>): TypedHonoFetcher<MountedClientApp<T, M>>;
45
+ export declare function honoFetcherMounted<T extends Hono, const M extends ValidMountPrefix<T>>(app: T, mountPath: M): TypedHonoFetcher<MountedClientApp<T, M>>;
46
+ /**
47
+ * Typed client for routes under `mountPath` on a parent fetcher.
48
+ *
49
+ * Use when transport is not `app.request` (service bindings, DO stubs, browser `fetch`, etc.).
50
+ *
51
+ * - **Worker app type** (schema keys include the mount, e.g. `/admin/users`): pass
52
+ * `typeof workerApp` and a {@link ValidMountPrefix}; client `url` values are paths
53
+ * **after** the mount (`/users`, not `/admin/users`). Prefer {@link honoFetcherMounted}
54
+ * with the app instance when using `app.request`.
55
+ * - **Sub-app type** (routes are `/users` on the sub-app, mount is only on the worker):
56
+ * pass `typeof adminRoutes` and the worker mount string (`"/admin"`); client URLs
57
+ * match the sub-app schema.
58
+ */
59
+ export declare function honoFetcherMounted<T extends Hono>(parentFetcher: ParentFetcher, mountPath: string, mountParams?: Record<string, string>): TypedHonoFetcher<HonoClientApp<T>>;
60
+ export declare function honoFetcherMounted<T extends Hono, const M extends ValidMountPrefix<T>>(parentFetcher: ParentFetcher, mountPath: M, mountParams: MountPathParams<M>): TypedHonoFetcher<MountedClientApp<T, M>>;
61
+ export declare function honoFetcherMounted<T extends Hono, const M extends ValidMountPrefix<T>>(parentFetcher: ParentFetcher, mountPath: M): TypedHonoFetcher<MountedClientApp<T, M>>;
62
+ export {};
@@ -0,0 +1,4 @@
1
+ export { honoFetcherMounted } from './chunk-VOOAFLGL.js';
2
+ import './chunk-ECAZNFX4.js';
3
+ //# sourceMappingURL=honoFetcherMounted.js.map
4
+ //# sourceMappingURL=honoFetcherMounted.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"honoFetcherMounted.js"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- export { honoDirectFetcher } from "./honoDirectFetcher.js";
2
- export type { DOSchemaKeys, DOSchemaMap, DOStubSchema, DOWithHonoApp, HonoDoFetcherStubInput, TypedDoFetcher, } from "./honoDoFetcher.js";
3
- export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName, } from "./honoDoFetcher.js";
4
- export type { BaseDisposableTypedHonoFetcher, BaseTypedHonoFetcher, HonoFetcherQueryParamValue, HonoFetcherQueryParams, HonoSchemaKeys, HttpMethod, JsonResponse, ParsePathParams, RpcDisposableJsonResponse, TypedDisposableWebSocketFetcher, TypedHonoFetcher, TypedWebSocketFetcher, WebSocketConfig, } from "./honoFetcher.js";
5
- export { honoFetcher } from "./honoFetcher.js";
1
+ export { honoDirectFetcher } from "./honoDirectFetcher";
2
+ export type { HonoClientApp, MountedClientApp, MountPathParams, ValidMountPrefix, } from "./honoFetcherMounted";
3
+ export { honoFetcherMounted } from "./honoFetcherMounted";
4
+ export type { DoRpcWithApp, DOSchemaKeys, DOSchemaMap, DOStubSchema, DOWithHonoApp, HonoDoFetcherStubInput, TypedDoFetcher, } from "./honoDoFetcher";
5
+ export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName, } from "./honoDoFetcher";
6
+ export type { BaseDisposableTypedHonoFetcher, BaseTypedHonoFetcher, HonoFetcherQueryParamValue, HonoFetcherQueryParams, HonoSchemaKeys, HttpMethod, JsonResponse, ParsePathParams, RpcDisposableJsonResponse, TypedDisposableWebSocketFetcher, TypedHonoFetcher, TypedWebSocketFetcher, WebSocketConfig, } from "./honoFetcher";
7
+ export { honoFetcher } from "./honoFetcher";
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- export { honoDirectFetcher } from './chunk-MZBZTJN5.js';
2
- export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName } from './chunk-WN4MD3WJ.js';
3
- export { honoFetcher } from './chunk-5KDMHM65.js';
1
+ export { honoDirectFetcher } from './chunk-OMZLJ6SZ.js';
2
+ export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName } from './chunk-ADIG352H.js';
3
+ export { honoFetcherMounted } from './chunk-VOOAFLGL.js';
4
+ export { honoFetcher } from './chunk-ECAZNFX4.js';
4
5
  //# sourceMappingURL=index.js.map
5
6
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firtoz/hono-fetcher",
3
- "version": "2.7.2",
3
+ "version": "2.8.0",
4
4
  "description": "Type-safe Hono API client with full TypeScript inference for routes, params, and payloads",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -56,8 +56,8 @@
56
56
  "url": "https://github.com/firtoz/fullstack-toolkit/issues"
57
57
  },
58
58
  "peerDependencies": {
59
- "@cloudflare/workers-types": "^4.20260423.1",
60
- "hono": "^4.12.14"
59
+ "@cloudflare/workers-types": "^4.20260503.1",
60
+ "hono": "^4.12.16"
61
61
  },
62
62
  "engines": {
63
63
  "node": ">=18.0.0"
@@ -66,9 +66,9 @@
66
66
  "access": "public"
67
67
  },
68
68
  "devDependencies": {
69
- "@hono/node-server": "^2.0.0",
69
+ "@hono/node-server": "^2.0.1",
70
70
  "@hono/zod-validator": "^0.7.6",
71
71
  "bun-types": "^1.3.13",
72
- "zod": "^4.3.6"
72
+ "zod": "^4.4.2"
73
73
  }
74
74
  }
@@ -14,6 +14,20 @@ export type DOWithHonoApp<S extends Schema = Schema> =
14
14
  app: Hono<any, S>;
15
15
  };
16
16
 
17
+ /**
18
+ * Nameable RPC surface for a Durable Object class that exposes a Hono `app`.
19
+ * Use when exporting types for `Env` bindings (avoids Alchemy `TS2883` when the
20
+ * full DO class type cannot be named in generated declarations).
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * export type ChatroomDoRpc = DoRpcWithApp<ChatroomDo>;
25
+ * // Env: { CHATROOM: DurableObjectNamespace<ChatroomDoRpc> }
26
+ * ```
27
+ */
28
+ export type DoRpcWithApp<T extends { app: Hono }> = Rpc.DurableObjectBranded &
29
+ Pick<T, "app">;
30
+
17
31
  export type DOSchemaMap<T extends DOWithHonoApp> = T extends DOWithHonoApp
18
32
  ? ExtractSchema<T["app"]>
19
33
  : never;
@@ -91,13 +91,13 @@ type FetcherParams<SchemaPath extends string> =
91
91
  HasPathParams<SchemaPath> extends true
92
92
  ? {
93
93
  params: ParsePathParams<SchemaPath>;
94
- query?: HonoFetcherQueryParams;
95
- init?: RequestInit;
94
+ query?: HonoFetcherQueryParams | undefined;
95
+ init?: RequestInit | undefined;
96
96
  }
97
97
  : {
98
98
  params?: never;
99
- query?: HonoFetcherQueryParams;
100
- init?: RequestInit;
99
+ query?: HonoFetcherQueryParams | undefined;
100
+ init?: RequestInit | undefined;
101
101
  };
102
102
 
103
103
  // biome-ignore lint/complexity/noBannedTypes: We need an empty object to remove the body and form keys from the request object
@@ -173,13 +173,13 @@ export interface WebSocketConfig {
173
173
  *
174
174
  * @default true
175
175
  */
176
- autoAccept?: boolean;
176
+ autoAccept?: boolean | undefined;
177
177
  /**
178
178
  * Arguments for Cloudflare’s `WebSocket#accept` (e.g. `{ allowHalfOpen: true }` when
179
179
  * [proxying](https://developers.cloudflare.com/workers/runtime-apis/websockets/#close-behavior)
180
180
  * and coordinating close on both sides). Used only when `autoAccept` is true.
181
181
  */
182
- acceptOptions?: WebSocketAcceptOptions;
182
+ acceptOptions?: WebSocketAcceptOptions | undefined;
183
183
  }
184
184
 
185
185
  export type TypedWebSocketFetcher<T extends Hono> = <
@@ -0,0 +1,227 @@
1
+ import type { Hono } from "hono";
2
+ import { Hono as HonoClass } from "hono";
3
+ import type { Env, Schema } from "hono/types";
4
+ import type { ExtractSchema } from "hono/types";
5
+ import {
6
+ honoFetcher,
7
+ type ParsePathParams,
8
+ type TypedHonoFetcher,
9
+ } from "./honoFetcher";
10
+
11
+ type SchemaRouteKeys<T extends Hono> = string & keyof ExtractSchema<T>;
12
+
13
+ /**
14
+ * Client-side view of a Hono app: same routes/schema as the server app but without
15
+ * server `Bindings` (service-binding and browser clients do not send Worker env).
16
+ *
17
+ * Use with a **sub-app** type when the mount path is only on the worker (e.g. mount
18
+ * `/admin` but routes on the type are `/users`). For typed mount prefixes derived from
19
+ * full worker paths, pass the **worker app** type and use {@link MountedClientApp}.
20
+ */
21
+ export type HonoClientApp<T extends Hono> =
22
+ T extends Hono<
23
+ infer E extends Env,
24
+ infer S extends Schema,
25
+ infer BasePath extends string
26
+ >
27
+ ? Hono<Omit<E, "Bindings">, S, BasePath>
28
+ : never;
29
+
30
+ type HasMorePathSegments<Rest extends string> = Rest extends
31
+ | `${string}/${string}`
32
+ | `:${string}/${string}`
33
+ ? true
34
+ : false;
35
+
36
+ type MountPrefixesWithPrefix<
37
+ Prefix extends string,
38
+ Route extends string,
39
+ > = Route extends `/${infer Seg}/${infer Rest}`
40
+ ? HasMorePathSegments<Rest> extends true
41
+ ?
42
+ | `${Prefix}/${Seg}`
43
+ | MountPrefixesWithPrefix<`${Prefix}/${Seg}`, `/${Rest}`>
44
+ : `${Prefix}/${Seg}`
45
+ : never;
46
+
47
+ /** Every mount prefix along a route (`/a/b/c` → `/a` | `/a/b`). */
48
+ type MountPrefixesOfRoute<Route extends string> =
49
+ Route extends `/${infer Seg}/${infer Rest}`
50
+ ? HasMorePathSegments<Rest> extends true
51
+ ? `/${Seg}` | MountPrefixesWithPrefix<`/${Seg}`, `/${Rest}`>
52
+ : `/${Seg}`
53
+ : never;
54
+
55
+ /**
56
+ * Mount prefix valid when the worker app schema has routes under `${M}/…`.
57
+ * Includes multi-segment prefixes (`/nested/deep`) and param segments (`/level1/:param`).
58
+ * Excludes top-level-only routes (`/users`, `/x`) — use {@link honoFetcher} for those.
59
+ */
60
+ export type ValidMountPrefix<T extends Hono> = MountPrefixesOfRoute<
61
+ SchemaRouteKeys<T>
62
+ >;
63
+
64
+ /** Path params required when the mount path contains `:param` segments. */
65
+ export type MountPathParams<M extends string> = ParsePathParams<M>;
66
+
67
+ type StripMountRoute<
68
+ Route extends string,
69
+ Mount extends string,
70
+ > = Route extends `${Mount}/${infer Rest}` ? `/${Rest}` : never;
71
+
72
+ type MountedSchema<T extends Hono, Mount extends string> = {
73
+ [Route in SchemaRouteKeys<T> as StripMountRoute<
74
+ Route,
75
+ Mount
76
+ > extends infer Stripped extends string
77
+ ? Stripped
78
+ : never]: ExtractSchema<T>[Route];
79
+ } & Schema;
80
+
81
+ /**
82
+ * Client view of routes under `Mount` on a **worker app** whose schema keys are full
83
+ * paths (e.g. `/admin/users` → client `url: "/users"` when `Mount` is `"/admin"`).
84
+ */
85
+ export type MountedClientApp<T extends Hono, Mount extends string> =
86
+ T extends Hono<
87
+ infer E extends Env,
88
+ infer _S extends Schema,
89
+ infer BasePath extends string
90
+ >
91
+ ? Hono<Omit<E, "Bindings">, MountedSchema<T, Mount>, BasePath>
92
+ : never;
93
+
94
+ type ClientAppForMount<T extends Hono, M extends string> =
95
+ M extends ValidMountPrefix<T> ? MountedClientApp<T, M> : HonoClientApp<T>;
96
+
97
+ type MountParamsArg<M extends string> =
98
+ ParsePathParams<M> extends never ? undefined : ParsePathParams<M>;
99
+
100
+ function normalizeMountPath(mountPath: string): string {
101
+ const trimmed = mountPath.trim();
102
+ if (trimmed === "" || trimmed === "/") {
103
+ return "";
104
+ }
105
+ const withLeading = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
106
+ return withLeading.endsWith("/") ? withLeading.slice(0, -1) : withLeading;
107
+ }
108
+
109
+ function substitutePathParams(
110
+ path: string,
111
+ params: Record<string, string>,
112
+ ): string {
113
+ return Object.entries(params).reduce(
114
+ (acc, [key, value]) => acc.replace(`:${key}`, value),
115
+ path,
116
+ );
117
+ }
118
+
119
+ type ParentFetcher = (
120
+ request: string,
121
+ init?: RequestInit,
122
+ ) => Response | Promise<Response>;
123
+
124
+ function createMountedFetcher<
125
+ T extends Hono,
126
+ const M extends ValidMountPrefix<T> | string,
127
+ >(
128
+ parentFetcher: ParentFetcher,
129
+ mountPath: M,
130
+ mountParams?: MountParamsArg<M>,
131
+ ): TypedHonoFetcher<
132
+ M extends ValidMountPrefix<T> ? MountedClientApp<T, M> : HonoClientApp<T>
133
+ > {
134
+ const normalized = normalizeMountPath(mountPath);
135
+ const prefix =
136
+ mountParams === undefined
137
+ ? normalized
138
+ : substitutePathParams(normalized, mountParams as Record<string, string>);
139
+ return honoFetcher<ClientAppForMount<T, M>>((request, init) => {
140
+ const path = request.startsWith("/") ? request : `/${request}`;
141
+ const url = prefix === "" ? path : `${prefix}${path}`;
142
+ return parentFetcher(url, init) as ReturnType<
143
+ ClientAppForMount<T, M>["request"]
144
+ >;
145
+ }) as TypedHonoFetcher<
146
+ M extends ValidMountPrefix<T> ? MountedClientApp<T, M> : HonoClientApp<T>
147
+ >;
148
+ }
149
+
150
+ function isHonoApp(value: ParentFetcher | Hono): value is Hono {
151
+ return value instanceof HonoClass;
152
+ }
153
+
154
+ /**
155
+ * Typed client for routes under `mountPath`, using `app.request` as transport.
156
+ * Infers both the app schema and (when valid) stripped mount paths — no type args needed.
157
+ *
158
+ * When `mountPath` contains `:param` segments, pass `mountParams` (same shape as route
159
+ * `params` on {@link honoFetcher}).
160
+ */
161
+ export function honoFetcherMounted<
162
+ T extends Hono,
163
+ const M extends ValidMountPrefix<T>,
164
+ >(
165
+ app: T,
166
+ mountPath: M,
167
+ mountParams: MountPathParams<M>,
168
+ ): TypedHonoFetcher<MountedClientApp<T, M>>;
169
+
170
+ export function honoFetcherMounted<
171
+ T extends Hono,
172
+ const M extends ValidMountPrefix<T>,
173
+ >(app: T, mountPath: M): TypedHonoFetcher<MountedClientApp<T, M>>;
174
+
175
+ /**
176
+ * Typed client for routes under `mountPath` on a parent fetcher.
177
+ *
178
+ * Use when transport is not `app.request` (service bindings, DO stubs, browser `fetch`, etc.).
179
+ *
180
+ * - **Worker app type** (schema keys include the mount, e.g. `/admin/users`): pass
181
+ * `typeof workerApp` and a {@link ValidMountPrefix}; client `url` values are paths
182
+ * **after** the mount (`/users`, not `/admin/users`). Prefer {@link honoFetcherMounted}
183
+ * with the app instance when using `app.request`.
184
+ * - **Sub-app type** (routes are `/users` on the sub-app, mount is only on the worker):
185
+ * pass `typeof adminRoutes` and the worker mount string (`"/admin"`); client URLs
186
+ * match the sub-app schema.
187
+ */
188
+ export function honoFetcherMounted<T extends Hono>(
189
+ parentFetcher: ParentFetcher,
190
+ mountPath: string,
191
+ mountParams?: Record<string, string>,
192
+ ): TypedHonoFetcher<HonoClientApp<T>>;
193
+
194
+ export function honoFetcherMounted<
195
+ T extends Hono,
196
+ const M extends ValidMountPrefix<T>,
197
+ >(
198
+ parentFetcher: ParentFetcher,
199
+ mountPath: M,
200
+ mountParams: MountPathParams<M>,
201
+ ): TypedHonoFetcher<MountedClientApp<T, M>>;
202
+
203
+ export function honoFetcherMounted<
204
+ T extends Hono,
205
+ const M extends ValidMountPrefix<T>,
206
+ >(
207
+ parentFetcher: ParentFetcher,
208
+ mountPath: M,
209
+ ): TypedHonoFetcher<MountedClientApp<T, M>>;
210
+
211
+ export function honoFetcherMounted<
212
+ T extends Hono,
213
+ const M extends ValidMountPrefix<T> | string,
214
+ >(
215
+ appOrFetcher: T | ParentFetcher,
216
+ mountPath: M,
217
+ mountParams?: MountParamsArg<M>,
218
+ ): TypedHonoFetcher<HonoClientApp<T> | MountedClientApp<T, M>> {
219
+ const parentFetcher = isHonoApp(appOrFetcher)
220
+ ? (url: string, init?: RequestInit) => appOrFetcher.request(url, init)
221
+ : appOrFetcher;
222
+ return createMountedFetcher<T, M>(
223
+ parentFetcher,
224
+ mountPath,
225
+ mountParams,
226
+ ) as TypedHonoFetcher<HonoClientApp<T> | MountedClientApp<T, M>>;
227
+ }
package/src/index.ts CHANGED
@@ -1,19 +1,28 @@
1
1
  // Convenience wrapper for direct HTTP fetching
2
- export { honoDirectFetcher } from "./honoDirectFetcher.js";
2
+ export { honoDirectFetcher } from "./honoDirectFetcher";
3
+ // Mounted sub-app client
4
+ export type {
5
+ HonoClientApp,
6
+ MountedClientApp,
7
+ MountPathParams,
8
+ ValidMountPrefix,
9
+ } from "./honoFetcherMounted";
10
+ export { honoFetcherMounted } from "./honoFetcherMounted";
3
11
  // Durable Object integration
4
12
  export type {
13
+ DoRpcWithApp,
5
14
  DOSchemaKeys,
6
15
  DOSchemaMap,
7
16
  DOStubSchema,
8
17
  DOWithHonoApp,
9
18
  HonoDoFetcherStubInput,
10
19
  TypedDoFetcher,
11
- } from "./honoDoFetcher.js";
20
+ } from "./honoDoFetcher";
12
21
  export {
13
22
  honoDoFetcher,
14
23
  honoDoFetcherWithId,
15
24
  honoDoFetcherWithName,
16
- } from "./honoDoFetcher.js";
25
+ } from "./honoDoFetcher";
17
26
  // Core fetcher functionality
18
27
  export type {
19
28
  BaseDisposableTypedHonoFetcher,
@@ -29,5 +38,5 @@ export type {
29
38
  TypedHonoFetcher,
30
39
  TypedWebSocketFetcher,
31
40
  WebSocketConfig,
32
- } from "./honoFetcher.js";
33
- export { honoFetcher } from "./honoFetcher.js";
41
+ } from "./honoFetcher";
42
+ export { honoFetcher } from "./honoFetcher";
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/honoFetcher.ts"],"names":[],"mappings":"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","file":"chunk-5KDMHM65.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\t/**\n\t * Arguments for Cloudflare’s `WebSocket#accept` (e.g. `{ allowHalfOpen: true }` when\n\t * [proxying](https://developers.cloudflare.com/workers/runtime-apis/websockets/#close-behavior)\n\t * and coordinating close on both sides). Used only when `autoAccept` is true.\n\t */\n\tacceptOptions?: WebSocketAcceptOptions;\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;\n\t\tconst acceptOptions = config?.acceptOptions;\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\tif (autoAccept && response.webSocket) {\n\t\t\t\tresponse.webSocket.accept(acceptOptions);\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"]}
@@ -1 +0,0 @@
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-WN4MD3WJ.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"]}