@nifrajs/runner 4.0.2 → 5.1.0
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/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +26 -17
package/dist/index.d.ts
CHANGED
|
@@ -44,7 +44,11 @@ export interface RunResult {
|
|
|
44
44
|
/** HTTP status - present when the app returned a response (absent when it threw). */
|
|
45
45
|
readonly status?: number;
|
|
46
46
|
readonly statusText?: string;
|
|
47
|
+
/** Response headers. Repeated `set-cookie` values are joined with `, `, as `Headers.get` does;
|
|
48
|
+
* `setCookie` keeps them apart. */
|
|
47
49
|
readonly headers?: Readonly<Record<string, string>>;
|
|
50
|
+
/** Each `set-cookie` value, in order - present when the response set at least one cookie. */
|
|
51
|
+
readonly setCookie?: readonly string[];
|
|
48
52
|
/** Parsed JSON when the response is JSON; otherwise the (possibly truncated) text. */
|
|
49
53
|
readonly body?: unknown;
|
|
50
54
|
/** The response body as text, truncated to `maxBodyChars`. */
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;0EAC0E;AAC1E,MAAM,WAAW,OAAO;IACtB,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CACtD;AAED,4CAA4C;AAC5C,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,uBAAuB;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACnD;6FACyF;IACzF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,2CAA2C;AAC3C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAA;IACpB,qFAAqF;IACrF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACnD,sFAAsF;IACtF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,iDAAiD;IACjD,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B,sDAAsD;IACtD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9F;AAED,MAAM,WAAW,UAAU;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAC/B;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;0EAC0E;AAC1E,MAAM,WAAW,OAAO;IACtB,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CACtD;AAED,4CAA4C;AAC5C,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,uBAAuB;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACnD;6FACyF;IACzF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,2CAA2C;AAC3C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAA;IACpB,qFAAqF;IACrF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B;uCACmC;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACnD,6FAA6F;IAC7F,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,sFAAsF;IACtF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,iDAAiD;IACjD,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B,sDAAsD;IACtD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9F;AAED,MAAM,WAAW,UAAU;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAC/B;AAwHD;8BAC8B;AAC9B,wBAAsB,UAAU,CAC9B,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,WAAW,EACjB,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,SAAS,CAAC,CA+CpB;AASD;;;GAGG;AACH,wBAAgB,MAAM,CACpB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,SAAS,EAAE,CAAC,CAMtB"}
|
package/dist/index.js
CHANGED
|
@@ -21,17 +21,9 @@ const now = () => typeof performance !== "undefined" && typeof performance.now =
|
|
|
21
21
|
? performance.now()
|
|
22
22
|
: Date.now();
|
|
23
23
|
const METHODS_WITHOUT_BODY = new Set(["GET", "HEAD"]);
|
|
24
|
-
/**
|
|
24
|
+
/** Any object that is not a native Fetch body; callers check `isRequestBody` first. */
|
|
25
25
|
function isJsonBody(body) {
|
|
26
|
-
|
|
27
|
-
return false;
|
|
28
|
-
if (body instanceof Uint8Array || body instanceof ArrayBuffer || body instanceof Blob)
|
|
29
|
-
return false;
|
|
30
|
-
if (typeof FormData !== "undefined" && body instanceof FormData)
|
|
31
|
-
return false;
|
|
32
|
-
if (typeof ReadableStream !== "undefined" && body instanceof ReadableStream)
|
|
33
|
-
return false;
|
|
34
|
-
return true;
|
|
26
|
+
return body !== null && typeof body === "object";
|
|
35
27
|
}
|
|
36
28
|
/** Runtime-check the non-JSON body shapes advertised by RequestSpec before crossing Fetch's API. */
|
|
37
29
|
function isRequestBody(body) {
|
|
@@ -55,16 +47,21 @@ function buildRequest(spec, origin) {
|
|
|
55
47
|
const headers = new Headers(spec.headers);
|
|
56
48
|
const init = { method, headers };
|
|
57
49
|
if (spec.body !== undefined && !METHODS_WITHOUT_BODY.has(method)) {
|
|
58
|
-
|
|
50
|
+
// A native Fetch body (URLSearchParams, FormData, a typed array) is an object too, so it is
|
|
51
|
+
// matched first and sent with the content type Fetch gives it.
|
|
52
|
+
if (isRequestBody(spec.body)) {
|
|
53
|
+
init.body = spec.body;
|
|
54
|
+
// Node's Request refuses a stream body without it; Bun and browsers accept it either way.
|
|
55
|
+
if (typeof ReadableStream !== "undefined" && spec.body instanceof ReadableStream)
|
|
56
|
+
init.duplex = "half";
|
|
57
|
+
}
|
|
58
|
+
else if (isJsonBody(spec.body)) {
|
|
59
59
|
if (!headers.has("content-type"))
|
|
60
60
|
headers.set("content-type", "application/json");
|
|
61
61
|
init.body = JSON.stringify(spec.body);
|
|
62
62
|
}
|
|
63
63
|
else {
|
|
64
|
-
|
|
65
|
-
throw new TypeError("@nifrajs/runner: body must be a Fetch Request body");
|
|
66
|
-
}
|
|
67
|
-
init.body = spec.body;
|
|
64
|
+
throw new TypeError("@nifrajs/runner: body must be a Fetch Request body");
|
|
68
65
|
}
|
|
69
66
|
}
|
|
70
67
|
return new Request(url, init);
|
|
@@ -72,10 +69,15 @@ function buildRequest(spec, origin) {
|
|
|
72
69
|
function headersToObject(headers) {
|
|
73
70
|
const out = {};
|
|
74
71
|
headers.forEach((value, key) => {
|
|
75
|
-
|
|
72
|
+
// Iteration yields each set-cookie on its own, so plain assignment would keep only the last.
|
|
73
|
+
out[key] = key === "set-cookie" && key in out ? `${out[key]}, ${value}` : value;
|
|
76
74
|
});
|
|
77
75
|
return out;
|
|
78
76
|
}
|
|
77
|
+
function setCookieOf(headers) {
|
|
78
|
+
const cookies = headers.getSetCookie();
|
|
79
|
+
return cookies.length === 0 ? {} : { setCookie: cookies };
|
|
80
|
+
}
|
|
79
81
|
/** Read at most the caller's character budget while enforcing a byte budget before decoding more data. */
|
|
80
82
|
async function readBoundedText(response, maxChars) {
|
|
81
83
|
if (response.body === null || maxChars === 0) {
|
|
@@ -107,9 +109,8 @@ async function readBoundedText(response, maxChars) {
|
|
|
107
109
|
// while the final string slice preserves the public character-count contract.
|
|
108
110
|
const byteBudget = Math.max(1, Math.min(Number.MAX_SAFE_INTEGER, remaining * 4));
|
|
109
111
|
const allowed = Math.min(value.byteLength, byteBudget);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
});
|
|
112
|
+
// Always streamed: a character split across two chunks decodes once the second arrives.
|
|
113
|
+
const chunk = decoder.decode(value.subarray(0, allowed), { stream: true });
|
|
113
114
|
chunks.push(chunk);
|
|
114
115
|
totalChars += chunk.length;
|
|
115
116
|
if (allowed < value.byteLength || chunk.length > remaining) {
|
|
@@ -165,6 +166,7 @@ export async function runRequest(app, spec, options = {}) {
|
|
|
165
166
|
status: res.status,
|
|
166
167
|
statusText: res.statusText,
|
|
167
168
|
headers: headersToObject(res.headers),
|
|
169
|
+
...setCookieOf(res.headers),
|
|
168
170
|
body,
|
|
169
171
|
bodyText,
|
|
170
172
|
...(captured.truncated ? { truncated: true } : {}),
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/runner",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Run requests through a nifra app's fetch and capture structured results - the engine behind the playground and the agent run/verify tool. Dependency-free; runs in the browser, Bun, Node, Deno, and on the edge.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
package/src/index.ts
CHANGED
|
@@ -47,7 +47,11 @@ export interface RunResult {
|
|
|
47
47
|
/** HTTP status - present when the app returned a response (absent when it threw). */
|
|
48
48
|
readonly status?: number
|
|
49
49
|
readonly statusText?: string
|
|
50
|
+
/** Response headers. Repeated `set-cookie` values are joined with `, `, as `Headers.get` does;
|
|
51
|
+
* `setCookie` keeps them apart. */
|
|
50
52
|
readonly headers?: Readonly<Record<string, string>>
|
|
53
|
+
/** Each `set-cookie` value, in order - present when the response set at least one cookie. */
|
|
54
|
+
readonly setCookie?: readonly string[]
|
|
51
55
|
/** Parsed JSON when the response is JSON; otherwise the (possibly truncated) text. */
|
|
52
56
|
readonly body?: unknown
|
|
53
57
|
/** The response body as text, truncated to `maxBodyChars`. */
|
|
@@ -77,14 +81,9 @@ const now = (): number =>
|
|
|
77
81
|
|
|
78
82
|
const METHODS_WITHOUT_BODY: ReadonlySet<string> = new Set(["GET", "HEAD"])
|
|
79
83
|
|
|
80
|
-
/**
|
|
84
|
+
/** Any object that is not a native Fetch body; callers check `isRequestBody` first. */
|
|
81
85
|
function isJsonBody(body: unknown): body is Record<string, unknown> | unknown[] {
|
|
82
|
-
|
|
83
|
-
if (body instanceof Uint8Array || body instanceof ArrayBuffer || body instanceof Blob)
|
|
84
|
-
return false
|
|
85
|
-
if (typeof FormData !== "undefined" && body instanceof FormData) return false
|
|
86
|
-
if (typeof ReadableStream !== "undefined" && body instanceof ReadableStream) return false
|
|
87
|
-
return true
|
|
86
|
+
return body !== null && typeof body === "object"
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
type RequestBody = Exclude<RequestInit["body"], undefined>
|
|
@@ -105,17 +104,21 @@ function buildRequest(spec: RequestSpec, origin: string): Request {
|
|
|
105
104
|
const url = /^https?:\/\//i.test(spec.path) ? spec.path : new URL(spec.path, origin).toString()
|
|
106
105
|
|
|
107
106
|
const headers = new Headers(spec.headers as Record<string, string> | undefined)
|
|
108
|
-
const init: RequestInit = { method, headers }
|
|
107
|
+
const init: RequestInit & { duplex?: "half" } = { method, headers }
|
|
109
108
|
|
|
110
109
|
if (spec.body !== undefined && !METHODS_WITHOUT_BODY.has(method)) {
|
|
111
|
-
|
|
110
|
+
// A native Fetch body (URLSearchParams, FormData, a typed array) is an object too, so it is
|
|
111
|
+
// matched first and sent with the content type Fetch gives it.
|
|
112
|
+
if (isRequestBody(spec.body)) {
|
|
113
|
+
init.body = spec.body
|
|
114
|
+
// Node's Request refuses a stream body without it; Bun and browsers accept it either way.
|
|
115
|
+
if (typeof ReadableStream !== "undefined" && spec.body instanceof ReadableStream)
|
|
116
|
+
init.duplex = "half"
|
|
117
|
+
} else if (isJsonBody(spec.body)) {
|
|
112
118
|
if (!headers.has("content-type")) headers.set("content-type", "application/json")
|
|
113
119
|
init.body = JSON.stringify(spec.body)
|
|
114
120
|
} else {
|
|
115
|
-
|
|
116
|
-
throw new TypeError("@nifrajs/runner: body must be a Fetch Request body")
|
|
117
|
-
}
|
|
118
|
-
init.body = spec.body
|
|
121
|
+
throw new TypeError("@nifrajs/runner: body must be a Fetch Request body")
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
124
|
return new Request(url, init)
|
|
@@ -124,11 +127,17 @@ function buildRequest(spec: RequestSpec, origin: string): Request {
|
|
|
124
127
|
function headersToObject(headers: Headers): Record<string, string> {
|
|
125
128
|
const out: Record<string, string> = {}
|
|
126
129
|
headers.forEach((value, key) => {
|
|
127
|
-
|
|
130
|
+
// Iteration yields each set-cookie on its own, so plain assignment would keep only the last.
|
|
131
|
+
out[key] = key === "set-cookie" && key in out ? `${out[key]}, ${value}` : value
|
|
128
132
|
})
|
|
129
133
|
return out
|
|
130
134
|
}
|
|
131
135
|
|
|
136
|
+
function setCookieOf(headers: Headers): { readonly setCookie?: readonly string[] } {
|
|
137
|
+
const cookies = headers.getSetCookie()
|
|
138
|
+
return cookies.length === 0 ? {} : { setCookie: cookies }
|
|
139
|
+
}
|
|
140
|
+
|
|
132
141
|
interface BoundedText {
|
|
133
142
|
readonly text: string
|
|
134
143
|
readonly truncated: boolean
|
|
@@ -163,9 +172,8 @@ async function readBoundedText(response: Response, maxChars: number): Promise<Bo
|
|
|
163
172
|
// while the final string slice preserves the public character-count contract.
|
|
164
173
|
const byteBudget = Math.max(1, Math.min(Number.MAX_SAFE_INTEGER, remaining * 4))
|
|
165
174
|
const allowed = Math.min(value.byteLength, byteBudget)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
})
|
|
175
|
+
// Always streamed: a character split across two chunks decodes once the second arrives.
|
|
176
|
+
const chunk = decoder.decode(value.subarray(0, allowed), { stream: true })
|
|
169
177
|
chunks.push(chunk)
|
|
170
178
|
totalChars += chunk.length
|
|
171
179
|
if (allowed < value.byteLength || chunk.length > remaining) {
|
|
@@ -225,6 +233,7 @@ export async function runRequest(
|
|
|
225
233
|
status: res.status,
|
|
226
234
|
statusText: res.statusText,
|
|
227
235
|
headers: headersToObject(res.headers),
|
|
236
|
+
...setCookieOf(res.headers),
|
|
228
237
|
body,
|
|
229
238
|
bodyText,
|
|
230
239
|
...(captured.truncated ? { truncated: true } : {}),
|