@owlmeans/api 0.1.18-rc.8 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -4
- package/agent-meta/manifest.json +3 -3
- package/agent-meta/skills/api/SKILL.md +117 -11
- package/build/consts.d.ts +7 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +7 -0
- package/build/consts.js.map +1 -1
- package/build/errors.d.ts +30 -2
- package/build/errors.d.ts.map +1 -1
- package/build/errors.js +46 -4
- package/build/errors.js.map +1 -1
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/service.d.ts +1 -6
- package/build/service.d.ts.map +1 -1
- package/build/service.js +39 -27
- package/build/service.js.map +1 -1
- package/build/status/consts.d.ts +5 -0
- package/build/status/consts.d.ts.map +1 -0
- package/build/status/consts.js +5 -0
- package/build/status/consts.js.map +1 -0
- package/build/status/consts.local.d.ts +10 -0
- package/build/status/consts.local.d.ts.map +1 -0
- package/build/status/consts.local.js +10 -0
- package/build/status/consts.local.js.map +1 -0
- package/build/status/helper.d.ts +4 -0
- package/build/status/helper.d.ts.map +1 -0
- package/build/status/helper.js +49 -0
- package/build/status/helper.js.map +1 -0
- package/build/status/index.d.ts +9 -0
- package/build/status/index.d.ts.map +1 -0
- package/build/status/index.js +9 -0
- package/build/status/index.js.map +1 -0
- package/build/status/marker.d.ts +3 -0
- package/build/status/marker.d.ts.map +1 -0
- package/build/status/marker.js +16 -0
- package/build/status/marker.js.map +1 -0
- package/build/status/types.d.ts +35 -0
- package/build/status/types.d.ts.map +1 -0
- package/build/status/types.js +2 -0
- package/build/status/types.js.map +1 -0
- package/build/types.local.d.ts +6 -0
- package/build/types.local.d.ts.map +1 -0
- package/build/types.local.js +2 -0
- package/build/types.local.js.map +1 -0
- package/build/utils/body/types.d.ts +36 -0
- package/build/utils/body/types.d.ts.map +1 -0
- package/build/utils/body/types.js +2 -0
- package/build/utils/body/types.js.map +1 -0
- package/build/utils/body.d.ts +4 -0
- package/build/utils/body.d.ts.map +1 -0
- package/build/utils/body.js +56 -0
- package/build/utils/body.js.map +1 -0
- package/build/utils/consts.d.ts +2 -0
- package/build/utils/consts.d.ts.map +1 -0
- package/build/utils/consts.js +2 -0
- package/build/utils/consts.js.map +1 -0
- package/build/utils/consts.local.d.ts +2 -0
- package/build/utils/consts.local.d.ts.map +1 -0
- package/build/utils/consts.local.js +2 -0
- package/build/utils/consts.local.js.map +1 -0
- package/build/utils/handler.d.ts.map +1 -1
- package/build/utils/index.d.ts +2 -1
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +1 -1
- package/build/utils/index.js.map +1 -1
- package/build/utils/response/types.d.ts +15 -0
- package/build/utils/response/types.d.ts.map +1 -0
- package/build/utils/response/types.js +2 -0
- package/build/utils/response/types.js.map +1 -0
- package/build/utils/response.d.ts +4 -0
- package/build/utils/response.d.ts.map +1 -0
- package/build/utils/response.js +99 -0
- package/build/utils/response.js.map +1 -0
- package/package.json +24 -14
- package/src/consts.ts +8 -0
- package/src/errors.ts +55 -4
- package/src/index.ts +5 -1
- package/src/service.ts +48 -40
- package/src/status/consts.local.ts +11 -0
- package/src/status/consts.ts +5 -0
- package/src/status/helper.ts +63 -0
- package/src/status/index.ts +11 -0
- package/src/status/marker.ts +19 -0
- package/src/status/types.ts +36 -0
- package/src/types.local.ts +7 -0
- package/src/utils/body/types.ts +37 -0
- package/src/utils/body.ts +70 -0
- package/src/utils/consts.local.ts +1 -0
- package/src/utils/consts.ts +1 -0
- package/src/utils/index.ts +2 -1
- package/src/utils/response/types.ts +15 -0
- package/src/utils/response.ts +117 -0
- package/tests/body.spec.ts +124 -0
- package/tests/handler.spec.ts +229 -0
- package/tests/tsconfig.json +11 -0
- package/tsconfig.json +1 -1
- package/build/.gitkeep +0 -0
- package/src/utils/handler.ts +0 -61
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @owlmeans/api
|
|
2
2
|
|
|
3
|
-
HTTP API client service for OwlMeans client contexts —
|
|
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
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
@@ -41,6 +41,14 @@ Creates an HTTP client service. `alias` defaults to `DEFAULT_ALIAS` (`'web-clien
|
|
|
41
41
|
|
|
42
42
|
Registers the API client in the context.
|
|
43
43
|
|
|
44
|
+
### Request bodies
|
|
45
|
+
|
|
46
|
+
Objects and arrays go as JSON. A string, number or boolean body on a `POST` without a
|
|
47
|
+
`content-type`, or under a JSON `content-type` on any method, is `JSON.stringify`'d, so the server
|
|
48
|
+
parses back the same value (`'abc'` travels as `"abc"`). A string that already is JSON text is sent
|
|
49
|
+
as it is — except under a `type: 'string'` body contract, where only a JSON string literal counts
|
|
50
|
+
as serialized and `'123'` arrives as the string `'123'`.
|
|
51
|
+
|
|
44
52
|
### Error Classes
|
|
45
53
|
|
|
46
54
|
- `ApiError` — base API error
|
|
@@ -54,7 +62,7 @@ Registers the API client in the context.
|
|
|
54
62
|
|
|
55
63
|
## Related Packages
|
|
56
64
|
|
|
57
|
-
- [`@owlmeans/client-
|
|
65
|
+
- [`@owlmeans/client-entrypoint`](../client-entrypoint) — bound protocols use this service to make requests
|
|
58
66
|
- [`@owlmeans/web-client`](../web-client) — registers this service via `makeContext`
|
|
59
67
|
|
|
60
68
|
<!-- owlmeans:agent-guidance:start -->
|
|
@@ -65,7 +73,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
65
73
|
your project's skill store (`.agents/skills/`):
|
|
66
74
|
|
|
67
75
|
```sh
|
|
68
|
-
npx @owlmeans/agent-skills
|
|
76
|
+
npx @owlmeans/agent-skills@^0.1.18
|
|
69
77
|
```
|
|
70
78
|
|
|
71
79
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion":
|
|
2
|
+
"schemaVersion": 3,
|
|
3
3
|
"package": "@owlmeans/api",
|
|
4
|
-
"version": "0.1.18
|
|
5
|
-
"generatedAt": "2026-
|
|
4
|
+
"version": "0.1.18",
|
|
5
|
+
"generatedAt": "2026-10-28T00:31:34.773Z",
|
|
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
|
|
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,134 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/api
|
|
9
9
|
|
|
10
10
|
**Layer:** Core
|
|
11
|
-
**Install:** `"@owlmeans/api": "^0.1.18
|
|
11
|
+
**Install:** `"@owlmeans/api": "^0.1.18"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
## Key Exports
|
|
14
14
|
|
|
15
15
|
| Export | Description |
|
|
16
16
|
|--------|-------------|
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
|
|
|
20
|
-
|
|
|
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
|
+
| `apiStatusHelper` (`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` — `apiStatusHelper` (`httpStatusOf`, `incidentIdOf`, `isAccessDenied`,
|
|
25
|
+
`isIncidentBody`), `parseClientMarker`, the markers and `INCIDENT_ID_HEADER`, importing only
|
|
26
|
+
`@owlmeans/error`: a browser package reads a status without pulling axios in.
|
|
27
|
+
|
|
28
|
+
`apiStatusHelper.isAccessDenied(error)` requires the server's `X-OwlMeans-Denial: access-denied`
|
|
29
|
+
marker and a 403. Do not classify all 403 responses as IAM denials: entitlement and other refusals
|
|
30
|
+
use that status too. The API client stamps the marker on its rejection even under production error
|
|
31
|
+
exposure.
|
|
32
|
+
|
|
33
|
+
## How a call is carried
|
|
34
|
+
|
|
35
|
+
`ep.call(...)` / `ep.invoke(...)` hand the request to whatever is bound to the route's protocol. When
|
|
36
|
+
a transport service is registered under `transportAlias(protocol)` it takes the call; otherwise this
|
|
37
|
+
package's client does, over HTTP. Either way the consumer writes the same line and never learns which
|
|
38
|
+
one ran.
|
|
39
|
+
|
|
40
|
+
When it is this client, the entrypoint answers where it lives — `path()` for the path, `address()` for
|
|
41
|
+
host, port, base, protocol and whether the hop is TLS — and `makeSecurityHelper` assembles the URL from
|
|
42
|
+
that. A per-request `host`, `base` or `unsecure` still overrides it. `:params` come from
|
|
43
|
+
`request.params` and a missing one is a `SyntaxError`, not a literal `:id` in the URL.
|
|
44
|
+
|
|
45
|
+
A request whose `canceled` flag is already set is dropped without a round trip.
|
|
46
|
+
|
|
47
|
+
## Per-request controls
|
|
48
|
+
|
|
49
|
+
These live on the request the caller passes and are forwarded to the transport:
|
|
50
|
+
|
|
51
|
+
- `timeout` — milliseconds for the single round trip; omitted or `0` means no timeout, so a stuck
|
|
52
|
+
peer hangs forever unless a caller bounds it.
|
|
53
|
+
- `signal` — an `AbortSignal`, which aborts the request in flight.
|
|
54
|
+
- `headers` — a `content-type` of `application/x-www-form-urlencoded` makes the body serialize
|
|
55
|
+
through `qs` instead of JSON; any other `content-type` the caller sets is kept.
|
|
56
|
+
|
|
57
|
+
## Request bodies
|
|
58
|
+
|
|
59
|
+
The body travels as JSON the server parses back to the value the caller passed:
|
|
60
|
+
|
|
61
|
+
| Body | On the wire |
|
|
62
|
+
|---|---|
|
|
63
|
+
| Object or array | JSON, serialized by axios — as always |
|
|
64
|
+
| String, number or boolean on a `POST` with no `content-type` | `content-type: application/json`, `JSON.stringify`'d (`'abc'` → `"abc"`, `42`, `true`) |
|
|
65
|
+
| String, number or boolean under a JSON `content-type` (`application/json`, `*+json`), any method | `JSON.stringify`'d |
|
|
66
|
+
| A string that already IS JSON text (`'{"a":1}'`, `'"abc"'`) | Sent as it is — a caller that serialized it itself keeps working |
|
|
67
|
+
| Any scalar under another `content-type` (`text/plain`), or with none on a non-`POST` | Untouched |
|
|
68
|
+
|
|
69
|
+
Under a body schema of `type: 'string'` (the protocol's contract, read from the entrypoint's
|
|
70
|
+
`filter.body`) a string is always a value: `'123'`, `'true'` or `'{…}'` arrive as those strings,
|
|
71
|
+
and only a JSON string literal counts as pre-serialized. Without a string contract a string that
|
|
72
|
+
parses as JSON is taken as serialized, so send a plain string that looks like JSON through a
|
|
73
|
+
`type: 'string'` contract. The caller's `headers` object is never rewritten. A bare unquoted string
|
|
74
|
+
under `application/json` is invalid JSON — `@owlmeans/server-api` answers it 400 — which is why it
|
|
75
|
+
is never sent.
|
|
76
|
+
|
|
77
|
+
## Reading the answer
|
|
78
|
+
|
|
79
|
+
`validateStatus` is `() => true`, so **no status throws**: every answer the peer sends, 4xx and 5xx
|
|
80
|
+
included, comes back and is mapped here.
|
|
81
|
+
|
|
82
|
+
| Status | Result |
|
|
83
|
+
|--------|--------|
|
|
84
|
+
| 200 / 202 | Resolves with the body and an `Ok` / `Accepted` outcome |
|
|
85
|
+
| 201 / 204 | Resolves with the body, or with the response headers when the body is empty |
|
|
86
|
+
| any other | Rejects |
|
|
87
|
+
|
|
88
|
+
Every rejection keeps the HTTP status and, when the server sent one, its incident id (the
|
|
89
|
+
`X-Incident-ID` header, read from `AxiosHeaders` or a plain object, else a bare UUID body — a
|
|
90
|
+
browser reads that header cross-origin only when the server exposes it, the body covers the rest):
|
|
91
|
+
|
|
92
|
+
| Body | Rejects with |
|
|
93
|
+
|---|---|
|
|
94
|
+
| Development — the marshaled `ResilientError` (carries the separator) | The original class, rebuilt through the registry, stamped `responseStatus` and `incidentId` |
|
|
95
|
+
| 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) |
|
|
96
|
+
|
|
97
|
+
An `@owlmeans/server-api` boundary in production exposure sends ONLY the incident id, so a typed
|
|
98
|
+
refusal's class never reaches the browser — its status does. Read it with
|
|
99
|
+
`apiStatusHelper.httpStatusOf(e)`: the stamped `responseStatus`, else an `ApiClientError`'s parsed
|
|
100
|
+
`status`, else a class's declared `httpStatus` (a 4xx; a 5xx only with `allowServerErrorStatus`,
|
|
101
|
+
exactly what the server answers), else an `api:client:*` marker in the message or type; `null` when
|
|
102
|
+
nothing states one. A consumer that acts on a refusal checks the class/marker AND the status
|
|
103
|
+
(`consentRefusalOf(e) || apiStatusHelper.httpStatusOf(e) === 428`). Markers without an id stay as
|
|
104
|
+
they were (`api:client:crashed:error`, `api:client:forbidden`). The status and id are rebuilt from
|
|
105
|
+
the marker in `finalizeUnmarshal()`, so they survive a marshal. No class of this family declares a
|
|
106
|
+
static `httpStatus`: a server that rethrows one still answers 500.
|
|
107
|
+
|
|
108
|
+
**A failure with no answer at all is a different family.** Suppressing status errors does not wrap
|
|
109
|
+
the call: an expired `timeout` (`ECONNABORTED`), an aborted `signal` (`CanceledError` /
|
|
110
|
+
`ERR_CANCELED`), a refused connection or a DNS failure rejects with the raw axios error, which is
|
|
111
|
+
none of the typed errors above. A caller that bounds a call has to catch that too — testing for
|
|
112
|
+
`ApiClientError` alone lets a timeout through as an unhandled rejection.
|
|
113
|
+
|
|
114
|
+
An `auth-token-refresh` response header is consumed here: when the context has an auth service, the
|
|
115
|
+
rotated token is handed to it, which is what keeps a long session alive without the caller doing
|
|
116
|
+
anything.
|
|
117
|
+
|
|
118
|
+
Building a URL without making the call is a different question — that is
|
|
119
|
+
`apiCallOf(ref).entrypointUrl(req, opts)` from `@owlmeans/client-entrypoint/utils`, or
|
|
120
|
+
`ep.url(req, { absolute })`.
|
|
21
121
|
|
|
22
122
|
## Usage
|
|
23
123
|
|
|
24
|
-
|
|
124
|
+
Most apps don't import this directly — they register it once in their context factory:
|
|
25
125
|
|
|
26
126
|
```typescript
|
|
27
|
-
import {
|
|
28
|
-
context
|
|
127
|
+
import { appendApiClient } from '@owlmeans/api'
|
|
128
|
+
appendApiClient(context)
|
|
29
129
|
```
|
|
30
130
|
|
|
131
|
+
`appendApiClient` only claims `cfg.webService` when it is unset, so an app that has already named a
|
|
132
|
+
transport keeps it.
|
|
133
|
+
|
|
31
134
|
## Depends On
|
|
32
135
|
|
|
33
136
|
- `@owlmeans/context` — service registration
|
|
34
|
-
- `@owlmeans/
|
|
35
|
-
- `
|
|
137
|
+
- `@owlmeans/entrypoint` — the entrypoint being addressed, `@owlmeans/client-route` — `clientRouteHelper.extractParams`
|
|
138
|
+
- `@owlmeans/config` — `makeSecurityHelper`, `@owlmeans/client-config` — the config shape
|
|
139
|
+
- `@owlmeans/auth-common` — the token-refresh header and the auth service it updates
|
|
140
|
+
- `@owlmeans/error`, `@owlmeans/route`
|
|
141
|
+
- `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
|
package/build/consts.d.ts.map
CHANGED
|
@@ -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
|
package/build/consts.js.map
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
package/build/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;
|
|
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(
|
|
18
|
-
super(`crashed:${
|
|
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(
|
|
25
|
-
super(`auth:${
|
|
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
|
package/build/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
CHANGED
|
@@ -2,4 +2,7 @@ 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, apiStatusHelper, createApiStatusHelper, parseClientMarker, API_CLIENT_MARKER, API_STATUS_MARKER, } from './status/index.js';
|
|
6
|
+
export type { ClientMarker, ApiStatusHelper } from './status/index.js';
|
|
7
|
+
export type { ResponseStatusCarrier } from './status/types.js';
|
|
5
8
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
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,eAAe,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,GAC9G,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACtE,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, apiStatusHelper, createApiStatusHelper, parseClientMarker, API_CLIENT_MARKER, API_STATUS_MARKER, } from './status/index.js';
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAE5B,OAAO,EACL,YAAY,EAAE,eAAe,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,GAC9G,MAAM,mBAAmB,CAAA"}
|
package/build/service.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import type { BasicContext } from '@owlmeans/context';
|
|
2
1
|
import type { ApiClient } from './types.js';
|
|
3
|
-
import {
|
|
4
|
-
type Config = BasicClientConfig;
|
|
5
|
-
interface Context<C extends Config = Config> extends BasicContext<C> {
|
|
6
|
-
}
|
|
2
|
+
import type { Config, Context } from './types.local.js';
|
|
7
3
|
export declare const createApiService: (alias?: string) => ApiClient;
|
|
8
4
|
export declare const appendApiClient: <C extends Config, T extends Context<C>>(ctx: T, alias?: string) => T;
|
|
9
|
-
export {};
|
|
10
5
|
//# sourceMappingURL=service.d.ts.map
|
package/build/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAQ3C,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AASvD,eAAO,MAAM,gBAAgB,WAAW,MAAM,KAAmB,SAsFhE,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,13 @@
|
|
|
1
1
|
import { assertContext, createService } from '@owlmeans/context';
|
|
2
|
-
import {
|
|
2
|
+
import { clientRouteHelper } from '@owlmeans/client-route';
|
|
3
3
|
import axios from 'axios';
|
|
4
|
-
import { DEFAULT_ALIAS } from './consts.js';
|
|
5
|
-
import {
|
|
6
|
-
import qs from 'qs';
|
|
4
|
+
import { DEFAULT_ALIAS, UNAUTHORIZED_ERROR } from './consts.js';
|
|
5
|
+
import { responseUtils } from './utils/response.js';
|
|
7
6
|
import { makeSecurityHelper } from '@owlmeans/config';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { AUTH_HEADER, DEF_AUTH_SRV, TOKEN_UPDATE } from '@owlmeans/auth-common';
|
|
8
|
+
import { bodyUtils } from './utils/body.js';
|
|
9
|
+
/** Whether a request carried an authentication header of its own. */
|
|
10
|
+
const presented = (headers) => headers != null && Object.entries(headers).some(([key, value]) => key.toLowerCase() === AUTH_HEADER && value != null && value !== '');
|
|
10
11
|
export const createApiService = (alias = DEFAULT_ALIAS) => {
|
|
11
12
|
const location = `api.service:${alias}`;
|
|
12
13
|
const client = createService(alias, {
|
|
@@ -16,37 +17,31 @@ export const createApiService = (alias = DEFAULT_ALIAS) => {
|
|
|
16
17
|
}
|
|
17
18
|
const context = assertContext(client.ctx, location);
|
|
18
19
|
const module = context.entrypoint(request.alias);
|
|
19
|
-
await module.resolve();
|
|
20
20
|
const route = module.route.route;
|
|
21
|
-
let path = module.
|
|
22
|
-
const params = extractParams(path);
|
|
21
|
+
let path = module.path();
|
|
22
|
+
const params = clientRouteHelper.extractParams(path);
|
|
23
23
|
path = params.reduce((path, param) => {
|
|
24
24
|
if (request.params[param] == null) {
|
|
25
25
|
throw new SyntaxError(`No value for param ${param}`);
|
|
26
26
|
}
|
|
27
27
|
return path.replace(`:${param}`, `${request.params[param]}`);
|
|
28
28
|
}, path);
|
|
29
|
-
if (route.host == null && request.host == null) {
|
|
30
|
-
throw new SyntaxError(`No host provided in ${module.alias} route`);
|
|
31
|
-
}
|
|
32
29
|
const helper = makeSecurityHelper(context);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
}
|
|
30
|
+
// The entrypoint answers where it lives — host, port, base, protocol and whether the hop is
|
|
31
|
+
// TLS — from its declaration and the service it names. A per-request host still overrides it.
|
|
32
|
+
const url = helper.makeUrl(module.address(), path, { host: request.host, base: request.base, forceUnsecure: request.unsecure });
|
|
33
|
+
// A scalar JSON body (a string, a number, a boolean) is serialized here, so the server parses
|
|
34
|
+
// back the value the caller sent; objects and arrays are left to axios.
|
|
35
|
+
const body = bodyUtils.requestBodyOf(request.body, request.headers, route.method, module.filter?.body);
|
|
36
|
+
const transformer = body.verbatim ? data => data : undefined;
|
|
37
|
+
const requestHeaders = body.contentType != null
|
|
38
|
+
? { ...request.headers, 'content-type': body.contentType }
|
|
39
|
+
: request.headers;
|
|
45
40
|
const response = await axios.request({
|
|
46
41
|
url, method: route.method,
|
|
47
42
|
params: request.query,
|
|
48
|
-
data: body,
|
|
49
|
-
headers:
|
|
43
|
+
data: body.data,
|
|
44
|
+
headers: requestHeaders,
|
|
50
45
|
transformRequest: transformer,
|
|
51
46
|
validateStatus: () => true,
|
|
52
47
|
// Per-request timeout (ms); axios aborts the request and rejects on expiry.
|
|
@@ -62,7 +57,24 @@ export const createApiService = (alias = DEFAULT_ALIAS) => {
|
|
|
62
57
|
const update = headers.get(TOKEN_UPDATE);
|
|
63
58
|
await auth.update(update == null || update === '' ? undefined : update);
|
|
64
59
|
}
|
|
65
|
-
|
|
60
|
+
else if (response.status === UNAUTHORIZED_ERROR
|
|
61
|
+
&& presented(request.headers)
|
|
62
|
+
&& context.hasService(DEF_AUTH_SRV)) {
|
|
63
|
+
/**
|
|
64
|
+
* A session the server has refused is not a session, and keeping it is worse than having
|
|
65
|
+
* none: the application goes on rendering its signed-in tree over a credential that fails
|
|
66
|
+
* every call it makes, and nothing on the client ever asks again — `authenticated()` reads
|
|
67
|
+
* storage and decodes an envelope, it does not consult the server. What that leaves is an
|
|
68
|
+
* app that looks signed in, works at nothing, and offers "Log out" where the control that
|
|
69
|
+
* would fix it should be.
|
|
70
|
+
*
|
|
71
|
+
* Narrow on purpose. Only a request that actually PRESENTED this context's bearer counts,
|
|
72
|
+
* so a 401 from an authentication attempt — bad code, wrong password, an expired challenge
|
|
73
|
+
* — never clears the session of whoever is already signed in.
|
|
74
|
+
*/
|
|
75
|
+
await context.service(DEF_AUTH_SRV).update(undefined);
|
|
76
|
+
}
|
|
77
|
+
responseUtils.processResponse(response, reply);
|
|
66
78
|
return [reply.error ?? reply.value, reply.outcome];
|
|
67
79
|
}
|
|
68
80
|
}, service => async () => {
|
package/build/service.js.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
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
|
+
//# sourceMappingURL=consts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../../src/status/consts.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,gBAAgB,CAAA;AAE9C,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,uBAAgC,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The prefix every `ApiClientError` message carries. */
|
|
2
|
+
export const API_CLIENT_MARKER = 'api:client:';
|
|
3
|
+
/** The marker of an `ApiStatusError`: `api:client:status:<n>[:<incident id>]`. */
|
|
4
|
+
export const API_STATUS_MARKER = `${API_CLIENT_MARKER}status:`;
|
|
5
|
+
//# sourceMappingURL=consts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../../src/status/consts.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAAA;AAE9C,kFAAkF;AAClF,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,iBAAiB,SAAS,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const UUID: RegExp;
|
|
2
|
+
/**
|
|
3
|
+
* `crashed` / `auth` / `forbidden` / `status:<n>` / the legacy bare `<n>`, then an optional
|
|
4
|
+
* `:<incident id>`. The look-ahead refuses a match that runs on into more marker text, so a
|
|
5
|
+
* doubled prefix (a subclass constructor handed a whole marshaled message) resolves to the inner,
|
|
6
|
+
* complete marker.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MARKER: RegExp;
|
|
9
|
+
export declare const NAMED_STATUS: Record<string, number>;
|
|
10
|
+
//# sourceMappingURL=consts.local.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.local.d.ts","sourceRoot":"","sources":["../../src/status/consts.local.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,QAAoE,CAAA;AAErF;;;;;GAKG;AACH,eAAO,MAAM,MAAM,QAAmG,CAAA;AAEtH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAA+C,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2
|
+
/**
|
|
3
|
+
* `crashed` / `auth` / `forbidden` / `status:<n>` / the legacy bare `<n>`, then an optional
|
|
4
|
+
* `:<incident id>`. The look-ahead refuses a match that runs on into more marker text, so a
|
|
5
|
+
* doubled prefix (a subclass constructor handed a whole marshaled message) resolves to the inner,
|
|
6
|
+
* complete marker.
|
|
7
|
+
*/
|
|
8
|
+
export const MARKER = /api:client:(?:(crashed|auth|forbidden)|status:(\d{3})|(\d{3}))(?::([\w.-]{1,128}))?(?![\w.:-])/;
|
|
9
|
+
export const NAMED_STATUS = { crashed: 500, auth: 401, forbidden: 403 };
|
|
10
|
+
//# sourceMappingURL=consts.local.js.map
|