@owlmeans/server-api 0.1.18-rc.33 → 0.1.18-rc.35

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
@@ -10,7 +10,7 @@ only when composing a context without that package. WebSocket routes belong to
10
10
  ## Installation
11
11
 
12
12
  ```bash
13
- bun add @owlmeans/server-api@^0.1.18-rc.33
13
+ bun add @owlmeans/server-api@^0.1.18-rc.35
14
14
  ```
15
15
 
16
16
  ## Concepts
@@ -240,7 +240,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
240
240
  your project's skill store (`.agents/skills/`):
241
241
 
242
242
  ```sh
243
- npx @owlmeans/agent-skills@^0.1.18-rc.28
243
+ npx @owlmeans/agent-skills@^0.1.18-rc.31
244
244
  ```
245
245
 
246
246
  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.33",
5
- "generatedAt": "2026-09-17T08:16:11.165Z",
4
+ "version": "0.1.18-rc.35",
5
+ "generatedAt": "2026-09-21T21:54:22.930Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -7,7 +7,7 @@ user-invocable: false
7
7
 
8
8
  # @owlmeans/server-api
9
9
 
10
- **Install:** `bun add @owlmeans/server-api@^0.1.18-rc.33`
10
+ **Install:** `bun add @owlmeans/server-api@^0.1.18-rc.35`
11
11
 
12
12
  Make handlers from the protocol declaration so input and output types stay coupled to the shared
13
13
  contract:
@@ -102,5 +102,12 @@ body and a status from `errorStatus(error)` (`./utils`), resolved in this order:
102
102
  `uploadedFile(request)` is the Fastify multipart boundary. Keep raw Fastify access there rather
103
103
  than reaching through `request.original` in application code.
104
104
 
105
+ ## Error exposure
106
+
107
+ `handleError` always assigns an incident UUID, attaches it to the logged error and returns it in the
108
+ `X-Incident-ID` response header (exposed through CORS). A production response body contains only
109
+ that id; an explicit `cfg.http.errors.exposure = 'development'` uses the typed marshalled form with
110
+ message and stack. Keep the default production-safe, and tell a client to report the incident id.
111
+
105
112
  Do not use unbound compatibility handler wrappers. For a WebSocket route use
106
113
  `@owlmeans/server-socket`'s `connection(protocol, callback)`.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAiC5D,KAAK,MAAM,GAAG,YAAY,CAAA;AAK1B,eAAO,MAAM,eAAe,UAAW,MAAM,KAAG,SAkJ/C,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,aAAa,CAAC,CAAC,CAAC,OACrE,CAAC,UAAS,MAAM,KACpB,CAAC,GAAG,eAWN,CAAA"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAkC5D,KAAK,MAAM,GAAG,YAAY,CAAA;AAK1B,eAAO,MAAM,eAAe,UAAW,MAAM,KAAG,SAkJ/C,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,aAAa,CAAC,CAAC,CAAC,OACrE,CAAC,UAAS,MAAM,KACpB,CAAC,GAAG,eAWN,CAAA"}
package/build/server.js CHANGED
@@ -5,6 +5,7 @@ import { RouteMethod } from '@owlmeans/route';
5
5
  import { createServerHandler, fixFormatDates } from './utils/index.js';
6
6
  import { provideResponse } from '@owlmeans/entrypoint';
7
7
  import { TOKEN_UPDATE } from '@owlmeans/auth-common';
8
+ import { INCIDENT_ID_HEADER } from './utils/error.js';
8
9
  import Fastify from 'fastify';
9
10
  import cors from '@fastify/cors';
10
11
  import rawBody from 'fastify-raw-body';
@@ -79,7 +80,7 @@ export const createApiServer = (alias) => {
79
80
  ...Object.values(RouteMethod).map(method => method.toUpperCase()),
80
81
  'HEAD', 'OPTIONS'
81
82
  ],
82
- exposedHeaders: [TOKEN_UPDATE]
83
+ exposedHeaders: [TOKEN_UPDATE, INCIDENT_ID_HEADER]
83
84
  });
84
85
  await server.register(Multipart, {
85
86
  throwFileSizeLimit: true,
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAClF,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE3E,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEpD,OAAO,OAAO,MAAM,SAAS,CAAA;AAE7B,OAAO,IAAI,MAAM,eAAe,CAAA;AAChC,OAAO,OAAO,MAAM,kBAAkB,CAAA;AACtC,OAAO,MAAM,MAAM,iBAAiB,CAAA;AACpC,OAAO,MAAM,MAAM,iBAAiB,CAAA;AACpC,OAAO,SAAS,MAAM,oBAAoB,CAAA;AAE1C,OAAO,aAAa,MAAM,aAAa,CAAA;AACvC,OAAO,GAAG,MAAM,KAAK,CAAA;AACrB,OAAO,SAAS,MAAM,YAAY,CAAA;AAGlC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC;IAClB,gBAAgB,EAAE,IAAI;IACtB,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,IAAI;IACjB,SAAS,EAAE,IAAI;IACf,MAAM,EAAE,KAAK;CACd,CAAC,CAAA;AACF,aAAa,CAAC,GAAG,CAAC,CAAA;AAClB,SAAS,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAA;AAKrC,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,EAAE,CAAA;AAElC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAa,EAAa,EAAE;IAC1D,MAAM,QAAQ,GAAG,WAAW,KAAK,EAAE,CAAA;IACnC,MAAM,cAAc,GAAG,CAAC,OAA4B,EAAW,EAAE,CAAC,aAAa,CAAkB,OAAO,EAAE,QAAQ,CAAC,CAAA;IAEnH,MAAM,OAAO,GAAG,aAAa,CAAY,KAAK,EAAE;QAC9C,MAAM,EAAE,OAAO,CAAC;YACd,MAAM,EAAE,IAAI;YACZ,SAAS;YACT;;;;;;;;;cASE;SACH,CAAC;QAEF,MAAM,EAAE,KAAK,IAAI,EAAE;YACjB,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,GAAc,CAAC,CAAA;YAEtD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YAExD,MAAM,IAAI,GAAG,MAAM,EAAE,YAAY,IAAI,MAAM,EAAE,IAAI,IAAI,IAAI,CAAA;YACzD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAA;YAC/D,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YAC3C,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,yBAAyB,IAAI,GAAG,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;YAEzF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;gBACzB,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;oBAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;gBACjB,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;QACJ,CAAC;KACF,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE;QACvB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACpC,MAAM,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;YAC5B,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;QACvD,CAAC;QAED,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,GAAc,CAAC,CAAA;QAEtD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QAC7B,oFAAoF;QACpF,MAAM,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;QAC7D,4DAA4D;QAC5D,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;YAC1B,MAAM,EAAE,GAAG;YACX,wEAAwE;YACxE,0EAA0E;YAC1E,mEAAmE;YACnE,wEAAwE;YACxE,6DAA6D;YAC7D,wEAAwE;YACxE,yEAAyE;YACzE,8EAA8E;YAC9E,OAAO,EAAE;gBACP,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;gBACjE,MAAM,EAAE,SAAS;aAClB;YACD,cAAc,EAAE,CAAC,YAAY,CAAC;SAC/B,CAAC,CAAA;QAEF,MAAM,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE;YAC/B,kBAAkB,EAAE,IAAI;YACxB,sEAAsE;YACtE,kBAAkB,EAAE,WAAW;YAC/B,MAAM,EAAE;gBACN,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;gBACzB,KAAK,EAAE,CAAC;aACT;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QAC7B,MAAM,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QAClF,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QAE7B,MAAM,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACpD,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,GAAc,CAAC,CAAA;YACtD,uEAAuE;YACvE,oEAAoE;YACpE,qEAAqE;YACrE,wEAAwE;YACxE,IAAI,SAAS,GAAG,KAAK,CAAC;YACtB,uDAAuD;YACtD,OAAe,CAAC,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,EAAoC,CAAC,MAAM,CAC1F,MAAM,CAAC,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,cAAc,EAAE,CAC3E,CAAC,MAAM,CAAmB,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;gBACnD,IAAI,OAAO,GAAG,MAAM,OAAO,CAAA;gBAC3B,IAAI,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;oBAC5B,OAAO,OAAO,CAAA;gBAChB,CAAC;gBAED,+EAA+E;gBAC/E,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;oBACzE,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;oBACvC,MAAM,cAAc,GAAG,cAAc,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;oBAClE,cAAc,CAAC,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAA;oBACtC,MAAM,MAAM,GAAY,MAAM,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAA;oBACrE,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;wBACnB,OAAO,GAAG,MAAM,CAAA;oBAClB,CAAC;oBACD,SAAS,GAAG,eAAe,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,SAAS,CAAA;gBACjE,CAAC;gBACD,OAAO,OAAO,CAAA;YAChB,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;QAC9B,CAAC,CAAC,CAAA;QAEF,OAAO,CAAC,WAAW,EAAoC;aACpD,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;aACnF,OAAO,CAAC,MAAM,CAAC,EAAE;YAChB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,IAAI,WAAW,CAAC,GAAG,CAAA;YAC3D,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;gBAC1B,OAAM;YACR,CAAC;YACD,MAAM,CAAC,KAAK,CAAC;gBACX,GAAG,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM;gBAC3B,MAAM,EAAE;oBACN,QAAQ,EAAE;wBACR,kBAAkB;wBAClB,mCAAmC;wBACnC,qBAAqB;qBACtB;oBACD,WAAW,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;oBACvD,GAAG,CACD;wBACE,WAAW,CAAC,IAAI;wBAChB,WAAW,CAAC,KAAK;wBACjB,WAAW,CAAC,GAAG;qBAChB,CAAC,QAAQ,CAAC,MAAM,CAAC;wBAChB,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,EAAE;wBACrD,CAAC,CAAC,EAAE,CACP;oBACD,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,IAAI,EAAE,CAAC;oBACnD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ;oBACjC,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,IAAI,EAAE,CAAC;iBACtD;gBACD,OAAO,EAAE,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC;aAC/C,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QAEJ,OAAO,CAAC,WAAW,GAAG,IAAI,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,GAAM,EAAE,KAAK,GAAW,aAAa,EAChB,EAAE;IACvB,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;IACtC,MAAM,OAAO,GAAG,GAA0B,CAAA;IAE1C,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;IAEhC,IAAI,OAAO,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;QACjC,OAAO,CAAC,YAAY,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IACzD,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/build/types.d.ts CHANGED
@@ -13,7 +13,16 @@ export interface Response extends FastifyReply {
13
13
  export interface ApiServerAppend {
14
14
  getApiServer: () => ApiServer;
15
15
  }
16
+ export type HttpErrorExposure = 'production' | 'development';
17
+ export interface HttpErrorsConfig {
18
+ /** Production is the safe default; development is the only mode that exposes stacks. */
19
+ exposure?: HttpErrorExposure;
20
+ }
21
+ export interface HttpConfig {
22
+ errors?: HttpErrorsConfig;
23
+ }
16
24
  export interface Config extends ServerConfig {
25
+ http?: HttpConfig;
17
26
  }
18
27
  export interface Context<C extends Config = Config> extends ServerContext<C>, ApiServerAppend {
19
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC5E,OAAO,kCAAkC,CAAA;AAEzC,MAAM,WAAW,SAAU,SAAQ,kBAAkB;IACnD,MAAM,EAAE,eAAe,CAAA;IAEvB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5B;AAED,MAAM,WAAW,OAAQ,SAAQ,cAAc;CAAI;AAEnD,MAAM,WAAW,QAAS,SAAQ,YAAY;CAAI;AAElD,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,SAAS,CAAA;CAC9B;AAED,MAAM,WAAW,MAAO,SAAQ,YAAY;CAAI;AAEhD,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC,EAC1E,eAAe;CAAI"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC5E,OAAO,kCAAkC,CAAA;AAEzC,MAAM,WAAW,SAAU,SAAQ,kBAAkB;IACnD,MAAM,EAAE,eAAe,CAAA;IAEvB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5B;AAED,MAAM,WAAW,OAAQ,SAAQ,cAAc;CAAI;AAEnD,MAAM,WAAW,QAAS,SAAQ,YAAY;CAAI;AAElD,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,SAAS,CAAA;CAC9B;AAED,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,aAAa,CAAA;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,wFAAwF;IACxF,QAAQ,CAAC,EAAE,iBAAiB,CAAA;CAC7B;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAC1B;AAED,MAAM,WAAW,MAAO,SAAQ,YAAY;IAC1C,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC,EAC1E,eAAe;CAAI"}
@@ -1,4 +1,6 @@
1
1
  import type { FastifyReply } from 'fastify';
2
+ import type { Config, HttpErrorExposure } from '../types.js';
3
+ export declare const INCIDENT_ID_HEADER = "X-Incident-ID";
2
4
  /**
3
5
  * What an error class may declare about the HTTP status it is answered with.
4
6
  *
@@ -8,15 +10,17 @@ import type { FastifyReply } from 'fastify';
8
10
  */
9
11
  export interface HttpStatusDeclaration {
10
12
  httpStatus?: unknown;
13
+ /** Explicit opt-in for a declared 5xx response. Client-error declarations need no opt-in. */
14
+ allowServerErrorStatus?: unknown;
11
15
  }
12
16
  /**
13
- * The 4xx status an error's class declares, or `null` when it declares none that may be honoured.
17
+ * The HTTP status an error's class declares, or `null` when it declares none that may be honoured.
14
18
  *
15
19
  * Read through the constructor chain — a static property is inherited, so a subclass answers what
16
- * its nearest declaring ancestor says, and redeclares to change it. Only an integer 4xx is honoured:
17
- * a declaration is how a class says "this is the caller's condition, not a fault", and a 5xx, a 2xx,
18
- * a string or a fraction says nothing a boundary can act on. The read is structural, so a class from
19
- * a duplicate module copy declares exactly what the imported one does.
20
+ * its nearest declaring ancestor says, and redeclares to change it. An integer 4xx is honoured
21
+ * directly. A 5xx is honoured only with `allowServerErrorStatus = true`, so a domain class must
22
+ * explicitly opt in to exposing a precise availability response. The read is structural, so a
23
+ * class from a duplicate module copy declares exactly what the imported one does.
20
24
  */
21
25
  export declare const declaredErrorStatus: (error: unknown) => number | null;
22
26
  /**
@@ -33,10 +37,21 @@ export declare const declaredErrorStatus: (error: unknown) => number | null;
33
37
  * **Order matters.** `AuthForbidden extends AuthorizationError`, so the 403 branch has to be
34
38
  * tested first or every refusal of a permission is reported as a failure to authenticate. Both
35
39
  * auth branches come before a class's own declaration, so an auth refusal can never be restated
36
- * as something else. Everything that is neither an auth error nor a class declaring a 4xx
40
+ * as something else. Everything that is neither an auth error nor a class declaring an allowed
37
41
  * {@link declaredErrorStatus} answers 500.
38
42
  */
39
43
  export declare const errorStatus: (error: unknown) => number;
44
+ export interface SerializedHttpError {
45
+ status: number;
46
+ body: string;
47
+ incidentId: string;
48
+ }
49
+ /** Invalid or absent configuration always resolves to the production-safe policy. */
50
+ export declare const errorExposure: (config?: Pick<Config, 'http'>) => HttpErrorExposure;
51
+ /** One central wire shape for every server error. */
52
+ export declare const serializeError: (error: Error, exposure?: HttpErrorExposure) => SerializedHttpError;
53
+ /** Apply safe response metadata carried by an error without coupling its package to Fastify. */
54
+ export declare const applyErrorHeaders: (error: unknown, reply: FastifyReply) => void;
40
55
  /**
41
56
  * Answer a thrown error: a status, and the marshalled `ResilientError` as the body.
42
57
  *
@@ -47,5 +62,5 @@ export declare const errorStatus: (error: unknown) => number;
47
62
  * Only when the thrown error answers 500 is the ENSURED error asked, because the rebuild is what
48
63
  * gives a status to a marshalled error that crossed a hop as a plain `Error`.
49
64
  */
50
- export declare const handleError: (error: Error, reply: FastifyReply) => void;
65
+ export declare const handleError: (error: Error, reply: FastifyReply, exposure?: HttpErrorExposure) => void;
51
66
  //# sourceMappingURL=error.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/utils/error.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAK3C;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAKD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,UAAW,OAAO,KAAG,MAAM,GAAG,IAW7D,CAAA;AAkCD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,WAAW,UAAW,OAAO,KAAG,MAkB5C,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,UAAW,KAAK,SAAS,YAAY,SAO5D,CAAA"}
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/utils/error.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAK3C,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAE5D,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AAEjD;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6FAA6F;IAC7F,sBAAsB,CAAC,EAAE,OAAO,CAAA;CACjC;AAOD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,UAAW,OAAO,KAAG,MAAM,GAAG,IAc7D,CAAA;AAkCD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,WAAW,UAAW,OAAO,KAAG,MAkB5C,CAAA;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,qFAAqF;AACrF,eAAO,MAAM,aAAa,YAAa,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,iBACmB,CAAA;AAsBjF,qDAAqD;AACrD,eAAO,MAAM,cAAc,UAClB,KAAK,aAAY,iBAAiB,KACxC,mBAcF,CAAA;AAED,gGAAgG;AAChG,eAAO,MAAM,iBAAiB,UAAW,OAAO,SAAS,YAAY,KAAG,IAQvE,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,UACf,KAAK,SAAS,YAAY,aAAY,iBAAiB,SAY/D,CAAA"}
@@ -1,28 +1,37 @@
1
1
  import { FORBIDDEN_ERROR, SERVER_ERROR, UNAUTHORIZED_ERROR } from '@owlmeans/api';
2
2
  import { AccessError, AuthFailedError } from '../errors.js';
3
3
  import { AuthForbidden, AuthorizationError } from '@owlmeans/auth';
4
- import { ResilientError } from '@owlmeans/error';
4
+ import { isResilientError, ResilientError, SEPARATOR } from '@owlmeans/error';
5
+ import { randomUUID } from 'node:crypto';
6
+ export const INCIDENT_ID_HEADER = 'X-Incident-ID';
5
7
  const CLIENT_ERROR_FIRST = 400;
6
8
  const CLIENT_ERROR_LAST = 499;
9
+ const SERVER_ERROR_FIRST = 500;
10
+ const SERVER_ERROR_LAST = 599;
7
11
  /**
8
- * The 4xx status an error's class declares, or `null` when it declares none that may be honoured.
12
+ * The HTTP status an error's class declares, or `null` when it declares none that may be honoured.
9
13
  *
10
14
  * Read through the constructor chain — a static property is inherited, so a subclass answers what
11
- * its nearest declaring ancestor says, and redeclares to change it. Only an integer 4xx is honoured:
12
- * a declaration is how a class says "this is the caller's condition, not a fault", and a 5xx, a 2xx,
13
- * a string or a fraction says nothing a boundary can act on. The read is structural, so a class from
14
- * a duplicate module copy declares exactly what the imported one does.
15
+ * its nearest declaring ancestor says, and redeclares to change it. An integer 4xx is honoured
16
+ * directly. A 5xx is honoured only with `allowServerErrorStatus = true`, so a domain class must
17
+ * explicitly opt in to exposing a precise availability response. The read is structural, so a
18
+ * class from a duplicate module copy declares exactly what the imported one does.
15
19
  */
16
20
  export const declaredErrorStatus = (error) => {
17
21
  if (error == null || typeof error !== 'object') {
18
22
  return null;
19
23
  }
20
- const status = error.constructor?.httpStatus;
21
- if (typeof status !== 'number' || !Number.isInteger(status)
22
- || status < CLIENT_ERROR_FIRST || status > CLIENT_ERROR_LAST) {
24
+ const declaration = error.constructor;
25
+ const status = declaration?.httpStatus;
26
+ if (typeof status !== 'number' || !Number.isInteger(status)) {
23
27
  return null;
24
28
  }
25
- return status;
29
+ if (status >= CLIENT_ERROR_FIRST && status <= CLIENT_ERROR_LAST)
30
+ return status;
31
+ if (status >= SERVER_ERROR_FIRST && status <= SERVER_ERROR_LAST
32
+ && declaration?.allowServerErrorStatus === true)
33
+ return status;
34
+ return null;
26
35
  };
27
36
  /**
28
37
  * The registered type names an error carries: its own `type`, and the static `typeName` of every
@@ -66,7 +75,7 @@ const isOfFamily = (names, family) => family.some(typeName => names.has(typeName
66
75
  * **Order matters.** `AuthForbidden extends AuthorizationError`, so the 403 branch has to be
67
76
  * tested first or every refusal of a permission is reported as a failure to authenticate. Both
68
77
  * auth branches come before a class's own declaration, so an auth refusal can never be restated
69
- * as something else. Everything that is neither an auth error nor a class declaring a 4xx
78
+ * as something else. Everything that is neither an auth error nor a class declaring an allowed
70
79
  * {@link declaredErrorStatus} answers 500.
71
80
  */
72
81
  export const errorStatus = (error) => {
@@ -87,6 +96,53 @@ export const errorStatus = (error) => {
87
96
  }
88
97
  return declaredErrorStatus(error) ?? SERVER_ERROR;
89
98
  };
99
+ /** Invalid or absent configuration always resolves to the production-safe policy. */
100
+ export const errorExposure = (config) => config?.http?.errors?.exposure === 'development' ? 'development' : 'production';
101
+ /**
102
+ * Preserve a typed/marshalled error when possible without asking `ensure` to convert a plain
103
+ * error. In particular, `ensure` deliberately rethrows SyntaxError; an HTTP boundary still has
104
+ * to serialize it safely and let the incident log retain the original object.
105
+ */
106
+ const normalizeForWire = (error) => {
107
+ if (error instanceof SyntaxError || isResilientError(error)) {
108
+ return error;
109
+ }
110
+ if (error.message.includes(SEPARATOR)) {
111
+ const ensured = ResilientError.ensure(error);
112
+ // The catch-all converter's legacy constructor shape shifts an ordinary error's fields.
113
+ // Only keep `ensure`'s result when a registered wire prefix actually rebuilt something.
114
+ if (ensured.type !== error.message || ensured.message !== error.stack) {
115
+ return ensured;
116
+ }
117
+ }
118
+ return error;
119
+ };
120
+ /** One central wire shape for every server error. */
121
+ export const serializeError = (error, exposure = 'production') => {
122
+ const normalized = normalizeForWire(error);
123
+ const thrown = errorStatus(error);
124
+ const status = thrown !== SERVER_ERROR ? thrown : errorStatus(normalized);
125
+ const incidentId = randomUUID();
126
+ // The server log owns every diagnostic. Production callers receive only its correlation key;
127
+ // development keeps the typed wire form so local clients can retain their usual error details.
128
+ Object.assign(error, { incidentId });
129
+ if (isResilientError(normalized))
130
+ normalized.incidentId = incidentId;
131
+ const body = exposure === 'production'
132
+ ? incidentId
133
+ : ResilientError.marshal(normalized, { includeStack: true, incidentId }).message;
134
+ return { status, body, incidentId };
135
+ };
136
+ /** Apply safe response metadata carried by an error without coupling its package to Fastify. */
137
+ export const applyErrorHeaders = (error, reply) => {
138
+ if (error == null || typeof error !== 'object') {
139
+ return;
140
+ }
141
+ const retryAfter = error.retryAfter;
142
+ if (typeof retryAfter === 'number' && Number.isFinite(retryAfter) && retryAfter > 0) {
143
+ reply.header('Retry-After', String(Math.ceil(retryAfter)));
144
+ }
145
+ };
90
146
  /**
91
147
  * Answer a thrown error: a status, and the marshalled `ResilientError` as the body.
92
148
  *
@@ -97,12 +153,16 @@ export const errorStatus = (error) => {
97
153
  * Only when the thrown error answers 500 is the ENSURED error asked, because the rebuild is what
98
154
  * gives a status to a marshalled error that crossed a hop as a plain `Error`.
99
155
  */
100
- export const handleError = (error, reply) => {
156
+ export const handleError = (error, reply, exposure = 'production') => {
101
157
  if (!reply.sent) {
102
- const resilient = ResilientError.ensure(error);
103
- const thrown = errorStatus(error);
104
- reply.code(thrown !== SERVER_ERROR ? thrown : errorStatus(resilient))
105
- .send(ResilientError.marshal(resilient).message);
158
+ const serialized = serializeError(error, exposure);
159
+ applyErrorHeaders(error, reply);
160
+ // Fastify/Pino's `err` serializer retains the original type, message and full stack. This is
161
+ // the one log entry that owns those details in production, keyed by the response incident ID.
162
+ reply.log.error({ err: error, incidentId: serialized.incidentId }, 'Request failed');
163
+ reply.header(INCIDENT_ID_HEADER, serialized.incidentId)
164
+ .code(serialized.status)
165
+ .send(serialized.body);
106
166
  }
107
167
  };
108
168
  //# sourceMappingURL=error.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/utils/error.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/utils/error.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/utils/payload.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,KAAK,CAAA;AAG1C,eAAO,MAAM,cAAc,UAAW,MAAM,OAAO,OAAO,cAAc,OAAO,KAAG,eAejF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,YAAY,gBAAgB,CAAC,CAAC,CAAC,SAAS,QAAQ,iBAAiB,OAAO,KAAG,OA0B3G,CAAA;AAGD,eAAO,MAAM,cAAc,WAAY,eAAe,oBAgBrD,CAAA"}
1
+ {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/utils/payload.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,KAAK,CAAA;AAG1C,eAAO,MAAM,cAAc,UAAW,MAAM,OAAO,OAAO,cAAc,OAAO,KAAG,eAejF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,YAAY,gBAAgB,CAAC,CAAC,CAAC,SAAS,QAAQ,iBAAiB,OAAO,KAAG,OAuB3G,CAAA;AAGD,eAAO,MAAM,cAAc,WAAY,eAAe,oBAgBrD,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import { EntrypointOutcome } from '@owlmeans/entrypoint';
2
2
  import { ACCEPTED, CREATED, OK } from '@owlmeans/api';
3
- import { errorStatus } from './error.js';
3
+ import { handleError } from './error.js';
4
4
  export const provideRequest = (alias, req, provision) => {
5
5
  provision = provision ?? false;
6
6
  return {
@@ -31,10 +31,7 @@ export const executeResponse = (response, reply, throwOnError) => {
31
31
  if (throwOnError ?? false) {
32
32
  throw response.error;
33
33
  }
34
- // The status of the error as rejected, resolved like `handleError`'s first step — every pipeline
35
- // caller passes `throwOnError` and lands there; a caller that does not must not report a refusal
36
- // as a crash either. Nothing is ensured here, so there is no rebuilt error to ask second.
37
- reply.code(errorStatus(response.error)).send(response.error.message);
34
+ handleError(response.error, reply);
38
35
  return true;
39
36
  }
40
37
  else if (response.outcome != null) {
@@ -1 +1 @@
1
- {"version":3,"file":"payload.js","sourceRoot":"","sources":["../../src/utils/payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAGxD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAErD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAa,EAAE,GAAY,EAAE,SAAmB,EAAmB,EAAE;IAClG,SAAS,GAAG,SAAS,IAAI,KAAK,CAAA;IAC9B,OAAO;QACL,KAAK;QACL,IAAI,EAAG,GAAW,CAAC,KAAK,IAAI,SAAS;QACrC,4FAA4F;QAC5F,oEAAoE;QACpE,MAAM,EAAG,GAAW,CAAC,OAAO,IAAI,SAAS;QACzC,MAAM,EAAE,GAAG,CAAC,MAA4D;QACxE,IAAI,EAAE,GAAG,CAAC,IAA2B;QACrC,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,KAAK,EAAE,GAAG,CAAC,KAA2D;QACtE,IAAI,EAAE,GAAG,CAAC,GAAG;QACb,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;KACtC,CAAA;AACH,CAAC,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAI,QAA6B,EAAE,KAAe,EAAE,YAAsB,EAAW,EAAE;IACpH,IAAI,QAAQ,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;QAC3B,IAAI,YAAY,IAAI,KAAK,EAAE,CAAC;YAC1B,MAAM,QAAQ,CAAC,KAAK,CAAA;QACtB,CAAC;QACD,iGAAiG;QACjG,iGAAiG;QACjG,0FAA0F;QAC1F,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACpE,OAAO,IAAI,CAAA;IACb,CAAC;SAAM,IAAI,QAAQ,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;QACpC,QAAQ,QAAQ,CAAC,OAAO,EAAE,CAAC;YACzB,KAAK,iBAAiB,CAAC,QAAQ;gBAC7B,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;gBACzC,MAAK;YACP,KAAK,iBAAiB,CAAC,OAAO;gBAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;gBACxC,MAAK;YACP,KAAK,iBAAiB,CAAC,EAAE,CAAC;YAC1B;gBACE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACvC,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAGD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAuB,EAAE,EAAE;IACxD,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,GAAG,QAAQ,CAAA;YACtB,MAAM,CAAC,MAAM,GAAG,WAAW,CAAA;YAC3B,IAAI,MAAM,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;gBAC5B,OAAO,MAAM,CAAC,QAAQ,CAAA;YACxB,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;YACrC,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,WAAW,CACpC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,cAAc,CAAC,KAAwB,CAAC,CAAC,CAAC,CACzG,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA"}
1
+ {"version":3,"file":"payload.js","sourceRoot":"","sources":["../../src/utils/payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAGxD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAErD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAa,EAAE,GAAY,EAAE,SAAmB,EAAmB,EAAE;IAClG,SAAS,GAAG,SAAS,IAAI,KAAK,CAAA;IAC9B,OAAO;QACL,KAAK;QACL,IAAI,EAAG,GAAW,CAAC,KAAK,IAAI,SAAS;QACrC,4FAA4F;QAC5F,oEAAoE;QACpE,MAAM,EAAG,GAAW,CAAC,OAAO,IAAI,SAAS;QACzC,MAAM,EAAE,GAAG,CAAC,MAA4D;QACxE,IAAI,EAAE,GAAG,CAAC,IAA2B;QACrC,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,KAAK,EAAE,GAAG,CAAC,KAA2D;QACtE,IAAI,EAAE,GAAG,CAAC,GAAG;QACb,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;KACtC,CAAA;AACH,CAAC,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAI,QAA6B,EAAE,KAAe,EAAE,YAAsB,EAAW,EAAE;IACpH,IAAI,QAAQ,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;QAC3B,IAAI,YAAY,IAAI,KAAK,EAAE,CAAC;YAC1B,MAAM,QAAQ,CAAC,KAAK,CAAA;QACtB,CAAC;QACD,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QAClC,OAAO,IAAI,CAAA;IACb,CAAC;SAAM,IAAI,QAAQ,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;QACpC,QAAQ,QAAQ,CAAC,OAAO,EAAE,CAAC;YACzB,KAAK,iBAAiB,CAAC,QAAQ;gBAC7B,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;gBACzC,MAAK;YACP,KAAK,iBAAiB,CAAC,OAAO;gBAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;gBACxC,MAAK;YACP,KAAK,iBAAiB,CAAC,EAAE,CAAC;YAC1B;gBACE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACvC,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAGD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAuB,EAAE,EAAE;IACxD,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,GAAG,QAAQ,CAAA;YACtB,MAAM,CAAC,MAAM,GAAG,WAAW,CAAA;YAC3B,IAAI,MAAM,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;gBAC5B,OAAO,MAAM,CAAC,QAAQ,CAAA;YACxB,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;YACrC,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,WAAW,CACpC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,cAAc,CAAC,KAAwB,CAAC,CAAC,CAAC,CACzG,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA"}
@@ -1,8 +1,9 @@
1
1
  import type { FastifyReply, FastifyRequest } from 'fastify';
2
2
  import type { ServerEntrypoint } from '@owlmeans/server-entrypoint';
3
3
  import type { CommonEntrypoint } from '@owlmeans/entrypoint';
4
- import type { ServerContext, ServerConfig } from '@owlmeans/server-context';
5
- type Config = ServerConfig;
4
+ import type { ServerContext } from '@owlmeans/server-context';
5
+ import type { Config as ApiConfig } from '../types.js';
6
+ type Config = ApiConfig;
6
7
  type Context = ServerContext<Config>;
7
8
  export declare const canServeModule: (context: Context, module: CommonEntrypoint) => module is ServerEntrypoint<unknown>;
8
9
  export declare const createServerHandler: (module: ServerEntrypoint<FastifyRequest>, location: string) => (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/utils/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE3D,OAAO,KAAK,EAAgB,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAG5D,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAQ3E,KAAK,MAAM,GAAG,YAAY,CAAA;AAC1B,KAAK,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;AAEpC,eAAO,MAAM,cAAc,YAAa,OAAO,UAAU,gBAAgB,KAAG,MAAM,IAAI,gBAAgB,CAAC,OAAO,CAe7G,CAAA;AAED,eAAO,MAAM,mBAAmB,WAAY,gBAAgB,CAAC,cAAc,CAAC,YAAY,MAAM,WAChF,cAAc,SAAS,YAAY,kBA0C9C,CAAA"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/utils/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE3D,OAAO,KAAK,EAAgB,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAG5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAO7D,OAAO,KAAK,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,aAAa,CAAA;AAEtD,KAAK,MAAM,GAAG,SAAS,CAAA;AACvB,KAAK,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;AAEpC,eAAO,MAAM,cAAc,YAAa,OAAO,UAAU,gBAAgB,KAAG,MAAM,IAAI,gBAAgB,CAAC,OAAO,CAc7G,CAAA;AAED,eAAO,MAAM,mBAAmB,WAAY,gBAAgB,CAAC,cAAc,CAAC,YAAY,MAAM,WAChF,cAAc,SAAS,YAAY,kBAuC9C,CAAA"}
@@ -3,7 +3,7 @@ import { assertContext } from '@owlmeans/context';
3
3
  import { provideResponse } from '@owlmeans/entrypoint';
4
4
  import { ResilientError } from '@owlmeans/error';
5
5
  import { OK } from '@owlmeans/api';
6
- import { handleError } from './error.js';
6
+ import { errorExposure, handleError } from './error.js';
7
7
  import { executeResponse, provideRequest } from './payload.js';
8
8
  import { authorize } from './guards.js';
9
9
  import { RouteProtocols } from '@owlmeans/route';
@@ -14,10 +14,9 @@ export const canServeModule = (context, module) => {
14
14
  if (module.route.route.service != null && module.route.route.service !== context.cfg.service) {
15
15
  return false;
16
16
  }
17
- // Only the protocols HTTP actually carries. A socket is upgraded elsewhere and a queued job is
18
- // taken off the broker by the worker — mounting either on the HTTP server would answer it twice.
19
- if (module.route.route.protocol === RouteProtocols.SOCKET
20
- || module.route.route.protocol === RouteProtocols.QUEUE) {
17
+ // HTTP serves only its own protocol. Custom transports are handled by their owning packages.
18
+ if (module.route.route.protocol != null
19
+ && module.route.route.protocol !== RouteProtocols.WEB) {
21
20
  return false;
22
21
  }
23
22
  return 'isIntermediate' in module.route;
@@ -51,15 +50,12 @@ export const createServerHandler = (module, location) => async (req, reply) => {
51
50
  }
52
51
  }
53
52
  catch (error) {
54
- console.error(`Error in ${module.alias} (${location})`);
55
- console.error(JSON.stringify(error, null, 2));
56
- console.error(error);
57
53
  if (module.fixer != null) {
58
54
  const fixer = context.service(module.fixer);
59
55
  fixer.handle(reply, ResilientError.ensure(error));
60
56
  return;
61
57
  }
62
- handleError(error, reply);
58
+ handleError(error, reply, errorExposure(context.cfg));
63
59
  }
64
60
  };
65
61
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/utils/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/utils/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/server-api",
3
- "version": "0.1.18-rc.33",
3
+ "version": "0.1.18-rc.35",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -33,15 +33,15 @@
33
33
  "@fastify/helmet": "^13.1.0",
34
34
  "@fastify/middie": "^9.0.2",
35
35
  "@fastify/multipart": "^9.0.1",
36
- "@owlmeans/api": "^0.1.18-rc.29",
37
- "@owlmeans/auth": "^0.1.18-rc.27",
38
- "@owlmeans/auth-common": "^0.1.18-rc.29",
39
- "@owlmeans/context": "^0.1.18-rc.25",
40
- "@owlmeans/error": "^0.1.18-rc.27",
41
- "@owlmeans/entrypoint": "^0.1.18-rc.28",
42
- "@owlmeans/route": "^0.1.18-rc.26",
43
- "@owlmeans/server-context": "^0.1.18-rc.28",
44
- "@owlmeans/server-entrypoint": "^0.1.18-rc.27",
36
+ "@owlmeans/api": "^0.1.18-rc.31",
37
+ "@owlmeans/auth": "^0.1.18-rc.29",
38
+ "@owlmeans/auth-common": "^0.1.18-rc.31",
39
+ "@owlmeans/context": "^0.1.18-rc.27",
40
+ "@owlmeans/error": "^0.1.18-rc.29",
41
+ "@owlmeans/entrypoint": "^0.1.18-rc.30",
42
+ "@owlmeans/route": "^0.1.18-rc.28",
43
+ "@owlmeans/server-context": "^0.1.18-rc.30",
44
+ "@owlmeans/server-entrypoint": "^0.1.18-rc.29",
45
45
  "ajv-errors": "^3.0.0",
46
46
  "fastify": "^5.0.0",
47
47
  "fastify-raw-body": "^5.0.0",
package/src/server.ts CHANGED
@@ -8,6 +8,7 @@ import { RouteMethod } from '@owlmeans/route'
8
8
  import { createServerHandler, fixFormatDates } from './utils/index.js'
9
9
  import { provideResponse } from '@owlmeans/entrypoint'
10
10
  import { TOKEN_UPDATE } from '@owlmeans/auth-common'
11
+ import { INCIDENT_ID_HEADER } from './utils/error.js'
11
12
 
12
13
  import Fastify from 'fastify'
13
14
  import type { FastifyRequest } from 'fastify'
@@ -99,7 +100,7 @@ export const createApiServer = (alias: string): ApiServer => {
99
100
  ...Object.values(RouteMethod).map(method => method.toUpperCase()),
100
101
  'HEAD', 'OPTIONS'
101
102
  ],
102
- exposedHeaders: [TOKEN_UPDATE]
103
+ exposedHeaders: [TOKEN_UPDATE, INCIDENT_ID_HEADER]
103
104
  })
104
105
 
105
106
  await server.register(Multipart, {
@@ -199,4 +200,3 @@ export const appendApiServer = <C extends Config, T extends ServerContext<C>>(
199
200
 
200
201
  return context
201
202
  }
202
-
package/src/types.ts CHANGED
@@ -18,7 +18,20 @@ export interface ApiServerAppend {
18
18
  getApiServer: () => ApiServer
19
19
  }
20
20
 
21
- export interface Config extends ServerConfig { }
21
+ export type HttpErrorExposure = 'production' | 'development'
22
+
23
+ export interface HttpErrorsConfig {
24
+ /** Production is the safe default; development is the only mode that exposes stacks. */
25
+ exposure?: HttpErrorExposure
26
+ }
27
+
28
+ export interface HttpConfig {
29
+ errors?: HttpErrorsConfig
30
+ }
31
+
32
+ export interface Config extends ServerConfig {
33
+ http?: HttpConfig
34
+ }
22
35
 
23
36
  export interface Context<C extends Config = Config> extends ServerContext<C>,
24
37
  ApiServerAppend { }
@@ -2,7 +2,11 @@ import { FORBIDDEN_ERROR, SERVER_ERROR, UNAUTHORIZED_ERROR } from '@owlmeans/api
2
2
  import type { FastifyReply } from 'fastify'
3
3
  import { AccessError, AuthFailedError } from '../errors.js'
4
4
  import { AuthForbidden, AuthorizationError } from '@owlmeans/auth'
5
- import { ResilientError } from '@owlmeans/error'
5
+ import { isResilientError, ResilientError, SEPARATOR } from '@owlmeans/error'
6
+ import { randomUUID } from 'node:crypto'
7
+ import type { Config, HttpErrorExposure } from '../types.js'
8
+
9
+ export const INCIDENT_ID_HEADER = 'X-Incident-ID'
6
10
 
7
11
  /**
8
12
  * What an error class may declare about the HTTP status it is answered with.
@@ -13,31 +17,38 @@ import { ResilientError } from '@owlmeans/error'
13
17
  */
14
18
  export interface HttpStatusDeclaration {
15
19
  httpStatus?: unknown
20
+ /** Explicit opt-in for a declared 5xx response. Client-error declarations need no opt-in. */
21
+ allowServerErrorStatus?: unknown
16
22
  }
17
23
 
18
24
  const CLIENT_ERROR_FIRST = 400
19
25
  const CLIENT_ERROR_LAST = 499
26
+ const SERVER_ERROR_FIRST = 500
27
+ const SERVER_ERROR_LAST = 599
20
28
 
21
29
  /**
22
- * The 4xx status an error's class declares, or `null` when it declares none that may be honoured.
30
+ * The HTTP status an error's class declares, or `null` when it declares none that may be honoured.
23
31
  *
24
32
  * Read through the constructor chain — a static property is inherited, so a subclass answers what
25
- * its nearest declaring ancestor says, and redeclares to change it. Only an integer 4xx is honoured:
26
- * a declaration is how a class says "this is the caller's condition, not a fault", and a 5xx, a 2xx,
27
- * a string or a fraction says nothing a boundary can act on. The read is structural, so a class from
28
- * a duplicate module copy declares exactly what the imported one does.
33
+ * its nearest declaring ancestor says, and redeclares to change it. An integer 4xx is honoured
34
+ * directly. A 5xx is honoured only with `allowServerErrorStatus = true`, so a domain class must
35
+ * explicitly opt in to exposing a precise availability response. The read is structural, so a
36
+ * class from a duplicate module copy declares exactly what the imported one does.
29
37
  */
30
38
  export const declaredErrorStatus = (error: unknown): number | null => {
31
39
  if (error == null || typeof error !== 'object') {
32
40
  return null
33
41
  }
34
- const status = (error.constructor as HttpStatusDeclaration | undefined)?.httpStatus
35
- if (typeof status !== 'number' || !Number.isInteger(status)
36
- || status < CLIENT_ERROR_FIRST || status > CLIENT_ERROR_LAST) {
42
+ const declaration = error.constructor as HttpStatusDeclaration | undefined
43
+ const status = declaration?.httpStatus
44
+ if (typeof status !== 'number' || !Number.isInteger(status)) {
37
45
  return null
38
46
  }
47
+ if (status >= CLIENT_ERROR_FIRST && status <= CLIENT_ERROR_LAST) return status
48
+ if (status >= SERVER_ERROR_FIRST && status <= SERVER_ERROR_LAST
49
+ && declaration?.allowServerErrorStatus === true) return status
39
50
 
40
- return status
51
+ return null
41
52
  }
42
53
 
43
54
  /**
@@ -86,7 +97,7 @@ const isOfFamily = (names: Set<string>, family: string[]): boolean =>
86
97
  * **Order matters.** `AuthForbidden extends AuthorizationError`, so the 403 branch has to be
87
98
  * tested first or every refusal of a permission is reported as a failure to authenticate. Both
88
99
  * auth branches come before a class's own declaration, so an auth refusal can never be restated
89
- * as something else. Everything that is neither an auth error nor a class declaring a 4xx
100
+ * as something else. Everything that is neither an auth error nor a class declaring an allowed
90
101
  * {@link declaredErrorStatus} answers 500.
91
102
  */
92
103
  export const errorStatus = (error: unknown): number => {
@@ -109,6 +120,66 @@ export const errorStatus = (error: unknown): number => {
109
120
  return declaredErrorStatus(error) ?? SERVER_ERROR
110
121
  }
111
122
 
123
+ export interface SerializedHttpError {
124
+ status: number
125
+ body: string
126
+ incidentId: string
127
+ }
128
+
129
+ /** Invalid or absent configuration always resolves to the production-safe policy. */
130
+ export const errorExposure = (config?: Pick<Config, 'http'>): HttpErrorExposure =>
131
+ config?.http?.errors?.exposure === 'development' ? 'development' : 'production'
132
+
133
+ /**
134
+ * Preserve a typed/marshalled error when possible without asking `ensure` to convert a plain
135
+ * error. In particular, `ensure` deliberately rethrows SyntaxError; an HTTP boundary still has
136
+ * to serialize it safely and let the incident log retain the original object.
137
+ */
138
+ const normalizeForWire = (error: Error): Error => {
139
+ if (error instanceof SyntaxError || isResilientError(error)) {
140
+ return error
141
+ }
142
+ if (error.message.includes(SEPARATOR)) {
143
+ const ensured = ResilientError.ensure(error)
144
+ // The catch-all converter's legacy constructor shape shifts an ordinary error's fields.
145
+ // Only keep `ensure`'s result when a registered wire prefix actually rebuilt something.
146
+ if (ensured.type !== error.message || ensured.message !== error.stack) {
147
+ return ensured
148
+ }
149
+ }
150
+ return error
151
+ }
152
+
153
+ /** One central wire shape for every server error. */
154
+ export const serializeError = (
155
+ error: Error, exposure: HttpErrorExposure = 'production'
156
+ ): SerializedHttpError => {
157
+ const normalized = normalizeForWire(error)
158
+ const thrown = errorStatus(error)
159
+ const status = thrown !== SERVER_ERROR ? thrown : errorStatus(normalized)
160
+ const incidentId = randomUUID()
161
+ // The server log owns every diagnostic. Production callers receive only its correlation key;
162
+ // development keeps the typed wire form so local clients can retain their usual error details.
163
+ Object.assign(error, { incidentId })
164
+ if (isResilientError(normalized)) normalized.incidentId = incidentId
165
+ const body = exposure === 'production'
166
+ ? incidentId
167
+ : ResilientError.marshal(normalized, { includeStack: true, incidentId }).message
168
+
169
+ return { status, body, incidentId }
170
+ }
171
+
172
+ /** Apply safe response metadata carried by an error without coupling its package to Fastify. */
173
+ export const applyErrorHeaders = (error: unknown, reply: FastifyReply): void => {
174
+ if (error == null || typeof error !== 'object') {
175
+ return
176
+ }
177
+ const retryAfter = (error as { retryAfter?: unknown }).retryAfter
178
+ if (typeof retryAfter === 'number' && Number.isFinite(retryAfter) && retryAfter > 0) {
179
+ reply.header('Retry-After', String(Math.ceil(retryAfter)))
180
+ }
181
+ }
182
+
112
183
  /**
113
184
  * Answer a thrown error: a status, and the marshalled `ResilientError` as the body.
114
185
  *
@@ -119,11 +190,17 @@ export const errorStatus = (error: unknown): number => {
119
190
  * Only when the thrown error answers 500 is the ENSURED error asked, because the rebuild is what
120
191
  * gives a status to a marshalled error that crossed a hop as a plain `Error`.
121
192
  */
122
- export const handleError = (error: Error, reply: FastifyReply) => {
193
+ export const handleError = (
194
+ error: Error, reply: FastifyReply, exposure: HttpErrorExposure = 'production'
195
+ ) => {
123
196
  if (!reply.sent) {
124
- const resilient = ResilientError.ensure(error as Error)
125
- const thrown = errorStatus(error)
126
- reply.code(thrown !== SERVER_ERROR ? thrown : errorStatus(resilient))
127
- .send(ResilientError.marshal(resilient).message)
197
+ const serialized = serializeError(error, exposure)
198
+ applyErrorHeaders(error, reply)
199
+ // Fastify/Pino's `err` serializer retains the original type, message and full stack. This is
200
+ // the one log entry that owns those details in production, keyed by the response incident ID.
201
+ reply.log.error({ err: error, incidentId: serialized.incidentId }, 'Request failed')
202
+ reply.header(INCIDENT_ID_HEADER, serialized.incidentId)
203
+ .code(serialized.status)
204
+ .send(serialized.body)
128
205
  }
129
206
  }
@@ -3,7 +3,7 @@ import type { AbstractResponse, AbstractRequest } from '@owlmeans/entrypoint'
3
3
  import type { Request, Response } from '../types.js'
4
4
  import { ACCEPTED, CREATED, OK } from '@owlmeans/api'
5
5
  import type { AnySchemaObject } from 'ajv'
6
- import { errorStatus } from './error.js'
6
+ import { handleError } from './error.js'
7
7
 
8
8
  export const provideRequest = (alias: string, req: Request, provision?: boolean): AbstractRequest => {
9
9
  provision = provision ?? false
@@ -36,10 +36,7 @@ export const executeResponse = <T>(response: AbstractResponse<T>, reply: Respons
36
36
  if (throwOnError ?? false) {
37
37
  throw response.error
38
38
  }
39
- // The status of the error as rejected, resolved like `handleError`'s first step — every pipeline
40
- // caller passes `throwOnError` and lands there; a caller that does not must not report a refusal
41
- // as a crash either. Nothing is ensured here, so there is no rebuilt error to ask second.
42
- reply.code(errorStatus(response.error)).send(response.error.message)
39
+ handleError(response.error, reply)
43
40
  return true
44
41
  } else if (response.outcome != null) {
45
42
  switch (response.outcome) {
@@ -5,15 +5,16 @@ import type { FixerService, ServerEntrypoint } from '@owlmeans/server-entrypoint
5
5
  import type { CommonEntrypoint } from '@owlmeans/entrypoint'
6
6
  import type { GateService } from '@owlmeans/entrypoint'
7
7
  import { provideResponse } from '@owlmeans/entrypoint'
8
- import type { ServerContext, ServerConfig } from '@owlmeans/server-context'
8
+ import type { ServerContext } from '@owlmeans/server-context'
9
9
  import { ResilientError } from '@owlmeans/error'
10
10
  import { OK } from '@owlmeans/api'
11
- import { handleError } from './error.js'
11
+ import { errorExposure, handleError } from './error.js'
12
12
  import { executeResponse, provideRequest } from './payload.js'
13
13
  import { authorize } from './guards.js'
14
14
  import { RouteProtocols } from '@owlmeans/route'
15
+ import type { Config as ApiConfig } from '../types.js'
15
16
 
16
- type Config = ServerConfig
17
+ type Config = ApiConfig
17
18
  type Context = ServerContext<Config>
18
19
 
19
20
  export const canServeModule = (context: Context, module: CommonEntrypoint): module is ServerEntrypoint<unknown> => {
@@ -23,10 +24,9 @@ export const canServeModule = (context: Context, module: CommonEntrypoint): modu
23
24
  if (module.route.route.service != null && module.route.route.service !== context.cfg.service) {
24
25
  return false
25
26
  }
26
- // Only the protocols HTTP actually carries. A socket is upgraded elsewhere and a queued job is
27
- // taken off the broker by the worker — mounting either on the HTTP server would answer it twice.
28
- if (module.route.route.protocol === RouteProtocols.SOCKET
29
- || module.route.route.protocol === RouteProtocols.QUEUE) {
27
+ // HTTP serves only its own protocol. Custom transports are handled by their owning packages.
28
+ if (module.route.route.protocol != null
29
+ && module.route.route.protocol !== RouteProtocols.WEB) {
30
30
  return false
31
31
  }
32
32
 
@@ -66,14 +66,11 @@ export const createServerHandler = (module: ServerEntrypoint<FastifyRequest>, lo
66
66
  reply.code(OK).send(response.value)
67
67
  }
68
68
  } catch (error) {
69
- console.error(`Error in ${module.alias} (${location})`)
70
- console.error(JSON.stringify(error, null, 2))
71
- console.error(error)
72
69
  if (module.fixer != null) {
73
70
  const fixer: FixerService = context.service(module.fixer)
74
71
  fixer.handle(reply, ResilientError.ensure(error as Error))
75
72
  return
76
73
  }
77
- handleError(error as Error, reply)
74
+ handleError(error as Error, reply, errorExposure(context.cfg))
78
75
  }
79
76
  }
@@ -2,9 +2,9 @@ import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
2
2
  import Fastify from 'fastify'
3
3
  import type { FastifyInstance } from 'fastify'
4
4
  import { AuthForbidden, AuthorizationError } from '@owlmeans/auth'
5
- import { ResilientError } from '@owlmeans/error'
5
+ import { ResilientError, SEPARATOR } from '@owlmeans/error'
6
6
  import { provideResponse } from '@owlmeans/entrypoint'
7
- import { errorStatus, handleError } from '../src/utils/error.js'
7
+ import { errorExposure, errorStatus, handleError, INCIDENT_ID_HEADER } from '../src/utils/error.js'
8
8
  import { executeResponse } from '../src/utils/payload.js'
9
9
  import { AccessError, AuthFailedError } from '../src/errors.js'
10
10
 
@@ -49,6 +49,26 @@ class DeclaringForbidden extends AuthForbidden {
49
49
  }
50
50
  }
51
51
 
52
+ class TemporarilyUnavailable extends ResilientError {
53
+ public static override typeName = 'ServerApiSpecTemporarilyUnavailable'
54
+ public static httpStatus = 503
55
+ public static allowServerErrorStatus = true
56
+
57
+ constructor() {
58
+ super(TemporarilyUnavailable.typeName, 'temporarily-unavailable')
59
+ }
60
+ }
61
+
62
+ class Throttled extends ResilientError {
63
+ public static override typeName = 'ServerApiSpecThrottled'
64
+ public static httpStatus = 429
65
+ public readonly retryAfter = 17.2
66
+
67
+ constructor() {
68
+ super(Throttled.typeName, 'throttled')
69
+ }
70
+ }
71
+
52
72
  const declaring = (typeName: string, httpStatus: unknown) => {
53
73
  class Declaring extends ResilientError {
54
74
  public static override typeName = typeName
@@ -136,6 +156,10 @@ beforeAll(async () => {
136
156
  handleError(thrown as Error, reply)
137
157
  return reply
138
158
  })
159
+ server.get('/handle-development', async (_req, reply) => {
160
+ handleError(thrown as Error, reply, 'development')
161
+ return reply
162
+ })
139
163
  server.get('/execute', async (_req, reply) => {
140
164
  const response = provideResponse(reply)
141
165
  response.reject(thrown as Error)
@@ -152,9 +176,41 @@ afterAll(async () => {
152
176
  const answer = async (error: unknown, path: string = '/handle') => {
153
177
  thrown = error
154
178
  const reply = await server.inject({ method: 'GET', url: path })
155
- return { status: reply.statusCode, body: reply.body }
179
+ return {
180
+ status: reply.statusCode,
181
+ body: reply.body,
182
+ incidentId: reply.headers[INCIDENT_ID_HEADER.toLowerCase()],
183
+ retryAfter: reply.headers['retry-after'],
184
+ }
156
185
  }
157
186
 
187
+ describe('@owlmeans/server-api — production error exposure', () => {
188
+ test('returns only an incident id in production and correlates it with the header', async () => {
189
+ const response = await answer(new Error('private-marker'))
190
+ expect(response.body).toBe(response.incidentId)
191
+ expect(response.body).not.toContain('private-marker')
192
+ expect(response.body).not.toContain(SEPARATOR)
193
+ expect(response.body).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/)
194
+ })
195
+
196
+ test('includes stacks only under explicit development exposure and safely serializes SyntaxError', async () => {
197
+ const development = await answer(new Error('development-marker'), '/handle-development')
198
+ const [type, message, stack, incidentId] = development.body.split(SEPARATOR, 4)
199
+ expect(type).toBe(ResilientError.typeName)
200
+ expect(message).toBe('development-marker')
201
+ expect(stack).toContain('development-marker')
202
+ expect(incidentId).toBe(development.incidentId)
203
+
204
+ const syntax = await answer(new SyntaxError('bad wiring'))
205
+ expect(syntax.status).toBe(500)
206
+ expect(syntax.body).toBe(syntax.incidentId)
207
+ expect(syntax.body).not.toContain('bad wiring')
208
+
209
+ expect(errorExposure()).toBe('production')
210
+ expect(errorExposure({ http: { errors: { exposure: 'development' } } })).toBe('development')
211
+ })
212
+ })
213
+
158
214
  describe('@owlmeans/server-api — handleError status', () => {
159
215
  test('keeps the auth mappings: forbidden 403, unauthenticated 401', async () => {
160
216
  expect((await answer(new AuthForbidden('x'))).status).toBe(403)
@@ -167,7 +223,7 @@ describe('@owlmeans/server-api — handleError status', () => {
167
223
  test('honours a 4xx a class declares — inherited, redeclared, or after a marshal hop', async () => {
168
224
  const refused = await answer(new OutOfCredit('create'))
169
225
  expect(refused.status).toBe(402)
170
- expect(ResilientError.ensure(new Error(refused.body))).toBeInstanceOf(OutOfCredit)
226
+ expect(refused.body).toBe(refused.incidentId)
171
227
 
172
228
  expect((await answer(new OutOfCreditForStory())).status).toBe(402)
173
229
  expect((await answer(new Busy())).status).toBe(409)
@@ -188,6 +244,18 @@ describe('@owlmeans/server-api — handleError status', () => {
188
244
  }
189
245
  })
190
246
 
247
+ test('honours an explicitly exposed 5xx without opening arbitrary server statuses', async () => {
248
+ expect((await answer(new TemporarilyUnavailable())).status).toBe(503)
249
+ const Unexposed = declaring('ServerApiSpecUnexposed503', 503)
250
+ expect((await answer(new Unexposed())).status).toBe(500)
251
+ })
252
+
253
+ test('emits Retry-After for a throttled response', async () => {
254
+ const response = await answer(new Throttled())
255
+ expect(response.status).toBe(429)
256
+ expect(response.retryAfter).toBe('18')
257
+ })
258
+
191
259
  test('executeResponse answers a rejected response with the same status', async () => {
192
260
  expect((await answer(new OutOfCredit('create'), '/execute')).status).toBe(402)
193
261
  expect((await answer(new AuthForbidden('x'), '/execute')).status).toBe(403)