@firtoz/hono-fetcher 2.7.2 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -2
- package/dist/chunk-2V3BY5GN.js +41 -0
- package/dist/chunk-2V3BY5GN.js.map +1 -0
- package/dist/{chunk-WN4MD3WJ.js → chunk-CHQGE36J.js} +3 -3
- package/dist/chunk-CHQGE36J.js.map +1 -0
- package/dist/{chunk-5KDMHM65.js → chunk-IL7N6DHV.js} +2 -2
- package/dist/chunk-IL7N6DHV.js.map +1 -0
- package/dist/{chunk-MZBZTJN5.js → chunk-QEZAY63D.js} +3 -3
- package/dist/{chunk-MZBZTJN5.js.map → chunk-QEZAY63D.js.map} +1 -1
- package/dist/honoDirectFetcher.js +2 -2
- package/dist/honoDoFetcher.d.ts +14 -0
- package/dist/honoDoFetcher.js +2 -2
- package/dist/honoFetcher.d.ts +17 -8
- package/dist/honoFetcher.js +1 -1
- package/dist/honoFetcherMounted.d.ts +62 -0
- package/dist/honoFetcherMounted.js +4 -0
- package/dist/honoFetcherMounted.js.map +1 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -3
- package/package.json +7 -7
- package/src/honoDoFetcher.ts +14 -0
- package/src/honoFetcher.ts +29 -8
- package/src/honoFetcherMounted.ts +227 -0
- package/src/index.ts +14 -5
- package/dist/chunk-5KDMHM65.js.map +0 -1
- package/dist/chunk-WN4MD3WJ.js.map +0 -1
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
|
-
//
|
|
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
|
|
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
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { honoFetcher } from './chunk-IL7N6DHV.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-2V3BY5GN.js.map
|
|
41
|
+
//# sourceMappingURL=chunk-2V3BY5GN.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-2V3BY5GN.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
|
-
import { honoFetcher } from './chunk-
|
|
1
|
+
import { honoFetcher } from './chunk-IL7N6DHV.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-
|
|
43
|
-
//# sourceMappingURL=chunk-
|
|
42
|
+
//# sourceMappingURL=chunk-CHQGE36J.js.map
|
|
43
|
+
//# sourceMappingURL=chunk-CHQGE36J.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-CHQGE36J.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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/honoFetcher.ts"],"names":[],"mappings":"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","file":"chunk-IL7N6DHV.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\n/**\n * `@hono/zod-validator` (0.8+) unions Zod 400 JSON bodies into route `output`.\n * Fetcher clients only model successful handler responses.\n */\ntype ExcludeZodValidatorFailureOutput<T> = Exclude<\n\tT,\n\t{ success: false; error: unknown }\n>;\n\ntype HonoRouteOutput<\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? ExcludeZodValidatorFailureOutput<\n\t\t\tHonoSchema<T>[M][SchemaPath][DollarM][\"output\"]\n\t\t>\n\t: never;\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<HonoRouteOutput<T, M, SchemaPath, DollarM>>\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<HonoRouteOutput<T, M, SchemaPath, DollarM>>\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-
|
|
1
|
+
import { honoFetcher } from './chunk-IL7N6DHV.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-
|
|
12
|
-
//# sourceMappingURL=chunk-
|
|
11
|
+
//# sourceMappingURL=chunk-QEZAY63D.js.map
|
|
12
|
+
//# sourceMappingURL=chunk-QEZAY63D.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-
|
|
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-QEZAY63D.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,4 +1,4 @@
|
|
|
1
|
-
export { honoDirectFetcher } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { honoDirectFetcher } from './chunk-QEZAY63D.js';
|
|
2
|
+
import './chunk-IL7N6DHV.js';
|
|
3
3
|
//# sourceMappingURL=honoDirectFetcher.js.map
|
|
4
4
|
//# sourceMappingURL=honoDirectFetcher.js.map
|
package/dist/honoDoFetcher.d.ts
CHANGED
|
@@ -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;
|
package/dist/honoDoFetcher.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName } from './chunk-CHQGE36J.js';
|
|
2
|
+
import './chunk-IL7N6DHV.js';
|
|
3
3
|
//# sourceMappingURL=honoDoFetcher.js.map
|
|
4
4
|
//# sourceMappingURL=honoDoFetcher.js.map
|
package/dist/honoFetcher.d.ts
CHANGED
|
@@ -37,19 +37,28 @@ 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: {
|
|
49
49
|
url: SchemaPath;
|
|
50
50
|
} & FetcherParams<SchemaPath> & (M extends "get" | "delete" ? EmptyObject : BodyParams<T, M, SchemaPath>)) => Promise<SchemaOutput<T, M, SchemaPath>>;
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
/**
|
|
52
|
+
* `@hono/zod-validator` (0.8+) unions Zod 400 JSON bodies into route `output`.
|
|
53
|
+
* Fetcher clients only model successful handler responses.
|
|
54
|
+
*/
|
|
55
|
+
type ExcludeZodValidatorFailureOutput<T> = Exclude<T, {
|
|
56
|
+
success: false;
|
|
57
|
+
error: unknown;
|
|
58
|
+
}>;
|
|
59
|
+
type HonoRouteOutput<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] ? ExcludeZodValidatorFailureOutput<HonoSchema<T>[M][SchemaPath][DollarM]["output"]> : never;
|
|
60
|
+
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<HonoRouteOutput<T, M, SchemaPath, DollarM>> : never;
|
|
61
|
+
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<HonoRouteOutput<T, M, SchemaPath, DollarM>> : never;
|
|
53
62
|
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"] ? {
|
|
54
63
|
body: HonoSchema<TApp>[TMethod][SchemaPath][DollarMethod]["input"]["json"];
|
|
55
64
|
} | {
|
|
@@ -76,13 +85,13 @@ export interface WebSocketConfig {
|
|
|
76
85
|
*
|
|
77
86
|
* @default true
|
|
78
87
|
*/
|
|
79
|
-
autoAccept?: boolean;
|
|
88
|
+
autoAccept?: boolean | undefined;
|
|
80
89
|
/**
|
|
81
90
|
* Arguments for Cloudflare’s `WebSocket#accept` (e.g. `{ allowHalfOpen: true }` when
|
|
82
91
|
* [proxying](https://developers.cloudflare.com/workers/runtime-apis/websockets/#close-behavior)
|
|
83
92
|
* and coordinating close on both sides). Used only when `autoAccept` is true.
|
|
84
93
|
*/
|
|
85
|
-
acceptOptions?: WebSocketAcceptOptions;
|
|
94
|
+
acceptOptions?: WebSocketAcceptOptions | undefined;
|
|
86
95
|
}
|
|
87
96
|
export type TypedWebSocketFetcher<T extends Hono> = <SchemaPath extends string & keyof HonoSchema<T>["get"]>(request: {
|
|
88
97
|
url: SchemaPath;
|
package/dist/honoFetcher.js
CHANGED
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"honoFetcherMounted.js"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export { honoDirectFetcher } from "./honoDirectFetcher
|
|
2
|
-
export type {
|
|
3
|
-
export {
|
|
4
|
-
export type {
|
|
5
|
-
export {
|
|
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-
|
|
2
|
-
export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName } from './chunk-
|
|
3
|
-
export {
|
|
1
|
+
export { honoDirectFetcher } from './chunk-QEZAY63D.js';
|
|
2
|
+
export { honoDoFetcher, honoDoFetcherWithId, honoDoFetcherWithName } from './chunk-CHQGE36J.js';
|
|
3
|
+
export { honoFetcherMounted } from './chunk-2V3BY5GN.js';
|
|
4
|
+
export { honoFetcher } from './chunk-IL7N6DHV.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.
|
|
3
|
+
"version": "2.8.1",
|
|
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.
|
|
60
|
-
"hono": "^4.12.
|
|
59
|
+
"@cloudflare/workers-types": "^4.20260529.1",
|
|
60
|
+
"hono": "^4.12.23"
|
|
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.
|
|
70
|
-
"@hono/zod-validator": "^0.
|
|
71
|
-
"bun-types": "^1.3.
|
|
72
|
-
"zod": "^4.3
|
|
69
|
+
"@hono/node-server": "^2.0.4",
|
|
70
|
+
"@hono/zod-validator": "^0.8.0",
|
|
71
|
+
"bun-types": "^1.3.14",
|
|
72
|
+
"zod": "^4.4.3"
|
|
73
73
|
}
|
|
74
74
|
}
|
package/src/honoDoFetcher.ts
CHANGED
|
@@ -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;
|
package/src/honoFetcher.ts
CHANGED
|
@@ -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
|
|
@@ -112,6 +112,27 @@ type TypedMethodFetcher<T extends Hono, M extends HttpMethod> = <
|
|
|
112
112
|
(M extends "get" | "delete" ? EmptyObject : BodyParams<T, M, SchemaPath>),
|
|
113
113
|
) => Promise<SchemaOutput<T, M, SchemaPath>>;
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* `@hono/zod-validator` (0.8+) unions Zod 400 JSON bodies into route `output`.
|
|
117
|
+
* Fetcher clients only model successful handler responses.
|
|
118
|
+
*/
|
|
119
|
+
type ExcludeZodValidatorFailureOutput<T> = Exclude<
|
|
120
|
+
T,
|
|
121
|
+
{ success: false; error: unknown }
|
|
122
|
+
>;
|
|
123
|
+
|
|
124
|
+
type HonoRouteOutput<
|
|
125
|
+
T extends Hono,
|
|
126
|
+
M extends HttpMethod,
|
|
127
|
+
SchemaPath extends string & keyof HonoSchema<T>[M],
|
|
128
|
+
DollarM extends `$${M}` & keyof HonoSchema<T>[M][SchemaPath] = `$${M}` &
|
|
129
|
+
keyof HonoSchema<T>[M][SchemaPath],
|
|
130
|
+
> = "output" extends keyof HonoSchema<T>[M][SchemaPath][DollarM]
|
|
131
|
+
? ExcludeZodValidatorFailureOutput<
|
|
132
|
+
HonoSchema<T>[M][SchemaPath][DollarM]["output"]
|
|
133
|
+
>
|
|
134
|
+
: never;
|
|
135
|
+
|
|
115
136
|
type SchemaOutput<
|
|
116
137
|
T extends Hono,
|
|
117
138
|
M extends HttpMethod,
|
|
@@ -119,7 +140,7 @@ type SchemaOutput<
|
|
|
119
140
|
DollarM extends `$${M}` & keyof HonoSchema<T>[M][SchemaPath] = `$${M}` &
|
|
120
141
|
keyof HonoSchema<T>[M][SchemaPath],
|
|
121
142
|
> = "output" extends keyof HonoSchema<T>[M][SchemaPath][DollarM]
|
|
122
|
-
? JsonResponse<
|
|
143
|
+
? JsonResponse<HonoRouteOutput<T, M, SchemaPath, DollarM>>
|
|
123
144
|
: never;
|
|
124
145
|
|
|
125
146
|
type DoSchemaOutput<
|
|
@@ -129,7 +150,7 @@ type DoSchemaOutput<
|
|
|
129
150
|
DollarM extends `$${M}` & keyof HonoSchema<T>[M][SchemaPath] = `$${M}` &
|
|
130
151
|
keyof HonoSchema<T>[M][SchemaPath],
|
|
131
152
|
> = "output" extends keyof HonoSchema<T>[M][SchemaPath][DollarM]
|
|
132
|
-
? RpcDisposableJsonResponse<
|
|
153
|
+
? RpcDisposableJsonResponse<HonoRouteOutput<T, M, SchemaPath, DollarM>>
|
|
133
154
|
: never;
|
|
134
155
|
|
|
135
156
|
type BodyParams<
|
|
@@ -173,13 +194,13 @@ export interface WebSocketConfig {
|
|
|
173
194
|
*
|
|
174
195
|
* @default true
|
|
175
196
|
*/
|
|
176
|
-
autoAccept?: boolean;
|
|
197
|
+
autoAccept?: boolean | undefined;
|
|
177
198
|
/**
|
|
178
199
|
* Arguments for Cloudflare’s `WebSocket#accept` (e.g. `{ allowHalfOpen: true }` when
|
|
179
200
|
* [proxying](https://developers.cloudflare.com/workers/runtime-apis/websockets/#close-behavior)
|
|
180
201
|
* and coordinating close on both sides). Used only when `autoAccept` is true.
|
|
181
202
|
*/
|
|
182
|
-
acceptOptions?: WebSocketAcceptOptions;
|
|
203
|
+
acceptOptions?: WebSocketAcceptOptions | undefined;
|
|
183
204
|
}
|
|
184
205
|
|
|
185
206
|
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
|
|
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
|
|
20
|
+
} from "./honoDoFetcher";
|
|
12
21
|
export {
|
|
13
22
|
honoDoFetcher,
|
|
14
23
|
honoDoFetcherWithId,
|
|
15
24
|
honoDoFetcherWithName,
|
|
16
|
-
} from "./honoDoFetcher
|
|
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
|
|
33
|
-
export { honoFetcher } from "./honoFetcher
|
|
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"]}
|