@c9up/ream 0.2.4 → 0.2.6
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 +3 -9
- package/dist/Application.d.ts +85 -5
- package/dist/Application.d.ts.map +1 -1
- package/dist/Application.js +171 -21
- package/dist/Application.js.map +1 -1
- package/dist/ErrorBoundary.d.ts +1 -7
- package/dist/ErrorBoundary.d.ts.map +1 -1
- package/dist/ErrorBoundary.js +31 -31
- package/dist/ErrorBoundary.js.map +1 -1
- package/dist/HttpKernel.js +2 -2
- package/dist/HttpKernel.js.map +1 -1
- package/dist/Ignitor.d.ts +18 -49
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +211 -173
- package/dist/Ignitor.js.map +1 -1
- package/dist/env/Env.d.ts +6 -0
- package/dist/env/Env.d.ts.map +1 -1
- package/dist/env/Env.js +6 -0
- package/dist/env/Env.js.map +1 -1
- package/dist/events/Emitter.d.ts +0 -6
- package/dist/events/Emitter.d.ts.map +1 -1
- package/dist/events/Emitter.js +42 -42
- package/dist/events/Emitter.js.map +1 -1
- package/dist/events/native.d.ts +13 -11
- package/dist/events/native.d.ts.map +1 -1
- package/dist/events/native.js.map +1 -1
- package/dist/helpers/compose.d.ts +30 -0
- package/dist/helpers/compose.d.ts.map +1 -0
- package/dist/helpers/compose.js +27 -0
- package/dist/helpers/compose.js.map +1 -0
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/index.js.map +1 -1
- package/dist/helpers/is.d.ts +69 -16
- package/dist/helpers/is.d.ts.map +1 -1
- package/dist/helpers/is.js +79 -16
- package/dist/helpers/is.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/middleware/Pipeline.d.ts +9 -3
- package/dist/middleware/Pipeline.d.ts.map +1 -1
- package/dist/middleware/Pipeline.js +10 -4
- package/dist/middleware/Pipeline.js.map +1 -1
- package/dist/native/generated.d.ts +243 -0
- package/dist/native/generated.d.ts.map +1 -0
- package/dist/native/generated.js +7 -0
- package/dist/native/generated.js.map +1 -0
- package/dist/rpc/RpcRouter.js +2 -2
- package/dist/rpc/RpcRouter.js.map +1 -1
- package/dist/services/app.d.ts +6 -0
- package/dist/services/app.d.ts.map +1 -1
- package/dist/services/app.js +8 -0
- package/dist/services/app.js.map +1 -1
- package/dist/services/config.d.ts +15 -0
- package/dist/services/config.d.ts.map +1 -0
- package/dist/services/config.js +18 -0
- package/dist/services/config.js.map +1 -0
- package/dist/services/encryption.d.ts +23 -0
- package/dist/services/encryption.d.ts.map +1 -0
- package/dist/services/encryption.js +33 -0
- package/dist/services/encryption.js.map +1 -0
- package/dist/services/urlBuilder.d.ts +21 -0
- package/dist/services/urlBuilder.d.ts.map +1 -0
- package/dist/services/urlBuilder.js +34 -0
- package/dist/services/urlBuilder.js.map +1 -0
- package/dist/session/drivers/CookieDriver.d.ts +1 -1
- package/dist/session/drivers/CookieDriver.d.ts.map +1 -1
- package/dist/session/drivers/CookieDriver.js +4 -4
- package/dist/session/drivers/CookieDriver.js.map +1 -1
- package/dist/testing/ApiResponse.d.ts +44 -44
- package/dist/testing/ApiResponse.d.ts.map +1 -1
- package/dist/testing/ApiResponse.js +48 -48
- package/dist/testing/ApiResponse.js.map +1 -1
- package/dist/testing/RequestBuilder.d.ts +39 -39
- package/dist/testing/RequestBuilder.d.ts.map +1 -1
- package/dist/testing/RequestBuilder.js +49 -49
- package/dist/testing/RequestBuilder.js.map +1 -1
- package/dist/testing/TestClient.d.ts +5 -5
- package/dist/testing/TestClient.d.ts.map +1 -1
- package/dist/testing/TestClient.js +7 -7
- package/dist/testing/TestClient.js.map +1 -1
- package/events.win32-x64-msvc.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +15 -2
- package/scheduler.win32-x64-msvc.node +0 -0
- package/scripts/build-napi-types.mjs +68 -0
- package/scripts/generate-napi-types.mjs +171 -0
- package/src/Application.ts +221 -21
- package/src/ErrorBoundary.ts +31 -31
- package/src/HttpKernel.ts +2 -2
- package/src/Ignitor.ts +228 -176
- package/src/env/Env.ts +6 -0
- package/src/events/Emitter.ts +42 -42
- package/src/events/native.ts +21 -14
- package/src/helpers/compose.ts +84 -0
- package/src/helpers/index.ts +1 -0
- package/src/helpers/is.ts +87 -16
- package/src/index.ts +3 -3
- package/src/middleware/Pipeline.ts +10 -4
- package/src/native/generated.ts +291 -0
- package/src/rpc/RpcRouter.ts +2 -2
- package/src/services/app.ts +9 -0
- package/src/services/config.ts +24 -0
- package/src/services/encryption.ts +42 -0
- package/src/services/urlBuilder.ts +41 -0
- package/src/session/drivers/CookieDriver.ts +4 -4
- package/src/testing/ApiResponse.ts +52 -52
- package/src/testing/RequestBuilder.ts +55 -55
- package/src/testing/TestClient.ts +8 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fluent HTTP request builder with assertion methods (
|
|
2
|
+
* Fluent HTTP request builder with assertion methods (`helix` model).
|
|
3
3
|
*
|
|
4
4
|
* await client
|
|
5
5
|
* .get('/api/users/42')
|
|
@@ -84,7 +84,7 @@ export interface AuthStrategy {
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* HTTP methods. The common verbs are named; `request(url, method)` also accepts
|
|
87
|
-
* any other method (e.g. `TRACE`, `CONNECT`) —
|
|
87
|
+
* any other method (e.g. `TRACE`, `CONNECT`) — helix parity — via the wider
|
|
88
88
|
* string fallback.
|
|
89
89
|
*/
|
|
90
90
|
export type HttpMethod =
|
|
@@ -104,7 +104,7 @@ const CSRF_COOKIE_NAME = 'XSRF-TOKEN'
|
|
|
104
104
|
/** Header the client echoes the token back in (Axios/Angular default). */
|
|
105
105
|
const CSRF_HEADER_NAME = 'x-xsrf-token'
|
|
106
106
|
|
|
107
|
-
/** Content-type / accept shorthands — mirrors
|
|
107
|
+
/** Content-type / accept shorthands — mirrors helix's `.type()`/`.accept()`. */
|
|
108
108
|
const TYPE_SHORTHANDS: Record<string, string> = {
|
|
109
109
|
json: 'application/json',
|
|
110
110
|
form: 'application/x-www-form-urlencoded',
|
|
@@ -119,14 +119,14 @@ function resolveMime(type: string): string {
|
|
|
119
119
|
return TYPE_SHORTHANDS[type] ?? type
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
/** Primitive accepted as a query-string value (mirrors `.qs()` in
|
|
122
|
+
/** Primitive accepted as a query-string value (mirrors `.qs()` in helix). */
|
|
123
123
|
type QueryValue = string | number | boolean
|
|
124
124
|
/** Query-string map — a value or an array of values (repeated key). */
|
|
125
125
|
export type QueryParams = Record<string, QueryValue | ReadonlyArray<QueryValue>>
|
|
126
126
|
|
|
127
|
-
/** A scalar accepted by `form()`/`fields()` —
|
|
127
|
+
/** A scalar accepted by `form()`/`fields()` — helix parity (arrays repeat the key). */
|
|
128
128
|
export type FieldValue = string | number | boolean | Buffer
|
|
129
|
-
/** A value accepted by a multipart `field()`/`file()` —
|
|
129
|
+
/** A value accepted by a multipart `field()`/`file()` — helix parity (incl. streams/blobs). */
|
|
130
130
|
export type MultipartValue = string | number | boolean | Buffer | Blob | Readable
|
|
131
131
|
|
|
132
132
|
/**
|
|
@@ -139,12 +139,12 @@ export type HttpSender = (
|
|
|
139
139
|
init: {
|
|
140
140
|
headers: Record<string, string>
|
|
141
141
|
body: Buffer
|
|
142
|
-
/** Per-request socket timeout (ms) —
|
|
142
|
+
/** Per-request socket timeout (ms) — helix `.timeout()`. Sender may honour it. */
|
|
143
143
|
timeoutMs?: number
|
|
144
144
|
},
|
|
145
145
|
) => Promise<TestResponse>
|
|
146
146
|
|
|
147
|
-
/** Options for a multipart file part — mirrors Adonis/
|
|
147
|
+
/** Options for a multipart file part — mirrors Adonis/helix's `.file()` options. */
|
|
148
148
|
export interface FilePart {
|
|
149
149
|
/** Filename advertised in the part's `Content-Disposition`. Defaults to the field name / basename. */
|
|
150
150
|
filename?: string
|
|
@@ -160,15 +160,15 @@ type MultipartPart =
|
|
|
160
160
|
| { kind: 'field'; name: string; value: MultipartValue }
|
|
161
161
|
| { kind: 'file'; name: string; filename: string; contentType: string; content: MultipartValue }
|
|
162
162
|
|
|
163
|
-
/** HTTP statuses that trigger redirect-following (
|
|
163
|
+
/** HTTP statuses that trigger redirect-following (helix `.redirects()`). */
|
|
164
164
|
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308])
|
|
165
|
-
/**
|
|
165
|
+
/** helix follows 5 redirects by default. */
|
|
166
166
|
const DEFAULT_MAX_REDIRECTS = 5
|
|
167
167
|
|
|
168
|
-
/** Consumer-registered macros/getters (
|
|
168
|
+
/** Consumer-registered macros/getters (helix `ApiRequest.macro`/`.getter`). */
|
|
169
169
|
const requestMacros = new Map<string, unknown>()
|
|
170
170
|
const requestGetters = new Map<string, (this: RequestBuilder, req: RequestBuilder) => unknown>()
|
|
171
|
-
/** Consumer-registered request body serializers (
|
|
171
|
+
/** Consumer-registered request body serializers (helix `ApiRequest.addSerializer`), by name. */
|
|
172
172
|
const requestSerializers = new Map<
|
|
173
173
|
string,
|
|
174
174
|
(data: unknown) => { body: Buffer; contentType: string }
|
|
@@ -194,7 +194,7 @@ export class RequestBuilder {
|
|
|
194
194
|
#timeoutMs: number | undefined
|
|
195
195
|
#maxRedirects = DEFAULT_MAX_REDIRECTS
|
|
196
196
|
#sent: Promise<ApiResponse> | null = null
|
|
197
|
-
// Lazy assertions (
|
|
197
|
+
// Lazy assertions (helix model): each `assert*`/`expect*` registers a check and
|
|
198
198
|
// returns `this` synchronously; the checks run in order when the builder is
|
|
199
199
|
// awaited (`then`) — after the single send.
|
|
200
200
|
#checks: Array<(res: ApiResponse) => void> = []
|
|
@@ -222,7 +222,7 @@ export class RequestBuilder {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
header(name: string, value: string | string[]): this {
|
|
225
|
-
//
|
|
225
|
+
// helix accepts string | string[]; a list is joined into one header value.
|
|
226
226
|
this.#headers[name.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value
|
|
227
227
|
return this
|
|
228
228
|
}
|
|
@@ -233,7 +233,7 @@ export class RequestBuilder {
|
|
|
233
233
|
return this
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
/**
|
|
236
|
+
/** helix `.unsafeJson()` — set a JSON body without any transform (alias of {@link json}). */
|
|
237
237
|
unsafeJson(data: unknown): this {
|
|
238
238
|
return this.json(data)
|
|
239
239
|
}
|
|
@@ -245,7 +245,7 @@ export class RequestBuilder {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
form(data: Record<string, FieldValue | FieldValue[]>): this {
|
|
248
|
-
// Array values repeat the key (`a=1&a=2`) —
|
|
248
|
+
// Array values repeat the key (`a=1&a=2`) — helix parity.
|
|
249
249
|
const pairs: string[] = []
|
|
250
250
|
for (const [k, v] of Object.entries(data)) {
|
|
251
251
|
const values = Array.isArray(v) ? v : [v]
|
|
@@ -258,13 +258,13 @@ export class RequestBuilder {
|
|
|
258
258
|
return this
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
/**
|
|
261
|
+
/** helix `.unsafeForm()` — set a urlencoded body without transform (alias of {@link form}). */
|
|
262
262
|
unsafeForm(data: Record<string, FieldValue | FieldValue[]>): this {
|
|
263
263
|
return this.form(data)
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
/**
|
|
267
|
-
* Add a `multipart/form-data` field — mirrors Adonis/
|
|
267
|
+
* Add a `multipart/form-data` field — mirrors Adonis/helix's `.field()`. Accepts
|
|
268
268
|
* a string/number/boolean, a Buffer, a Blob, a Readable stream, or an array of
|
|
269
269
|
* these (repeated part). Calling `.field()`/`.fields()`/`.file()` switches the
|
|
270
270
|
* body to multipart; the boundary + `Content-Type` header are produced at send
|
|
@@ -282,7 +282,7 @@ export class RequestBuilder {
|
|
|
282
282
|
return this
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
/** Add several multipart fields at once —
|
|
285
|
+
/** Add several multipart fields at once — helix `.fields()`. */
|
|
286
286
|
fields(map: Record<string, MultipartValue | MultipartValue[]>): this {
|
|
287
287
|
for (const [name, value] of Object.entries(map)) {
|
|
288
288
|
this.field(name, value)
|
|
@@ -291,10 +291,10 @@ export class RequestBuilder {
|
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
/**
|
|
294
|
-
* Attach a file as a `multipart/form-data` part — mirrors Adonis/
|
|
294
|
+
* Attach a file as a `multipart/form-data` part — mirrors Adonis/helix's
|
|
295
295
|
* `.file(field, value, { filename, contentType })`. `value` is a Buffer, a
|
|
296
296
|
* Blob, a Readable stream, OR a string absolute PATH to a file on disk (read at
|
|
297
|
-
* call time,
|
|
297
|
+
* call time, helix parity). The filename defaults to the path basename / field
|
|
298
298
|
* name; streams/blobs are drained to Buffers at send time.
|
|
299
299
|
*/
|
|
300
300
|
file(field: string, value: Buffer | string | Blob | Readable, options: FilePart = {}): this {
|
|
@@ -322,7 +322,7 @@ export class RequestBuilder {
|
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
/**
|
|
325
|
-
* Append query-string params — mirrors
|
|
325
|
+
* Append query-string params — mirrors helix's `.qs()`. Values are
|
|
326
326
|
* URL-encoded; arrays repeat the key (`?tag=a&tag=b`). Merges with any query
|
|
327
327
|
* string already on the path and with earlier `.qs()` calls.
|
|
328
328
|
*/
|
|
@@ -338,7 +338,7 @@ export class RequestBuilder {
|
|
|
338
338
|
}
|
|
339
339
|
|
|
340
340
|
/**
|
|
341
|
-
* Append query params WITHOUT any validation/normalisation —
|
|
341
|
+
* Append query params WITHOUT any validation/normalisation — helix
|
|
342
342
|
* `.unsafeQs({ … })`. Values are still URL-encoded so the wire stays valid, but
|
|
343
343
|
* (unlike `.qs()`) no key filtering is applied; arrays repeat the key.
|
|
344
344
|
*/
|
|
@@ -346,13 +346,13 @@ export class RequestBuilder {
|
|
|
346
346
|
return this.qs(params)
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
-
/** Pass a bearer token as the `Authorization` header —
|
|
349
|
+
/** Pass a bearer token as the `Authorization` header — helix `.bearerToken()`. */
|
|
350
350
|
bearerToken(token: string): this {
|
|
351
351
|
this.#headers.authorization = `Bearer ${token}`
|
|
352
352
|
return this
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
-
/** Pass HTTP Basic credentials as the `Authorization` header —
|
|
355
|
+
/** Pass HTTP Basic credentials as the `Authorization` header — helix `.basicAuth()`. */
|
|
356
356
|
basicAuth(user: string, password: string): this {
|
|
357
357
|
const encoded = Buffer.from(`${user}:${password}`, 'utf8').toString('base64')
|
|
358
358
|
this.#headers.authorization = `Basic ${encoded}`
|
|
@@ -361,7 +361,7 @@ export class RequestBuilder {
|
|
|
361
361
|
|
|
362
362
|
/**
|
|
363
363
|
* Set the request `Content-Type` from a shorthand (`'json'`, `'form'`, …) or a
|
|
364
|
-
* full MIME type — mirrors
|
|
364
|
+
* full MIME type — mirrors helix's `.type()`.
|
|
365
365
|
*/
|
|
366
366
|
type(mime: string): this {
|
|
367
367
|
this.#headers['content-type'] = resolveMime(mime)
|
|
@@ -370,52 +370,52 @@ export class RequestBuilder {
|
|
|
370
370
|
|
|
371
371
|
/**
|
|
372
372
|
* Set the `Accept` header from a shorthand (`'json'`, `'html'`, …) or a full
|
|
373
|
-
* MIME type — mirrors
|
|
373
|
+
* MIME type — mirrors helix's `.accept()`.
|
|
374
374
|
*/
|
|
375
375
|
accept(mime: string): this {
|
|
376
376
|
this.#headers.accept = resolveMime(mime)
|
|
377
377
|
return this
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
-
/** Per-request timeout in ms —
|
|
380
|
+
/** Per-request timeout in ms — helix `.timeout()`. Honoured by the sender. */
|
|
381
381
|
timeout(ms: number): this {
|
|
382
382
|
this.#timeoutMs = ms
|
|
383
383
|
return this
|
|
384
384
|
}
|
|
385
385
|
|
|
386
386
|
/**
|
|
387
|
-
* Follow up to `count` redirects before resolving —
|
|
388
|
-
* Defaults to 5 (
|
|
387
|
+
* Follow up to `count` redirects before resolving — helix `.redirects()`.
|
|
388
|
+
* Defaults to 5 (helix default); pass `0` to disable following.
|
|
389
389
|
*/
|
|
390
390
|
redirects(count: number): this {
|
|
391
391
|
this.#maxRedirects = Math.max(0, count)
|
|
392
392
|
return this
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
// TLS knobs —
|
|
395
|
+
// TLS knobs — helix parity. The test server is plain HTTP over loopback, so
|
|
396
396
|
// these are inert (there is no TLS handshake to configure); they exist so
|
|
397
|
-
//
|
|
398
|
-
/** No-op: the loopback test server has no TLS —
|
|
397
|
+
// helix-shaped code type-checks and runs unchanged. NAMED as a ream deviation.
|
|
398
|
+
/** No-op: the loopback test server has no TLS — helix `.trustLocalhost()`. */
|
|
399
399
|
trustLocalhost(_trust = true): this {
|
|
400
400
|
return this
|
|
401
401
|
}
|
|
402
|
-
/** No-op: no TLS on the loopback test server —
|
|
402
|
+
/** No-op: no TLS on the loopback test server — helix `.ca()`. */
|
|
403
403
|
ca(_cert: string): this {
|
|
404
404
|
return this
|
|
405
405
|
}
|
|
406
|
-
/** No-op: no TLS on the loopback test server —
|
|
406
|
+
/** No-op: no TLS on the loopback test server — helix `.cert()`. */
|
|
407
407
|
cert(_chain: string): this {
|
|
408
408
|
return this
|
|
409
409
|
}
|
|
410
|
-
/** No-op: no TLS on the loopback test server —
|
|
410
|
+
/** No-op: no TLS on the loopback test server — helix `.privateKey()`. */
|
|
411
411
|
privateKey(_key: string): this {
|
|
412
412
|
return this
|
|
413
413
|
}
|
|
414
|
-
/** No-op: no TLS on the loopback test server —
|
|
414
|
+
/** No-op: no TLS on the loopback test server — helix `.pfx()`. */
|
|
415
415
|
pfx(_encoded: string | Buffer): this {
|
|
416
416
|
return this
|
|
417
417
|
}
|
|
418
|
-
/** No-op: no TLS on the loopback test server —
|
|
418
|
+
/** No-op: no TLS on the loopback test server — helix `.disableTLSCerts()`. */
|
|
419
419
|
disableTLSCerts(): this {
|
|
420
420
|
return this
|
|
421
421
|
}
|
|
@@ -437,7 +437,7 @@ export class RequestBuilder {
|
|
|
437
437
|
return this
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
-
/**
|
|
440
|
+
/** helix's spelling of {@link withCsrf}. */
|
|
441
441
|
withCsrfToken(token?: string): this {
|
|
442
442
|
return this.withCsrf(token)
|
|
443
443
|
}
|
|
@@ -482,10 +482,10 @@ export class RequestBuilder {
|
|
|
482
482
|
|
|
483
483
|
/**
|
|
484
484
|
* Thenable — `await client.get('/x')` sends the request and resolves to the
|
|
485
|
-
* {@link ApiResponse} (
|
|
485
|
+
* {@link ApiResponse} (helix parity). Assertion methods return `this`;
|
|
486
486
|
* awaiting one resolves via this `then` to the response after running the checks.
|
|
487
487
|
*/
|
|
488
|
-
// biome-ignore lint/suspicious/noThenProperty: the thenable IS the public API — `await client.get('/x')` is the documented
|
|
488
|
+
// biome-ignore lint/suspicious/noThenProperty: the thenable IS the public API — `await client.get('/x')` is the documented helix ergonomic; removing `.then` breaks every await-the-builder call site.
|
|
489
489
|
then<R1 = ApiResponse, R2 = never>(
|
|
490
490
|
onFulfilled?: ((value: ApiResponse) => R1 | PromiseLike<R1>) | null,
|
|
491
491
|
onRejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null,
|
|
@@ -519,7 +519,7 @@ export class RequestBuilder {
|
|
|
519
519
|
}
|
|
520
520
|
expectJson(expected: unknown): this {
|
|
521
521
|
// ream alias: partial JSON match with its own message (kept for back-compat;
|
|
522
|
-
// `assertBodyContains` is the
|
|
522
|
+
// `assertBodyContains` is the helix-named equivalent).
|
|
523
523
|
return this.#assert((res) => {
|
|
524
524
|
if (!partialMatch(res.json(), expected)) {
|
|
525
525
|
throw new ExpectationError(
|
|
@@ -529,7 +529,7 @@ export class RequestBuilder {
|
|
|
529
529
|
})
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
-
// ──
|
|
532
|
+
// ── helix assertions (lazy — delegate to the response) ─────────────────────
|
|
533
533
|
|
|
534
534
|
assertStatus(code: number): this {
|
|
535
535
|
return this.#assert((res) => res.assertStatus(code))
|
|
@@ -562,9 +562,9 @@ export class RequestBuilder {
|
|
|
562
562
|
return this.#assert((res) => res.assertRedirectsTo(path))
|
|
563
563
|
}
|
|
564
564
|
|
|
565
|
-
// ── Request debugging (
|
|
565
|
+
// ── Request debugging (helix `request.dump*` — dumps the REQUEST) ──────────
|
|
566
566
|
|
|
567
|
-
/** Print the pending request (method/path/headers/body) —
|
|
567
|
+
/** Print the pending request (method/path/headers/body) — helix `request.dump()`. */
|
|
568
568
|
dump(): this {
|
|
569
569
|
process.stderr.write(
|
|
570
570
|
`[helix:request.dump] ${this.#method} ${this.#path}\n` +
|
|
@@ -574,17 +574,17 @@ export class RequestBuilder {
|
|
|
574
574
|
)
|
|
575
575
|
return this
|
|
576
576
|
}
|
|
577
|
-
/** Print only the pending request body —
|
|
577
|
+
/** Print only the pending request body — helix `request.dumpBody()`. */
|
|
578
578
|
dumpBody(): this {
|
|
579
579
|
process.stderr.write(`[helix:request.dumpBody] ${capBody(this.#body.toString('utf8'), 2048)}\n`)
|
|
580
580
|
return this
|
|
581
581
|
}
|
|
582
|
-
/** Print only the pending request headers —
|
|
582
|
+
/** Print only the pending request headers — helix `request.dumpHeaders()`. */
|
|
583
583
|
dumpHeaders(): this {
|
|
584
584
|
process.stderr.write(`[helix:request.dumpHeaders] ${JSON.stringify(this.#headers, null, 2)}\n`)
|
|
585
585
|
return this
|
|
586
586
|
}
|
|
587
|
-
/** Print only the pending request cookies —
|
|
587
|
+
/** Print only the pending request cookies — helix `request.dumpCookies()`. */
|
|
588
588
|
dumpCookies(): this {
|
|
589
589
|
process.stderr.write(`[helix:request.dumpCookies] ${JSON.stringify(this.#cookies, null, 2)}\n`)
|
|
590
590
|
return this
|
|
@@ -640,7 +640,7 @@ export class RequestBuilder {
|
|
|
640
640
|
// json()/form()/body(); encode them with a fresh boundary at send time.
|
|
641
641
|
if (this.#multipart.length > 0) {
|
|
642
642
|
const boundary = `----ReamRequestBuilder${crypto.randomUUID().replace(/-/g, '')}`
|
|
643
|
-
// Drain any stream/blob part to a Buffer before encoding (
|
|
643
|
+
// Drain any stream/blob part to a Buffer before encoding (helix parity).
|
|
644
644
|
const resolved: ResolvedPart[] = await Promise.all(
|
|
645
645
|
this.#multipart.map(async (p) =>
|
|
646
646
|
p.kind === 'field'
|
|
@@ -679,7 +679,7 @@ export class RequestBuilder {
|
|
|
679
679
|
timeoutMs: this.#timeoutMs,
|
|
680
680
|
})
|
|
681
681
|
|
|
682
|
-
// Follow redirects up to the configured budget —
|
|
682
|
+
// Follow redirects up to the configured budget — helix `.redirects()` (5 by default).
|
|
683
683
|
const chain: string[] = []
|
|
684
684
|
let followed = 0
|
|
685
685
|
while (
|
|
@@ -715,7 +715,7 @@ export class RequestBuilder {
|
|
|
715
715
|
/**
|
|
716
716
|
* Serialize `data` with a registered serializer and set it as the request body
|
|
717
717
|
* — the send-side twin of {@link ApiResponse.addParser}. Register serializers
|
|
718
|
-
* via {@link RequestBuilder.addSerializer} (
|
|
718
|
+
* via {@link RequestBuilder.addSerializer} (helix `ApiRequest.addSerializer`).
|
|
719
719
|
*/
|
|
720
720
|
serialize(name: string, data: unknown): this {
|
|
721
721
|
const serializer = requestSerializers.get(name)
|
|
@@ -731,9 +731,9 @@ export class RequestBuilder {
|
|
|
731
731
|
}
|
|
732
732
|
|
|
733
733
|
/**
|
|
734
|
-
* Register a shared property (
|
|
734
|
+
* Register a shared property (helix `ApiRequest.macro`) or lazy getter
|
|
735
735
|
* (`ApiRequest.getter`). The callback is invoked with `this` bound to the
|
|
736
|
-
* builder AND the builder as its first arg, so both
|
|
736
|
+
* builder AND the builder as its first arg, so both helix's
|
|
737
737
|
* `function () { return this.header('x') }` and `(req) => …` work.
|
|
738
738
|
*/
|
|
739
739
|
static macro(name: string, value: unknown): void {
|
|
@@ -743,7 +743,7 @@ export class RequestBuilder {
|
|
|
743
743
|
requestGetters.set(name, fn)
|
|
744
744
|
}
|
|
745
745
|
|
|
746
|
-
/** Register a request body serializer —
|
|
746
|
+
/** Register a request body serializer — helix `ApiRequest.addSerializer`. */
|
|
747
747
|
static addSerializer(
|
|
748
748
|
name: string,
|
|
749
749
|
fn: (data: unknown) => { body: Buffer; contentType: string },
|
|
@@ -751,7 +751,7 @@ export class RequestBuilder {
|
|
|
751
751
|
requestSerializers.set(name, fn)
|
|
752
752
|
}
|
|
753
753
|
|
|
754
|
-
/** Register a response body parser —
|
|
754
|
+
/** Register a response body parser — helix `ApiRequest.addParser` (delegates to ApiResponse). */
|
|
755
755
|
static addParser(
|
|
756
756
|
contentType: string,
|
|
757
757
|
fn: (body: string, headers: Record<string, string>) => unknown,
|
|
@@ -37,7 +37,7 @@ export { ApiResponse, type TestResponse } from './RequestBuilder.js'
|
|
|
37
37
|
* named routes without the full Router.
|
|
38
38
|
*
|
|
39
39
|
* A value may be a bare path string OR `{ path, method }` — when the method is
|
|
40
|
-
* present, `visit(name)` uses it (
|
|
40
|
+
* present, `visit(name)` uses it (helix route-registry parity, e.g.
|
|
41
41
|
* `visit('users.store').json(...).assertCreated()` issues a POST).
|
|
42
42
|
*/
|
|
43
43
|
export type RouteEntry = string | { path: string; method?: HttpMethod }
|
|
@@ -98,7 +98,7 @@ export class TestClient {
|
|
|
98
98
|
return this
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
// Verb shortcuts return the RICH builder — the full
|
|
101
|
+
// Verb shortcuts return the RICH builder — the full helix surface
|
|
102
102
|
// (assertOk/assertStatus/assertBody/… + auth/csrf) AND awaitable (`await
|
|
103
103
|
// client.get('/x')` sends and resolves to the response). One unified builder,
|
|
104
104
|
// no split between `get()` and `fluent()`.
|
|
@@ -129,14 +129,14 @@ export class TestClient {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
|
-
* HEAD request —
|
|
132
|
+
* HEAD request — helix's `.head()`. Returns the rich builder so you
|
|
133
133
|
* can assert status/headers (a HEAD response carries headers, no body).
|
|
134
134
|
*/
|
|
135
135
|
head(path: string): RequestBuilder {
|
|
136
136
|
return this.fluent('HEAD', path)
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
/** OPTIONS request —
|
|
139
|
+
/** OPTIONS request — helix's `.options()`. */
|
|
140
140
|
options(path: string): RequestBuilder {
|
|
141
141
|
return this.fluent('OPTIONS', path)
|
|
142
142
|
}
|
|
@@ -161,7 +161,7 @@ export class TestClient {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
/**
|
|
164
|
-
* GET a named route —
|
|
164
|
+
* GET a named route — helix's `.visit()`. Resolves `name` against
|
|
165
165
|
* the `routes` manifest (`router.namedManifest()`), fills `:param`
|
|
166
166
|
* placeholders, and returns the rich chainable builder. Throws a clear error
|
|
167
167
|
* when no manifest was configured or the name is unknown.
|
|
@@ -173,14 +173,14 @@ export class TestClient {
|
|
|
173
173
|
)
|
|
174
174
|
}
|
|
175
175
|
const entry = this.#routes[name]
|
|
176
|
-
// Method comes from the registry when the entry carries one (
|
|
176
|
+
// Method comes from the registry when the entry carries one (helix parity),
|
|
177
177
|
// otherwise GET.
|
|
178
178
|
const method: HttpMethod = typeof entry === 'object' && entry.method ? entry.method : 'GET'
|
|
179
179
|
return this.fluent(method, resolveNamedRoute(this.#routes, name, params))
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
/**
|
|
183
|
-
* Generic request —
|
|
183
|
+
* Generic request — helix's `client.request(url, method)` (note the
|
|
184
184
|
* arg order: URL first, method second, defaulting to GET). Returns the same
|
|
185
185
|
* rich, awaitable builder as the verb shortcuts.
|
|
186
186
|
*/
|
|
@@ -294,7 +294,7 @@ async function sendRequest(
|
|
|
294
294
|
// and surfaced as flaky `bob accepts` / `non-member 403` failures
|
|
295
295
|
// in kitchen-sink's workspace.test.ts. 30s matches the helix
|
|
296
296
|
// `--timeout=60000` per-test budget without masking real hangs. A
|
|
297
|
-
// per-request `.timeout(ms)` (
|
|
297
|
+
// per-request `.timeout(ms)` (helix) overrides the default.
|
|
298
298
|
socket.setTimeout(timeoutMs && timeoutMs > 0 ? timeoutMs : 30_000, () => {
|
|
299
299
|
socket.destroy()
|
|
300
300
|
reject(new Error('TestClient request timed out'))
|