@owlmeans/server-api 0.1.18-rc.17 → 0.1.18-rc.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -84,7 +84,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
84
84
  your project's skill store (`.agents/skills/`):
85
85
 
86
86
  ```sh
87
- npx @owlmeans/agent-skills@^0.1.18-rc.12
87
+ npx @owlmeans/agent-skills@^0.1.18-rc.13
88
88
  ```
89
89
 
90
90
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/server-api",
4
- "version": "0.1.18-rc.17",
5
- "generatedAt": "2026-09-04T22:43:25.433Z",
4
+ "version": "0.1.18-rc.18",
5
+ "generatedAt": "2026-09-10T18:54:42.484Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: server-api
3
- description: How to use @owlmeans/server-api — the Fastify-based API server factory, what it mounts and validates, the request pipeline (intermediates, guards, gates, handlers), holdApiPort() for the boot window, and the error-to-status mapping. Auto-invoked when importing server-api types, extending the server middleware stack, or working out why a request answered the status it did.
3
+ description: How to use @owlmeans/server-api — typed protocol handlers, the Fastify-based API server factory, request pipeline, validation, holdApiPort() boot window, and error-to-status mapping. Auto-invoked when importing server-api types, writing protocol handlers, extending the middleware stack, or working out why a request answered the status it did.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,12 +8,14 @@ user-invocable: false
8
8
  # @owlmeans/server-api
9
9
 
10
10
  **Layer:** Server
11
- **Install:** `"@owlmeans/server-api": "^0.1.18-rc.17"` in `dependencies`
11
+ **Install:** `"@owlmeans/server-api": "^0.1.18-rc.18"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
15
15
  | Export | Description |
16
16
  |--------|-------------|
17
+ | `handlers<Context>()` | Build `body`, `params`, and `request` handlers inferred from an immutable protocol |
18
+ | `uploadedFile(request)` | Read Fastify multipart through the typed handler boundary |
17
19
  | `createApiServer(alias)` | Factory for the Fastify-based API server |
18
20
  | `appendApiServer(ctx, alias?)` | Register it and expose `ctx.getApiServer()` |
19
21
  | `handleRequest` / `handleBody` / `handleParams` / `handleIntermediate` | Wrap an async function as an entrypoint handler |
@@ -42,9 +44,30 @@ appendApiServer(context)
42
44
 
43
45
  ## Writing handlers
44
46
 
45
- `handleRequest(fn)` gives the whole request, `handleBody<T>(fn)` the validated body, `handleParams<T>(fn)`
46
- the validated URL params, and `handleIntermediate(fn)` a function that returns a context (or `null`
47
- to leave the chain unchanged) instead of a value. All four resolve the handler's context the same
47
+ For a typed protocol, pass the declaration once and let `handlers<Context>()` infer every request
48
+ section and the response. `body` and `params` are available only when the protocol contract
49
+ declares that section; `request` receives the complete typed request plus alias/auth/entity/path
50
+ metadata.
51
+
52
+ ```typescript
53
+ const handle = handlers<Context>()
54
+
55
+ export const create = handle.body(item.create, async (payload, context, request) => {
56
+ const owner = request.entity?.id
57
+ return await context.resource<ItemResource>('item').create({ ...payload, owner })
58
+ })
59
+ ```
60
+
61
+ The result is a protocol-bound implementation consumed by `bind(protocol, implementation)` from
62
+ `@owlmeans/server-entrypoint` (both exports are available from `@owlmeans/server-app`). Throwing is
63
+ still the failure path; the wrapper resolves successful returns with `EntrypointOutcome.Ok`.
64
+ Use `uploadedFile(request)` when the protocol handler needs the Fastify multipart object.
65
+
66
+ For a legacy entrypoint, `handleRequest(fn)` gives the whole request, `handleBody<T>(fn)` the
67
+ validated body, `handleParams<T>(fn)` the validated URL params, and `handleIntermediate(fn)` a
68
+ function that returns a context (or `null` to leave the chain unchanged) instead of a value.
69
+
70
+ All handler forms resolve the handler's context the same
48
71
  way: from the request when it arrived over HTTP, and from the entrypoint's own context otherwise —
49
72
  the same handler runs unchanged when a queued job or a socket frame reaches it, and only the way it
50
73
  was reached differs.
package/build/index.d.ts CHANGED
@@ -4,4 +4,5 @@ export * from './consts.js';
4
4
  export * from './errors.js';
5
5
  export * from './helper.js';
6
6
  export * from './hold.js';
7
+ export * from './protocol.js';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA"}
package/build/index.js CHANGED
@@ -3,4 +3,5 @@ export * from './consts.js';
3
3
  export * from './errors.js';
4
4
  export * from './helper.js';
5
5
  export * from './hold.js';
6
+ export * from './protocol.js';
6
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA"}
@@ -0,0 +1,26 @@
1
+ import type { BasicConfig, BasicContext } from '@owlmeans/context';
2
+ import type { BodyOf, EntrypointProtocolDeclaration, HandlerRequest, OpenValue, ParamsOf, RequestOf, ResponseOf } from '@owlmeans/entrypoint';
3
+ import type { BoundEntrypointHandler } from '@owlmeans/server-entrypoint';
4
+ type HandlerResponse<Response> = [Response] extends [undefined] ? void | undefined : Response;
5
+ type MaybePromise<Value> = Value | Promise<Value>;
6
+ type BodyProtocol<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends {
7
+ body: OpenValue;
8
+ } ? Protocol : never;
9
+ type ParamsProtocol<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends {
10
+ params: object;
11
+ } ? Protocol : never;
12
+ /**
13
+ * Make server handlers that infer request sections and response values from their protocol.
14
+ * The protocol is passed once; application callbacks never name request or reply generics.
15
+ */
16
+ export declare const handlers: <Context extends BasicContext<BasicConfig>>() => {
17
+ body: <Protocol extends EntrypointProtocolDeclaration>(protocol: Protocol, handler: Protocol extends BodyProtocol<Protocol> ? (payload: BodyOf<Protocol>, context: Context, request: HandlerRequest<RequestOf<Protocol>>) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>> : never) => BoundEntrypointHandler<Protocol>;
18
+ params: <Protocol extends EntrypointProtocolDeclaration>(protocol: Protocol, handler: Protocol extends ParamsProtocol<Protocol> ? (payload: ParamsOf<Protocol>, context: Context, request: HandlerRequest<RequestOf<Protocol>>) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>> : never) => BoundEntrypointHandler<Protocol>;
19
+ request: <Protocol extends EntrypointProtocolDeclaration>(protocol: Protocol, handler: (request: HandlerRequest<RequestOf<Protocol>>, context: Context) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>) => BoundEntrypointHandler<Protocol>;
20
+ };
21
+ /** Access a Fastify-specific upload through an explicit boundary helper, never `request.original`. */
22
+ export declare const uploadedFile: (request: HandlerRequest<{
23
+ body: object;
24
+ }>) => Promise<import('@fastify/multipart').MultipartFile | undefined>;
25
+ export {};
26
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,KAAK,EACV,MAAM,EAAE,6BAA6B,EAAE,cAAc,EAAe,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAC/G,MAAM,sBAAsB,CAAA;AAE7B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAEzE,KAAK,eAAe,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,QAAQ,CAAA;AAC7F,KAAK,YAAY,CAAC,KAAK,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;AACjD,KAAK,YAAY,CAAC,QAAQ,SAAS,6BAA6B,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GAC/G,QAAQ,GAAG,KAAK,CAAA;AACpB,KAAK,cAAc,CAAC,QAAQ,SAAS,6BAA6B,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAChH,QAAQ,GAAG,KAAK,CAAA;AAyCpB;;;GAGG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,SAAS,YAAY,CAAC,WAAW,CAAC;IAChE,IAAI,GAAG,QAAQ,SAAS,6BAA6B,YACzC,QAAQ,WACT,QAAQ,SAAS,YAAY,CAAC,QAAQ,CAAC,GAAG,CAC/C,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,EACzB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,KACzC,YAAY,CAAC,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,GACtD,KAAK,KACR,sBAAsB,CAAC,QAAQ,CAAC;IAKnC,MAAM,GAAG,QAAQ,SAAS,6BAA6B,YAC3C,QAAQ,WACT,QAAQ,SAAS,cAAc,CAAC,QAAQ,CAAC,GAAG,CACjD,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAC3B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,KACzC,YAAY,CAAC,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,GACtD,KAAK,KACR,sBAAsB,CAAC,QAAQ,CAAC;IAKnC,OAAO,GAAG,QAAQ,SAAS,6BAA6B,YAC5C,QAAQ,WACT,CACP,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,EAC5C,OAAO,EAAE,OAAO,KACb,YAAY,CAAC,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,KACvD,sBAAsB,CAAC,QAAQ,CAAC;CACnC,CAAA;AAEF,sGAAsG;AACtG,eAAO,MAAM,YAAY,YACd,cAAc,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,KACxC,OAAO,CAAC,OAAO,oBAAoB,EAAE,aAAa,GAAG,SAAS,CAKhE,CAAA"}
@@ -0,0 +1,41 @@
1
+ import { assertContext } from '@owlmeans/context';
2
+ import { EntrypointOutcome } from '@owlmeans/entrypoint';
3
+ const contextFor = (request, fallback) => {
4
+ const original = request.original;
5
+ if (original != null && typeof original === 'object' && '_ctx' in original) {
6
+ return original._ctx ?? fallback;
7
+ }
8
+ return fallback;
9
+ };
10
+ const bind = (protocol, execute) => ({
11
+ protocol,
12
+ bind: ref => async (request, response) => {
13
+ const fallback = assertContext(ref.ref?.ctx, protocol.alias);
14
+ try {
15
+ const typedRequest = request;
16
+ const value = await execute(typedRequest, contextFor(request, fallback));
17
+ response.resolve(value, EntrypointOutcome.Ok);
18
+ }
19
+ catch (error) {
20
+ response.reject(error);
21
+ }
22
+ return response.value;
23
+ },
24
+ });
25
+ /**
26
+ * Make server handlers that infer request sections and response values from their protocol.
27
+ * The protocol is passed once; application callbacks never name request or reply generics.
28
+ */
29
+ export const handlers = () => ({
30
+ body: (protocol, handler) => bind(protocol, (request, context) => handler(request.body, context, request)),
31
+ params: (protocol, handler) => bind(protocol, (request, context) => handler(request.params, context, request)),
32
+ request: (protocol, handler) => bind(protocol, handler),
33
+ });
34
+ /** Access a Fastify-specific upload through an explicit boundary helper, never `request.original`. */
35
+ export const uploadedFile = async (request) => {
36
+ const original = request.original;
37
+ if (original == null || typeof original !== 'object' || !('file' in original))
38
+ return undefined;
39
+ return original.file();
40
+ };
41
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAkBxD,MAAM,UAAU,GAAG,CACjB,OAAwB,EACxB,QAAiB,EACR,EAAE;IACX,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;IACjC,IAAI,QAAQ,IAAI,IAAI,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC3E,OAAQ,QAAoC,CAAC,IAAI,IAAI,QAAQ,CAAA;IAC/D,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAED,MAAM,IAAI,GAAG,CACX,QAAkB,EAClB,OAAgI,EAC9F,EAAE,CAAC,CAAC;IACtC,QAAQ;IACR,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;QACvC,MAAM,QAAQ,GAAG,aAAa,CAAuB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;QAElF,IAAI,CAAC;YACH,MAAM,YAAY,GAAI,OAA8E,CAAA;YACpG,MAAM,KAAK,GAAG,MAAM,OAAO,CACzB,YAAY,EACZ,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAC9B,CAAA;YACD,QAAQ,CAAC,OAAO,CAAC,KAA6B,EAAE,iBAAiB,CAAC,EAAE,CAAC,CAAA;QACvE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,MAAM,CAAC,KAAc,CAAC,CAAA;QACjC,CAAC;QAED,OAAO,QAAQ,CAAC,KAAK,CAAA;IACvB,CAAC;CACF,CAAC,CAAA;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,GAA8C,EAAE,CAAC,CAAC;IACxE,IAAI,EAAE,CACJ,QAAkB,EAClB,OAKS,EACyB,EAAE,CAAC,IAAI,CAAoB,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAC3F,OAEwD,CAAC,OAAO,CAAC,IAAwB,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAEhH,MAAM,EAAE,CACN,QAAkB,EAClB,OAKS,EACyB,EAAE,CAAC,IAAI,CAAoB,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAC3F,OAEwD,CAAC,OAAO,CAAC,MAA4B,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAEpH,OAAO,EAAE,CACP,QAAkB,EAClB,OAGwD,EACtB,EAAE,CAAC,IAAI,CAAoB,QAAQ,EAAE,OAAO,CAAC;CAClF,CAAC,CAAA;AAEF,sGAAsG;AACtG,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,OAAyC,EACwB,EAAE;IACnE,MAAM,QAAQ,GAAI,OAA2B,CAAC,QAAQ,CAAA;IACtD,IAAI,QAAQ,IAAI,IAAI,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAA;IAE/F,OAAQ,QAA6C,CAAC,IAAI,EAAE,CAAA;AAC9D,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/server-api",
3
- "version": "0.1.18-rc.17",
3
+ "version": "0.1.18-rc.18",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -32,15 +32,15 @@
32
32
  "@fastify/helmet": "^13.1.0",
33
33
  "@fastify/middie": "^9.0.2",
34
34
  "@fastify/multipart": "^9.0.1",
35
- "@owlmeans/api": "^0.1.18-rc.13",
36
- "@owlmeans/auth": "^0.1.18-rc.9",
37
- "@owlmeans/auth-common": "^0.1.18-rc.13",
38
- "@owlmeans/context": "^0.1.18-rc.8",
39
- "@owlmeans/error": "^0.1.18-rc.8",
40
- "@owlmeans/entrypoint": "^0.1.18-rc.11",
41
- "@owlmeans/route": "^0.1.18-rc.9",
42
- "@owlmeans/server-context": "^0.1.18-rc.12",
43
- "@owlmeans/server-entrypoint": "^0.1.18-rc.11",
35
+ "@owlmeans/api": "^0.1.18-rc.14",
36
+ "@owlmeans/auth": "^0.1.18-rc.10",
37
+ "@owlmeans/auth-common": "^0.1.18-rc.14",
38
+ "@owlmeans/context": "^0.1.18-rc.9",
39
+ "@owlmeans/error": "^0.1.18-rc.9",
40
+ "@owlmeans/entrypoint": "^0.1.18-rc.12",
41
+ "@owlmeans/route": "^0.1.18-rc.10",
42
+ "@owlmeans/server-context": "^0.1.18-rc.13",
43
+ "@owlmeans/server-entrypoint": "^0.1.18-rc.12",
44
44
  "ajv-errors": "^3.0.0",
45
45
  "fastify": "^5.0.0",
46
46
  "fastify-raw-body": "^5.0.0",
package/src/index.ts CHANGED
@@ -5,3 +5,4 @@ export * from './consts.js'
5
5
  export * from './errors.js'
6
6
  export * from './helper.js'
7
7
  export * from './hold.js'
8
+ export * from './protocol.js'
@@ -0,0 +1,104 @@
1
+ import { assertContext } from '@owlmeans/context'
2
+ import type { BasicConfig, BasicContext } from '@owlmeans/context'
3
+ import { EntrypointOutcome } from '@owlmeans/entrypoint'
4
+ import type {
5
+ BodyOf, EntrypointProtocolDeclaration, HandlerRequest, OpenRequest, OpenValue, ParamsOf, RequestOf, ResponseOf,
6
+ } from '@owlmeans/entrypoint'
7
+ import type { AbstractRequest } from '@owlmeans/entrypoint'
8
+ import type { BoundEntrypointHandler } from '@owlmeans/server-entrypoint'
9
+
10
+ type HandlerResponse<Response> = [Response] extends [undefined] ? void | undefined : Response
11
+ type MaybePromise<Value> = Value | Promise<Value>
12
+ type BodyProtocol<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends { body: OpenValue }
13
+ ? Protocol : never
14
+ type ParamsProtocol<Protocol extends EntrypointProtocolDeclaration> = RequestOf<Protocol> extends { params: object }
15
+ ? Protocol : never
16
+
17
+ interface ContextCarrier<Context> {
18
+ _ctx?: Context
19
+ }
20
+
21
+ const contextFor = <Context extends BasicContext<BasicConfig>>(
22
+ request: AbstractRequest,
23
+ fallback: Context,
24
+ ): Context => {
25
+ const original = request.original
26
+ if (original != null && typeof original === 'object' && '_ctx' in original) {
27
+ return (original as ContextCarrier<Context>)._ctx ?? fallback
28
+ }
29
+
30
+ return fallback
31
+ }
32
+
33
+ const bind = <Protocol extends EntrypointProtocolDeclaration, Context extends BasicContext<BasicConfig>>(
34
+ protocol: Protocol,
35
+ execute: (request: HandlerRequest<RequestOf<Protocol>>, context: Context) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>,
36
+ ): BoundEntrypointHandler<Protocol> => ({
37
+ protocol,
38
+ bind: ref => async (request, response) => {
39
+ const fallback = assertContext<BasicConfig, Context>(ref.ref?.ctx, protocol.alias)
40
+
41
+ try {
42
+ const typedRequest = (request as HandlerRequest<OpenRequest>) as HandlerRequest<RequestOf<Protocol>>
43
+ const value = await execute(
44
+ typedRequest,
45
+ contextFor(request, fallback),
46
+ )
47
+ response.resolve(value as ResponseOf<Protocol>, EntrypointOutcome.Ok)
48
+ } catch (error) {
49
+ response.reject(error as Error)
50
+ }
51
+
52
+ return response.value
53
+ },
54
+ })
55
+
56
+ /**
57
+ * Make server handlers that infer request sections and response values from their protocol.
58
+ * The protocol is passed once; application callbacks never name request or reply generics.
59
+ */
60
+ export const handlers = <Context extends BasicContext<BasicConfig>>() => ({
61
+ body: <Protocol extends EntrypointProtocolDeclaration>(
62
+ protocol: Protocol,
63
+ handler: Protocol extends BodyProtocol<Protocol> ? (
64
+ payload: BodyOf<Protocol>,
65
+ context: Context,
66
+ request: HandlerRequest<RequestOf<Protocol>>,
67
+ ) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>
68
+ : never,
69
+ ): BoundEntrypointHandler<Protocol> => bind<Protocol, Context>(protocol, (request, context) =>
70
+ (handler as (
71
+ payload: BodyOf<Protocol>, context: Context, request: HandlerRequest<RequestOf<Protocol>>,
72
+ ) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>)(request.body as BodyOf<Protocol>, context, request)),
73
+
74
+ params: <Protocol extends EntrypointProtocolDeclaration>(
75
+ protocol: Protocol,
76
+ handler: Protocol extends ParamsProtocol<Protocol> ? (
77
+ payload: ParamsOf<Protocol>,
78
+ context: Context,
79
+ request: HandlerRequest<RequestOf<Protocol>>,
80
+ ) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>
81
+ : never,
82
+ ): BoundEntrypointHandler<Protocol> => bind<Protocol, Context>(protocol, (request, context) =>
83
+ (handler as (
84
+ payload: ParamsOf<Protocol>, context: Context, request: HandlerRequest<RequestOf<Protocol>>,
85
+ ) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>)(request.params as ParamsOf<Protocol>, context, request)),
86
+
87
+ request: <Protocol extends EntrypointProtocolDeclaration>(
88
+ protocol: Protocol,
89
+ handler: (
90
+ request: HandlerRequest<RequestOf<Protocol>>,
91
+ context: Context,
92
+ ) => MaybePromise<HandlerResponse<ResponseOf<Protocol>>>,
93
+ ): BoundEntrypointHandler<Protocol> => bind<Protocol, Context>(protocol, handler),
94
+ })
95
+
96
+ /** Access a Fastify-specific upload through an explicit boundary helper, never `request.original`. */
97
+ export const uploadedFile = async (
98
+ request: HandlerRequest<{ body: object }>,
99
+ ): Promise<import('@fastify/multipart').MultipartFile | undefined> => {
100
+ const original = (request as AbstractRequest).original
101
+ if (original == null || typeof original !== 'object' || !('file' in original)) return undefined
102
+
103
+ return (original as import('fastify').FastifyRequest).file()
104
+ }