@zeroxsolutions/response 0.14.6 → 0.14.7

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 CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.14.7 (2026-10-21)
2
+
3
+ ### 🚀 Features
4
+
5
+ - **response:** let an upstream answer state its own source ([fdc8078](https://github.com/zeroxsolutions/zeroxsolutions/commit/fdc8078))
6
+
7
+ ### ❤️ Thank You
8
+
9
+ - Lương Văn Tú
10
+
1
11
  ## 0.14.6 (2026-10-21)
2
12
 
3
13
  ### 🚀 Features
package/README.md CHANGED
@@ -246,6 +246,11 @@ exactly one error per failure. Of that error it keeps only what an answer's `met
246
246
  pointers name the SERVICE's request shape, not the one this caller sent, so echoing them would point a
247
247
  client at members it has no way to correct.
248
248
 
249
+ An answer can state a `source` of its own instead, in this transport's request terms. It is fixed per
250
+ upstream code, so it suits a refusal whose slot is the same on every occurrence; a slot that varies per
251
+ occurrence (one member of a list, say) has no way through, since the upstream error's `source` is the only
252
+ place that varies and it describes the wrong request. The fallback states none.
253
+
249
254
  A `meta` callback returns its parse, never `upstream` as received - the schema is what keeps a member the
250
255
  service adds later from crossing. Returning `undefined` answers the fallback: a mapped code missing the
251
256
  data its contract promises is an answer the gateway does not recognise.
@@ -298,6 +303,30 @@ export const translateCatalogueError = translator(
298
303
  throw error({ ...BASE_ERROR_CODES.FORBIDDEN, code: 'api.forbidden' });
299
304
  ```
300
305
 
306
+ A mapped answer states where in the client's request the refusal belongs with `source`, one of the
307
+ standard's `pointer`, `parameter` or `header`. JSON:API holds a `pointer` to "a value in the request
308
+ document that exists", so it names the member this transport's client sent, never the one the service
309
+ was sent:
310
+
311
+ ```ts
312
+ // entrypoints/http/error-map.ts - the client names the place by its slug; the service was sent its id
313
+ export const translateCatalogueWriteError = translator(
314
+ CATALOGUE_ERROR_CODES,
315
+ {
316
+ 'catalogue.place.not_found': {
317
+ status: 422,
318
+ title: 'Unprocessable Entity',
319
+ code: 'api.place.not_found',
320
+ source: { pointer: '/data/attributes/placeSlug' },
321
+ },
322
+ },
323
+ { status: 502, title: 'Bad Gateway', code: 'api.upstream_failed' },
324
+ );
325
+ ```
326
+
327
+ The translated error renders `source: { pointer: '/data/attributes/placeSlug' }` whatever `source` the
328
+ service's error carried, and a member the standard does not define, `pointr`, does not compile.
329
+
301
330
  The lists are values rather than type arguments: with nothing passed there is no list to fall back on,
302
331
  where a type argument left out would widen to `string`. Neither is read at runtime. `error` also refuses
303
332
  a key its answer does not declare, so a misspelled `detial` does not compile, and a thrown answer's `meta`
@@ -1,5 +1,5 @@
1
1
  import type { UpstreamErrorMap } from './error-map.js';
2
- import type { ErrorDocument, ErrorLinks } from './errors/error-document.js';
2
+ import type { ErrorDocument, ErrorLinks, ErrorSource } from './errors/error-document.js';
3
3
  import type { ErrorStatus } from './errors/error-registry.js';
4
4
  import { JsonApiError } from './errors/jsonapi-error.js';
5
5
  import type { Meta } from './primitives.js';
@@ -11,6 +11,11 @@ export interface UpstreamErrorAnswer extends ErrorStatus {
11
11
  code: string;
12
12
  /** Human-readable and request-specific; the error's `message` falls back to the title without one. */
13
13
  detail?: string;
14
+ /**
15
+ * Where in this transport's own request the error originates, the same on every occurrence of the code - omit to render
16
+ * no `source`. The upstream error's own `source` is never read: it names a slot of the request the service received.
17
+ */
18
+ source?: ErrorSource;
14
19
  /** A page about this occurrence, and a page about its type of problem. */
15
20
  links?: ErrorLinks;
16
21
  /**
@@ -28,7 +33,8 @@ export interface UpstreamErrorAnswer extends ErrorStatus {
28
33
  *
29
34
  * The document's FIRST error decides, because `createErrorHandler` renders exactly one per failure. Nothing else of the
30
35
  * service's error survives unless an answer's `meta` reads it: its `code` names a namespace this surface's contract
31
- * never declared, and its `detail` and `source` describe a request this client never sent.
36
+ * never declared, and its `detail` and `source` describe a request this client never sent. An answer states its own
37
+ * `source` instead; the fallback states none, since the code it answers was not recognised.
32
38
  */
33
- export declare function createUpstreamErrorTranslator(answers: UpstreamErrorMap, fallback: Omit<UpstreamErrorAnswer, 'meta'>): (document: ErrorDocument) => JsonApiError;
39
+ export declare function createUpstreamErrorTranslator(answers: UpstreamErrorMap, fallback: Omit<UpstreamErrorAnswer, 'meta' | 'source'>): (document: ErrorDocument) => JsonApiError;
34
40
  //# sourceMappingURL=upstream-error.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"upstream-error.d.ts","sourceRoot":"","sources":["../src/upstream-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C;;;GAGG;AACH,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,GAAG,SAAS,KAAK,IAAI,GAAG,SAAS,CAAC;CACzD;AAaD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAC,GAC1C,CAAC,QAAQ,EAAE,aAAa,KAAK,YAAY,CAa3C"}
1
+ {"version":3,"file":"upstream-error.d.ts","sourceRoot":"","sources":["../src/upstream-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C;;;GAGG;AACH,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,GAAG,SAAS,KAAK,IAAI,GAAG,SAAS,CAAC;CACzD;AAcD;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE,MAAM,GAAG,QAAQ,CAAC,GACrD,CAAC,QAAQ,EAAE,aAAa,KAAK,YAAY,CAa3C"}
@@ -5,6 +5,7 @@ function toJsonApiError(answer, meta) {
5
5
  title: answer.title,
6
6
  code: answer.code,
7
7
  detail: answer.detail,
8
+ source: answer.source,
8
9
  links: answer.links,
9
10
  meta,
10
11
  });
@@ -15,7 +16,8 @@ function toJsonApiError(answer, meta) {
15
16
  *
16
17
  * The document's FIRST error decides, because `createErrorHandler` renders exactly one per failure. Nothing else of the
17
18
  * service's error survives unless an answer's `meta` reads it: its `code` names a namespace this surface's contract
18
- * never declared, and its `detail` and `source` describe a request this client never sent.
19
+ * never declared, and its `detail` and `source` describe a request this client never sent. An answer states its own
20
+ * `source` instead; the fallback states none, since the code it answers was not recognised.
19
21
  */
20
22
  export function createUpstreamErrorTranslator(answers, fallback) {
21
23
  return (document) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxsolutions/response",
3
- "version": "0.14.6",
3
+ "version": "0.14.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,