@beignet/web 0.0.3 → 0.0.4
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 +87 -0
- package/README.md +79 -9
- package/dist/index.d.ts +21 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +48 -5
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +62 -2
- package/dist/testing.js.map +1 -1
- package/package.json +4 -2
- package/src/index.ts +58 -7
- package/src/testing.ts +146 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,92 @@
|
|
|
1
1
|
# @beignet/web
|
|
2
2
|
|
|
3
|
+
## 0.0.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8bcb31f: Mark package READMEs with Beignet's experimental alpha status and 0.0.x stability expectations.
|
|
8
|
+
- d137044: Declare `@beignet/core` as a peer dependency with a lockstep version range in
|
|
9
|
+
every integration and provider package instead of a regular `"*"` dependency.
|
|
10
|
+
Installs now always resolve a single shared copy of core, so `instanceof`
|
|
11
|
+
checks such as `isContractError` and upload error identity keep working, and
|
|
12
|
+
mixed Beignet versions fail loudly at install time instead of at runtime.
|
|
13
|
+
|
|
14
|
+
If your package manager does not install peer dependencies automatically, add
|
|
15
|
+
`@beignet/core` to your app alongside these packages. `@beignet/nuqs` now also
|
|
16
|
+
declares `@beignet/react-query` as a peer dependency, and
|
|
17
|
+
`@beignet/provider-storage-s3` now expects you to install
|
|
18
|
+
`@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner` yourself, matching
|
|
19
|
+
how other providers treat their SDKs.
|
|
20
|
+
|
|
21
|
+
- ac78cdf: Make `createAuthHooks` the single route-scoped auth hook API. The factory is
|
|
22
|
+
now curried — `createAuthHooks<AppContext>()({ resolve })` — so added context
|
|
23
|
+
fields are inferred from `resolve` instead of passed as a type argument, and a
|
|
24
|
+
new optional `headers` schema validates the raw lowercase request header
|
|
25
|
+
record before `resolve` runs, giving `resolve` typed credential headers
|
|
26
|
+
without contract casts. On `required()` hooks a header schema failure returns
|
|
27
|
+
a framework-owned 401; `optional()` hooks skip auth resolution; `public()`
|
|
28
|
+
hooks never parse headers. `defineRouteHook` (and the `RouteHookBuilder`
|
|
29
|
+
types) are removed — write non-auth route hooks as plain `RouteHook` object
|
|
30
|
+
literals.
|
|
31
|
+
- 1a79090: Emit Node-compatible ESM: all relative imports in published packages now carry explicit .js extensions, fixing ERR_MODULE_NOT_FOUND when running the CLI or importing package dist files under plain Node.
|
|
32
|
+
- 82c48dc: Add canonical test port and context fixtures, and update generated tests to use them.
|
|
33
|
+
- 303ba07: Define the public HTTP adapter contract and expose the Web Fetch adapter implementation.
|
|
34
|
+
- 493d23b: Make the framework own context assembly with a server context blueprint and first-class service contexts.
|
|
35
|
+
|
|
36
|
+
- `createServer(...)` (and the Next/Web adapters) replace the `createContext` option with `context`. Gate-less contexts keep the plain request-factory shorthand; contexts that declare a `gate` must use the blueprint form `{ gate: (ports) => ports.gate, request, service }`. Returning `gate` from a context factory or hook addition is now a compile error.
|
|
37
|
+
- The gate port gains `gate.attach(ctx)`: it attaches a live, non-enumerable `ctx.gate` getter that re-binds against the receiving object on every access, so identity changes (including auth-hook elevation) can never authorize against a stale context, and spread copies drop the gate loudly instead of silently keeping stale identity. `bind(...)` remains the low-level primitive.
|
|
38
|
+
- Servers expose `createRequestContext(req)` and `createServiceContext(input?)`; the optional `context.service` factory powers schedule, outbox, command, and background contexts. Provider `setup`, `start`, and `stop` receive a late-bound `createServiceContext` so infra providers no longer hand-build background contexts.
|
|
39
|
+
- `createTestContextFactory(...)` now attaches the gate after all `extra` and override fields merge, fixing a stale-identity bug in tests.
|
|
40
|
+
- CLI templates, generators, and doctor diagnostics emit and check the new context blueprint and service-context wrappers.
|
|
41
|
+
|
|
42
|
+
- 89390fe: Decouple devtools from app code with server-owned instrumentation.
|
|
43
|
+
|
|
44
|
+
- `@beignet/core/tracing` is a new dependency-free subpath with the W3C trace
|
|
45
|
+
primitives (`TraceContext`, `createTraceContext`, `createChildTraceContext`,
|
|
46
|
+
`parseTraceparent`, `createTraceparent`, `createTraceId`, `createSpanId`).
|
|
47
|
+
App context types now use `Partial<TraceContext>` instead of importing
|
|
48
|
+
`DevtoolsTraceContext` from `@beignet/devtools`.
|
|
49
|
+
- `createServer(...)` owns request instrumentation through a new
|
|
50
|
+
`instrumentation` option (`ServerInstrumentationOptions | false`). The server
|
|
51
|
+
resolves request IDs and trace context before user hooks and context
|
|
52
|
+
creation, passes them to context factories as `requestId` and `trace`,
|
|
53
|
+
writes `x-request-id`/`traceparent` response headers by default, and records
|
|
54
|
+
request/error events into the instrumentation port resolved from final ports
|
|
55
|
+
(`ports.instrumentation`, then `ports.devtools`). The ambient request
|
|
56
|
+
context (`enterActiveRequestContext` and friends) moved into
|
|
57
|
+
`@beignet/core/server`.
|
|
58
|
+
- `createUseCase(...)` instruments runs by default, resolving the
|
|
59
|
+
instrumentation port from `ctx.ports` per run; opt out with
|
|
60
|
+
`instrumentation: false`. App `onRun` observers run in addition.
|
|
61
|
+
- `createInstrumentedAuditLog({ audit, instrumentation })` in
|
|
62
|
+
`@beignet/core/ports` replaces `createDevtoolsAuditLog`.
|
|
63
|
+
- `createServer(...)` gains `validateResponses` (default `true`) to skip
|
|
64
|
+
route-owned response validation, mirroring the client option.
|
|
65
|
+
- Removed from `@beignet/devtools`: `createDevtoolsHooks`,
|
|
66
|
+
`createDevtoolsUseCaseObserver`, `createDevtoolsAuditLog`, and the
|
|
67
|
+
trace/request-context modules. Apps now only need `createDevtoolsProvider()`
|
|
68
|
+
plus the devtools route; deleting both leaves the rest of the app compiling
|
|
69
|
+
and running unchanged.
|
|
70
|
+
- `@beignet/next` cron and upload helpers resolve their instrumentation sink
|
|
71
|
+
with `resolveProviderInstrumentationPort(ctx.ports)` instead of hardcoding
|
|
72
|
+
`ports.devtools`.
|
|
73
|
+
- `beignet create` templates generate the decoupled shape.
|
|
74
|
+
|
|
75
|
+
- e9c3209: Add reusable actor, tenant, audit assertion, and default-header request helpers for production app tests.
|
|
76
|
+
- 493d23b: Add typed provider-contributed ports. `InferProviderPorts` (replacing
|
|
77
|
+
`ProvidedPortsOfList`) merges the ports a provider list contributes so apps can
|
|
78
|
+
type `ctx.ports` as `AppPorts & InferProviderPorts<typeof providers>` without
|
|
79
|
+
casts. The curried `definePorts<AppPorts>()({ bound, deferred })` form replaces
|
|
80
|
+
throwing stub boilerplate: deferred keys boot as descriptive placeholders and
|
|
81
|
+
`createServer(...)` fails startup with the unbound key list (configurable via
|
|
82
|
+
`onUnboundPorts: "error" | "warn" | "ignore"`). App-local providers can declare
|
|
83
|
+
required ports, app context, and service-context input through the curried
|
|
84
|
+
`createProvider<Requires, Context, ServiceInput>()` form, which types
|
|
85
|
+
`setup({ ports, createServiceContext })` and lifecycle contexts end to end.
|
|
86
|
+
CLI templates and `beignet make` generate and maintain the new forms.
|
|
87
|
+
- 192c6ad: Routes can bind a contract directly to a use case with `{ contract, useCase }` — status is inferred from a sole 2xx response, input defaults to merged path/query/body parts, and validated inputs are not re-parsed when the use case reuses the contract schema by reference. Full `handle` routes remain the escape hatch for headers, streaming, and multi-status responses.
|
|
88
|
+
- 255527d: `createTestApp` defaults `onUnboundPorts` to "ignore" and surfaces unhandled error messages in test responses.
|
|
89
|
+
|
|
3
90
|
## 0.0.3
|
|
4
91
|
|
|
5
92
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @beignet/web
|
|
2
2
|
|
|
3
|
+
> [!CAUTION]
|
|
4
|
+
> Beignet is experimental alpha software. The `0.0.x` package line is for early
|
|
5
|
+
> evaluation, and APIs may change between releases while the framework settles.
|
|
6
|
+
|
|
3
7
|
Web Fetch adapter for Beignet's framework runtime.
|
|
4
8
|
|
|
5
9
|
Use this package when your runtime accepts a standard `Request` and returns a
|
|
@@ -9,7 +13,7 @@ or tests that should avoid a framework-specific adapter.
|
|
|
9
13
|
## Installation
|
|
10
14
|
|
|
11
15
|
```bash
|
|
12
|
-
npm install @beignet/web
|
|
16
|
+
npm install @beignet/web @beignet/core
|
|
13
17
|
```
|
|
14
18
|
|
|
15
19
|
## Quick start
|
|
@@ -21,8 +25,8 @@ import { routes } from "./server/routes";
|
|
|
21
25
|
export const server = await createFetchServer({
|
|
22
26
|
ports,
|
|
23
27
|
routes,
|
|
24
|
-
|
|
25
|
-
requestId
|
|
28
|
+
context: ({ ports, requestId }) => ({
|
|
29
|
+
requestId,
|
|
26
30
|
ports,
|
|
27
31
|
}),
|
|
28
32
|
mapUnhandledError: () => ({
|
|
@@ -56,6 +60,7 @@ import {
|
|
|
56
60
|
createFetchHandler,
|
|
57
61
|
toRequestLike,
|
|
58
62
|
toWebResponse,
|
|
63
|
+
webFetchAdapter,
|
|
59
64
|
} from "@beignet/web";
|
|
60
65
|
```
|
|
61
66
|
|
|
@@ -65,28 +70,53 @@ import {
|
|
|
65
70
|
framework-neutral request shape.
|
|
66
71
|
- `toWebResponse(response)` converts a Beignet response to a standard
|
|
67
72
|
`Response`.
|
|
73
|
+
- `webFetchAdapter` is the concrete
|
|
74
|
+
`HttpAdapter<Request, Response>` implementation for Web Fetch runtimes.
|
|
68
75
|
|
|
69
76
|
Native `Response` instances returned by handlers are passed through unchanged.
|
|
70
77
|
Plain Beignet responses are serialized as JSON unless the body is `undefined`
|
|
71
78
|
or `null`.
|
|
72
79
|
|
|
80
|
+
## Adapter contract
|
|
81
|
+
|
|
82
|
+
`@beignet/core/server` owns framework behavior: route matching, hooks, request
|
|
83
|
+
validation, response validation, error mapping, response ownership, and provider
|
|
84
|
+
lifecycle. `@beignet/web` owns only the Web Fetch edge conversion.
|
|
85
|
+
|
|
86
|
+
The exported `webFetchAdapter` implements the formal core adapter contract:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import type { HttpAdapter } from "@beignet/core/server";
|
|
90
|
+
|
|
91
|
+
export const adapter: HttpAdapter<Request, Response> = webFetchAdapter;
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use the same shape for a future runtime adapter with non-Fetch native request
|
|
95
|
+
or response types.
|
|
96
|
+
|
|
73
97
|
## Testing
|
|
74
98
|
|
|
75
99
|
Use `@beignet/web/testing` to exercise routes through the same Web Fetch
|
|
76
|
-
adapter without opening a network port.
|
|
100
|
+
adapter without opening a network port. Use `@beignet/core/testing` beside it
|
|
101
|
+
when the route needs an app-style context and test ports.
|
|
77
102
|
|
|
78
103
|
```typescript
|
|
104
|
+
import { createTestContextFactory, createTestPorts } from "@beignet/core/testing";
|
|
79
105
|
import { createTestApp } from "@beignet/web/testing";
|
|
80
106
|
import { getTodo } from "./features/todos/contracts";
|
|
81
107
|
import { routes } from "./server/routes";
|
|
82
108
|
|
|
109
|
+
const fixture = createTestPorts<AppContext["ports"]>({
|
|
110
|
+
base: appPorts,
|
|
111
|
+
overrides: { todos: createInMemoryTodoRepository() },
|
|
112
|
+
});
|
|
113
|
+
const createContext = createTestContextFactory<AppContext, AppContext["ports"]>({
|
|
114
|
+
ports: fixture.ports,
|
|
115
|
+
});
|
|
83
116
|
const app = await createTestApp({
|
|
84
|
-
ports,
|
|
117
|
+
ports: fixture.ports,
|
|
85
118
|
routes,
|
|
86
|
-
|
|
87
|
-
requestId: "test-request",
|
|
88
|
-
ports,
|
|
89
|
-
}),
|
|
119
|
+
context: () => createContext(),
|
|
90
120
|
});
|
|
91
121
|
|
|
92
122
|
const todo = await app.request(getTodo, {
|
|
@@ -97,3 +127,43 @@ const todo = await app.request(getTodo, {
|
|
|
97
127
|
`app.request(contract, args)` uses the same typed call arguments as
|
|
98
128
|
`@beignet/core/client`, while `app.safeRequest(contract, args)` returns a typed
|
|
99
129
|
success/error result instead of throwing.
|
|
130
|
+
|
|
131
|
+
`createTestApp(...)` applies two test-friendly defaults, and an explicit
|
|
132
|
+
option always wins:
|
|
133
|
+
|
|
134
|
+
- `onUnboundPorts` defaults to `"ignore"`, so apps with deferred provider
|
|
135
|
+
ports boot without installing every provider. Reading an unbound port still
|
|
136
|
+
throws on use. Production servers created with `createFetchServer(...)` keep
|
|
137
|
+
the strict `"error"` default.
|
|
138
|
+
- `mapUnhandledError` defaults to a mapper that returns
|
|
139
|
+
`{ status: 500, body: { code: "INTERNAL_SERVER_ERROR", message: err.message } }`,
|
|
140
|
+
so failing tests show the real error message instead of a generic response.
|
|
141
|
+
|
|
142
|
+
Apps that declare their context blueprint with `defineServerContext(...)` in
|
|
143
|
+
`server/context.ts` can pass the same value to both the runtime server and
|
|
144
|
+
`createTestApp(...)`:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { appContext } from "../server/context";
|
|
148
|
+
|
|
149
|
+
const app = await createTestApp({ ports, routes, context: appContext });
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Use `createTestRequester(...)` when a test suite repeats the same auth, tenant,
|
|
153
|
+
or correlation headers:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { createTestApp, createTestRequester } from "@beignet/web/testing";
|
|
157
|
+
|
|
158
|
+
const app = await createTestApp({ ports, routes, context: createContext });
|
|
159
|
+
const authedRequest = createTestRequester(app, {
|
|
160
|
+
headers: {
|
|
161
|
+
"x-user-id": "user_1",
|
|
162
|
+
"x-tenant-id": "tenant_1",
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const todo = await authedRequest.request(getTodo, {
|
|
167
|
+
path: { id: "todo_1" },
|
|
168
|
+
});
|
|
169
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import type { AnyPorts } from "@beignet/core/ports";
|
|
2
|
-
import type {
|
|
3
|
-
import type { ContractLike, CreateServerOptions, Handler, HttpRequestLike, HttpResponse, ResolveContract, RouteDef, ServerInstance } from "@beignet/core/server";
|
|
2
|
+
import type { InferProviderPorts, ServiceProvider } from "@beignet/core/providers";
|
|
3
|
+
import type { ContractLike, CreateServerOptions, Handler, HttpAdapter, HttpRequestLike, HttpResponse, ResolveContract, RouteDef, ServerInstance } from "@beignet/core/server";
|
|
4
4
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
5
5
|
/**
|
|
6
6
|
* Server types re-exported for Web Fetch apps.
|
|
7
7
|
*/
|
|
8
|
-
export type { CreateServerOptions, HttpRequestLike, HttpResponse, RouteDef, RouteGroup, RouteHook, ServerInstance, } from "@beignet/core/server";
|
|
8
|
+
export type { AnyUseCaseLike, CreateServerOptions, HandlerRouteDef, HttpAdapter, HttpAdapterApiHandler, HttpAdapterHandler, HttpRequestLike, HttpResponse, RouteDef, RouteGroup, RouteHook, ServerInstance, UseCaseRouteDef, } from "@beignet/core/server";
|
|
9
9
|
/**
|
|
10
10
|
* Server helpers re-exported for Web Fetch apps.
|
|
11
11
|
*/
|
|
12
|
-
export { contractsFromRoutes, createServer, defineRoute, defineRouteGroup,
|
|
13
|
-
type AnyProvider = ServiceProvider<unknown, StandardSchemaV1<any, any>, AnyPorts>;
|
|
12
|
+
export { contractsFromRoutes, createServer, defaultBinderInput, defineRoute, defineRouteGroup, defineRoutes, } from "@beignet/core/server";
|
|
13
|
+
type AnyProvider = ServiceProvider<unknown, StandardSchemaV1<any, any>, AnyPorts, any, any>;
|
|
14
14
|
/**
|
|
15
15
|
* Beignet server adapted to the Web Fetch API.
|
|
16
16
|
*/
|
|
17
|
-
export interface FetchServer<Ctx, Ports extends AnyPorts = AnyPorts> {
|
|
17
|
+
export interface FetchServer<Ctx, Ports extends AnyPorts = AnyPorts, ServiceInput = void> {
|
|
18
18
|
/**
|
|
19
19
|
* Catch-all Web Fetch handler.
|
|
20
20
|
*/
|
|
@@ -29,6 +29,15 @@ export interface FetchServer<Ctx, Ports extends AnyPorts = AnyPorts> {
|
|
|
29
29
|
route: <CLike extends ContractLike>(contractLike: CLike) => {
|
|
30
30
|
handle: (fn: Handler<Ctx, ResolveContract<CLike>>) => (req: Request) => Promise<Response>;
|
|
31
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* Build a fully assembled request context from a framework-neutral request.
|
|
34
|
+
*/
|
|
35
|
+
createRequestContext: ServerInstance<Ctx, Ports, ServiceInput>["createRequestContext"];
|
|
36
|
+
/**
|
|
37
|
+
* Build a fully assembled service context for schedules, outbox drains,
|
|
38
|
+
* commands, and background work.
|
|
39
|
+
*/
|
|
40
|
+
createServiceContext: ServerInstance<Ctx, Ports, ServiceInput>["createServiceContext"];
|
|
32
41
|
/**
|
|
33
42
|
* Registered contract inputs.
|
|
34
43
|
*/
|
|
@@ -50,6 +59,11 @@ export declare function toRequestLike(req: Request): HttpRequestLike;
|
|
|
50
59
|
* Convert a Beignet response into a native `Response`.
|
|
51
60
|
*/
|
|
52
61
|
export declare function toWebResponse(resLike: HttpResponse): Response;
|
|
62
|
+
/**
|
|
63
|
+
* Concrete adapter implementation for Web-standard `Request` and `Response`
|
|
64
|
+
* runtimes.
|
|
65
|
+
*/
|
|
66
|
+
export declare const webFetchAdapter: HttpAdapter<Request, Response>;
|
|
53
67
|
/**
|
|
54
68
|
* Create a Web Fetch handler from a Beignet server or API handler.
|
|
55
69
|
*/
|
|
@@ -62,5 +76,5 @@ export declare function createFetchHandler(serverOrHandler: Pick<ServerInstance<
|
|
|
62
76
|
* `@beignet/next`, which builds on the same Web adapter and adds Next-specific
|
|
63
77
|
* helpers.
|
|
64
78
|
*/
|
|
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 &
|
|
79
|
+
export declare function createFetchServer<Ctx, Ports extends AnyPorts = AnyPorts, ServiceInput = void, Routes extends readonly RouteDef<Ctx>[] = readonly RouteDef<Ctx>[], Providers extends readonly AnyProvider[] = readonly AnyProvider[]>(options: CreateServerOptions<Ctx, Ports, ServiceInput, Routes, Providers>): Promise<FetchServer<Ctx, Ports & InferProviderPorts<Providers>, ServiceInput>>;
|
|
66
80
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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,kBAAkB,EAClB,eAAe,EAChB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACnB,OAAO,EACP,WAAW,EACX,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,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,SAAS,EACT,cAAc,EACd,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAE9B;;GAEG;AACH,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,YAAY,GACb,MAAM,sBAAsB,CAAC;AAE9B,KAAK,WAAW,GAAG,eAAe,CAChC,OAAO,EAEP,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,EAC1B,QAAQ,EAER,GAAG,EAEH,GAAG,CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW,CAC1B,GAAG,EACH,KAAK,SAAS,QAAQ,GAAG,QAAQ,EACjC,YAAY,GAAG,IAAI;IAEnB;;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,oBAAoB,EAAE,cAAc,CAClC,GAAG,EACH,KAAK,EACL,YAAY,CACb,CAAC,sBAAsB,CAAC,CAAC;IAC1B;;;OAGG;IACH,oBAAoB,EAAE,cAAc,CAClC,GAAG,EACH,KAAK,EACL,YAAY,CACb,CAAC,sBAAsB,CAAC,CAAC;IAC1B;;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;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,OAAO,EAAE,QAAQ,CAQ1D,CAAC;AAEF;;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,YAAY,GAAG,IAAI,EACnB,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,YAAY,EAAE,MAAM,EAAE,SAAS,CAAC,GACxE,OAAO,CACR,WAAW,CAAC,GAAG,EAAE,KAAK,GAAG,kBAAkB,CAAC,SAAS,CAAC,EAAE,YAAY,CAAC,CACtE,CAgBA"}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createServer } from "@beignet/core/server";
|
|
|
2
2
|
/**
|
|
3
3
|
* Server helpers re-exported for Web Fetch apps.
|
|
4
4
|
*/
|
|
5
|
-
export { contractsFromRoutes, createServer, defineRoute, defineRouteGroup,
|
|
5
|
+
export { contractsFromRoutes, createServer, defaultBinderInput, defineRoute, defineRouteGroup, defineRoutes, } from "@beignet/core/server";
|
|
6
6
|
/**
|
|
7
7
|
* Convert a native `Request` into Beignet's framework-neutral request shape.
|
|
8
8
|
*/
|
|
@@ -35,6 +35,16 @@ export function toWebResponse(resLike) {
|
|
|
35
35
|
// biome-ignore lint/suspicious/noExplicitAny: Body can be any JSON-serializable value.
|
|
36
36
|
return Response.json(body, { status: resLike.status, headers });
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Concrete adapter implementation for Web-standard `Request` and `Response`
|
|
40
|
+
* runtimes.
|
|
41
|
+
*/
|
|
42
|
+
export const webFetchAdapter = {
|
|
43
|
+
name: "web-fetch",
|
|
44
|
+
toRequestLike,
|
|
45
|
+
toNativeResponse: toWebResponse,
|
|
46
|
+
createHandler: (handler) => async (req) => webFetchAdapter.toNativeResponse(await handler(webFetchAdapter.toRequestLike(req))),
|
|
47
|
+
};
|
|
38
48
|
/**
|
|
39
49
|
* Create a Web Fetch handler from a Beignet server or API handler.
|
|
40
50
|
*/
|
|
@@ -42,7 +52,7 @@ export function createFetchHandler(serverOrHandler) {
|
|
|
42
52
|
const handler = typeof serverOrHandler === "function"
|
|
43
53
|
? serverOrHandler
|
|
44
54
|
: serverOrHandler.api;
|
|
45
|
-
return
|
|
55
|
+
return webFetchAdapter.createHandler(handler);
|
|
46
56
|
}
|
|
47
57
|
/**
|
|
48
58
|
* Create a Beignet server adapted to the Web Fetch API.
|
|
@@ -61,6 +71,8 @@ export async function createFetchServer(options) {
|
|
|
61
71
|
route: (contract) => ({
|
|
62
72
|
handle: (fn) => createFetchHandler(runtime.route(contract).handle(fn)),
|
|
63
73
|
}),
|
|
74
|
+
createRequestContext: runtime.createRequestContext,
|
|
75
|
+
createServiceContext: runtime.createServiceContext,
|
|
64
76
|
contracts: runtime.contracts,
|
|
65
77
|
stop: () => runtime.stop(),
|
|
66
78
|
ports: runtime.ports,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type CallArgs, type ClientConfig, type EndpointResult, type InferEndpointContractError, type InferSuccessResponse } from "@beignet/core/client";
|
|
2
2
|
import type { AnyPorts } from "@beignet/core/ports";
|
|
3
|
-
import type {
|
|
3
|
+
import type { InferProviderPorts, ServiceProvider } from "@beignet/core/providers";
|
|
4
4
|
import type { ContractLike, CreateServerOptions, ResolveContract, RouteDef } from "@beignet/core/server";
|
|
5
5
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
6
|
-
import { type FetchServer } from "./index";
|
|
7
|
-
type AnyProvider = ServiceProvider<unknown, StandardSchemaV1<any, any>, AnyPorts>;
|
|
6
|
+
import { type FetchServer } from "./index.js";
|
|
7
|
+
type AnyProvider = ServiceProvider<unknown, StandardSchemaV1<any, any>, AnyPorts, any, any>;
|
|
8
8
|
/**
|
|
9
9
|
* Client options accepted by `createTestApp(...)`.
|
|
10
10
|
*/
|
|
@@ -19,7 +19,7 @@ export type TestAppClientOptions<TProvidedHeaders extends string = never> = Omit
|
|
|
19
19
|
/**
|
|
20
20
|
* Options for creating a Web Fetch test app.
|
|
21
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> & {
|
|
22
|
+
export type CreateTestAppOptions<Ctx, Ports extends AnyPorts, Routes extends readonly RouteDef<Ctx>[], Providers extends readonly AnyProvider[], TProvidedHeaders extends string = never, ServiceInput = void> = CreateServerOptions<Ctx, Ports, ServiceInput, Routes, Providers> & {
|
|
23
23
|
/**
|
|
24
24
|
* Typed client behavior for `request(...)` and `safeRequest(...)`.
|
|
25
25
|
*/
|
|
@@ -59,6 +59,29 @@ export type TestApp<Ctx, Ports extends AnyPorts, TProvidedHeaders extends string
|
|
|
59
59
|
*/
|
|
60
60
|
stop: () => Promise<void>;
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Default call arguments merged into every typed test request.
|
|
64
|
+
*/
|
|
65
|
+
export type TestRequestDefaults<TDefaultHeaders extends string = string> = {
|
|
66
|
+
/**
|
|
67
|
+
* Headers to apply to each request unless a call overrides the same key.
|
|
68
|
+
*/
|
|
69
|
+
headers?: Record<TDefaultHeaders, string>;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Request helpers with default call arguments already applied.
|
|
73
|
+
*/
|
|
74
|
+
export type TestRequester<TProvidedHeaders extends string = never> = {
|
|
75
|
+
/**
|
|
76
|
+
* Call a contract through the test app using default request values.
|
|
77
|
+
*/
|
|
78
|
+
request: <CLike extends ContractLike>(contract: CLike, ...args: CallArgs<ResolveContract<CLike>, TProvidedHeaders>) => Promise<InferSuccessResponse<ResolveContract<CLike>>>;
|
|
79
|
+
/**
|
|
80
|
+
* Call a contract through the test app using default request values and
|
|
81
|
+
* return a typed success/error result instead of throwing.
|
|
82
|
+
*/
|
|
83
|
+
safeRequest: <CLike extends ContractLike>(contract: CLike, ...args: CallArgs<ResolveContract<CLike>, TProvidedHeaders>) => Promise<EndpointResult<ResolveContract<CLike>, InferEndpointContractError<ResolveContract<CLike>>>>;
|
|
84
|
+
};
|
|
62
85
|
/**
|
|
63
86
|
* Create a `fetch` implementation that dispatches requests to a Beignet Web
|
|
64
87
|
* server without opening a network port.
|
|
@@ -67,6 +90,17 @@ export type TestApp<Ctx, Ports extends AnyPorts, TProvidedHeaders extends string
|
|
|
67
90
|
* @returns Fetch-compatible function backed by `server.fetch`.
|
|
68
91
|
*/
|
|
69
92
|
export declare function createTestFetch<Ctx, Ports extends AnyPorts>(server: Pick<FetchServer<Ctx, Ports>, "fetch">): typeof fetch;
|
|
93
|
+
/**
|
|
94
|
+
* Create typed request helpers that merge default headers into each call.
|
|
95
|
+
*
|
|
96
|
+
* Use this for route tests that repeatedly need the same auth, tenant, locale,
|
|
97
|
+
* or correlation headers while still letting individual calls override them.
|
|
98
|
+
*
|
|
99
|
+
* @param app - Test app returned by `createTestApp(...)`.
|
|
100
|
+
* @param defaults - Default call values to merge into every request.
|
|
101
|
+
* @returns Typed `request(...)` and `safeRequest(...)` helpers.
|
|
102
|
+
*/
|
|
103
|
+
export declare function createTestRequester<Ctx, Ports extends AnyPorts, const TProvidedHeaders extends string = never, const TDefaultHeaders extends string = never>(app: Pick<TestApp<Ctx, Ports, TProvidedHeaders>, "request" | "safeRequest">, defaults: TestRequestDefaults<TDefaultHeaders>): TestRequester<TProvidedHeaders | TDefaultHeaders>;
|
|
70
104
|
/**
|
|
71
105
|
* Create a Beignet route/runtime test harness.
|
|
72
106
|
*
|
|
@@ -74,9 +108,18 @@ export declare function createTestFetch<Ctx, Ports extends AnyPorts>(server: Pic
|
|
|
74
108
|
* request parsing, route hooks, response validation, and error ownership as a
|
|
75
109
|
* Web Fetch runtime without requiring Next.js or a network listener.
|
|
76
110
|
*
|
|
111
|
+
* Test-friendly defaults differ from production servers in two ways, and an
|
|
112
|
+
* explicit option always wins:
|
|
113
|
+
*
|
|
114
|
+
* - `onUnboundPorts` defaults to `"ignore"` so apps with deferred provider
|
|
115
|
+
* ports boot without installing every provider. Reading an unbound port
|
|
116
|
+
* still throws on use.
|
|
117
|
+
* - `mapUnhandledError` defaults to a mapper that surfaces `err.message` in
|
|
118
|
+
* the 500 response body so failing tests show the real error.
|
|
119
|
+
*
|
|
77
120
|
* @param options - Server options plus optional typed-client settings.
|
|
78
121
|
* @returns Test app with typed `request(...)` helpers and a bound `fetch`.
|
|
79
122
|
*/
|
|
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 &
|
|
123
|
+
export declare function createTestApp<Ctx, Ports extends AnyPorts = AnyPorts, ServiceInput = void, 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, ServiceInput>): Promise<TestApp<Ctx, Ports & InferProviderPorts<Providers>, TProvidedHeaders>>;
|
|
81
124
|
export {};
|
|
82
125
|
//# sourceMappingURL=testing.d.ts.map
|
package/dist/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,YAAY,EAEjB,KAAK,cAAc,EACnB,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,EAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
|
package/dist/testing.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createClient, } from "@beignet/core/client";
|
|
2
|
-
import { createFetchServer } from "./index";
|
|
2
|
+
import { createFetchServer } from "./index.js";
|
|
3
3
|
/**
|
|
4
4
|
* Create a `fetch` implementation that dispatches requests to a Beignet Web
|
|
5
5
|
* server without opening a network port.
|
|
@@ -13,6 +13,37 @@ export function createTestFetch(server) {
|
|
|
13
13
|
return server.fetch(req);
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Create typed request helpers that merge default headers into each call.
|
|
18
|
+
*
|
|
19
|
+
* Use this for route tests that repeatedly need the same auth, tenant, locale,
|
|
20
|
+
* or correlation headers while still letting individual calls override them.
|
|
21
|
+
*
|
|
22
|
+
* @param app - Test app returned by `createTestApp(...)`.
|
|
23
|
+
* @param defaults - Default call values to merge into every request.
|
|
24
|
+
* @returns Typed `request(...)` and `safeRequest(...)` helpers.
|
|
25
|
+
*/
|
|
26
|
+
export function createTestRequester(app, defaults) {
|
|
27
|
+
return {
|
|
28
|
+
request: (contract, ...args) => {
|
|
29
|
+
const mergedArgs = mergeTestRequestArgs(args, defaults);
|
|
30
|
+
return app.request(contract, ...mergedArgs);
|
|
31
|
+
},
|
|
32
|
+
safeRequest: (contract, ...args) => {
|
|
33
|
+
const mergedArgs = mergeTestRequestArgs(args, defaults);
|
|
34
|
+
return app.safeRequest(contract, ...mergedArgs);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function defaultTestUnhandledErrorMapper({ err, }) {
|
|
39
|
+
return {
|
|
40
|
+
status: 500,
|
|
41
|
+
body: {
|
|
42
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
43
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
16
47
|
/**
|
|
17
48
|
* Create a Beignet route/runtime test harness.
|
|
18
49
|
*
|
|
@@ -20,12 +51,25 @@ export function createTestFetch(server) {
|
|
|
20
51
|
* request parsing, route hooks, response validation, and error ownership as a
|
|
21
52
|
* Web Fetch runtime without requiring Next.js or a network listener.
|
|
22
53
|
*
|
|
54
|
+
* Test-friendly defaults differ from production servers in two ways, and an
|
|
55
|
+
* explicit option always wins:
|
|
56
|
+
*
|
|
57
|
+
* - `onUnboundPorts` defaults to `"ignore"` so apps with deferred provider
|
|
58
|
+
* ports boot without installing every provider. Reading an unbound port
|
|
59
|
+
* still throws on use.
|
|
60
|
+
* - `mapUnhandledError` defaults to a mapper that surfaces `err.message` in
|
|
61
|
+
* the 500 response body so failing tests show the real error.
|
|
62
|
+
*
|
|
23
63
|
* @param options - Server options plus optional typed-client settings.
|
|
24
64
|
* @returns Test app with typed `request(...)` helpers and a bound `fetch`.
|
|
25
65
|
*/
|
|
26
66
|
export async function createTestApp(options) {
|
|
27
67
|
const { client: clientOptions, ...serverOptions } = options;
|
|
28
|
-
const server = await createFetchServer(
|
|
68
|
+
const server = await createFetchServer({
|
|
69
|
+
...serverOptions,
|
|
70
|
+
onUnboundPorts: serverOptions.onUnboundPorts ?? "ignore",
|
|
71
|
+
mapUnhandledError: serverOptions.mapUnhandledError ?? defaultTestUnhandledErrorMapper,
|
|
72
|
+
});
|
|
29
73
|
const fetch = createTestFetch(server);
|
|
30
74
|
const client = createClient({
|
|
31
75
|
...clientOptions,
|
|
@@ -42,4 +86,20 @@ export async function createTestApp(options) {
|
|
|
42
86
|
stop: () => server.stop(),
|
|
43
87
|
};
|
|
44
88
|
}
|
|
89
|
+
function mergeTestRequestArgs(args, defaults) {
|
|
90
|
+
const [arg] = args;
|
|
91
|
+
const defaultHeaders = defaults.headers ?? {};
|
|
92
|
+
if (!arg) {
|
|
93
|
+
return [{ headers: defaultHeaders }];
|
|
94
|
+
}
|
|
95
|
+
return [
|
|
96
|
+
{
|
|
97
|
+
...arg,
|
|
98
|
+
headers: {
|
|
99
|
+
...defaultHeaders,
|
|
100
|
+
...arg.headers,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
}
|
|
45
105
|
//# sourceMappingURL=testing.js.map
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,GAIb,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beignet/web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Web Fetch adapter for Beignet",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,8 +55,10 @@
|
|
|
55
55
|
"engines": {
|
|
56
56
|
"node": ">=18.0.0"
|
|
57
57
|
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@beignet/core": ">=0.0.3 <1.0.0"
|
|
60
|
+
},
|
|
58
61
|
"dependencies": {
|
|
59
|
-
"@beignet/core": "*",
|
|
60
62
|
"@standard-schema/spec": "^1.0.0"
|
|
61
63
|
},
|
|
62
64
|
"devDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { AnyPorts } from "@beignet/core/ports";
|
|
2
2
|
import type {
|
|
3
|
-
|
|
3
|
+
InferProviderPorts,
|
|
4
4
|
ServiceProvider,
|
|
5
5
|
} from "@beignet/core/providers";
|
|
6
6
|
import type {
|
|
7
7
|
ContractLike,
|
|
8
8
|
CreateServerOptions,
|
|
9
9
|
Handler,
|
|
10
|
+
HttpAdapter,
|
|
10
11
|
HttpRequestLike,
|
|
11
12
|
HttpResponse,
|
|
12
13
|
ResolveContract,
|
|
@@ -20,13 +21,19 @@ import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
|
20
21
|
* Server types re-exported for Web Fetch apps.
|
|
21
22
|
*/
|
|
22
23
|
export type {
|
|
24
|
+
AnyUseCaseLike,
|
|
23
25
|
CreateServerOptions,
|
|
26
|
+
HandlerRouteDef,
|
|
27
|
+
HttpAdapter,
|
|
28
|
+
HttpAdapterApiHandler,
|
|
29
|
+
HttpAdapterHandler,
|
|
24
30
|
HttpRequestLike,
|
|
25
31
|
HttpResponse,
|
|
26
32
|
RouteDef,
|
|
27
33
|
RouteGroup,
|
|
28
34
|
RouteHook,
|
|
29
35
|
ServerInstance,
|
|
36
|
+
UseCaseRouteDef,
|
|
30
37
|
} from "@beignet/core/server";
|
|
31
38
|
|
|
32
39
|
/**
|
|
@@ -35,9 +42,9 @@ export type {
|
|
|
35
42
|
export {
|
|
36
43
|
contractsFromRoutes,
|
|
37
44
|
createServer,
|
|
45
|
+
defaultBinderInput,
|
|
38
46
|
defineRoute,
|
|
39
47
|
defineRouteGroup,
|
|
40
|
-
defineRouteHook,
|
|
41
48
|
defineRoutes,
|
|
42
49
|
} from "@beignet/core/server";
|
|
43
50
|
|
|
@@ -45,13 +52,21 @@ type AnyProvider = ServiceProvider<
|
|
|
45
52
|
unknown,
|
|
46
53
|
// biome-ignore lint/suspicious/noExplicitAny: provider config types are erased at this level
|
|
47
54
|
StandardSchemaV1<any, any>,
|
|
48
|
-
AnyPorts
|
|
55
|
+
AnyPorts,
|
|
56
|
+
// biome-ignore lint/suspicious/noExplicitAny: provider context types are erased at this level
|
|
57
|
+
any,
|
|
58
|
+
// biome-ignore lint/suspicious/noExplicitAny: provider service-input types are erased at this level
|
|
59
|
+
any
|
|
49
60
|
>;
|
|
50
61
|
|
|
51
62
|
/**
|
|
52
63
|
* Beignet server adapted to the Web Fetch API.
|
|
53
64
|
*/
|
|
54
|
-
export interface FetchServer<
|
|
65
|
+
export interface FetchServer<
|
|
66
|
+
Ctx,
|
|
67
|
+
Ports extends AnyPorts = AnyPorts,
|
|
68
|
+
ServiceInput = void,
|
|
69
|
+
> {
|
|
55
70
|
/**
|
|
56
71
|
* Catch-all Web Fetch handler.
|
|
57
72
|
*/
|
|
@@ -70,6 +85,23 @@ export interface FetchServer<Ctx, Ports extends AnyPorts = AnyPorts> {
|
|
|
70
85
|
fn: Handler<Ctx, ResolveContract<CLike>>,
|
|
71
86
|
) => (req: Request) => Promise<Response>;
|
|
72
87
|
};
|
|
88
|
+
/**
|
|
89
|
+
* Build a fully assembled request context from a framework-neutral request.
|
|
90
|
+
*/
|
|
91
|
+
createRequestContext: ServerInstance<
|
|
92
|
+
Ctx,
|
|
93
|
+
Ports,
|
|
94
|
+
ServiceInput
|
|
95
|
+
>["createRequestContext"];
|
|
96
|
+
/**
|
|
97
|
+
* Build a fully assembled service context for schedules, outbox drains,
|
|
98
|
+
* commands, and background work.
|
|
99
|
+
*/
|
|
100
|
+
createServiceContext: ServerInstance<
|
|
101
|
+
Ctx,
|
|
102
|
+
Ports,
|
|
103
|
+
ServiceInput
|
|
104
|
+
>["createServiceContext"];
|
|
73
105
|
/**
|
|
74
106
|
* Registered contract inputs.
|
|
75
107
|
*/
|
|
@@ -121,6 +153,20 @@ export function toWebResponse(resLike: HttpResponse): Response {
|
|
|
121
153
|
return Response.json(body as any, { status: resLike.status, headers });
|
|
122
154
|
}
|
|
123
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Concrete adapter implementation for Web-standard `Request` and `Response`
|
|
158
|
+
* runtimes.
|
|
159
|
+
*/
|
|
160
|
+
export const webFetchAdapter: HttpAdapter<Request, Response> = {
|
|
161
|
+
name: "web-fetch",
|
|
162
|
+
toRequestLike,
|
|
163
|
+
toNativeResponse: toWebResponse,
|
|
164
|
+
createHandler: (handler) => async (req) =>
|
|
165
|
+
webFetchAdapter.toNativeResponse(
|
|
166
|
+
await handler(webFetchAdapter.toRequestLike(req)),
|
|
167
|
+
),
|
|
168
|
+
};
|
|
169
|
+
|
|
124
170
|
/**
|
|
125
171
|
* Create a Web Fetch handler from a Beignet server or API handler.
|
|
126
172
|
*/
|
|
@@ -134,7 +180,7 @@ export function createFetchHandler(
|
|
|
134
180
|
? serverOrHandler
|
|
135
181
|
: serverOrHandler.api;
|
|
136
182
|
|
|
137
|
-
return
|
|
183
|
+
return webFetchAdapter.createHandler(handler);
|
|
138
184
|
}
|
|
139
185
|
|
|
140
186
|
/**
|
|
@@ -148,11 +194,14 @@ export function createFetchHandler(
|
|
|
148
194
|
export async function createFetchServer<
|
|
149
195
|
Ctx,
|
|
150
196
|
Ports extends AnyPorts = AnyPorts,
|
|
197
|
+
ServiceInput = void,
|
|
151
198
|
Routes extends readonly RouteDef<Ctx>[] = readonly RouteDef<Ctx>[],
|
|
152
199
|
Providers extends readonly AnyProvider[] = readonly AnyProvider[],
|
|
153
200
|
>(
|
|
154
|
-
options: CreateServerOptions<Ctx, Ports, Routes, Providers>,
|
|
155
|
-
): Promise<
|
|
201
|
+
options: CreateServerOptions<Ctx, Ports, ServiceInput, Routes, Providers>,
|
|
202
|
+
): Promise<
|
|
203
|
+
FetchServer<Ctx, Ports & InferProviderPorts<Providers>, ServiceInput>
|
|
204
|
+
> {
|
|
156
205
|
const runtime = await createServer(options);
|
|
157
206
|
const fetch = createFetchHandler(runtime);
|
|
158
207
|
|
|
@@ -162,6 +211,8 @@ export async function createFetchServer<
|
|
|
162
211
|
route: (contract) => ({
|
|
163
212
|
handle: (fn) => createFetchHandler(runtime.route(contract).handle(fn)),
|
|
164
213
|
}),
|
|
214
|
+
createRequestContext: runtime.createRequestContext,
|
|
215
|
+
createServiceContext: runtime.createServiceContext,
|
|
165
216
|
contracts: runtime.contracts,
|
|
166
217
|
stop: () => runtime.stop(),
|
|
167
218
|
ports: runtime.ports,
|
package/src/testing.ts
CHANGED
|
@@ -8,23 +8,28 @@ import {
|
|
|
8
8
|
} from "@beignet/core/client";
|
|
9
9
|
import type { AnyPorts } from "@beignet/core/ports";
|
|
10
10
|
import type {
|
|
11
|
-
|
|
11
|
+
InferProviderPorts,
|
|
12
12
|
ServiceProvider,
|
|
13
13
|
} from "@beignet/core/providers";
|
|
14
14
|
import type {
|
|
15
15
|
ContractLike,
|
|
16
16
|
CreateServerOptions,
|
|
17
|
+
HttpResponse,
|
|
17
18
|
ResolveContract,
|
|
18
19
|
RouteDef,
|
|
19
20
|
} from "@beignet/core/server";
|
|
20
21
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
21
|
-
import { createFetchServer, type FetchServer } from "./index";
|
|
22
|
+
import { createFetchServer, type FetchServer } from "./index.js";
|
|
22
23
|
|
|
23
24
|
type AnyProvider = ServiceProvider<
|
|
24
25
|
unknown,
|
|
25
26
|
// biome-ignore lint/suspicious/noExplicitAny: provider config types are erased at this level.
|
|
26
27
|
StandardSchemaV1<any, any>,
|
|
27
|
-
AnyPorts
|
|
28
|
+
AnyPorts,
|
|
29
|
+
// biome-ignore lint/suspicious/noExplicitAny: provider context types are erased at this level.
|
|
30
|
+
any,
|
|
31
|
+
// biome-ignore lint/suspicious/noExplicitAny: provider service-input types are erased at this level.
|
|
32
|
+
any
|
|
28
33
|
>;
|
|
29
34
|
|
|
30
35
|
/**
|
|
@@ -49,7 +54,8 @@ export type CreateTestAppOptions<
|
|
|
49
54
|
Routes extends readonly RouteDef<Ctx>[],
|
|
50
55
|
Providers extends readonly AnyProvider[],
|
|
51
56
|
TProvidedHeaders extends string = never,
|
|
52
|
-
|
|
57
|
+
ServiceInput = void,
|
|
58
|
+
> = CreateServerOptions<Ctx, Ports, ServiceInput, Routes, Providers> & {
|
|
53
59
|
/**
|
|
54
60
|
* Typed client behavior for `request(...)` and `safeRequest(...)`.
|
|
55
61
|
*/
|
|
@@ -106,6 +112,42 @@ export type TestApp<
|
|
|
106
112
|
stop: () => Promise<void>;
|
|
107
113
|
};
|
|
108
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Default call arguments merged into every typed test request.
|
|
117
|
+
*/
|
|
118
|
+
export type TestRequestDefaults<TDefaultHeaders extends string = string> = {
|
|
119
|
+
/**
|
|
120
|
+
* Headers to apply to each request unless a call overrides the same key.
|
|
121
|
+
*/
|
|
122
|
+
headers?: Record<TDefaultHeaders, string>;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Request helpers with default call arguments already applied.
|
|
127
|
+
*/
|
|
128
|
+
export type TestRequester<TProvidedHeaders extends string = never> = {
|
|
129
|
+
/**
|
|
130
|
+
* Call a contract through the test app using default request values.
|
|
131
|
+
*/
|
|
132
|
+
request: <CLike extends ContractLike>(
|
|
133
|
+
contract: CLike,
|
|
134
|
+
...args: CallArgs<ResolveContract<CLike>, TProvidedHeaders>
|
|
135
|
+
) => Promise<InferSuccessResponse<ResolveContract<CLike>>>;
|
|
136
|
+
/**
|
|
137
|
+
* Call a contract through the test app using default request values and
|
|
138
|
+
* return a typed success/error result instead of throwing.
|
|
139
|
+
*/
|
|
140
|
+
safeRequest: <CLike extends ContractLike>(
|
|
141
|
+
contract: CLike,
|
|
142
|
+
...args: CallArgs<ResolveContract<CLike>, TProvidedHeaders>
|
|
143
|
+
) => Promise<
|
|
144
|
+
EndpointResult<
|
|
145
|
+
ResolveContract<CLike>,
|
|
146
|
+
InferEndpointContractError<ResolveContract<CLike>>
|
|
147
|
+
>
|
|
148
|
+
>;
|
|
149
|
+
};
|
|
150
|
+
|
|
109
151
|
/**
|
|
110
152
|
* Create a `fetch` implementation that dispatches requests to a Beignet Web
|
|
111
153
|
* server without opening a network port.
|
|
@@ -123,6 +165,59 @@ export function createTestFetch<Ctx, Ports extends AnyPorts>(
|
|
|
123
165
|
}) as typeof fetch;
|
|
124
166
|
}
|
|
125
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Create typed request helpers that merge default headers into each call.
|
|
170
|
+
*
|
|
171
|
+
* Use this for route tests that repeatedly need the same auth, tenant, locale,
|
|
172
|
+
* or correlation headers while still letting individual calls override them.
|
|
173
|
+
*
|
|
174
|
+
* @param app - Test app returned by `createTestApp(...)`.
|
|
175
|
+
* @param defaults - Default call values to merge into every request.
|
|
176
|
+
* @returns Typed `request(...)` and `safeRequest(...)` helpers.
|
|
177
|
+
*/
|
|
178
|
+
export function createTestRequester<
|
|
179
|
+
Ctx,
|
|
180
|
+
Ports extends AnyPorts,
|
|
181
|
+
const TProvidedHeaders extends string = never,
|
|
182
|
+
const TDefaultHeaders extends string = never,
|
|
183
|
+
>(
|
|
184
|
+
app: Pick<TestApp<Ctx, Ports, TProvidedHeaders>, "request" | "safeRequest">,
|
|
185
|
+
defaults: TestRequestDefaults<TDefaultHeaders>,
|
|
186
|
+
): TestRequester<TProvidedHeaders | TDefaultHeaders> {
|
|
187
|
+
return {
|
|
188
|
+
request: (contract, ...args) => {
|
|
189
|
+
const mergedArgs = mergeTestRequestArgs(args, defaults) as CallArgs<
|
|
190
|
+
ResolveContract<typeof contract>,
|
|
191
|
+
TProvidedHeaders
|
|
192
|
+
>;
|
|
193
|
+
|
|
194
|
+
return app.request(contract, ...mergedArgs);
|
|
195
|
+
},
|
|
196
|
+
safeRequest: (contract, ...args) => {
|
|
197
|
+
const mergedArgs = mergeTestRequestArgs(args, defaults) as CallArgs<
|
|
198
|
+
ResolveContract<typeof contract>,
|
|
199
|
+
TProvidedHeaders
|
|
200
|
+
>;
|
|
201
|
+
|
|
202
|
+
return app.safeRequest(contract, ...mergedArgs);
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function defaultTestUnhandledErrorMapper({
|
|
208
|
+
err,
|
|
209
|
+
}: {
|
|
210
|
+
err: unknown;
|
|
211
|
+
}): HttpResponse {
|
|
212
|
+
return {
|
|
213
|
+
status: 500,
|
|
214
|
+
body: {
|
|
215
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
216
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
126
221
|
/**
|
|
127
222
|
* Create a Beignet route/runtime test harness.
|
|
128
223
|
*
|
|
@@ -130,12 +225,22 @@ export function createTestFetch<Ctx, Ports extends AnyPorts>(
|
|
|
130
225
|
* request parsing, route hooks, response validation, and error ownership as a
|
|
131
226
|
* Web Fetch runtime without requiring Next.js or a network listener.
|
|
132
227
|
*
|
|
228
|
+
* Test-friendly defaults differ from production servers in two ways, and an
|
|
229
|
+
* explicit option always wins:
|
|
230
|
+
*
|
|
231
|
+
* - `onUnboundPorts` defaults to `"ignore"` so apps with deferred provider
|
|
232
|
+
* ports boot without installing every provider. Reading an unbound port
|
|
233
|
+
* still throws on use.
|
|
234
|
+
* - `mapUnhandledError` defaults to a mapper that surfaces `err.message` in
|
|
235
|
+
* the 500 response body so failing tests show the real error.
|
|
236
|
+
*
|
|
133
237
|
* @param options - Server options plus optional typed-client settings.
|
|
134
238
|
* @returns Test app with typed `request(...)` helpers and a bound `fetch`.
|
|
135
239
|
*/
|
|
136
240
|
export async function createTestApp<
|
|
137
241
|
Ctx,
|
|
138
242
|
Ports extends AnyPorts = AnyPorts,
|
|
243
|
+
ServiceInput = void,
|
|
139
244
|
Routes extends readonly RouteDef<Ctx>[] = readonly RouteDef<Ctx>[],
|
|
140
245
|
Providers extends readonly AnyProvider[] = readonly AnyProvider[],
|
|
141
246
|
const TProvidedHeaders extends string = never,
|
|
@@ -145,13 +250,19 @@ export async function createTestApp<
|
|
|
145
250
|
Ports,
|
|
146
251
|
Routes,
|
|
147
252
|
Providers,
|
|
148
|
-
TProvidedHeaders
|
|
253
|
+
TProvidedHeaders,
|
|
254
|
+
ServiceInput
|
|
149
255
|
>,
|
|
150
256
|
): Promise<
|
|
151
|
-
TestApp<Ctx, Ports &
|
|
257
|
+
TestApp<Ctx, Ports & InferProviderPorts<Providers>, TProvidedHeaders>
|
|
152
258
|
> {
|
|
153
259
|
const { client: clientOptions, ...serverOptions } = options;
|
|
154
|
-
const server = await createFetchServer(
|
|
260
|
+
const server = await createFetchServer({
|
|
261
|
+
...serverOptions,
|
|
262
|
+
onUnboundPorts: serverOptions.onUnboundPorts ?? "ignore",
|
|
263
|
+
mapUnhandledError:
|
|
264
|
+
serverOptions.mapUnhandledError ?? defaultTestUnhandledErrorMapper,
|
|
265
|
+
});
|
|
155
266
|
const fetch = createTestFetch(server);
|
|
156
267
|
const client = createClient<TProvidedHeaders>({
|
|
157
268
|
...clientOptions,
|
|
@@ -170,3 +281,31 @@ export async function createTestApp<
|
|
|
170
281
|
stop: () => server.stop(),
|
|
171
282
|
};
|
|
172
283
|
}
|
|
284
|
+
|
|
285
|
+
function mergeTestRequestArgs<
|
|
286
|
+
CLike extends ContractLike,
|
|
287
|
+
TProvidedHeaders extends string,
|
|
288
|
+
>(
|
|
289
|
+
args: CallArgs<ResolveContract<CLike>, TProvidedHeaders>,
|
|
290
|
+
defaults: TestRequestDefaults<string>,
|
|
291
|
+
): CallArgs<ResolveContract<CLike>, TProvidedHeaders> {
|
|
292
|
+
const [arg] = args;
|
|
293
|
+
const defaultHeaders = defaults.headers ?? {};
|
|
294
|
+
|
|
295
|
+
if (!arg) {
|
|
296
|
+
return [{ headers: defaultHeaders }] as CallArgs<
|
|
297
|
+
ResolveContract<CLike>,
|
|
298
|
+
TProvidedHeaders
|
|
299
|
+
>;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return [
|
|
303
|
+
{
|
|
304
|
+
...arg,
|
|
305
|
+
headers: {
|
|
306
|
+
...defaultHeaders,
|
|
307
|
+
...arg.headers,
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
] as CallArgs<ResolveContract<CLike>, TProvidedHeaders>;
|
|
311
|
+
}
|