@beignet/web 0.0.3
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/CHANGELOG.md +18 -0
- package/README.md +99 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +82 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +45 -0
- package/dist/testing.js.map +1 -0
- package/package.json +68 -0
- package/src/index.ts +169 -0
- package/src/testing.ts +172 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @beignet/web
|
|
2
|
+
|
|
3
|
+
## 0.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8bd9085: Add `@beignet/web` as a framework-neutral Web Fetch adapter, include a `@beignet/web/testing` route test harness, and have `@beignet/next` reuse the Web adapter for standard Request/Response handling.
|
|
8
|
+
- Updated dependencies [3160184]
|
|
9
|
+
- Updated dependencies [254ef6d]
|
|
10
|
+
- Updated dependencies [4cb1784]
|
|
11
|
+
- Updated dependencies [8bd9085]
|
|
12
|
+
- @beignet/core@0.0.3
|
|
13
|
+
|
|
14
|
+
## 0.0.2
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Add the Web Fetch adapter package.
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# @beignet/web
|
|
2
|
+
|
|
3
|
+
Web Fetch adapter for Beignet's framework runtime.
|
|
4
|
+
|
|
5
|
+
Use this package when your runtime accepts a standard `Request` and returns a
|
|
6
|
+
standard `Response`, such as Cloudflare Workers, Bun, Deno, Node fetch servers,
|
|
7
|
+
or tests that should avoid a framework-specific adapter.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @beignet/web
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { createFetchServer } from "@beignet/web";
|
|
19
|
+
import { routes } from "./server/routes";
|
|
20
|
+
|
|
21
|
+
export const server = await createFetchServer({
|
|
22
|
+
ports,
|
|
23
|
+
routes,
|
|
24
|
+
createContext: ({ ports, req }) => ({
|
|
25
|
+
requestId: req.headers.get("x-request-id") ?? crypto.randomUUID(),
|
|
26
|
+
ports,
|
|
27
|
+
}),
|
|
28
|
+
mapUnhandledError: () => ({
|
|
29
|
+
status: 500,
|
|
30
|
+
body: {
|
|
31
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
32
|
+
message: "Internal server error",
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export default {
|
|
38
|
+
fetch: server.fetch,
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Bun
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { server } from "./server";
|
|
46
|
+
|
|
47
|
+
Bun.serve({
|
|
48
|
+
fetch: server.fetch,
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Lower-level helpers
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import {
|
|
56
|
+
createFetchHandler,
|
|
57
|
+
toRequestLike,
|
|
58
|
+
toWebResponse,
|
|
59
|
+
} from "@beignet/web";
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- `createFetchHandler(server)` adapts a Beignet server instance or API handler
|
|
63
|
+
to `(req: Request) => Promise<Response>`.
|
|
64
|
+
- `toRequestLike(req)` converts a standard `Request` to Beignet's
|
|
65
|
+
framework-neutral request shape.
|
|
66
|
+
- `toWebResponse(response)` converts a Beignet response to a standard
|
|
67
|
+
`Response`.
|
|
68
|
+
|
|
69
|
+
Native `Response` instances returned by handlers are passed through unchanged.
|
|
70
|
+
Plain Beignet responses are serialized as JSON unless the body is `undefined`
|
|
71
|
+
or `null`.
|
|
72
|
+
|
|
73
|
+
## Testing
|
|
74
|
+
|
|
75
|
+
Use `@beignet/web/testing` to exercise routes through the same Web Fetch
|
|
76
|
+
adapter without opening a network port.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { createTestApp } from "@beignet/web/testing";
|
|
80
|
+
import { getTodo } from "./features/todos/contracts";
|
|
81
|
+
import { routes } from "./server/routes";
|
|
82
|
+
|
|
83
|
+
const app = await createTestApp({
|
|
84
|
+
ports,
|
|
85
|
+
routes,
|
|
86
|
+
createContext: ({ ports }) => ({
|
|
87
|
+
requestId: "test-request",
|
|
88
|
+
ports,
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const todo = await app.request(getTodo, {
|
|
93
|
+
path: { id: "todo_1" },
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`app.request(contract, args)` uses the same typed call arguments as
|
|
98
|
+
`@beignet/core/client`, while `app.safeRequest(contract, args)` returns a typed
|
|
99
|
+
success/error result instead of throwing.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { AnyPorts } from "@beignet/core/ports";
|
|
2
|
+
import type { ProvidedPortsOfList, ServiceProvider } from "@beignet/core/providers";
|
|
3
|
+
import type { ContractLike, CreateServerOptions, Handler, HttpRequestLike, HttpResponse, ResolveContract, RouteDef, ServerInstance } from "@beignet/core/server";
|
|
4
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
5
|
+
/**
|
|
6
|
+
* Server types re-exported for Web Fetch apps.
|
|
7
|
+
*/
|
|
8
|
+
export type { CreateServerOptions, HttpRequestLike, HttpResponse, RouteDef, RouteGroup, RouteHook, ServerInstance, } from "@beignet/core/server";
|
|
9
|
+
/**
|
|
10
|
+
* Server helpers re-exported for Web Fetch apps.
|
|
11
|
+
*/
|
|
12
|
+
export { contractsFromRoutes, createServer, defineRoute, defineRouteGroup, defineRouteHook, defineRoutes, } from "@beignet/core/server";
|
|
13
|
+
type AnyProvider = ServiceProvider<unknown, StandardSchemaV1<any, any>, AnyPorts>;
|
|
14
|
+
/**
|
|
15
|
+
* Beignet server adapted to the Web Fetch API.
|
|
16
|
+
*/
|
|
17
|
+
export interface FetchServer<Ctx, Ports extends AnyPorts = AnyPorts> {
|
|
18
|
+
/**
|
|
19
|
+
* Catch-all Web Fetch handler.
|
|
20
|
+
*/
|
|
21
|
+
fetch: (req: Request) => Promise<Response>;
|
|
22
|
+
/**
|
|
23
|
+
* Alias for `fetch` for API route adapters that expect an `api` property.
|
|
24
|
+
*/
|
|
25
|
+
api: (req: Request) => Promise<Response>;
|
|
26
|
+
/**
|
|
27
|
+
* Register and build a single Web Fetch route handler imperatively.
|
|
28
|
+
*/
|
|
29
|
+
route: <CLike extends ContractLike>(contractLike: CLike) => {
|
|
30
|
+
handle: (fn: Handler<Ctx, ResolveContract<CLike>>) => (req: Request) => Promise<Response>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Registered contract inputs.
|
|
34
|
+
*/
|
|
35
|
+
contracts: readonly ContractLike[];
|
|
36
|
+
/**
|
|
37
|
+
* Stop installed providers.
|
|
38
|
+
*/
|
|
39
|
+
stop: () => Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Final app ports after provider setup.
|
|
42
|
+
*/
|
|
43
|
+
ports: Ports;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Convert a native `Request` into Beignet's framework-neutral request shape.
|
|
47
|
+
*/
|
|
48
|
+
export declare function toRequestLike(req: Request): HttpRequestLike;
|
|
49
|
+
/**
|
|
50
|
+
* Convert a Beignet response into a native `Response`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function toWebResponse(resLike: HttpResponse): Response;
|
|
53
|
+
/**
|
|
54
|
+
* Create a Web Fetch handler from a Beignet server or API handler.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createFetchHandler(serverOrHandler: Pick<ServerInstance<unknown>, "api"> | ((req: HttpRequestLike) => Promise<HttpResponse>)): (req: Request) => Promise<Response>;
|
|
57
|
+
/**
|
|
58
|
+
* Create a Beignet server adapted to the Web Fetch API.
|
|
59
|
+
*
|
|
60
|
+
* Use this for runtimes that accept standard `Request` objects and return
|
|
61
|
+
* standard `Response` objects. Next.js apps should usually use
|
|
62
|
+
* `@beignet/next`, which builds on the same Web adapter and adds Next-specific
|
|
63
|
+
* helpers.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createFetchServer<Ctx, Ports extends AnyPorts = AnyPorts, Routes extends readonly RouteDef<Ctx>[] = readonly RouteDef<Ctx>[], Providers extends readonly AnyProvider[] = readonly AnyProvider[]>(options: CreateServerOptions<Ctx, Ports, Routes, Providers>): Promise<FetchServer<Ctx, Ports & ProvidedPortsOfList<Providers>>>;
|
|
66
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EAChB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACnB,OAAO,EACP,eAAe,EACf,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,cAAc,EACf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;GAEG;AACH,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,SAAS,EACT,cAAc,GACf,MAAM,sBAAsB,CAAC;AAE9B;;GAEG;AACH,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,YAAY,GACb,MAAM,sBAAsB,CAAC;AAE9B,KAAK,WAAW,GAAG,eAAe,CAChC,OAAO,EAEP,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,EAC1B,QAAQ,CACT,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,GAAG,EAAE,KAAK,SAAS,QAAQ,GAAG,QAAQ;IACjE;;OAEG;IACH,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3C;;OAEG;IACH,GAAG,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzC;;OAEG;IACH,KAAK,EAAE,CAAC,KAAK,SAAS,YAAY,EAChC,YAAY,EAAE,KAAK,KAChB;QACH,MAAM,EAAE,CACN,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC,KACrC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;KAC1C,CAAC;IACF;;OAEG;IACH,SAAS,EAAE,SAAS,YAAY,EAAE,CAAC;IACnC;;OAEG;IACH,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,CAa3D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,QAAQ,CAc7D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,eAAe,EACX,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,GACpC,CAAC,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC,GACpD,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAOrC;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EACH,KAAK,SAAS,QAAQ,GAAG,QAAQ,EACjC,MAAM,SAAS,SAAS,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,QAAQ,CAAC,GAAG,CAAC,EAAE,EAClE,SAAS,SAAS,SAAS,WAAW,EAAE,GAAG,SAAS,WAAW,EAAE,EAEjE,OAAO,EAAE,mBAAmB,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,GAC1D,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC,CAAC,CAcnE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createServer } from "@beignet/core/server";
|
|
2
|
+
/**
|
|
3
|
+
* Server helpers re-exported for Web Fetch apps.
|
|
4
|
+
*/
|
|
5
|
+
export { contractsFromRoutes, createServer, defineRoute, defineRouteGroup, defineRouteHook, defineRoutes, } from "@beignet/core/server";
|
|
6
|
+
/**
|
|
7
|
+
* Convert a native `Request` into Beignet's framework-neutral request shape.
|
|
8
|
+
*/
|
|
9
|
+
export function toRequestLike(req) {
|
|
10
|
+
return {
|
|
11
|
+
method: req.method,
|
|
12
|
+
url: req.url,
|
|
13
|
+
headers: new Headers(req.headers),
|
|
14
|
+
raw: req,
|
|
15
|
+
json: () => req.json(),
|
|
16
|
+
text: () => req.text(),
|
|
17
|
+
arrayBuffer: () => req.arrayBuffer(),
|
|
18
|
+
blob: () => req.blob(),
|
|
19
|
+
formData: () => req.formData(),
|
|
20
|
+
clone: () => toRequestLike(req.clone()),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Convert a Beignet response into a native `Response`.
|
|
25
|
+
*/
|
|
26
|
+
export function toWebResponse(resLike) {
|
|
27
|
+
if (resLike instanceof Response) {
|
|
28
|
+
return resLike;
|
|
29
|
+
}
|
|
30
|
+
const headers = new Headers(resLike.headers);
|
|
31
|
+
const body = resLike.body;
|
|
32
|
+
if (body === undefined || body === null) {
|
|
33
|
+
return new Response(null, { status: resLike.status, headers });
|
|
34
|
+
}
|
|
35
|
+
// biome-ignore lint/suspicious/noExplicitAny: Body can be any JSON-serializable value.
|
|
36
|
+
return Response.json(body, { status: resLike.status, headers });
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a Web Fetch handler from a Beignet server or API handler.
|
|
40
|
+
*/
|
|
41
|
+
export function createFetchHandler(serverOrHandler) {
|
|
42
|
+
const handler = typeof serverOrHandler === "function"
|
|
43
|
+
? serverOrHandler
|
|
44
|
+
: serverOrHandler.api;
|
|
45
|
+
return async (req) => toWebResponse(await handler(toRequestLike(req)));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create a Beignet server adapted to the Web Fetch API.
|
|
49
|
+
*
|
|
50
|
+
* Use this for runtimes that accept standard `Request` objects and return
|
|
51
|
+
* standard `Response` objects. Next.js apps should usually use
|
|
52
|
+
* `@beignet/next`, which builds on the same Web adapter and adds Next-specific
|
|
53
|
+
* helpers.
|
|
54
|
+
*/
|
|
55
|
+
export async function createFetchServer(options) {
|
|
56
|
+
const runtime = await createServer(options);
|
|
57
|
+
const fetch = createFetchHandler(runtime);
|
|
58
|
+
return {
|
|
59
|
+
fetch,
|
|
60
|
+
api: fetch,
|
|
61
|
+
route: (contract) => ({
|
|
62
|
+
handle: (fn) => createFetchHandler(runtime.route(contract).handle(fn)),
|
|
63
|
+
}),
|
|
64
|
+
contracts: runtime.contracts,
|
|
65
|
+
stop: () => runtime.stop(),
|
|
66
|
+
ports: runtime.ports,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAgBpD;;GAEG;AACH,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,YAAY,GACb,MAAM,sBAAsB,CAAC;AA6C9B;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,OAAO,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;QACjC,GAAG,EAAE,GAAG;QACR,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE;QACtB,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE;QACtB,WAAW,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,WAAW,EAAE;QACpC,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE;QACtB,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE;QAC9B,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;KACxC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,OAAqB;IACjD,IAAI,OAAO,YAAY,QAAQ,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE1B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QACxC,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,uFAAuF;IACvF,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAW,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;AACzE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,eAEqD;IAErD,MAAM,OAAO,GACX,OAAO,eAAe,KAAK,UAAU;QACnC,CAAC,CAAC,eAAe;QACjB,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC;IAE1B,OAAO,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACzE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAMrC,OAA2D;IAE3D,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAE1C,OAAO;QACL,KAAK;QACL,GAAG,EAAE,KAAK;QACV,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACpB,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;SACvE,CAAC;QACF,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE;QAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;KACrB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type CallArgs, type ClientConfig, type EndpointResult, type InferEndpointContractError, type InferSuccessResponse } from "@beignet/core/client";
|
|
2
|
+
import type { AnyPorts } from "@beignet/core/ports";
|
|
3
|
+
import type { ProvidedPortsOfList, ServiceProvider } from "@beignet/core/providers";
|
|
4
|
+
import type { ContractLike, CreateServerOptions, ResolveContract, RouteDef } from "@beignet/core/server";
|
|
5
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
6
|
+
import { type FetchServer } from "./index";
|
|
7
|
+
type AnyProvider = ServiceProvider<unknown, StandardSchemaV1<any, any>, AnyPorts>;
|
|
8
|
+
/**
|
|
9
|
+
* Client options accepted by `createTestApp(...)`.
|
|
10
|
+
*/
|
|
11
|
+
export type TestAppClientOptions<TProvidedHeaders extends string = never> = Omit<ClientConfig<TProvidedHeaders>, "baseUrl" | "fetch"> & {
|
|
12
|
+
/**
|
|
13
|
+
* Base URL used when the typed client builds requests.
|
|
14
|
+
*
|
|
15
|
+
* @default "http://beignet.test"
|
|
16
|
+
*/
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Options for creating a Web Fetch test app.
|
|
21
|
+
*/
|
|
22
|
+
export type CreateTestAppOptions<Ctx, Ports extends AnyPorts, Routes extends readonly RouteDef<Ctx>[], Providers extends readonly AnyProvider[], TProvidedHeaders extends string = never> = CreateServerOptions<Ctx, Ports, Routes, Providers> & {
|
|
23
|
+
/**
|
|
24
|
+
* Typed client behavior for `request(...)` and `safeRequest(...)`.
|
|
25
|
+
*/
|
|
26
|
+
client?: TestAppClientOptions<TProvidedHeaders>;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Beignet app harness for route/runtime tests.
|
|
30
|
+
*/
|
|
31
|
+
export type TestApp<Ctx, Ports extends AnyPorts, TProvidedHeaders extends string = never> = {
|
|
32
|
+
/**
|
|
33
|
+
* Underlying Web Fetch server.
|
|
34
|
+
*/
|
|
35
|
+
server: FetchServer<Ctx, Ports>;
|
|
36
|
+
/**
|
|
37
|
+
* Fetch implementation bound to the in-memory Beignet server.
|
|
38
|
+
*/
|
|
39
|
+
fetch: typeof fetch;
|
|
40
|
+
/**
|
|
41
|
+
* Final app ports after provider setup.
|
|
42
|
+
*/
|
|
43
|
+
ports: Ports;
|
|
44
|
+
/**
|
|
45
|
+
* Contracts registered through the server route list.
|
|
46
|
+
*/
|
|
47
|
+
contracts: FetchServer<Ctx, Ports>["contracts"];
|
|
48
|
+
/**
|
|
49
|
+
* Call a contract through the test app and return the parsed success body.
|
|
50
|
+
*/
|
|
51
|
+
request: <CLike extends ContractLike>(contract: CLike, ...args: CallArgs<ResolveContract<CLike>, TProvidedHeaders>) => Promise<InferSuccessResponse<ResolveContract<CLike>>>;
|
|
52
|
+
/**
|
|
53
|
+
* Call a contract through the test app and return a typed success/error
|
|
54
|
+
* result instead of throwing.
|
|
55
|
+
*/
|
|
56
|
+
safeRequest: <CLike extends ContractLike>(contract: CLike, ...args: CallArgs<ResolveContract<CLike>, TProvidedHeaders>) => Promise<EndpointResult<ResolveContract<CLike>, InferEndpointContractError<ResolveContract<CLike>>>>;
|
|
57
|
+
/**
|
|
58
|
+
* Stop installed providers.
|
|
59
|
+
*/
|
|
60
|
+
stop: () => Promise<void>;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Create a `fetch` implementation that dispatches requests to a Beignet Web
|
|
64
|
+
* server without opening a network port.
|
|
65
|
+
*
|
|
66
|
+
* @param server - Web Fetch server under test.
|
|
67
|
+
* @returns Fetch-compatible function backed by `server.fetch`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createTestFetch<Ctx, Ports extends AnyPorts>(server: Pick<FetchServer<Ctx, Ports>, "fetch">): typeof fetch;
|
|
70
|
+
/**
|
|
71
|
+
* Create a Beignet route/runtime test harness.
|
|
72
|
+
*
|
|
73
|
+
* The harness uses `@beignet/web` under the hood, so tests exercise the same
|
|
74
|
+
* request parsing, route hooks, response validation, and error ownership as a
|
|
75
|
+
* Web Fetch runtime without requiring Next.js or a network listener.
|
|
76
|
+
*
|
|
77
|
+
* @param options - Server options plus optional typed-client settings.
|
|
78
|
+
* @returns Test app with typed `request(...)` helpers and a bound `fetch`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function createTestApp<Ctx, Ports extends AnyPorts = AnyPorts, Routes extends readonly RouteDef<Ctx>[] = readonly RouteDef<Ctx>[], Providers extends readonly AnyProvider[] = readonly AnyProvider[], const TProvidedHeaders extends string = never>(options: CreateTestAppOptions<Ctx, Ports, Routes, Providers, TProvidedHeaders>): Promise<TestApp<Ctx, Ports & ProvidedPortsOfList<Providers>, TProvidedHeaders>>;
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createClient, } from "@beignet/core/client";
|
|
2
|
+
import { createFetchServer } from "./index";
|
|
3
|
+
/**
|
|
4
|
+
* Create a `fetch` implementation that dispatches requests to a Beignet Web
|
|
5
|
+
* server without opening a network port.
|
|
6
|
+
*
|
|
7
|
+
* @param server - Web Fetch server under test.
|
|
8
|
+
* @returns Fetch-compatible function backed by `server.fetch`.
|
|
9
|
+
*/
|
|
10
|
+
export function createTestFetch(server) {
|
|
11
|
+
return ((input, init) => {
|
|
12
|
+
const req = new Request(input, init);
|
|
13
|
+
return server.fetch(req);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create a Beignet route/runtime test harness.
|
|
18
|
+
*
|
|
19
|
+
* The harness uses `@beignet/web` under the hood, so tests exercise the same
|
|
20
|
+
* request parsing, route hooks, response validation, and error ownership as a
|
|
21
|
+
* Web Fetch runtime without requiring Next.js or a network listener.
|
|
22
|
+
*
|
|
23
|
+
* @param options - Server options plus optional typed-client settings.
|
|
24
|
+
* @returns Test app with typed `request(...)` helpers and a bound `fetch`.
|
|
25
|
+
*/
|
|
26
|
+
export async function createTestApp(options) {
|
|
27
|
+
const { client: clientOptions, ...serverOptions } = options;
|
|
28
|
+
const server = await createFetchServer(serverOptions);
|
|
29
|
+
const fetch = createTestFetch(server);
|
|
30
|
+
const client = createClient({
|
|
31
|
+
...clientOptions,
|
|
32
|
+
baseUrl: clientOptions?.baseUrl ?? "http://beignet.test",
|
|
33
|
+
fetch,
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
server,
|
|
37
|
+
fetch,
|
|
38
|
+
ports: server.ports,
|
|
39
|
+
contracts: server.contracts,
|
|
40
|
+
request: (contract, ...args) => client.endpoint(contract).call(...args),
|
|
41
|
+
safeRequest: (contract, ...args) => client.endpoint(contract).safeCall(...args),
|
|
42
|
+
stop: () => server.stop(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,GAIb,MAAM,sBAAsB,CAAC;AAa9B,OAAO,EAAE,iBAAiB,EAAoB,MAAM,SAAS,CAAC;AAwF9D;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,MAA8C;IAE9C,OAAO,CAAC,CAAC,KAAwB,EAAE,IAAkB,EAAE,EAAE;QACvD,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAErC,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC,CAAiB,CAAC;AACrB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAOjC,OAMC;IAID,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,GAAG,OAAO,CAAC;IAC5D,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,YAAY,CAAmB;QAC5C,GAAG,aAAa;QAChB,OAAO,EAAE,aAAa,EAAE,OAAO,IAAI,qBAAqB;QACxD,KAAK;KACN,CAAC,CAAC;IAEH,OAAO;QACL,MAAM;QACN,KAAK;QACL,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,OAAO,EAAE,CAAC,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QACvE,WAAW,EAAE,CAAC,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,CACjC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;QAC7C,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE;KAC1B,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@beignet/web",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Web Fetch adapter for Beignet",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./testing": {
|
|
14
|
+
"types": "./dist/testing.d.ts",
|
|
15
|
+
"default": "./dist/testing.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"!src/**/*.test.ts",
|
|
22
|
+
"!src/**/*.test.tsx",
|
|
23
|
+
"!src/**/*.test-d.ts",
|
|
24
|
+
"README.md",
|
|
25
|
+
"CHANGELOG.md"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"dev": "tsc --watch",
|
|
30
|
+
"clean": "rm -rf dist coverage .turbo",
|
|
31
|
+
"test": "bun test",
|
|
32
|
+
"test:coverage": "bun test --coverage",
|
|
33
|
+
"lint": "biome check ."
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"contract",
|
|
37
|
+
"api",
|
|
38
|
+
"typescript",
|
|
39
|
+
"fetch",
|
|
40
|
+
"web-standard"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/taylorbryant/beignet.git",
|
|
46
|
+
"directory": "packages/web"
|
|
47
|
+
},
|
|
48
|
+
"author": "Taylor Bryant",
|
|
49
|
+
"homepage": "https://github.com/taylorbryant/beignet#readme",
|
|
50
|
+
"bugs": "https://github.com/taylorbryant/beignet/issues",
|
|
51
|
+
"sideEffects": false,
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@beignet/core": "*",
|
|
60
|
+
"@standard-schema/spec": "^1.0.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/bun": "^1.3.13",
|
|
64
|
+
"@types/node": "^20.10.0",
|
|
65
|
+
"typescript": "^5.3.0",
|
|
66
|
+
"zod": "^4.0.0"
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { AnyPorts } from "@beignet/core/ports";
|
|
2
|
+
import type {
|
|
3
|
+
ProvidedPortsOfList,
|
|
4
|
+
ServiceProvider,
|
|
5
|
+
} from "@beignet/core/providers";
|
|
6
|
+
import type {
|
|
7
|
+
ContractLike,
|
|
8
|
+
CreateServerOptions,
|
|
9
|
+
Handler,
|
|
10
|
+
HttpRequestLike,
|
|
11
|
+
HttpResponse,
|
|
12
|
+
ResolveContract,
|
|
13
|
+
RouteDef,
|
|
14
|
+
ServerInstance,
|
|
15
|
+
} from "@beignet/core/server";
|
|
16
|
+
import { createServer } from "@beignet/core/server";
|
|
17
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Server types re-exported for Web Fetch apps.
|
|
21
|
+
*/
|
|
22
|
+
export type {
|
|
23
|
+
CreateServerOptions,
|
|
24
|
+
HttpRequestLike,
|
|
25
|
+
HttpResponse,
|
|
26
|
+
RouteDef,
|
|
27
|
+
RouteGroup,
|
|
28
|
+
RouteHook,
|
|
29
|
+
ServerInstance,
|
|
30
|
+
} from "@beignet/core/server";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Server helpers re-exported for Web Fetch apps.
|
|
34
|
+
*/
|
|
35
|
+
export {
|
|
36
|
+
contractsFromRoutes,
|
|
37
|
+
createServer,
|
|
38
|
+
defineRoute,
|
|
39
|
+
defineRouteGroup,
|
|
40
|
+
defineRouteHook,
|
|
41
|
+
defineRoutes,
|
|
42
|
+
} from "@beignet/core/server";
|
|
43
|
+
|
|
44
|
+
type AnyProvider = ServiceProvider<
|
|
45
|
+
unknown,
|
|
46
|
+
// biome-ignore lint/suspicious/noExplicitAny: provider config types are erased at this level
|
|
47
|
+
StandardSchemaV1<any, any>,
|
|
48
|
+
AnyPorts
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Beignet server adapted to the Web Fetch API.
|
|
53
|
+
*/
|
|
54
|
+
export interface FetchServer<Ctx, Ports extends AnyPorts = AnyPorts> {
|
|
55
|
+
/**
|
|
56
|
+
* Catch-all Web Fetch handler.
|
|
57
|
+
*/
|
|
58
|
+
fetch: (req: Request) => Promise<Response>;
|
|
59
|
+
/**
|
|
60
|
+
* Alias for `fetch` for API route adapters that expect an `api` property.
|
|
61
|
+
*/
|
|
62
|
+
api: (req: Request) => Promise<Response>;
|
|
63
|
+
/**
|
|
64
|
+
* Register and build a single Web Fetch route handler imperatively.
|
|
65
|
+
*/
|
|
66
|
+
route: <CLike extends ContractLike>(
|
|
67
|
+
contractLike: CLike,
|
|
68
|
+
) => {
|
|
69
|
+
handle: (
|
|
70
|
+
fn: Handler<Ctx, ResolveContract<CLike>>,
|
|
71
|
+
) => (req: Request) => Promise<Response>;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Registered contract inputs.
|
|
75
|
+
*/
|
|
76
|
+
contracts: readonly ContractLike[];
|
|
77
|
+
/**
|
|
78
|
+
* Stop installed providers.
|
|
79
|
+
*/
|
|
80
|
+
stop: () => Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Final app ports after provider setup.
|
|
83
|
+
*/
|
|
84
|
+
ports: Ports;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Convert a native `Request` into Beignet's framework-neutral request shape.
|
|
89
|
+
*/
|
|
90
|
+
export function toRequestLike(req: Request): HttpRequestLike {
|
|
91
|
+
return {
|
|
92
|
+
method: req.method,
|
|
93
|
+
url: req.url,
|
|
94
|
+
headers: new Headers(req.headers),
|
|
95
|
+
raw: req,
|
|
96
|
+
json: () => req.json(),
|
|
97
|
+
text: () => req.text(),
|
|
98
|
+
arrayBuffer: () => req.arrayBuffer(),
|
|
99
|
+
blob: () => req.blob(),
|
|
100
|
+
formData: () => req.formData(),
|
|
101
|
+
clone: () => toRequestLike(req.clone()),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Convert a Beignet response into a native `Response`.
|
|
107
|
+
*/
|
|
108
|
+
export function toWebResponse(resLike: HttpResponse): Response {
|
|
109
|
+
if (resLike instanceof Response) {
|
|
110
|
+
return resLike;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const headers = new Headers(resLike.headers);
|
|
114
|
+
const body = resLike.body;
|
|
115
|
+
|
|
116
|
+
if (body === undefined || body === null) {
|
|
117
|
+
return new Response(null, { status: resLike.status, headers });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// biome-ignore lint/suspicious/noExplicitAny: Body can be any JSON-serializable value.
|
|
121
|
+
return Response.json(body as any, { status: resLike.status, headers });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create a Web Fetch handler from a Beignet server or API handler.
|
|
126
|
+
*/
|
|
127
|
+
export function createFetchHandler(
|
|
128
|
+
serverOrHandler:
|
|
129
|
+
| Pick<ServerInstance<unknown>, "api">
|
|
130
|
+
| ((req: HttpRequestLike) => Promise<HttpResponse>),
|
|
131
|
+
): (req: Request) => Promise<Response> {
|
|
132
|
+
const handler =
|
|
133
|
+
typeof serverOrHandler === "function"
|
|
134
|
+
? serverOrHandler
|
|
135
|
+
: serverOrHandler.api;
|
|
136
|
+
|
|
137
|
+
return async (req) => toWebResponse(await handler(toRequestLike(req)));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Create a Beignet server adapted to the Web Fetch API.
|
|
142
|
+
*
|
|
143
|
+
* Use this for runtimes that accept standard `Request` objects and return
|
|
144
|
+
* standard `Response` objects. Next.js apps should usually use
|
|
145
|
+
* `@beignet/next`, which builds on the same Web adapter and adds Next-specific
|
|
146
|
+
* helpers.
|
|
147
|
+
*/
|
|
148
|
+
export async function createFetchServer<
|
|
149
|
+
Ctx,
|
|
150
|
+
Ports extends AnyPorts = AnyPorts,
|
|
151
|
+
Routes extends readonly RouteDef<Ctx>[] = readonly RouteDef<Ctx>[],
|
|
152
|
+
Providers extends readonly AnyProvider[] = readonly AnyProvider[],
|
|
153
|
+
>(
|
|
154
|
+
options: CreateServerOptions<Ctx, Ports, Routes, Providers>,
|
|
155
|
+
): Promise<FetchServer<Ctx, Ports & ProvidedPortsOfList<Providers>>> {
|
|
156
|
+
const runtime = await createServer(options);
|
|
157
|
+
const fetch = createFetchHandler(runtime);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
fetch,
|
|
161
|
+
api: fetch,
|
|
162
|
+
route: (contract) => ({
|
|
163
|
+
handle: (fn) => createFetchHandler(runtime.route(contract).handle(fn)),
|
|
164
|
+
}),
|
|
165
|
+
contracts: runtime.contracts,
|
|
166
|
+
stop: () => runtime.stop(),
|
|
167
|
+
ports: runtime.ports,
|
|
168
|
+
};
|
|
169
|
+
}
|
package/src/testing.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CallArgs,
|
|
3
|
+
type ClientConfig,
|
|
4
|
+
createClient,
|
|
5
|
+
type EndpointResult,
|
|
6
|
+
type InferEndpointContractError,
|
|
7
|
+
type InferSuccessResponse,
|
|
8
|
+
} from "@beignet/core/client";
|
|
9
|
+
import type { AnyPorts } from "@beignet/core/ports";
|
|
10
|
+
import type {
|
|
11
|
+
ProvidedPortsOfList,
|
|
12
|
+
ServiceProvider,
|
|
13
|
+
} from "@beignet/core/providers";
|
|
14
|
+
import type {
|
|
15
|
+
ContractLike,
|
|
16
|
+
CreateServerOptions,
|
|
17
|
+
ResolveContract,
|
|
18
|
+
RouteDef,
|
|
19
|
+
} from "@beignet/core/server";
|
|
20
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
21
|
+
import { createFetchServer, type FetchServer } from "./index";
|
|
22
|
+
|
|
23
|
+
type AnyProvider = ServiceProvider<
|
|
24
|
+
unknown,
|
|
25
|
+
// biome-ignore lint/suspicious/noExplicitAny: provider config types are erased at this level.
|
|
26
|
+
StandardSchemaV1<any, any>,
|
|
27
|
+
AnyPorts
|
|
28
|
+
>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Client options accepted by `createTestApp(...)`.
|
|
32
|
+
*/
|
|
33
|
+
export type TestAppClientOptions<TProvidedHeaders extends string = never> =
|
|
34
|
+
Omit<ClientConfig<TProvidedHeaders>, "baseUrl" | "fetch"> & {
|
|
35
|
+
/**
|
|
36
|
+
* Base URL used when the typed client builds requests.
|
|
37
|
+
*
|
|
38
|
+
* @default "http://beignet.test"
|
|
39
|
+
*/
|
|
40
|
+
baseUrl?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Options for creating a Web Fetch test app.
|
|
45
|
+
*/
|
|
46
|
+
export type CreateTestAppOptions<
|
|
47
|
+
Ctx,
|
|
48
|
+
Ports extends AnyPorts,
|
|
49
|
+
Routes extends readonly RouteDef<Ctx>[],
|
|
50
|
+
Providers extends readonly AnyProvider[],
|
|
51
|
+
TProvidedHeaders extends string = never,
|
|
52
|
+
> = CreateServerOptions<Ctx, Ports, Routes, Providers> & {
|
|
53
|
+
/**
|
|
54
|
+
* Typed client behavior for `request(...)` and `safeRequest(...)`.
|
|
55
|
+
*/
|
|
56
|
+
client?: TestAppClientOptions<TProvidedHeaders>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Beignet app harness for route/runtime tests.
|
|
61
|
+
*/
|
|
62
|
+
export type TestApp<
|
|
63
|
+
Ctx,
|
|
64
|
+
Ports extends AnyPorts,
|
|
65
|
+
TProvidedHeaders extends string = never,
|
|
66
|
+
> = {
|
|
67
|
+
/**
|
|
68
|
+
* Underlying Web Fetch server.
|
|
69
|
+
*/
|
|
70
|
+
server: FetchServer<Ctx, Ports>;
|
|
71
|
+
/**
|
|
72
|
+
* Fetch implementation bound to the in-memory Beignet server.
|
|
73
|
+
*/
|
|
74
|
+
fetch: typeof fetch;
|
|
75
|
+
/**
|
|
76
|
+
* Final app ports after provider setup.
|
|
77
|
+
*/
|
|
78
|
+
ports: Ports;
|
|
79
|
+
/**
|
|
80
|
+
* Contracts registered through the server route list.
|
|
81
|
+
*/
|
|
82
|
+
contracts: FetchServer<Ctx, Ports>["contracts"];
|
|
83
|
+
/**
|
|
84
|
+
* Call a contract through the test app and return the parsed success body.
|
|
85
|
+
*/
|
|
86
|
+
request: <CLike extends ContractLike>(
|
|
87
|
+
contract: CLike,
|
|
88
|
+
...args: CallArgs<ResolveContract<CLike>, TProvidedHeaders>
|
|
89
|
+
) => Promise<InferSuccessResponse<ResolveContract<CLike>>>;
|
|
90
|
+
/**
|
|
91
|
+
* Call a contract through the test app and return a typed success/error
|
|
92
|
+
* result instead of throwing.
|
|
93
|
+
*/
|
|
94
|
+
safeRequest: <CLike extends ContractLike>(
|
|
95
|
+
contract: CLike,
|
|
96
|
+
...args: CallArgs<ResolveContract<CLike>, TProvidedHeaders>
|
|
97
|
+
) => Promise<
|
|
98
|
+
EndpointResult<
|
|
99
|
+
ResolveContract<CLike>,
|
|
100
|
+
InferEndpointContractError<ResolveContract<CLike>>
|
|
101
|
+
>
|
|
102
|
+
>;
|
|
103
|
+
/**
|
|
104
|
+
* Stop installed providers.
|
|
105
|
+
*/
|
|
106
|
+
stop: () => Promise<void>;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Create a `fetch` implementation that dispatches requests to a Beignet Web
|
|
111
|
+
* server without opening a network port.
|
|
112
|
+
*
|
|
113
|
+
* @param server - Web Fetch server under test.
|
|
114
|
+
* @returns Fetch-compatible function backed by `server.fetch`.
|
|
115
|
+
*/
|
|
116
|
+
export function createTestFetch<Ctx, Ports extends AnyPorts>(
|
|
117
|
+
server: Pick<FetchServer<Ctx, Ports>, "fetch">,
|
|
118
|
+
): typeof fetch {
|
|
119
|
+
return ((input: RequestInfo | URL, init?: RequestInit) => {
|
|
120
|
+
const req = new Request(input, init);
|
|
121
|
+
|
|
122
|
+
return server.fetch(req);
|
|
123
|
+
}) as typeof fetch;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create a Beignet route/runtime test harness.
|
|
128
|
+
*
|
|
129
|
+
* The harness uses `@beignet/web` under the hood, so tests exercise the same
|
|
130
|
+
* request parsing, route hooks, response validation, and error ownership as a
|
|
131
|
+
* Web Fetch runtime without requiring Next.js or a network listener.
|
|
132
|
+
*
|
|
133
|
+
* @param options - Server options plus optional typed-client settings.
|
|
134
|
+
* @returns Test app with typed `request(...)` helpers and a bound `fetch`.
|
|
135
|
+
*/
|
|
136
|
+
export async function createTestApp<
|
|
137
|
+
Ctx,
|
|
138
|
+
Ports extends AnyPorts = AnyPorts,
|
|
139
|
+
Routes extends readonly RouteDef<Ctx>[] = readonly RouteDef<Ctx>[],
|
|
140
|
+
Providers extends readonly AnyProvider[] = readonly AnyProvider[],
|
|
141
|
+
const TProvidedHeaders extends string = never,
|
|
142
|
+
>(
|
|
143
|
+
options: CreateTestAppOptions<
|
|
144
|
+
Ctx,
|
|
145
|
+
Ports,
|
|
146
|
+
Routes,
|
|
147
|
+
Providers,
|
|
148
|
+
TProvidedHeaders
|
|
149
|
+
>,
|
|
150
|
+
): Promise<
|
|
151
|
+
TestApp<Ctx, Ports & ProvidedPortsOfList<Providers>, TProvidedHeaders>
|
|
152
|
+
> {
|
|
153
|
+
const { client: clientOptions, ...serverOptions } = options;
|
|
154
|
+
const server = await createFetchServer(serverOptions);
|
|
155
|
+
const fetch = createTestFetch(server);
|
|
156
|
+
const client = createClient<TProvidedHeaders>({
|
|
157
|
+
...clientOptions,
|
|
158
|
+
baseUrl: clientOptions?.baseUrl ?? "http://beignet.test",
|
|
159
|
+
fetch,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
server,
|
|
164
|
+
fetch,
|
|
165
|
+
ports: server.ports,
|
|
166
|
+
contracts: server.contracts,
|
|
167
|
+
request: (contract, ...args) => client.endpoint(contract).call(...args),
|
|
168
|
+
safeRequest: (contract, ...args) =>
|
|
169
|
+
client.endpoint(contract).safeCall(...args),
|
|
170
|
+
stop: () => server.stop(),
|
|
171
|
+
};
|
|
172
|
+
}
|