@owlmeans/api 0.1.18-rc.4 → 0.1.18-rc.40

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.
Files changed (42) hide show
  1. package/README.md +4 -4
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/api/SKILL.md +96 -11
  4. package/build/consts.d.ts +7 -0
  5. package/build/consts.d.ts.map +1 -1
  6. package/build/consts.js +7 -0
  7. package/build/consts.js.map +1 -1
  8. package/build/errors.d.ts +30 -2
  9. package/build/errors.d.ts.map +1 -1
  10. package/build/errors.js +46 -4
  11. package/build/errors.js.map +1 -1
  12. package/build/index.d.ts +2 -0
  13. package/build/index.d.ts.map +1 -1
  14. package/build/index.js +1 -0
  15. package/build/index.js.map +1 -1
  16. package/build/service.d.ts.map +1 -1
  17. package/build/service.js +25 -8
  18. package/build/service.js.map +1 -1
  19. package/build/status/index.d.ts +31 -0
  20. package/build/status/index.d.ts.map +1 -0
  21. package/build/status/index.js +65 -0
  22. package/build/status/index.js.map +1 -0
  23. package/build/status/marker.d.ts +12 -0
  24. package/build/status/marker.d.ts.map +1 -0
  25. package/build/status/marker.js +27 -0
  26. package/build/status/marker.js.map +1 -0
  27. package/build/utils/handler.d.ts +7 -0
  28. package/build/utils/handler.d.ts.map +1 -1
  29. package/build/utils/handler.js +60 -26
  30. package/build/utils/handler.js.map +1 -1
  31. package/package.json +18 -10
  32. package/src/consts.ts +8 -0
  33. package/src/errors.ts +55 -4
  34. package/src/index.ts +4 -1
  35. package/src/service.ts +33 -8
  36. package/src/status/index.ts +85 -0
  37. package/src/status/marker.ts +37 -0
  38. package/src/utils/handler.ts +71 -25
  39. package/tests/handler.spec.ts +193 -0
  40. package/tests/tsconfig.json +11 -0
  41. package/tsconfig.json +1 -1
  42. package/build/.gitkeep +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @owlmeans/api
2
2
 
3
- HTTP API client service for OwlMeans client contexts — handles module-based requests with auth token injection.
3
+ HTTP API client service for OwlMeans client contexts — carries entrypoint calls with auth token injection.
4
4
 
5
5
  ## Overview
6
6
 
@@ -12,7 +12,7 @@ HTTP API client service for OwlMeans client contexts — handles module-based re
12
12
  ## Installation
13
13
 
14
14
  ```bash
15
- bun add @owlmeans/api
15
+ bun add @owlmeans/api@^0.1.18-rc.40
16
16
  ```
17
17
 
18
18
  ## Usage
@@ -54,7 +54,7 @@ Registers the API client in the context.
54
54
 
55
55
  ## Related Packages
56
56
 
57
- - [`@owlmeans/client-module`](../client-module) — `ClientModule<T>` uses this service to make requests
57
+ - [`@owlmeans/client-entrypoint`](../client-entrypoint) — bound protocols use this service to make requests
58
58
  - [`@owlmeans/web-client`](../web-client) — registers this service via `makeContext`
59
59
 
60
60
  <!-- owlmeans:agent-guidance:start -->
@@ -65,7 +65,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
65
65
  your project's skill store (`.agents/skills/`):
66
66
 
67
67
  ```sh
68
- npx @owlmeans/agent-skills
68
+ npx @owlmeans/agent-skills@^0.1.18-rc.41
69
69
  ```
70
70
 
71
71
  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/api",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.518Z",
4
+ "version": "0.1.18-rc.40",
5
+ "generatedAt": "2026-09-28T02:06:37.895Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: api
3
- description: How to use @owlmeans/api — HTTP client service (axios-based) used to call entrypoints across services. Auto-invoked when importing the API service or when ctx.entrypoint(...).call() under the hood is involved.
3
+ description: How to use @owlmeans/api — the axios-based HTTP client service that carries entrypoint calls between services, its status-to-outcome mapping, typed transport errors that keep the HTTP status and incident id (the ./status subpath), and per-request timeout/abort. Auto-invoked when importing the API client, reading a failed call's status, or when ctx.entrypoint(...).call() under the hood is involved.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,28 +8,113 @@ user-invocable: false
8
8
  # @owlmeans/api
9
9
 
10
10
  **Layer:** Core
11
- **Install:** `"@owlmeans/api": "^0.1.18-rc.0"` in `dependencies`
11
+ **Install:** `"@owlmeans/api": "^0.1.18-rc.40"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
15
15
  | Export | Description |
16
16
  |--------|-------------|
17
- | `makeApiService()` | Factory for the HTTP client service (axios) |
18
- | `Api` types | Service interface |
19
- | Errors | Typed transport errors (Network, Timeout, HttpStatus) |
20
- | Constants | Default timeout, retry counts |
17
+ | `createApiService(alias?)` | Factory for the HTTP client service (axios) |
18
+ | `appendApiClient(ctx, alias?)` | Register it and make it the context's default `webService` when none is set |
19
+ | `ApiClient` | Service interface — a single `handler(req, reply)` |
20
+ | `ApiError`, `ApiClientError`, `ServerCrashedError`, `ServerAuthError`, `ApiStatusError` | Typed transport errors; an `ApiClientError` carries the answered `status` and the server's `incidentId` |
21
+ | `httpStatusOf`, `incidentIdOf`, `isAccessDenied`, `isIncidentBody`, `parseClientMarker`, `API_CLIENT_MARKER`, `API_STATUS_MARKER` | Read a failure's HTTP status, incident id and IAM denial marker (also the `./status` subpath) |
22
+ | Constants | Status codes (`OK`, `CREATED`, `ACCEPTED`, `FINISHED`, `UNAUTHORIZED_ERROR`, `FORBIDDEN_ERROR`, `SERVER_ERROR`), `INCIDENT_ID_HEADER` (`X-Incident-ID`), `DEFAULT_ALIAS` (`web-client`) |
23
+
24
+ Subpath `./status` — `httpStatusOf`, `incidentIdOf`, `isIncidentBody`, `parseClientMarker`, the
25
+ markers and `INCIDENT_ID_HEADER`, importing only `@owlmeans/error`: a browser package reads a
26
+ status without pulling axios in.
27
+
28
+ `isAccessDenied(error)` requires the server's `X-OwlMeans-Denial: access-denied` marker and a 403.
29
+ Do not classify all 403 responses as IAM denials: entitlement and other refusals use that status
30
+ too. The API client stamps the marker on its rejection even under production error exposure.
31
+
32
+ ## How a call is carried
33
+
34
+ `ep.call(...)` / `ep.invoke(...)` hand the request to whatever is bound to the route's protocol. When
35
+ a transport service is registered under `transportAlias(protocol)` it takes the call; otherwise this
36
+ package's client does, over HTTP. Either way the consumer writes the same line and never learns which
37
+ one ran.
38
+
39
+ When it is this client, the entrypoint answers where it lives — `path()` for the path, `address()` for
40
+ host, port, base, protocol and whether the hop is TLS — and `makeSecurityHelper` assembles the URL from
41
+ that. A per-request `host`, `base` or `unsecure` still overrides it. `:params` come from
42
+ `request.params` and a missing one is a `SyntaxError`, not a literal `:id` in the URL.
43
+
44
+ A request whose `canceled` flag is already set is dropped without a round trip.
45
+
46
+ ## Per-request controls
47
+
48
+ These live on the request the caller passes and are forwarded to the transport:
49
+
50
+ - `timeout` — milliseconds for the single round trip; omitted or `0` means no timeout, so a stuck
51
+ peer hangs forever unless a caller bounds it.
52
+ - `signal` — an `AbortSignal`, which aborts the request in flight.
53
+ - `headers` — a `content-type` of `application/x-www-form-urlencoded` makes the body serialize
54
+ through `qs` instead of JSON. A string body on a `POST` with no `content-type` gets
55
+ `application/json` and is sent verbatim rather than re-quoted.
56
+
57
+ ## Reading the answer
58
+
59
+ `validateStatus` is `() => true`, so **no status throws**: every answer the peer sends, 4xx and 5xx
60
+ included, comes back and is mapped here.
61
+
62
+ | Status | Result |
63
+ |--------|--------|
64
+ | 200 / 202 | Resolves with the body and an `Ok` / `Accepted` outcome |
65
+ | 201 / 204 | Resolves with the body, or with the response headers when the body is empty |
66
+ | any other | Rejects |
67
+
68
+ Every rejection keeps the HTTP status and, when the server sent one, its incident id (the
69
+ `X-Incident-ID` header, read from `AxiosHeaders` or a plain object, else a bare UUID body — a
70
+ browser reads that header cross-origin only when the server exposes it, the body covers the rest):
71
+
72
+ | Body | Rejects with |
73
+ |---|---|
74
+ | Development — the marshaled `ResilientError` (carries the separator) | The original class, rebuilt through the registry, stamped `responseStatus` and `incidentId` |
75
+ | Production — a bare incident UUID; also proxy HTML, framework JSON, anything else | `ServerCrashedError` `api:client:crashed:<id>` (500) · `ServerAuthError` `api:client:auth:<id>` (401) · `ApiClientError` `api:client:forbidden[:<id>]` (403) · `ApiStatusError` `api:client:status:<n>[:<id>]` (every other status) |
76
+
77
+ An `@owlmeans/server-api` boundary in production exposure sends ONLY the incident id, so a typed
78
+ refusal's class never reaches the browser — its status does. Read it with `httpStatusOf(e)`: the
79
+ stamped `responseStatus`, else an `ApiClientError`'s parsed `status`, else a class's declared
80
+ `httpStatus` (a 4xx; a 5xx only with `allowServerErrorStatus`, exactly what the server answers),
81
+ else an `api:client:*` marker in the message or type; `null` when nothing states one. A consumer
82
+ that acts on a refusal checks the class/marker AND the status (`consentRefusalOf(e) ||
83
+ httpStatusOf(e) === 428`). Markers without an id stay as they were (`api:client:crashed:error`,
84
+ `api:client:forbidden`). The status and id are rebuilt from the marker in `finalizeUnmarshal()`,
85
+ so they survive a marshal. No class of this family declares a static `httpStatus`: a server that
86
+ rethrows one still answers 500.
87
+
88
+ **A failure with no answer at all is a different family.** Suppressing status errors does not wrap
89
+ the call: an expired `timeout` (`ECONNABORTED`), an aborted `signal` (`CanceledError` /
90
+ `ERR_CANCELED`), a refused connection or a DNS failure rejects with the raw axios error, which is
91
+ none of the typed errors above. A caller that bounds a call has to catch that too — testing for
92
+ `ApiClientError` alone lets a timeout through as an unhandled rejection.
93
+
94
+ An `auth-token-refresh` response header is consumed here: when the context has an auth service, the
95
+ rotated token is handed to it, which is what keeps a long session alive without the caller doing
96
+ anything.
97
+
98
+ Building a URL without making the call is a different question — that is `entrypointUrl` from
99
+ `@owlmeans/client-entrypoint/utils`, or `ep.url(req, { absolute })`.
21
100
 
22
101
  ## Usage
23
102
 
24
- The api service backs `ctx.entrypoint<ClientEntrypoint<T>>(alias).call(...)` cross-service calls. Most apps don't import it directly — they register it once in their context factory:
103
+ Most apps don't import this directly — they register it once in their context factory:
25
104
 
26
105
  ```typescript
27
- import { makeApiService } from '@owlmeans/api'
28
- context.registerService(makeApiService())
106
+ import { appendApiClient } from '@owlmeans/api'
107
+ appendApiClient(context)
29
108
  ```
30
109
 
110
+ `appendApiClient` only claims `cfg.webService` when it is unset, so an app that has already named a
111
+ transport keeps it.
112
+
31
113
  ## Depends On
32
114
 
33
115
  - `@owlmeans/context` — service registration
34
- - `@owlmeans/error`, `@owlmeans/i18n`
35
- - `axios` (runtime)
116
+ - `@owlmeans/entrypoint` — the entrypoint being addressed, `@owlmeans/client-route` — `extractParams`
117
+ - `@owlmeans/config` — `makeSecurityHelper`, `@owlmeans/client-config` — the config shape
118
+ - `@owlmeans/auth-common` — the token-refresh header and the auth service it updates
119
+ - `@owlmeans/error`, `@owlmeans/route`
120
+ - `axios`, `qs` (runtime)
package/build/consts.d.ts CHANGED
@@ -5,6 +5,13 @@ export declare const OK = 200;
5
5
  export declare const CREATED = 201;
6
6
  export declare const ACCEPTED = 202;
7
7
  export declare const FINISHED = 204;
8
+ /**
9
+ * The response header an `@owlmeans/server-api` boundary puts its incident id in — the key to the
10
+ * server log entry that owns a failure's full detail.
11
+ */
12
+ export declare const INCIDENT_ID_HEADER = "X-Incident-ID";
13
+ export declare const DENIAL_KIND_HEADER = "X-OwlMeans-Denial";
14
+ export declare const ACCESS_DENIED_KIND = "access-denied";
8
15
  export declare const DEFAULT_ALIAS = "web-client";
9
16
  export declare const protocols: string[];
10
17
  //# sourceMappingURL=consts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,YAAY,MAAM,CAAA;AAE/B,eAAO,MAAM,kBAAkB,MAAM,CAAA;AAErC,eAAO,MAAM,eAAe,MAAM,CAAA;AAElC,eAAO,MAAM,EAAE,MAAM,CAAA;AAErB,eAAO,MAAM,OAAO,MAAM,CAAA;AAE1B,eAAO,MAAM,QAAQ,MAAM,CAAA;AAE3B,eAAO,MAAM,QAAQ,MAAM,CAAA;AAE3B,eAAO,MAAM,aAAa,eAAe,CAAA;AAEzC,eAAO,MAAM,SAAS,UAAiC,CAAA"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,YAAY,MAAM,CAAA;AAE/B,eAAO,MAAM,kBAAkB,MAAM,CAAA;AAErC,eAAO,MAAM,eAAe,MAAM,CAAA;AAElC,eAAO,MAAM,EAAE,MAAM,CAAA;AAErB,eAAO,MAAM,OAAO,MAAM,CAAA;AAE1B,eAAO,MAAM,QAAQ,MAAM,CAAA;AAE3B,eAAO,MAAM,QAAQ,MAAM,CAAA;AAE3B;;;GAGG;AACH,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AACjD,eAAO,MAAM,kBAAkB,sBAAsB,CAAA;AACrD,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AAEjD,eAAO,MAAM,aAAa,eAAe,CAAA;AAEzC,eAAO,MAAM,SAAS,UAAiC,CAAA"}
package/build/consts.js CHANGED
@@ -5,6 +5,13 @@ export const OK = 200;
5
5
  export const CREATED = 201;
6
6
  export const ACCEPTED = 202;
7
7
  export const FINISHED = 204;
8
+ /**
9
+ * The response header an `@owlmeans/server-api` boundary puts its incident id in — the key to the
10
+ * server log entry that owns a failure's full detail.
11
+ */
12
+ export const INCIDENT_ID_HEADER = 'X-Incident-ID';
13
+ export const DENIAL_KIND_HEADER = 'X-OwlMeans-Denial';
14
+ export const ACCESS_DENIED_KIND = 'access-denied';
8
15
  export const DEFAULT_ALIAS = 'web-client';
9
16
  export const protocols = ['http', 'https', 'wss', 'ws'];
10
17
  //# sourceMappingURL=consts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;AAE/B,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAA;AAErC,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAElC,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,CAAA;AAErB,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAA;AAE1B,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAA;AAE3B,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAA;AAE3B,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAA;AAEzC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA"}
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;AAE/B,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAA;AAErC,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAElC,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,CAAA;AAErB,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAA;AAE1B,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAA;AAE3B,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAA;AAE3B;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAA;AACjD,MAAM,CAAC,MAAM,kBAAkB,GAAG,mBAAmB,CAAA;AACrD,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAA;AAEjD,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAA;AAEzC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA"}
package/build/errors.d.ts CHANGED
@@ -3,16 +3,44 @@ export declare class ApiError extends ResilientError {
3
3
  static typeName: string;
4
4
  constructor(message?: string);
5
5
  }
6
+ /**
7
+ * A request the peer answered with a failure.
8
+ *
9
+ * `status` is the HTTP status the marker in the message states (`api:client:crashed:<id>` is 500,
10
+ * `api:client:auth:<id>` 401, `api:client:forbidden[:<id>]` 403, `api:client:status:<n>[:<id>]`
11
+ * any other); `incidentId` is the server's correlation id when the marker carries one. Only
12
+ * `type` and `message` survive a marshal, so both are rebuilt from the message in
13
+ * `finalizeUnmarshal()`. No class of this family declares a static `httpStatus`: a server that
14
+ * rethrows one answers 500, as before.
15
+ */
6
16
  export declare class ApiClientError extends ApiError {
7
17
  static typeName: string;
18
+ /** The HTTP status the peer answered with, when the message states one. */
19
+ status?: number;
8
20
  constructor(message?: string);
21
+ protected applyStatus(): void;
22
+ finalizeUnmarshal(): void;
9
23
  }
24
+ /** The peer answered 500. Marker `api:client:crashed:<incident id | error>`. */
10
25
  export declare class ServerCrashedError extends ApiClientError {
11
26
  static typeName: string;
12
- constructor(message?: string);
27
+ constructor(incidentId?: string);
13
28
  }
29
+ /** The peer answered 401. Marker `api:client:auth:<incident id | error>`. */
14
30
  export declare class ServerAuthError extends ApiClientError {
15
31
  static typeName: string;
16
- constructor(message?: string);
32
+ constructor(incidentId?: string);
33
+ }
34
+ /**
35
+ * The peer answered any other non-2xx status with no typed error in the body — a production
36
+ * incident id, a proxy's HTML page, a framework's JSON. Marker `api:client:status:<n>[:<id>]`.
37
+ */
38
+ export declare class ApiStatusError extends ApiClientError {
39
+ static typeName: string;
40
+ /** The marker body: `<status>[:<incident id>]`. */
41
+ static encode(status: number, incidentId?: string): string;
42
+ constructor(status: number, incidentId?: string);
43
+ /** A marker body (`ApiStatusError.encode`) or a marshaled message — the error registry's path. */
44
+ constructor(message: string);
17
45
  }
18
46
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,qBAAa,QAAS,SAAQ,cAAc;IAC1C,OAAuB,QAAQ,SAAa;IAE5C,YAAY,OAAO,GAAE,MAAgB,EAEpC;CACF;AAED,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,OAAuB,QAAQ,SAAmB;IAElD,YAAY,OAAO,GAAE,MAAgB,EAGpC;CACF;AAED,qBAAa,kBAAmB,SAAQ,cAAc;IACpD,OAAuB,QAAQ,SAA4C;IAE3E,YAAY,OAAO,GAAE,MAAgB,EAGpC;CACF;AAED,qBAAa,eAAgB,SAAQ,cAAc;IACjD,OAAuB,QAAQ,SAAyC;IAExE,YAAY,OAAO,GAAE,MAAgB,EAGpC;CACF"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGhD,qBAAa,QAAS,SAAQ,cAAc;IAC1C,OAAuB,QAAQ,SAAa;IAE5C,YAAY,OAAO,GAAE,MAAgB,EAEpC;CACF;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,OAAuB,QAAQ,SAAmB;IAElD,2EAA2E;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IAEtB,YAAY,OAAO,GAAE,MAAgB,EAIpC;IAED,SAAS,CAAC,WAAW,IAAI,IAAI,CAM5B;IAEQ,iBAAiB,IAAI,IAAI,CAEjC;CACF;AAED,gFAAgF;AAChF,qBAAa,kBAAmB,SAAQ,cAAc;IACpD,OAAuB,QAAQ,SAA4C;IAE3E,YAAY,UAAU,CAAC,EAAE,MAAM,EAG9B;CACF;AAED,6EAA6E;AAC7E,qBAAa,eAAgB,SAAQ,cAAc;IACjD,OAAuB,QAAQ,SAAyC;IAExE,YAAY,UAAU,CAAC,EAAE,MAAM,EAG9B;CACF;AAED;;;GAGG;AACH,qBAAa,cAAe,SAAQ,cAAc;IAChD,OAAuB,QAAQ,SAAqC;IAEpE,mDAAmD;IACnD,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzD;IAED,YAAY,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAC;IAChD,kGAAkG;IAClG,YAAY,OAAO,EAAE,MAAM,EAAC;CAK7B"}
package/build/errors.js CHANGED
@@ -1,33 +1,75 @@
1
1
  import { ResilientError } from '@owlmeans/error';
2
+ import { parseClientMarker } from './status/marker.js';
2
3
  export class ApiError extends ResilientError {
3
4
  static typeName = 'ApiError';
4
5
  constructor(message = 'error') {
5
6
  super(ApiError.typeName, `api:${message}`);
6
7
  }
7
8
  }
9
+ /**
10
+ * A request the peer answered with a failure.
11
+ *
12
+ * `status` is the HTTP status the marker in the message states (`api:client:crashed:<id>` is 500,
13
+ * `api:client:auth:<id>` 401, `api:client:forbidden[:<id>]` 403, `api:client:status:<n>[:<id>]`
14
+ * any other); `incidentId` is the server's correlation id when the marker carries one. Only
15
+ * `type` and `message` survive a marshal, so both are rebuilt from the message in
16
+ * `finalizeUnmarshal()`. No class of this family declares a static `httpStatus`: a server that
17
+ * rethrows one answers 500, as before.
18
+ */
8
19
  export class ApiClientError extends ApiError {
9
20
  static typeName = 'ApiClientError';
21
+ /** The HTTP status the peer answered with, when the message states one. */
22
+ status;
10
23
  constructor(message = 'error') {
11
24
  super(`client:${message}`);
12
25
  this.type = ApiClientError.typeName;
26
+ this.applyStatus();
27
+ }
28
+ applyStatus() {
29
+ const marker = parseClientMarker(this.message);
30
+ this.status = marker?.status;
31
+ if (marker?.incidentId != null) {
32
+ this.incidentId ??= marker.incidentId;
33
+ }
34
+ }
35
+ finalizeUnmarshal() {
36
+ this.applyStatus();
13
37
  }
14
38
  }
39
+ /** The peer answered 500. Marker `api:client:crashed:<incident id | error>`. */
15
40
  export class ServerCrashedError extends ApiClientError {
16
41
  static typeName = `${ApiClientError.typeName}ServerCrashed`;
17
- constructor(message = 'error') {
18
- super(`crashed:${message}`);
42
+ constructor(incidentId) {
43
+ super(`crashed:${incidentId ?? 'error'}`);
19
44
  this.type = ServerCrashedError.typeName;
20
45
  }
21
46
  }
47
+ /** The peer answered 401. Marker `api:client:auth:<incident id | error>`. */
22
48
  export class ServerAuthError extends ApiClientError {
23
49
  static typeName = `${ApiClientError.typeName}ServerAuth`;
24
- constructor(message = 'error') {
25
- super(`auth:${message}`);
50
+ constructor(incidentId) {
51
+ super(`auth:${incidentId ?? 'error'}`);
26
52
  this.type = ServerAuthError.typeName;
27
53
  }
28
54
  }
55
+ /**
56
+ * The peer answered any other non-2xx status with no typed error in the body — a production
57
+ * incident id, a proxy's HTML page, a framework's JSON. Marker `api:client:status:<n>[:<id>]`.
58
+ */
59
+ export class ApiStatusError extends ApiClientError {
60
+ static typeName = `${ApiClientError.typeName}Status`;
61
+ /** The marker body: `<status>[:<incident id>]`. */
62
+ static encode(status, incidentId) {
63
+ return incidentId != null && incidentId !== '' ? `${status}:${incidentId}` : `${status}`;
64
+ }
65
+ constructor(status = 'error', incidentId) {
66
+ super(`status:${typeof status === 'number' ? ApiStatusError.encode(status, incidentId) : status}`);
67
+ this.type = ApiStatusError.typeName;
68
+ }
69
+ }
29
70
  ResilientError.registerErrorClass(ApiError);
30
71
  ResilientError.registerErrorClass(ApiClientError);
31
72
  ResilientError.registerErrorClass(ServerCrashedError);
32
73
  ResilientError.registerErrorClass(ServerAuthError);
74
+ ResilientError.registerErrorClass(ApiStatusError);
33
75
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,MAAM,OAAO,QAAS,SAAQ,cAAc;IACnC,MAAM,CAAU,QAAQ,GAAG,UAAU,CAAA;IAE5C,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,OAAO,EAAE,CAAC,CAAA;IAC5C,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,QAAQ;IACnC,MAAM,CAAU,QAAQ,GAAG,gBAAgB,CAAA;IAElD,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,UAAU,OAAO,EAAE,CAAC,CAAA;QAC1B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAA;IACrC,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,cAAc;IAC7C,MAAM,CAAU,QAAQ,GAAG,GAAG,cAAc,CAAC,QAAQ,eAAe,CAAA;IAE3E,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,WAAW,OAAO,EAAE,CAAC,CAAA;QAC3B,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC,QAAQ,CAAA;IACzC,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,cAAc;IAC1C,MAAM,CAAU,QAAQ,GAAG,GAAG,cAAc,CAAC,QAAQ,YAAY,CAAA;IAExE,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,QAAQ,OAAO,EAAE,CAAC,CAAA;QACxB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAA;IACtC,CAAC;CACF;AAED,cAAc,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAA;AAC3C,cAAc,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAA;AACjD,cAAc,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAA;AACrD,cAAc,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAA"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAEtD,MAAM,OAAO,QAAS,SAAQ,cAAc;IACnC,MAAM,CAAU,QAAQ,GAAG,UAAU,CAAA;IAE5C,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,OAAO,EAAE,CAAC,CAAA;IAC5C,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,cAAe,SAAQ,QAAQ;IACnC,MAAM,CAAU,QAAQ,GAAG,gBAAgB,CAAA;IAElD,2EAA2E;IACpE,MAAM,CAAS;IAEtB,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,UAAU,OAAO,EAAE,CAAC,CAAA;QAC1B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAA;QACnC,IAAI,CAAC,WAAW,EAAE,CAAA;IACpB,CAAC;IAES,WAAW;QACnB,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC9C,IAAI,CAAC,MAAM,GAAG,MAAM,EAAE,MAAM,CAAA;QAC5B,IAAI,MAAM,EAAE,UAAU,IAAI,IAAI,EAAE,CAAC;YAC/B,IAAI,CAAC,UAAU,KAAK,MAAM,CAAC,UAAU,CAAA;QACvC,CAAC;IACH,CAAC;IAEQ,iBAAiB;QACxB,IAAI,CAAC,WAAW,EAAE,CAAA;IACpB,CAAC;CACF;AAED,gFAAgF;AAChF,MAAM,OAAO,kBAAmB,SAAQ,cAAc;IAC7C,MAAM,CAAU,QAAQ,GAAG,GAAG,cAAc,CAAC,QAAQ,eAAe,CAAA;IAE3E,YAAY,UAAmB;QAC7B,KAAK,CAAC,WAAW,UAAU,IAAI,OAAO,EAAE,CAAC,CAAA;QACzC,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC,QAAQ,CAAA;IACzC,CAAC;CACF;AAED,6EAA6E;AAC7E,MAAM,OAAO,eAAgB,SAAQ,cAAc;IAC1C,MAAM,CAAU,QAAQ,GAAG,GAAG,cAAc,CAAC,QAAQ,YAAY,CAAA;IAExE,YAAY,UAAmB;QAC7B,KAAK,CAAC,QAAQ,UAAU,IAAI,OAAO,EAAE,CAAC,CAAA;QACtC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAA;IACtC,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,cAAe,SAAQ,cAAc;IACzC,MAAM,CAAU,QAAQ,GAAG,GAAG,cAAc,CAAC,QAAQ,QAAQ,CAAA;IAEpE,mDAAmD;IACnD,MAAM,CAAC,MAAM,CAAC,MAAc,EAAE,UAAmB;QAC/C,OAAO,UAAU,IAAI,IAAI,IAAI,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAAA;IAC1F,CAAC;IAKD,YAAY,MAAM,GAAoB,OAAO,EAAE,UAAmB;QAChE,KAAK,CAAC,UAAU,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAA;QAClG,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAA;IACrC,CAAC;CACF;AAED,cAAc,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAA;AAC3C,cAAc,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAA;AACjD,cAAc,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAA;AACrD,cAAc,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAA;AAClD,cAAc,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAA"}
package/build/index.d.ts CHANGED
@@ -2,4 +2,6 @@ export * from './errors.js';
2
2
  export * from './consts.js';
3
3
  export * from './service.js';
4
4
  export type * from './types.js';
5
+ export { httpStatusOf, incidentIdOf, isIncidentBody, parseClientMarker, API_CLIENT_MARKER, API_STATUS_MARKER, } from './status/index.js';
6
+ export type { ClientMarker, ResponseStatusCarrier } from './status/index.js';
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,mBAAmB,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,mBAAmB,YAAY,CAAA;AAC/B,OAAO,EACL,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,GACpG,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA"}
package/build/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './errors.js';
2
2
  export * from './consts.js';
3
3
  export * from './service.js';
4
+ export { httpStatusOf, incidentIdOf, isIncidentBody, parseClientMarker, API_CLIENT_MARKER, API_STATUS_MARKER, } from './status/index.js';
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAE5B,OAAO,EACL,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,GACpG,MAAM,mBAAmB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAM3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAO3D,KAAK,MAAM,GAAG,iBAAiB,CAAA;AAE/B,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAC;CACnE;AAED,eAAO,MAAM,gBAAgB,WAAW,MAAM,KAAmB,SA4EhE,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,UAAS,MAAM,KAAmB,CAU/G,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAM3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAa3D,KAAK,MAAM,GAAG,iBAAiB,CAAA;AAE/B,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAC;CACnE;AAED,eAAO,MAAM,gBAAgB,WAAW,MAAM,KAAmB,SA+FhE,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,UAAS,MAAM,KAAmB,CAU/G,CAAA"}
package/build/service.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import { assertContext, createService } from '@owlmeans/context';
2
2
  import { extractParams } from '@owlmeans/client-route';
3
3
  import axios from 'axios';
4
- import { DEFAULT_ALIAS } from './consts.js';
4
+ import { DEFAULT_ALIAS, UNAUTHORIZED_ERROR } from './consts.js';
5
5
  import { processResponse } from './utils/handler.js';
6
6
  import qs from 'qs';
7
7
  import { makeSecurityHelper } from '@owlmeans/config';
8
8
  import { RouteMethod } from '@owlmeans/route';
9
- import { DEF_AUTH_SRV, TOKEN_UPDATE } from '@owlmeans/auth-common';
9
+ import { AUTH_HEADER, DEF_AUTH_SRV, TOKEN_UPDATE } from '@owlmeans/auth-common';
10
+ /** Whether a request carried an authentication header of its own. */
11
+ const presented = (headers) => headers != null && Object.entries(headers).some(([key, value]) => key.toLowerCase() === AUTH_HEADER && value != null && value !== '');
10
12
  export const createApiService = (alias = DEFAULT_ALIAS) => {
11
13
  const location = `api.service:${alias}`;
12
14
  const client = createService(alias, {
@@ -16,9 +18,8 @@ export const createApiService = (alias = DEFAULT_ALIAS) => {
16
18
  }
17
19
  const context = assertContext(client.ctx, location);
18
20
  const module = context.entrypoint(request.alias);
19
- await module.resolve();
20
21
  const route = module.route.route;
21
- let path = module.getPath();
22
+ let path = module.path();
22
23
  const params = extractParams(path);
23
24
  path = params.reduce((path, param) => {
24
25
  if (request.params[param] == null) {
@@ -26,11 +27,10 @@ export const createApiService = (alias = DEFAULT_ALIAS) => {
26
27
  }
27
28
  return path.replace(`:${param}`, `${request.params[param]}`);
28
29
  }, path);
29
- if (route.host == null && request.host == null) {
30
- throw new SyntaxError(`No host provided in ${module.alias} route`);
31
- }
32
30
  const helper = makeSecurityHelper(context);
33
- const url = helper.makeUrl(route, path, { host: request.host, base: request.base, forceUnsecure: request.unsecure });
31
+ // The entrypoint answers where it lives — host, port, base, protocol and whether the hop is
32
+ // TLS — from its declaration and the service it names. A per-request host still overrides it.
33
+ const url = helper.makeUrl(module.address(), path, { host: request.host, base: request.base, forceUnsecure: request.unsecure });
34
34
  let transformer = undefined;
35
35
  let body = request.body != null && Object.entries((request.headers ?? {})).find(([key, value]) => key.toLowerCase() === 'content-type' && value?.includes('application/x-www-form-urlencoded')) ? qs.stringify(request.body) : request.body;
36
36
  // @TODO Probably this hack needs to be made a little bit less dirty
@@ -62,6 +62,23 @@ export const createApiService = (alias = DEFAULT_ALIAS) => {
62
62
  const update = headers.get(TOKEN_UPDATE);
63
63
  await auth.update(update == null || update === '' ? undefined : update);
64
64
  }
65
+ else if (response.status === UNAUTHORIZED_ERROR
66
+ && presented(request.headers)
67
+ && context.hasService(DEF_AUTH_SRV)) {
68
+ /**
69
+ * A session the server has refused is not a session, and keeping it is worse than having
70
+ * none: the application goes on rendering its signed-in tree over a credential that fails
71
+ * every call it makes, and nothing on the client ever asks again — `authenticated()` reads
72
+ * storage and decodes an envelope, it does not consult the server. What that leaves is an
73
+ * app that looks signed in, works at nothing, and offers "Log out" where the control that
74
+ * would fix it should be.
75
+ *
76
+ * Narrow on purpose. Only a request that actually PRESENTED this context's bearer counts,
77
+ * so a 401 from an authentication attempt — bad code, wrong password, an expired challenge
78
+ * — never clears the session of whoever is already signed in.
79
+ */
80
+ await context.service(DEF_AUTH_SRV).update(undefined);
81
+ }
65
82
  processResponse(response, reply);
66
83
  return [reply.error ?? reply.value, reply.outcome];
67
84
  }
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEhE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAEtD,OAAO,KAAuB,MAAM,OAAO,CAAA;AAG3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEpD,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAQlE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAK,GAAW,aAAa,EAAa,EAAE;IAC3E,MAAM,QAAQ,GAAG,eAAe,KAAK,EAAE,CAAA;IACvC,MAAM,MAAM,GAAc,aAAa,CAAY,KAAK,EAAE;QACxD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YAChC,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;gBAC9B,OAAM;YACR,CAAC;YACD,MAAM,OAAO,GAAG,aAAa,CAAkB,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;YACpE,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAmB,OAAO,CAAC,KAAK,CAAC,CAAA;YAClE,MAAM,MAAM,CAAC,OAAO,EAAE,CAAA;YACtB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAA;YAChC,IAAI,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAA;YAC3B,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;YAClC,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBAEnC,IAAI,OAAO,CAAC,MAAM,CAAC,KAAY,CAAC,IAAI,IAAI,EAAE,CAAC;oBACzC,MAAM,IAAI,WAAW,CAAC,sBAAsB,KAAK,EAAE,CAAC,CAAA;gBACtD,CAAC;gBACD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,KAAY,CAAC,EAAE,CAAC,CAAA;YACrE,CAAC,EAAE,IAAI,CAAC,CAAA;YACR,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,IAAI,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;gBAC/C,MAAM,IAAI,WAAW,CAAC,uBAAuB,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAA;YACpE,CAAC;YAED,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;YAE1C,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;YAEpH,IAAI,WAAW,GAAwC,SAAS,CAAA;YAEhE,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAC7E,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CACf,GAAG,CAAC,WAAW,EAAE,KAAK,cAAc,IAAI,KAAK,EAAE,QAAQ,CAAC,mCAAmC,CAAC,CAC/F,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAA;YAE7C,oEAAoE;YACpE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW,CAAC,IAAI,EAAE,CAAC;gBAClE,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,cAAc,CAAC,EAAE,CAAC;oBACpF,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAA;oBACpD,qEAAqE;oBACrE,kEAAkE;oBAClE,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,IAAI,CAAA;gBAC5B,CAAC;YACH,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC;gBACnC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM;gBACzB,MAAM,EAAE,OAAO,CAAC,KAAK;gBACrB,IAAI,EAAE,IAAI;gBACV,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,gBAAgB,EAAE,WAAW;gBAC7B,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI;gBAC1B,4EAA4E;gBAC5E,uEAAuE;gBACvE,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,6EAA6E;gBAC7E,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAC,CAAA;YAEF,8EAA8E;YAC9E,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAuB,CAAA;YAChD,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBAClE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAc,YAAY,CAAC,CAAA;gBACvD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAW,CAAA;gBAClD,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,IAAI,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;YACzE,CAAC;YAED,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;YAEhC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAQ,CAAA;QAC3D,CAAC;KACF,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE;QACvB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAyC,GAAM,EAAE,KAAK,GAAW,aAAa,EAAK,EAAE;IAClH,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAA;IAEvC,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;IAE5B,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;QAC/B,GAAG,CAAC,GAAG,CAAC,UAAU,GAAG,KAAK,CAAA;IAC5B,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA"}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ export { INCIDENT_ID_HEADER } from '../consts.js';
2
+ export { DENIAL_KIND_HEADER, ACCESS_DENIED_KIND } from '../consts.js';
3
+ export { API_CLIENT_MARKER, API_STATUS_MARKER, parseClientMarker } from './marker.js';
4
+ export type { ClientMarker } from './marker.js';
5
+ /** The HTTP status the API client stamps on an error it rebuilt from a response body. */
6
+ export interface ResponseStatusCarrier {
7
+ responseStatus?: number;
8
+ denialKind?: string;
9
+ }
10
+ /** True only for a server-labelled auth/IAM refusal, never an unrelated 403. */
11
+ export declare const isAccessDenied: (error: unknown) => boolean;
12
+ /**
13
+ * The HTTP status an error answers to, or `null` when nothing states one.
14
+ *
15
+ * Read in order: the status the API client stamped on the error it rebuilt from a response
16
+ * (`responseStatus`); the status an `ApiClientError` parsed from its marker; the `httpStatus` its
17
+ * class declares — a 4xx always, a 5xx only with `allowServerErrorStatus`, exactly what an
18
+ * `@owlmeans/server-api` boundary answers with; finally an `api:client:*` marker in the message or
19
+ * the type of anything else. This is what lets a browser recognise a refusal from its status
20
+ * alone when a production body carries nothing but an incident id.
21
+ */
22
+ export declare const httpStatusOf: (error: unknown) => number | null;
23
+ /** The server's incident id carried by an error — its own field, else its marker — or `null`. */
24
+ export declare const incidentIdOf: (error: unknown) => string | null;
25
+ /**
26
+ * Whether a response body is a production incident body — the bare incident UUID an
27
+ * `@owlmeans/server-api` boundary sends instead of the marshaled error. A development body
28
+ * carries the `ResilientError` separator and is never one.
29
+ */
30
+ export declare const isIncidentBody: (data: unknown) => data is string;
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/status/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAErE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACrF,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,yFAAyF;AACzF,MAAM,WAAW,qBAAqB;IACpC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,gFAAgF;AAChF,eAAO,MAAM,cAAc,UAAW,OAAO,KAAG,OAGuB,CAAA;AAavE;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,UAAW,OAAO,KAAG,MAAM,GAAG,IAkBtD,CAAA;AAED,iGAAiG;AACjG,eAAO,MAAM,YAAY,UAAW,OAAO,KAAG,MAAM,GAAG,IAUtD,CAAA;AAID;;;;GAIG;AACH,eAAO,MAAM,cAAc,SAAU,OAAO,KAAG,IAAI,IAAI,MACyC,CAAA"}
@@ -0,0 +1,65 @@
1
+ import { ResilientError } from '@owlmeans/error';
2
+ import { ApiClientError } from '../errors.js';
3
+ import { parseClientMarker } from './marker.js';
4
+ export { INCIDENT_ID_HEADER } from '../consts.js';
5
+ export { DENIAL_KIND_HEADER, ACCESS_DENIED_KIND } from '../consts.js';
6
+ import { ACCESS_DENIED_KIND } from '../consts.js';
7
+ export { API_CLIENT_MARKER, API_STATUS_MARKER, parseClientMarker } from './marker.js';
8
+ /** True only for a server-labelled auth/IAM refusal, never an unrelated 403. */
9
+ export const isAccessDenied = (error) => httpStatusOf(error) === 403
10
+ && typeof error === 'object' && error != null
11
+ && error.denialKind === ACCESS_DENIED_KIND;
12
+ const httpStatus = (value) => typeof value === 'number' && Number.isInteger(value) && value >= 100 && value <= 599 ? value : null;
13
+ /** The first `api:client:*` marker in an error's `message`, else in its `type`. */
14
+ const markerOf = (error) => {
15
+ const { message, type } = error;
16
+ return (typeof message === 'string' ? parseClientMarker(message) : null)
17
+ ?? (typeof type === 'string' ? parseClientMarker(type) : null);
18
+ };
19
+ /**
20
+ * The HTTP status an error answers to, or `null` when nothing states one.
21
+ *
22
+ * Read in order: the status the API client stamped on the error it rebuilt from a response
23
+ * (`responseStatus`); the status an `ApiClientError` parsed from its marker; the `httpStatus` its
24
+ * class declares — a 4xx always, a 5xx only with `allowServerErrorStatus`, exactly what an
25
+ * `@owlmeans/server-api` boundary answers with; finally an `api:client:*` marker in the message or
26
+ * the type of anything else. This is what lets a browser recognise a refusal from its status
27
+ * alone when a production body carries nothing but an incident id.
28
+ */
29
+ export const httpStatusOf = (error) => {
30
+ if (error == null || typeof error !== 'object') {
31
+ return null;
32
+ }
33
+ const stamped = httpStatus(error.responseStatus);
34
+ if (stamped != null) {
35
+ return stamped;
36
+ }
37
+ if (error instanceof ApiClientError && httpStatus(error.status) != null) {
38
+ return error.status;
39
+ }
40
+ const declaration = error.constructor;
41
+ const declared = httpStatus(declaration?.httpStatus);
42
+ if (declared != null && (declared < 500 || declaration?.allowServerErrorStatus === true)) {
43
+ return declared;
44
+ }
45
+ return markerOf(error)?.status ?? null;
46
+ };
47
+ /** The server's incident id carried by an error — its own field, else its marker — or `null`. */
48
+ export const incidentIdOf = (error) => {
49
+ if (error == null || typeof error !== 'object') {
50
+ return null;
51
+ }
52
+ const own = error.incidentId;
53
+ if (typeof own === 'string' && own !== '') {
54
+ return own;
55
+ }
56
+ return markerOf(error)?.incidentId ?? null;
57
+ };
58
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
59
+ /**
60
+ * Whether a response body is a production incident body — the bare incident UUID an
61
+ * `@owlmeans/server-api` boundary sends instead of the marshaled error. A development body
62
+ * carries the `ResilientError` separator and is never one.
63
+ */
64
+ export const isIncidentBody = (data) => typeof data === 'string' && !data.includes(ResilientError.separator) && UUID.test(data.trim());
65
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/status/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /** The prefix every `ApiClientError` message carries. */
2
+ export declare const API_CLIENT_MARKER = "api:client:";
3
+ /** The marker of an `ApiStatusError`: `api:client:status:<n>[:<incident id>]`. */
4
+ export declare const API_STATUS_MARKER = "api:client:status:";
5
+ /** What an `api:client:*` marker states. */
6
+ export interface ClientMarker {
7
+ status?: number;
8
+ incidentId?: string;
9
+ }
10
+ /** Read the first `api:client:*` marker in a text, or `null` when it carries none. */
11
+ export declare const parseClientMarker: (text: string) => ClientMarker | null;
12
+ //# sourceMappingURL=marker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marker.d.ts","sourceRoot":"","sources":["../../src/status/marker.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,gBAAgB,CAAA;AAE9C,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,uBAAgC,CAAA;AAY9D,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,sFAAsF;AACtF,eAAO,MAAM,iBAAiB,SAAU,MAAM,KAAG,YAAY,GAAG,IAa/D,CAAA"}