@lovrozagar/oat 0.6.0 → 0.6.1
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 +14 -6
- package/dist/runtime/async.d.ts +19 -4
- package/dist/runtime/async.d.ts.map +1 -1
- package/dist/runtime/async.js +55 -6
- package/dist/runtime/async.js.map +1 -1
- package/dist/runtime/checks.d.ts.map +1 -1
- package/dist/runtime/checks.js +67 -20
- package/dist/runtime/checks.js.map +1 -1
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +8 -5
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/fixture.d.ts.map +1 -1
- package/dist/runtime/fixture.js +86 -21
- package/dist/runtime/fixture.js.map +1 -1
- package/dist/runtime/sse.d.ts +17 -0
- package/dist/runtime/sse.d.ts.map +1 -0
- package/dist/runtime/sse.js +62 -0
- package/dist/runtime/sse.js.map +1 -0
- package/dist/spec/collection.d.ts +2 -0
- package/dist/spec/collection.d.ts.map +1 -1
- package/dist/spec/collection.js +16 -0
- package/dist/spec/collection.js.map +1 -1
- package/dist/spec/graph.d.ts +2 -0
- package/dist/spec/graph.d.ts.map +1 -1
- package/dist/spec/graph.js +2 -1
- package/dist/spec/graph.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -807,7 +807,7 @@ Numbers use the ladder `1, 2, 5, 10, 20, 50, 100` so **lexical order ≠ numeric
|
|
|
807
807
|
|
|
808
808
|
Empty schemas (`{}`, `true`, `additionalProperties: {}`) and cyclic `$ref`s after inlining stop the walk — they become a scalar or `{}`, never another object descent. A `RangeError` during generation is a `COVERAGE_GAP` on that entity naming the operationId and JSON pointer (`fixture generation overflow on table.create (/)`), not `blocked by unknown`.
|
|
809
809
|
|
|
810
|
-
String fields honour `format` and `
|
|
810
|
+
String fields honour `format`, `pattern`, and `minLength` together: `email` → `oat-{variant}-{index}@example.test`, `uri` / `url` → `https://example.test/...`, `uuid` → a fixed-shape UUID, `pattern` → a string that matches (or the field is omitted / the entity is a gap). A generated string is padded to `minLength` (repeat the last character) without breaking `pattern`. `"Quarterly Report N"` is only used when the document does not constrain the string. `minLength` greater than `maxLength` omits an optional field and records `missingRequired` on a required one.
|
|
811
811
|
|
|
812
812
|
An operation with `x-invite` is not `entity.create`. oat does not POST a generated invitee. The invite check sends `granteeField` = the peer's `inviteAs`. Missing `inviteAs` is a coverage gap naming the tag.
|
|
813
813
|
|
|
@@ -894,7 +894,7 @@ Collection shape is derived from the success JSON schema, not from hardcoded wra
|
|
|
894
894
|
| page | `page`, `pageNumber`, `page_number`, `offset` |
|
|
895
895
|
| limit | `limit`, `perPage`, `per_page`, `pageSize`, `page_size` |
|
|
896
896
|
|
|
897
|
-
Success schema is the first JSON media type on responses `200`, `201`, `202`, `2XX`, or `default`. Request schema is taken from `requestBody` preferring `multipart/form-data`, then `application/x-www-form-urlencoded`, then JSON. A
|
|
897
|
+
Success schema is the first JSON media type on responses `200`, `201`, `202`, `2XX`, or `default`. Request schema is taken from `requestBody` preferring `multipart/form-data`, then `application/x-www-form-urlencoded`, then JSON. A success response that lists `text/event-stream` is a stream: oat consumes it to the end and does not treat the raw body as a JSON schema defect. Media type is the stream tag — there is no `x-stream`.
|
|
898
898
|
|
|
899
899
|
## OpenAPI meta tags
|
|
900
900
|
|
|
@@ -973,6 +973,8 @@ An invite operation is **not** the entity's fixture create, even when it is `POS
|
|
|
973
973
|
|
|
974
974
|
Timeline asserted: cannot read → invite → still cannot → accept → can → revoke → cannot.
|
|
975
975
|
|
|
976
|
+
Accept and revoke send the documented JSON request body when the operation declares one, filled from the invite token / grant id (and other known scope values). A path-only accept (`POST /invites/{token}` with no body) stays path-only — oat does not invent a body.
|
|
977
|
+
|
|
976
978
|
**Fallback:** the check does not run.
|
|
977
979
|
|
|
978
980
|
### `x-query`
|
|
@@ -1007,11 +1009,17 @@ x-async:
|
|
|
1007
1009
|
pollIntervalMs: 2000
|
|
1008
1010
|
```
|
|
1009
1011
|
|
|
1010
|
-
|
|
1012
|
+
`x-async` means **POST is a receipt, then poll**. It is not how you mark a stream. A stream is a success response that lists `text/event-stream`.
|
|
1013
|
+
|
|
1014
|
+
When the start response is JSON, oat polls `poll` until `until` matches, then treats that payload as the result. `poll` may be an operationId or `"GET /path/{id}"`. Defaults: `timeoutMs: 120000`, `pollIntervalMs: 2000`.
|
|
1015
|
+
|
|
1016
|
+
When the start response is `text/event-stream`, oat parses `event:` / `data:` frames. `data` is JSON when it starts with `{` / `[`. `idFrom` is resolved against **each event's JSON `data`**, first hit wins — `$.batch_id` on `event: batch` data `{ "batch_id": "…" }` works. A frame named `complete` / `error`, or one whose `data` matches `until`, is the terminal record; `successWhen` is applied to it and oat does **not** GET the poll route. Poll only if the stream ended without a terminal frame **and** `idFrom` resolved.
|
|
1017
|
+
|
|
1018
|
+
A spec may leave both `x-async` and `text/event-stream` on the same operation. The stream is still the result; `x-async` only supplies `idFrom` / `until` / the poll fallback.
|
|
1011
1019
|
|
|
1012
1020
|
`until` / `successWhen` use the same `field.op.value` predicates as filters (`eq`, `in`, …).
|
|
1013
1021
|
|
|
1014
|
-
**Fallback:** treated as synchronous; async checks are `COVERAGE_GAP`.
|
|
1022
|
+
**Fallback:** treated as synchronous; async checks are `COVERAGE_GAP`. A 2xx stream without `x-async` is consumed and recorded; async checks do not run.
|
|
1015
1023
|
|
|
1016
1024
|
### `x-effects`
|
|
1017
1025
|
|
|
@@ -1235,8 +1243,8 @@ Order is fixed (foundations first) so cascade suppression has a cause to point a
|
|
|
1235
1243
|
| `softdelete.absent-from-default-list` | a soft-deleted row is gone from the default list | `x-soft-delete` | `list.read-after-write` |
|
|
1236
1244
|
| `invalidation.declared-route-changes` | after a write, the other entity's listed route actually changes | `x-invalidate` naming another entity | list, persist |
|
|
1237
1245
|
| `effects.declared-effect-occurs` | `x-effects` cardinality delta is observed on the named list | `x-effects` | `list.read-after-write` |
|
|
1238
|
-
| `async.reaches-terminal-state` |
|
|
1239
|
-
| `async.receipt-identifies-the-job` | `idFrom` on the receipt resolves to a pollable job
|
|
1246
|
+
| `async.reaches-terminal-state` | `x-async` reaches `until` (poll, or a terminal SSE frame) before `timeoutMs` | `x-async` | — |
|
|
1247
|
+
| `async.receipt-identifies-the-job` | `idFrom` on the receipt (JSON object or SSE event JSON) resolves to a pollable job | `x-async` + `idFrom` | — |
|
|
1240
1248
|
| `patch.minimality` | PATCH `{ name }` does not clear other writable fields | update + item route | — |
|
|
1241
1249
|
| `idempotency.replay-does-not-duplicate` | same Idempotency-Key + same body does not create a second row | create + documented Idempotency-Key header | list, persist |
|
|
1242
1250
|
| `delete.absent-record-returns-404` | DELETE of a missing id is 404, not 200 | delete | — |
|
package/dist/runtime/async.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Async operation support.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* most of the surface.
|
|
4
|
+
* `x-async` means the HTTP response is a receipt, then oat polls. A `text/event-stream` body is
|
|
5
|
+
* the result itself — parse events, do not `JSON.parse` the raw string. Poll only when the
|
|
6
|
+
* stream ends without a terminal frame and `idFrom` resolved.
|
|
8
7
|
*/
|
|
9
8
|
import type { AsyncSpec } from "../spec/extensions.ts";
|
|
10
9
|
import type { Client, Exchange } from "./client.ts";
|
|
10
|
+
import { type SseEvent } from "./sse.ts";
|
|
11
11
|
export interface AsyncOutcome {
|
|
12
12
|
/** Terminal state reached, or null on timeout. */
|
|
13
13
|
terminal: Record<string, unknown> | null;
|
|
@@ -23,6 +23,21 @@ export interface AsyncOutcome {
|
|
|
23
23
|
* language as the filter grammar rather than inventing a second one.
|
|
24
24
|
*/
|
|
25
25
|
export declare function matchesPredicate(record: Record<string, unknown>, expression: string): boolean;
|
|
26
|
+
export declare function readPath(body: unknown, path: string): unknown;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve `idFrom` against a JSON receipt, or against each SSE event's JSON `data` (first hit).
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveAsyncId(receipt: unknown, idFrom: string): unknown;
|
|
31
|
+
export interface StreamAsyncView {
|
|
32
|
+
id: unknown;
|
|
33
|
+
/** Event data that carried `idFrom`, for binding the poll path. */
|
|
34
|
+
idRecord: Record<string, unknown> | null;
|
|
35
|
+
/** `complete` / `error` frame, or a frame whose `data` matches `until`. */
|
|
36
|
+
terminal: Record<string, unknown> | null;
|
|
37
|
+
events: SseEvent[];
|
|
38
|
+
}
|
|
39
|
+
/** Read job id and terminal state from SSE frames. `null` when the body is not a stream. */
|
|
40
|
+
export declare function inspectStreamAsync(receipt: unknown, spec: AsyncSpec): StreamAsyncView | null;
|
|
26
41
|
/**
|
|
27
42
|
* Polls until `until` matches, the timeout expires, or the poll route stops responding.
|
|
28
43
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async.d.ts","sourceRoot":"","sources":["../../src/runtime/async.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"async.d.ts","sourceRoot":"","sources":["../../src/runtime/async.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAE,KAAK,QAAQ,EAAa,MAAM,UAAU,CAAA;AAGnD,MAAM,WAAW,YAAY;IAC5B,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACxC,SAAS,EAAE,QAAQ,EAAE,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;IACjB,2FAA2F;IAC3F,SAAS,EAAE,OAAO,CAAA;CAClB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CA0B7F;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAU7D;AAOD;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAYxE;AAED,MAAM,WAAW,eAAe;IAC/B,EAAE,EAAE,OAAO,CAAA;IACX,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACxC,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACxC,MAAM,EAAE,QAAQ,EAAE,CAAA;CAClB;AAED,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,eAAe,GAAG,IAAI,CAsB5F;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC/B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,OAAO,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,cAAc,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GACjD,OAAO,CAAC,YAAY,CAAC,CA4EvB"}
|
package/dist/runtime/async.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Async operation support.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* most of the surface.
|
|
4
|
+
* `x-async` means the HTTP response is a receipt, then oat polls. A `text/event-stream` body is
|
|
5
|
+
* the result itself — parse events, do not `JSON.parse` the raw string. Poll only when the
|
|
6
|
+
* stream ends without a terminal frame and `idFrom` resolved.
|
|
8
7
|
*/
|
|
8
|
+
import { sseEvents } from "./sse.js";
|
|
9
9
|
import { fillPath } from "./world.js";
|
|
10
10
|
/**
|
|
11
11
|
* A single PostgREST-style predicate, reused here so `until`/`successWhen` speak the same
|
|
@@ -41,7 +41,7 @@ export function matchesPredicate(record, expression) {
|
|
|
41
41
|
return false;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
function readPath(body, path) {
|
|
44
|
+
export function readPath(body, path) {
|
|
45
45
|
let node = body;
|
|
46
46
|
for (const segment of path
|
|
47
47
|
.replace(/^\$\.?/, "")
|
|
@@ -53,6 +53,55 @@ function readPath(body, path) {
|
|
|
53
53
|
}
|
|
54
54
|
return node;
|
|
55
55
|
}
|
|
56
|
+
function recordOf(data) {
|
|
57
|
+
if (data === null || typeof data !== "object" || Array.isArray(data))
|
|
58
|
+
return null;
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve `idFrom` against a JSON receipt, or against each SSE event's JSON `data` (first hit).
|
|
63
|
+
*/
|
|
64
|
+
export function resolveAsyncId(receipt, idFrom) {
|
|
65
|
+
const events = sseEvents(receipt);
|
|
66
|
+
if (events !== null) {
|
|
67
|
+
for (const ev of events) {
|
|
68
|
+
const record = recordOf(ev.data);
|
|
69
|
+
if (record === null)
|
|
70
|
+
continue;
|
|
71
|
+
const found = readPath(record, idFrom);
|
|
72
|
+
if (found !== undefined && found !== null)
|
|
73
|
+
return found;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return readPath(receipt, idFrom);
|
|
78
|
+
}
|
|
79
|
+
/** Read job id and terminal state from SSE frames. `null` when the body is not a stream. */
|
|
80
|
+
export function inspectStreamAsync(receipt, spec) {
|
|
81
|
+
const events = sseEvents(receipt);
|
|
82
|
+
if (events === null)
|
|
83
|
+
return null;
|
|
84
|
+
let id;
|
|
85
|
+
let idRecord = null;
|
|
86
|
+
let terminal = null;
|
|
87
|
+
for (const ev of events) {
|
|
88
|
+
const record = recordOf(ev.data);
|
|
89
|
+
if (record === null)
|
|
90
|
+
continue;
|
|
91
|
+
if (id === undefined && spec.idFrom !== undefined) {
|
|
92
|
+
const found = readPath(record, spec.idFrom);
|
|
93
|
+
if (found !== undefined && found !== null) {
|
|
94
|
+
id = found;
|
|
95
|
+
idRecord = record;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const named = ev.event === "complete" || ev.event === "error";
|
|
99
|
+
const untilHit = spec.until !== undefined && matchesPredicate(record, spec.until);
|
|
100
|
+
if (named || untilHit)
|
|
101
|
+
terminal = record;
|
|
102
|
+
}
|
|
103
|
+
return { events, id, idRecord, terminal };
|
|
104
|
+
}
|
|
56
105
|
/**
|
|
57
106
|
* Polls until `until` matches, the timeout expires, or the poll route stops responding.
|
|
58
107
|
*
|
|
@@ -65,7 +114,7 @@ export async function driveAsync(client, spec, receipt, scope, headers, refreshI
|
|
|
65
114
|
const began = performance.now();
|
|
66
115
|
const pollScope = { ...scope };
|
|
67
116
|
if (spec.idFrom !== undefined) {
|
|
68
|
-
const id =
|
|
117
|
+
const id = resolveAsyncId(receipt, spec.idFrom);
|
|
69
118
|
if (id !== undefined && id !== null) {
|
|
70
119
|
/* The receipt names the job; bind it to whichever poll-path parameter is unresolved. */
|
|
71
120
|
const parameters = [...spec.poll.matchAll(/\{([^}]+)\}/g)].map((m) => m[1] ?? "");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async.js","sourceRoot":"","sources":["../../src/runtime/async.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"async.js","sourceRoot":"","sources":["../../src/runtime/async.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../../src/runtime/checks.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../../src/runtime/checks.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime/checks.js
CHANGED
|
@@ -11,7 +11,7 @@ import { pathTemplateMatches } from "../spec/load.js";
|
|
|
11
11
|
import { encodeForOperation } from "./body.js";
|
|
12
12
|
import { STRING_PAYLOADS, payloadFits } from "./payloads.js";
|
|
13
13
|
import { describeFeatureGate, isDocumentedFeatureGateDenial, reportFeatureGateSchemaDrift } from "./feature-gate.js";
|
|
14
|
-
import { driveAsync } from "./async.js";
|
|
14
|
+
import { driveAsync, inspectStreamAsync, matchesPredicate, resolveAsyncId } from "./async.js";
|
|
15
15
|
import { buildCohort } from "./fixture.js";
|
|
16
16
|
import { fillPath } from "./world.js";
|
|
17
17
|
/* ------------------------------------------------------------------- helpers */
|
|
@@ -2607,8 +2607,14 @@ const inviteGrantsThenRevokes = {
|
|
|
2607
2607
|
if (token === undefined) {
|
|
2608
2608
|
return ctx.findings.unresolved(this.id, ctx.entityName, `invite response has no token at ${spec.tokenPointer}`);
|
|
2609
2609
|
}
|
|
2610
|
-
const
|
|
2611
|
-
|
|
2610
|
+
const acceptScope = { ...resource, token };
|
|
2611
|
+
const acceptBody = documentedJsonBody(ctx, acceptOp, acceptScope);
|
|
2612
|
+
const accepted = await ctx.client.request(acceptOp.method, fillPath(acceptOp.path, acceptScope), {
|
|
2613
|
+
...(acceptBody === undefined ? {} : { body: acceptBody }),
|
|
2614
|
+
headers: {
|
|
2615
|
+
...delegate.headers(),
|
|
2616
|
+
...(acceptBody === undefined ? {} : { "content-type": "application/json" }),
|
|
2617
|
+
},
|
|
2612
2618
|
});
|
|
2613
2619
|
if (standDownForFeatureGate(ctx, acceptOp, accepted, this.id))
|
|
2614
2620
|
return;
|
|
@@ -2623,7 +2629,15 @@ const inviteGrantsThenRevokes = {
|
|
|
2623
2629
|
if (grantId === undefined) {
|
|
2624
2630
|
return ctx.findings.unresolved(this.id, ctx.entityName, `invite response has no grant id at ${spec.grantPointer}, so revoke cannot be expressed`);
|
|
2625
2631
|
}
|
|
2626
|
-
const
|
|
2632
|
+
const revokeScope = { ...resource, grant_id: grantId, token };
|
|
2633
|
+
const revokeBody = documentedJsonBody(ctx, revokeOp, revokeScope);
|
|
2634
|
+
const revoked = await ctx.client.request(revokeOp.method, fillPath(revokeOp.path, revokeScope), {
|
|
2635
|
+
...(revokeBody === undefined ? {} : { body: revokeBody }),
|
|
2636
|
+
headers: {
|
|
2637
|
+
...owner.headers(),
|
|
2638
|
+
...(revokeBody === undefined ? {} : { "content-type": "application/json" }),
|
|
2639
|
+
},
|
|
2640
|
+
});
|
|
2627
2641
|
if (standDownForFeatureGate(ctx, revokeOp, revoked, this.id))
|
|
2628
2642
|
return;
|
|
2629
2643
|
if (revoked.status >= 300) {
|
|
@@ -2978,7 +2992,7 @@ const successSchemaHonoured = {
|
|
|
2978
2992
|
const exchange = createExchange(ctx);
|
|
2979
2993
|
if (exchange === undefined)
|
|
2980
2994
|
return;
|
|
2981
|
-
if (isEventStream(exchange))
|
|
2995
|
+
if (isEventStream(exchange, createOp))
|
|
2982
2996
|
return;
|
|
2983
2997
|
if (!validator.documents(raw, exchange.status)) {
|
|
2984
2998
|
return ctx.findings.unresolved(this.id, ctx.entityName, `create returned ${exchange.status}, which the document declares no schema for — ` +
|
|
@@ -3039,10 +3053,41 @@ function submittedFields(body) {
|
|
|
3039
3053
|
return body;
|
|
3040
3054
|
return {};
|
|
3041
3055
|
}
|
|
3042
|
-
|
|
3056
|
+
/** Document first, then the live `Content-Type`. Media type is the stream tag — not `x-async`. */
|
|
3057
|
+
function isEventStream(exchange, op) {
|
|
3058
|
+
if (op?.eventStream === true)
|
|
3059
|
+
return true;
|
|
3043
3060
|
const type = exchange.responseHeaders["content-type"] ?? "";
|
|
3044
3061
|
return type.includes("text/event-stream");
|
|
3045
3062
|
}
|
|
3063
|
+
/**
|
|
3064
|
+
* JSON request body declared on the operation, filled from known flow values.
|
|
3065
|
+
*
|
|
3066
|
+
* Path-only accept/revoke (`POST /invites/{token}` with no body) stay path-only.
|
|
3067
|
+
*/
|
|
3068
|
+
function documentedJsonBody(ctx, op, fields) {
|
|
3069
|
+
const raw = ctx.model.rawOperations.get(op.operationId);
|
|
3070
|
+
const picked = raw === undefined ? null : requestContent(raw);
|
|
3071
|
+
if (picked === null || !picked.mediaType.includes("json"))
|
|
3072
|
+
return undefined;
|
|
3073
|
+
const properties = picked.schema.properties;
|
|
3074
|
+
if (properties !== null && typeof properties === "object") {
|
|
3075
|
+
const names = Object.keys(properties);
|
|
3076
|
+
if (names.length > 0) {
|
|
3077
|
+
const body = {};
|
|
3078
|
+
for (const name of names) {
|
|
3079
|
+
if (fields[name] !== undefined)
|
|
3080
|
+
body[name] = fields[name];
|
|
3081
|
+
}
|
|
3082
|
+
return body;
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
if (fields.token !== undefined)
|
|
3086
|
+
return { token: fields.token };
|
|
3087
|
+
if (fields.grant_id !== undefined)
|
|
3088
|
+
return { grant_id: fields.grant_id };
|
|
3089
|
+
return { ...fields };
|
|
3090
|
+
}
|
|
3046
3091
|
function findConstrained(schema, predicate) {
|
|
3047
3092
|
const props = schema.properties;
|
|
3048
3093
|
if (props === null || typeof props !== "object")
|
|
@@ -3374,7 +3419,21 @@ const asyncReachesTerminalState = {
|
|
|
3374
3419
|
ctx.findings.gap(this.id, ctx.entityName, `${op.operationId} could not be started`, `returned ${start.status}; the async lifecycle after it is untested`);
|
|
3375
3420
|
continue;
|
|
3376
3421
|
}
|
|
3377
|
-
const
|
|
3422
|
+
const streamed = isEventStream(start, op);
|
|
3423
|
+
const fromStream = streamed ? inspectStreamAsync(start.responseBody, spec) : null;
|
|
3424
|
+
if (fromStream?.terminal !== null && fromStream?.terminal !== undefined) {
|
|
3425
|
+
if (spec.successWhen !== undefined && !matchesPredicate(fromStream.terminal, spec.successWhen)) {
|
|
3426
|
+
ctx.findings.gap(this.id, ctx.entityName, `${op.operationId} reached a non-success terminal state`, `terminal state did not satisfy "${spec.successWhen}"; downstream effects of ` +
|
|
3427
|
+
"this operation are untested");
|
|
3428
|
+
}
|
|
3429
|
+
continue;
|
|
3430
|
+
}
|
|
3431
|
+
const receipt = fromStream?.idRecord !== null && fromStream?.idRecord !== undefined ? fromStream.idRecord : start.responseBody;
|
|
3432
|
+
if (streamed && fromStream !== null && (fromStream.id === undefined || fromStream.id === null)) {
|
|
3433
|
+
ctx.findings.backend(this.id, ctx.entityName, `${op.operationId} job disappeared before completing`, "the stream ended without a terminal frame and without a job id, so the poll route cannot be named.", [start]);
|
|
3434
|
+
continue;
|
|
3435
|
+
}
|
|
3436
|
+
const outcome = await driveAsync(ctx.client, spec, receipt, ctx.scope, ctx.auth, ctx.refreshIfStale);
|
|
3378
3437
|
if (outcome.timedOut) {
|
|
3379
3438
|
ctx.findings.backend(this.id, ctx.entityName, `${op.operationId} never reached a terminal state`, `polled ${spec.poll} ${outcome.polls} time(s) over ${Math.round(outcome.elapsedMs)}ms ` +
|
|
3380
3439
|
`without satisfying "${spec.until ?? "any terminal state"}". A job that neither ` +
|
|
@@ -3414,19 +3473,7 @@ const asyncReceiptIsResolvable = {
|
|
|
3414
3473
|
const started = ctx.client.transcript.find((e) => e.method === op.method && e.status < 300 && new URL(e.url).pathname === resolved);
|
|
3415
3474
|
if (started === undefined)
|
|
3416
3475
|
continue;
|
|
3417
|
-
const
|
|
3418
|
-
const path = spec.idFrom
|
|
3419
|
-
.replace(/^\$\.?/, "")
|
|
3420
|
-
.split(".")
|
|
3421
|
-
.filter(Boolean);
|
|
3422
|
-
let node = body;
|
|
3423
|
-
for (const segment of path) {
|
|
3424
|
-
if (node === null || typeof node !== "object") {
|
|
3425
|
-
node = undefined;
|
|
3426
|
-
break;
|
|
3427
|
-
}
|
|
3428
|
-
node = node[segment];
|
|
3429
|
-
}
|
|
3476
|
+
const node = resolveAsyncId(started.responseBody, spec.idFrom);
|
|
3430
3477
|
if (node !== undefined && node !== null)
|
|
3431
3478
|
continue;
|
|
3432
3479
|
ctx.findings.spec(this.id, ctx.entityName, `${op.operationId} response does not carry the job identifier it declares`, `x-async names "${spec.idFrom}" as the job id, but the response has no such value. ` +
|