@c9up/ream 0.1.33 → 0.2.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/Application.d.ts +68 -0
- package/dist/Application.d.ts.map +1 -1
- package/dist/Application.js +142 -6
- package/dist/Application.js.map +1 -1
- package/dist/ConfigLoader.d.ts +4 -0
- package/dist/ConfigLoader.d.ts.map +1 -1
- package/dist/ConfigLoader.js +0 -8
- package/dist/ConfigLoader.js.map +1 -1
- package/dist/HealthCheck.d.ts +14 -7
- package/dist/HealthCheck.d.ts.map +1 -1
- package/dist/HealthCheck.js +21 -12
- package/dist/HealthCheck.js.map +1 -1
- package/dist/HttpKernel.d.ts +12 -0
- package/dist/HttpKernel.d.ts.map +1 -1
- package/dist/HttpKernel.js +15 -4
- package/dist/HttpKernel.js.map +1 -1
- package/dist/Ignitor.d.ts +8 -0
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +11 -0
- package/dist/Ignitor.js.map +1 -1
- package/dist/bodyparser/BodyParserMiddleware.d.ts +13 -2
- package/dist/bodyparser/BodyParserMiddleware.d.ts.map +1 -1
- package/dist/bodyparser/BodyParserMiddleware.js +50 -30
- package/dist/bodyparser/BodyParserMiddleware.js.map +1 -1
- package/dist/bodyparser/MultipartFile.d.ts +65 -6
- package/dist/bodyparser/MultipartFile.d.ts.map +1 -1
- package/dist/bodyparser/MultipartFile.js +100 -7
- package/dist/bodyparser/MultipartFile.js.map +1 -1
- package/dist/console/BaseCommand.d.ts +8 -1
- package/dist/console/BaseCommand.d.ts.map +1 -1
- package/dist/console/BaseCommand.js +1 -1
- package/dist/console/BaseCommand.js.map +1 -1
- package/dist/console/Kernel.d.ts +15 -0
- package/dist/console/Kernel.d.ts.map +1 -1
- package/dist/console/Kernel.js +17 -0
- package/dist/console/Kernel.js.map +1 -1
- package/dist/directories.d.ts +52 -0
- package/dist/directories.d.ts.map +1 -0
- package/dist/directories.js +38 -0
- package/dist/directories.js.map +1 -0
- package/dist/events/Emitter.d.ts +49 -6
- package/dist/events/Emitter.d.ts.map +1 -1
- package/dist/events/Emitter.js +117 -7
- package/dist/events/Emitter.js.map +1 -1
- package/dist/events/EventsBuffer.d.ts +47 -0
- package/dist/events/EventsBuffer.d.ts.map +1 -0
- package/dist/events/EventsBuffer.js +107 -0
- package/dist/events/EventsBuffer.js.map +1 -0
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +1 -0
- package/dist/events/index.js.map +1 -1
- package/dist/health/BaseCheck.d.ts +19 -0
- package/dist/health/BaseCheck.d.ts.map +1 -0
- package/dist/health/BaseCheck.js +24 -0
- package/dist/health/BaseCheck.js.map +1 -0
- package/dist/health/HealthChecks.d.ts +15 -0
- package/dist/health/HealthChecks.d.ts.map +1 -0
- package/dist/health/HealthChecks.js +69 -0
- package/dist/health/HealthChecks.js.map +1 -0
- package/dist/health/Result.d.ts +27 -0
- package/dist/health/Result.d.ts.map +1 -0
- package/dist/health/Result.js +55 -0
- package/dist/health/Result.js.map +1 -0
- package/dist/health/checks/DiskSpaceCheck.d.ts +29 -0
- package/dist/health/checks/DiskSpaceCheck.d.ts.map +1 -0
- package/dist/health/checks/DiskSpaceCheck.js +58 -0
- package/dist/health/checks/DiskSpaceCheck.js.map +1 -0
- package/dist/health/checks/MemoryHeapCheck.d.ts +21 -0
- package/dist/health/checks/MemoryHeapCheck.d.ts.map +1 -0
- package/dist/health/checks/MemoryHeapCheck.js +55 -0
- package/dist/health/checks/MemoryHeapCheck.js.map +1 -0
- package/dist/health/checks/MemoryRSSCheck.d.ts +21 -0
- package/dist/health/checks/MemoryRSSCheck.d.ts.map +1 -0
- package/dist/health/checks/MemoryRSSCheck.js +55 -0
- package/dist/health/checks/MemoryRSSCheck.js.map +1 -0
- package/dist/health/checks/memory.d.ts +24 -0
- package/dist/health/checks/memory.d.ts.map +1 -0
- package/dist/health/checks/memory.js +88 -0
- package/dist/health/checks/memory.js.map +1 -0
- package/dist/health/index.d.ts +9 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/health/index.js +8 -0
- package/dist/health/index.js.map +1 -0
- package/dist/health/tracing.d.ts +12 -0
- package/dist/health/tracing.d.ts.map +1 -0
- package/dist/health/tracing.js +11 -0
- package/dist/health/tracing.js.map +1 -0
- package/dist/health/types.d.ts +51 -0
- package/dist/health/types.d.ts.map +1 -0
- package/dist/health/types.js +5 -0
- package/dist/health/types.js.map +1 -0
- package/dist/helpers/bytes.d.ts +28 -0
- package/dist/helpers/bytes.d.ts.map +1 -0
- package/dist/helpers/bytes.js +63 -0
- package/dist/helpers/bytes.js.map +1 -0
- package/dist/helpers/string.d.ts +4 -0
- package/dist/helpers/string.d.ts.map +1 -1
- package/dist/helpers/string.js +2 -0
- package/dist/helpers/string.js.map +1 -1
- package/dist/http/HttpContext.d.ts +38 -2
- package/dist/http/HttpContext.d.ts.map +1 -1
- package/dist/http/HttpContext.js +81 -3
- package/dist/http/HttpContext.js.map +1 -1
- package/dist/http/Request.d.ts +57 -3
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +51 -38
- package/dist/http/Request.js.map +1 -1
- package/dist/http/Response.d.ts +82 -2
- package/dist/http/Response.d.ts.map +1 -1
- package/dist/http/Response.js +240 -13
- package/dist/http/Response.js.map +1 -1
- package/dist/http/SseStream.d.ts +10 -0
- package/dist/http/SseStream.d.ts.map +1 -1
- package/dist/http/SseStream.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/migrations/MigrationRegistry.d.ts +28 -0
- package/dist/migrations/MigrationRegistry.d.ts.map +1 -0
- package/dist/migrations/MigrationRegistry.js +41 -0
- package/dist/migrations/MigrationRegistry.js.map +1 -0
- package/dist/migrations/types.d.ts +64 -0
- package/dist/migrations/types.d.ts.map +1 -0
- package/dist/migrations/types.js +15 -0
- package/dist/migrations/types.js.map +1 -0
- package/dist/router/Router.d.ts +112 -9
- package/dist/router/Router.d.ts.map +1 -1
- package/dist/router/Router.js +206 -28
- package/dist/router/Router.js.map +1 -1
- package/dist/security/CookieSigner.d.ts +43 -4
- package/dist/security/CookieSigner.d.ts.map +1 -1
- package/dist/security/CookieSigner.js +68 -10
- package/dist/security/CookieSigner.js.map +1 -1
- package/dist/session/Session.d.ts +46 -1
- package/dist/session/Session.d.ts.map +1 -1
- package/dist/session/Session.js +57 -4
- package/dist/session/Session.js.map +1 -1
- package/dist/session/SessionMiddleware.d.ts.map +1 -1
- package/dist/session/SessionMiddleware.js +19 -9
- package/dist/session/SessionMiddleware.js.map +1 -1
- 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 +3 -3
- package/dist/session/drivers/CookieDriver.js.map +1 -1
- package/dist/session/drivers/DatabaseDriver.d.ts +1 -1
- package/dist/session/drivers/DatabaseDriver.d.ts.map +1 -1
- package/dist/session/drivers/DatabaseDriver.js +2 -2
- package/dist/session/drivers/DatabaseDriver.js.map +1 -1
- package/dist/session/drivers/FileDriver.d.ts +1 -1
- package/dist/session/drivers/FileDriver.d.ts.map +1 -1
- package/dist/session/drivers/FileDriver.js +7 -5
- package/dist/session/drivers/FileDriver.js.map +1 -1
- package/dist/session/drivers/MemoryDriver.d.ts +1 -1
- package/dist/session/drivers/MemoryDriver.d.ts.map +1 -1
- package/dist/session/drivers/MemoryDriver.js +1 -1
- package/dist/session/drivers/MemoryDriver.js.map +1 -1
- package/dist/session/drivers/RedisDriver.d.ts +1 -1
- package/dist/session/drivers/RedisDriver.d.ts.map +1 -1
- package/dist/session/drivers/RedisDriver.js +11 -4
- package/dist/session/drivers/RedisDriver.js.map +1 -1
- package/events.win32-x64-msvc.node +0 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +5 -2
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/Application.ts +169 -6
- package/src/ConfigLoader.ts +6 -0
- package/src/HealthCheck.ts +27 -14
- package/src/HttpKernel.ts +27 -4
- package/src/Ignitor.ts +17 -0
- package/src/bodyparser/BodyParserMiddleware.ts +70 -36
- package/src/bodyparser/MultipartFile.ts +125 -8
- package/src/console/BaseCommand.ts +8 -1
- package/src/console/Kernel.ts +18 -0
- package/src/directories.ts +81 -0
- package/src/events/Emitter.ts +129 -13
- package/src/events/EventsBuffer.ts +131 -0
- package/src/events/index.ts +7 -0
- package/src/health/BaseCheck.ts +30 -0
- package/src/health/HealthChecks.ts +83 -0
- package/src/health/Result.ts +69 -0
- package/src/health/checks/DiskSpaceCheck.ts +69 -0
- package/src/health/checks/MemoryHeapCheck.ts +68 -0
- package/src/health/checks/MemoryRSSCheck.ts +68 -0
- package/src/health/checks/memory.ts +115 -0
- package/src/health/index.ts +13 -0
- package/src/health/tracing.ts +15 -0
- package/src/health/types.ts +51 -0
- package/src/helpers/bytes.ts +71 -0
- package/src/helpers/string.ts +2 -0
- package/src/http/HttpContext.ts +86 -4
- package/src/http/Request.ts +82 -10
- package/src/http/Response.ts +283 -14
- package/src/http/SseStream.ts +10 -0
- package/src/index.ts +27 -1
- package/src/migrations/MigrationRegistry.ts +49 -0
- package/src/migrations/types.ts +68 -0
- package/src/router/Router.ts +241 -32
- package/src/security/CookieSigner.ts +92 -18
- package/src/session/Session.ts +71 -5
- package/src/session/SessionMiddleware.ts +19 -9
- package/src/session/drivers/CookieDriver.ts +4 -4
- package/src/session/drivers/DatabaseDriver.ts +3 -3
- package/src/session/drivers/FileDriver.ts +8 -6
- package/src/session/drivers/MemoryDriver.ts +2 -2
- package/src/session/drivers/RedisDriver.ts +12 -5
- package/tsconfig.app.json +13 -13
- package/tsconfig.base.json +19 -0
- package/tsconfig.client.json +16 -0
- package/tsconfig.package.json +15 -0
package/src/http/Request.ts
CHANGED
|
@@ -66,7 +66,7 @@ export class Request extends Macroable {
|
|
|
66
66
|
#signedUrl?: SignedUrl
|
|
67
67
|
#allowMethodSpoofing = false
|
|
68
68
|
#trustProxy = false
|
|
69
|
-
#routeInfo?: { name?: string; pattern: string }
|
|
69
|
+
#routeInfo?: { name?: string; pattern: string; reference?: string }
|
|
70
70
|
#response?: { fresh(): boolean }
|
|
71
71
|
#parsedBody: Dict<unknown> | undefined
|
|
72
72
|
#parsedQs: Dict<unknown> | undefined
|
|
@@ -122,13 +122,32 @@ export class Request extends Macroable {
|
|
|
122
122
|
return this.#raw.method
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* The context this request belongs to (AdonisJS `request.ctx`).
|
|
127
|
+
*
|
|
128
|
+
* Wired by HttpContext. Optional because a Request built by hand — a test,
|
|
129
|
+
* a fixture — has no context around it.
|
|
130
|
+
*/
|
|
131
|
+
ctx?: object
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Seat the parsed body (AdonisJS `setInitialBody`).
|
|
135
|
+
*
|
|
136
|
+
* What a body parser calls once it has decoded the payload. An alias of
|
|
137
|
+
* {@link setParsedBody}, which is the name ream used first — both are here so
|
|
138
|
+
* a middleware written against either resolves.
|
|
139
|
+
*/
|
|
140
|
+
setInitialBody(body: Dict<unknown>): void {
|
|
141
|
+
this.setParsedBody(body)
|
|
142
|
+
}
|
|
143
|
+
|
|
125
144
|
/** @internal Give the request access to its response — wired by HttpContext (for `fresh()`). */
|
|
126
145
|
setResponse(response: { fresh(): boolean }): void {
|
|
127
146
|
this.#response = response
|
|
128
147
|
}
|
|
129
148
|
|
|
130
149
|
/** @internal Record the matched route — wired by HttpContext (for `matchesRoute()`). */
|
|
131
|
-
setRouteInfo(info: { name?: string; pattern: string }): void {
|
|
150
|
+
setRouteInfo(info: { name?: string; pattern: string; reference?: string }): void {
|
|
132
151
|
this.#routeInfo = info
|
|
133
152
|
}
|
|
134
153
|
|
|
@@ -146,10 +165,26 @@ export class Request extends Macroable {
|
|
|
146
165
|
return !this.fresh()
|
|
147
166
|
}
|
|
148
167
|
|
|
149
|
-
/**
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
168
|
+
/**
|
|
169
|
+
* True when the matched route's name, pattern, or controller reference is one
|
|
170
|
+
* of `identifier` (AdonisJS `matchesRoute`).
|
|
171
|
+
*
|
|
172
|
+
* Takes a list as well as a single value, as upstream does — the usual call
|
|
173
|
+
* is "am I on any of these routes", and a caller with several had to write
|
|
174
|
+
* the `.some()` themselves.
|
|
175
|
+
*
|
|
176
|
+
* The reference is the `'ControllerName.method'` form the router already
|
|
177
|
+
* accepts as a handler, which is the identity AdonisJS matches on through
|
|
178
|
+
* `route.handler.reference`. Absent for an inline handler, which has no name
|
|
179
|
+
* to be addressed by.
|
|
180
|
+
*/
|
|
181
|
+
matchesRoute(identifier: string | string[]): boolean {
|
|
182
|
+
const info = this.#routeInfo
|
|
183
|
+
if (!info) return false
|
|
184
|
+
const candidates = Array.isArray(identifier) ? identifier : [identifier]
|
|
185
|
+
return candidates.some(
|
|
186
|
+
(one) => info.name === one || info.pattern === one || info.reference === one,
|
|
187
|
+
)
|
|
153
188
|
}
|
|
154
189
|
|
|
155
190
|
/**
|
|
@@ -161,8 +196,15 @@ export class Request extends Macroable {
|
|
|
161
196
|
this.#allowMethodSpoofing = enabled
|
|
162
197
|
}
|
|
163
198
|
|
|
164
|
-
/**
|
|
165
|
-
|
|
199
|
+
/**
|
|
200
|
+
* The request URL — the PATHNAME by default, as in AdonisJS
|
|
201
|
+
* (`url(includeQueryString)`, false unless asked). Ream defaulted to
|
|
202
|
+
* including the query string, so a value used as a cache key, a log line or
|
|
203
|
+
* a route comparison silently carried the query with it.
|
|
204
|
+
*
|
|
205
|
+
* Pass `true` for path + query; {@link completeUrl} adds the origin.
|
|
206
|
+
*/
|
|
207
|
+
url(includeQs = false): string {
|
|
166
208
|
if (includeQs && this.#raw.query) {
|
|
167
209
|
return `${this.#raw.path}?${this.#raw.query}`
|
|
168
210
|
}
|
|
@@ -330,7 +372,14 @@ export class Request extends Macroable {
|
|
|
330
372
|
* Signed cookie value, verified with APP_KEY (AdonisJS default). Returns
|
|
331
373
|
* `defaultValue` (or null) when absent OR when the signature is invalid
|
|
332
374
|
* (tampered / not signed).
|
|
375
|
+
*
|
|
376
|
+
* NAMED DEVIATION — the fallback is `??`, where AdonisJS writes `||`. A
|
|
377
|
+
* cookie legitimately holding `0`, `false` or `""` is a value, not an
|
|
378
|
+
* absence, and upstream hands back the default for all three. Kept.
|
|
333
379
|
*/
|
|
380
|
+
cookie(name: string): string | null
|
|
381
|
+
cookie(name: string, defaultValue: string): string
|
|
382
|
+
cookie(name: string, defaultValue?: string): string | null
|
|
334
383
|
cookie(name: string, defaultValue?: string): string | null {
|
|
335
384
|
const raw = this.plainCookie<string>(name, undefined, { encoded: false })
|
|
336
385
|
const value = raw === null ? null : this.#cookieSigner ? this.#cookieSigner.unsign(raw) : raw
|
|
@@ -347,6 +396,14 @@ export class Request extends Macroable {
|
|
|
347
396
|
*
|
|
348
397
|
* Pass `encoded: false` to skip unpacking entirely.
|
|
349
398
|
*/
|
|
399
|
+
plainCookie<T = string>(name: string): T | null
|
|
400
|
+
plainCookie<T = string>(name: string, defaultValue: T, options?: { encoded?: boolean }): T
|
|
401
|
+
plainCookie<T = string>(
|
|
402
|
+
name: string,
|
|
403
|
+
defaultValue: undefined,
|
|
404
|
+
options?: { encoded?: boolean },
|
|
405
|
+
): T | null
|
|
406
|
+
plainCookie<T = string>(name: string, defaultValue?: T, options?: { encoded?: boolean }): T | null
|
|
350
407
|
plainCookie<T = string>(
|
|
351
408
|
name: string,
|
|
352
409
|
defaultValue?: T,
|
|
@@ -365,6 +422,9 @@ export class Request extends Macroable {
|
|
|
365
422
|
* Returns `defaultValue` (or null) when absent, undecryptable, or no
|
|
366
423
|
* encryption service.
|
|
367
424
|
*/
|
|
425
|
+
encryptedCookie(name: string): string | null
|
|
426
|
+
encryptedCookie(name: string, defaultValue: string): string
|
|
427
|
+
encryptedCookie(name: string, defaultValue?: string): string | null
|
|
368
428
|
encryptedCookie(name: string, defaultValue?: string): string | null {
|
|
369
429
|
const raw = this.plainCookie<string>(name, undefined, { encoded: false })
|
|
370
430
|
const value = raw === null || !this.#cookieSigner ? null : this.#cookieSigner.decrypt(raw)
|
|
@@ -384,7 +444,9 @@ export class Request extends Macroable {
|
|
|
384
444
|
*/
|
|
385
445
|
hasValidSignature(purpose?: string): boolean {
|
|
386
446
|
if (!this.#signedUrl) return false
|
|
387
|
-
|
|
447
|
+
// `url(true)`: the signature covers the path AND the query string —
|
|
448
|
+
// the expiry and the signature itself live in the query.
|
|
449
|
+
return this.#signedUrl.verify(this.url(true), purpose)
|
|
388
450
|
}
|
|
389
451
|
|
|
390
452
|
/** Get the raw body as a string (decoded from base64 if binary). */
|
|
@@ -407,6 +469,9 @@ export class Request extends Macroable {
|
|
|
407
469
|
// ─── Headers ──────────────────────────────────────────────
|
|
408
470
|
|
|
409
471
|
/** Get a single request header (case-insensitive), or `defaultValue` when absent. */
|
|
472
|
+
header(key: string): string | undefined
|
|
473
|
+
header(key: string, defaultValue: string): string
|
|
474
|
+
header(key: string, defaultValue?: string): string | undefined
|
|
410
475
|
header(key: string, defaultValue?: string): string | undefined {
|
|
411
476
|
return this.#raw.headers[key.toLowerCase()] ?? defaultValue
|
|
412
477
|
}
|
|
@@ -426,6 +491,9 @@ export class Request extends Macroable {
|
|
|
426
491
|
* value was not a string would be worse than the path it describes.
|
|
427
492
|
* Use {@link params} to get the segments themselves.
|
|
428
493
|
*/
|
|
494
|
+
param(key: string): string | undefined
|
|
495
|
+
param(key: string, defaultValue: string): string
|
|
496
|
+
param(key: string, defaultValue?: string): string | undefined
|
|
429
497
|
param(key: string, defaultValue?: string): string | undefined {
|
|
430
498
|
const value = this.#params[key]
|
|
431
499
|
if (value === undefined) return defaultValue
|
|
@@ -663,7 +731,11 @@ export class Request extends Macroable {
|
|
|
663
731
|
all(): Dict<unknown> {
|
|
664
732
|
if (!this.#merged) {
|
|
665
733
|
this.#ensureParsedBody()
|
|
666
|
-
|
|
734
|
+
// `{ ...body, ...qs }` — the QUERY STRING wins, as in AdonisJS
|
|
735
|
+
// (`#requestData = { ...#requestBody, ...#requestQs }`). Ream had it the
|
|
736
|
+
// other way round, so `?id=1` with a body `{ id: 2 }` read 2 here and 1
|
|
737
|
+
// there — silently, on the request field an app trusts most.
|
|
738
|
+
this.#merged = { ...this.#parsedBody, ...this.qs() }
|
|
667
739
|
// Snapshot the first-seen input as the immutable "original" (flash old-input).
|
|
668
740
|
if (this.#original === undefined) this.#original = { ...this.#merged }
|
|
669
741
|
}
|
package/src/http/Response.ts
CHANGED
|
@@ -7,11 +7,20 @@
|
|
|
7
7
|
* @implements FR21
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { randomUUID } from 'node:crypto'
|
|
11
|
+
import { createReadStream } from 'node:fs'
|
|
12
|
+
import { readFile, stat } from 'node:fs/promises'
|
|
11
13
|
import { basename } from 'node:path'
|
|
12
14
|
import etag from 'etag'
|
|
13
15
|
import { contentType } from 'mime-types'
|
|
14
16
|
import { durationToSeconds } from '../helpers/duration.js'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Default ceiling on a buffered response body — the same 100MB the Rust layer
|
|
20
|
+
* caps an incoming body at (`MAX_BODY_BYTES` in crates/ream-http/src/server.rs).
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 100 * 1024 * 1024
|
|
23
|
+
|
|
15
24
|
import type { CookieSigner } from '../security/CookieSigner.js'
|
|
16
25
|
import { Macroable } from '../utils/Macroable.js'
|
|
17
26
|
import { E_HTTP_REQUEST_ABORTED } from './Exception.js'
|
|
@@ -133,13 +142,32 @@ export interface CookieOptions {
|
|
|
133
142
|
|
|
134
143
|
export class Response extends Macroable {
|
|
135
144
|
#status = 200
|
|
136
|
-
/** A body still being drained by {@link stream}; awaited by {@link
|
|
145
|
+
/** A body still being drained by {@link stream}; awaited by {@link finish}. */
|
|
137
146
|
#pendingStream: Promise<void> | undefined
|
|
147
|
+
/** The detached chunk pump, when one is running. See {@link streamed}. */
|
|
148
|
+
#pumping: Promise<void> | undefined
|
|
149
|
+
/** Whether {@link finish} has run — its idempotence guard. */
|
|
150
|
+
#finalised = false
|
|
138
151
|
#headers: Record<string, string> = {}
|
|
139
152
|
#cookies: string[] = []
|
|
140
153
|
#body = ''
|
|
141
154
|
readonly #finishCallbacks: Array<(err: Error | null) => void> = []
|
|
142
155
|
#finished = false
|
|
156
|
+
/**
|
|
157
|
+
* The context this response belongs to (AdonisJS `response.ctx`).
|
|
158
|
+
*
|
|
159
|
+
* Wired by HttpContext. Optional because a Response built by hand — a test,
|
|
160
|
+
* a fixture — has no context around it.
|
|
161
|
+
*/
|
|
162
|
+
ctx?: object
|
|
163
|
+
|
|
164
|
+
/** Default JSONP callback name (AdonisJS `http.jsonpCallbackName`). */
|
|
165
|
+
#jsonpCallbackName = 'callback'
|
|
166
|
+
/**
|
|
167
|
+
* Ceiling on a buffered response body. Mirrors the 100MB the Rust layer caps
|
|
168
|
+
* an incoming body at, so the two directions are explained the same way.
|
|
169
|
+
*/
|
|
170
|
+
#maxBodyBytes = DEFAULT_MAX_RESPONSE_BYTES
|
|
143
171
|
#redirectBuilderFactory?: () => RedirectBuilder
|
|
144
172
|
#streamBackend?: StreamBackend
|
|
145
173
|
#streamId?: string
|
|
@@ -268,7 +296,16 @@ export class Response extends Macroable {
|
|
|
268
296
|
* (valid in JSON, but break JS), so neither the callback nor the payload can
|
|
269
297
|
* inject script.
|
|
270
298
|
*/
|
|
271
|
-
|
|
299
|
+
/**
|
|
300
|
+
* Default callback name when `jsonp()` is called without one (AdonisJS reads
|
|
301
|
+
* it from `http.jsonpCallbackName`). Injected by HttpKernel, the same way the
|
|
302
|
+
* cookie signer and signed-URL services are.
|
|
303
|
+
*/
|
|
304
|
+
setJsonpCallbackName(name: string): void {
|
|
305
|
+
this.#jsonpCallbackName = name
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
jsonp(body: unknown, callbackName: string = this.#jsonpCallbackName): void {
|
|
272
309
|
// Sanitise the callback name to identifier-safe characters — the JSONP XSS
|
|
273
310
|
// guard (an attacker-controlled callback must not inject script).
|
|
274
311
|
const safeCallback = callbackName.replace(/[^\w$.]/g, '')
|
|
@@ -323,6 +360,7 @@ export class Response extends Macroable {
|
|
|
323
360
|
* unambiguous.
|
|
324
361
|
*/
|
|
325
362
|
sendBuffer(buffer: Buffer): void {
|
|
363
|
+
this.#assertBodyFits(buffer.length)
|
|
326
364
|
if (!this.#headers['content-type']) {
|
|
327
365
|
this.#headers['content-type'] = 'application/octet-stream'
|
|
328
366
|
}
|
|
@@ -331,6 +369,34 @@ export class Response extends Macroable {
|
|
|
331
369
|
this.#finished = true
|
|
332
370
|
}
|
|
333
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Refuse a body too large to hold in memory.
|
|
374
|
+
*
|
|
375
|
+
* A response body is serialised whole across the NAPI boundary — there is no
|
|
376
|
+
* chunked write yet — and a binary one is base64'd on the way, so it costs
|
|
377
|
+
* roughly 2.3x its size in transient memory: the Buffer, the encoded string,
|
|
378
|
+
* and the Rust-side decode. Without a ceiling a large file did not fail, it
|
|
379
|
+
* grew until the process died, with no message naming the cause.
|
|
380
|
+
*
|
|
381
|
+
* Raise it with `maxResponseBytes` in the kernel config when you know the
|
|
382
|
+
* memory is there. For anything genuinely large, hand out a signed URL from
|
|
383
|
+
* `@c9up/archive` instead and let the client fetch it from storage — the
|
|
384
|
+
* bytes then never pass through the server at all.
|
|
385
|
+
*/
|
|
386
|
+
#assertBodyFits(bytes: number): void {
|
|
387
|
+
if (bytes <= this.#maxBodyBytes) return
|
|
388
|
+
throw new Error(
|
|
389
|
+
`[E_RESPONSE_TOO_LARGE] Response body is ${Math.round(bytes / 1_048_576)}MB, over the ${Math.round(this.#maxBodyBytes / 1_048_576)}MB ceiling. ` +
|
|
390
|
+
'The body is held whole in memory (and base64-encoded, ~2.3x) because responses are not streamed yet. ' +
|
|
391
|
+
'Raise `maxResponseBytes` in the kernel config, or serve large files with a signed URL from @c9up/archive.',
|
|
392
|
+
)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** @internal Set the ceiling — injected by HttpKernel from its config. */
|
|
396
|
+
setMaxBodyBytes(bytes: number): void {
|
|
397
|
+
this.#maxBodyBytes = bytes
|
|
398
|
+
}
|
|
399
|
+
|
|
334
400
|
/** Send 204 No Content. */
|
|
335
401
|
noContent(): void {
|
|
336
402
|
this.#status = 204
|
|
@@ -588,14 +654,31 @@ export class Response extends Macroable {
|
|
|
588
654
|
|
|
589
655
|
// ─── Redirect ─────────────────────────────────────────────
|
|
590
656
|
|
|
591
|
-
/**
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
657
|
+
/**
|
|
658
|
+
* Redirect, or get the builder.
|
|
659
|
+
*
|
|
660
|
+
* `redirect()` with no argument hands back the builder to chain on. With a
|
|
661
|
+
* PATH it redirects immediately, as AdonisJS does
|
|
662
|
+
* (`redirect(path, forwardQueryString?, statusCode?)`); `'back'` goes to the
|
|
663
|
+
* referrer. Ream always returned the builder, so `response.redirect('/login')`
|
|
664
|
+
* — the shortest and most obvious spelling — silently did nothing.
|
|
665
|
+
*/
|
|
666
|
+
redirect(): RedirectBuilder
|
|
667
|
+
redirect(path: string, forwardQueryString?: boolean, statusCode?: number): void
|
|
668
|
+
redirect(path?: string, forwardQueryString = false, statusCode = 302): RedirectBuilder | void {
|
|
669
|
+
if (!this.#redirectBuilderFactory) {
|
|
670
|
+
throw new Error(
|
|
671
|
+
'redirect() requires an HttpContext. Response was created outside a request handler.',
|
|
672
|
+
)
|
|
595
673
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
)
|
|
674
|
+
const builder = this.#redirectBuilderFactory()
|
|
675
|
+
if (path === undefined) return builder
|
|
676
|
+
if (forwardQueryString) builder.withQs()
|
|
677
|
+
if (path === 'back') {
|
|
678
|
+
builder.status(statusCode).back()
|
|
679
|
+
return
|
|
680
|
+
}
|
|
681
|
+
builder.status(statusCode).toPath(path)
|
|
599
682
|
}
|
|
600
683
|
|
|
601
684
|
/** @internal Set the redirect builder factory (injected by HttpContext). */
|
|
@@ -670,7 +753,40 @@ export class Response extends Macroable {
|
|
|
670
753
|
* @internal Wait for a body still being produced (see {@link stream}). The
|
|
671
754
|
* kernel calls this before serializing; everything else resolves at once.
|
|
672
755
|
*/
|
|
673
|
-
|
|
756
|
+
/**
|
|
757
|
+
* Resolves when a streamed body has finished feeding the socket.
|
|
758
|
+
*
|
|
759
|
+
* Deliberately NOT part of {@link finish}: the response has to reach Rust
|
|
760
|
+
* with its stream id before the body can be attached, so waiting for the
|
|
761
|
+
* pump there would deadlock — and closing the stream first is what made the
|
|
762
|
+
* client see `E_STREAM_UNKNOWN` instead of the file. This is for a test that
|
|
763
|
+
* wants the finished body, and for a shutdown that would rather let an
|
|
764
|
+
* in-flight download end than cut it.
|
|
765
|
+
*/
|
|
766
|
+
streamed(): Promise<void> {
|
|
767
|
+
return this.#pumping ?? Promise.resolve()
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Finalise the response — the terminal step (AdonisJS `response.finish()`).
|
|
772
|
+
*
|
|
773
|
+
* Same role and same guarantees as upstream: idempotent, it stamps the
|
|
774
|
+
* request id, and afterwards nothing more is added to the response. The
|
|
775
|
+
* kernel calls it before reading the response out; an app rarely needs to.
|
|
776
|
+
*
|
|
777
|
+
* NAMED DEVIATION — it returns a promise where upstream returns void, and it
|
|
778
|
+
* writes nothing. Ream never owns the socket: the whole response crosses the
|
|
779
|
+
* NAPI boundary and Rust writes it, so "finish" here means *sealed and ready
|
|
780
|
+
* to hand over* rather than *flushed*. The await is for a body still being
|
|
781
|
+
* produced — `download()` and a buffered `stream()` fill it asynchronously.
|
|
782
|
+
*
|
|
783
|
+
* A STREAMED body is deliberately not awaited: it feeds the socket after the
|
|
784
|
+
* response has been handed over. See {@link streamed}.
|
|
785
|
+
*/
|
|
786
|
+
async finish(): Promise<void> {
|
|
787
|
+
if (this.#finalised) return
|
|
788
|
+
this.#finalised = true
|
|
789
|
+
this.setRequestId()
|
|
674
790
|
const pending = this.#pendingStream
|
|
675
791
|
if (pending === undefined) return
|
|
676
792
|
this.#pendingStream = undefined
|
|
@@ -687,9 +803,25 @@ export class Response extends Macroable {
|
|
|
687
803
|
generateEtag = false,
|
|
688
804
|
errorCallback?: (error: NodeJS.ErrnoException) => [string, number?],
|
|
689
805
|
): void {
|
|
690
|
-
|
|
806
|
+
// Read asynchronously, parked on the same pending-body slot `stream()`
|
|
807
|
+
// uses and `finish()` already awaits before serialisation. It used to be
|
|
808
|
+
// `readFileSync`, which stalled the event loop for EVERY concurrent
|
|
809
|
+
// request while one client downloaded — the file's size became every
|
|
810
|
+
// other request's latency.
|
|
811
|
+
this.#pendingStream = this.#readForDownload(filePath, generateEtag, errorCallback)
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
async #readForDownload(
|
|
815
|
+
filePath: string,
|
|
816
|
+
generateEtag: boolean,
|
|
817
|
+
errorCallback?: (error: NodeJS.ErrnoException) => [string, number?],
|
|
818
|
+
): Promise<void> {
|
|
819
|
+
// Stat first: a missing or unreadable file must answer 404 BEFORE any
|
|
820
|
+
// header goes out, and once a stream starts there is no status left to
|
|
821
|
+
// change.
|
|
822
|
+
let size: number
|
|
691
823
|
try {
|
|
692
|
-
|
|
824
|
+
size = (await stat(filePath)).size
|
|
693
825
|
} catch (error) {
|
|
694
826
|
if (errorCallback) {
|
|
695
827
|
const [message, status] = errorCallback(error as NodeJS.ErrnoException)
|
|
@@ -699,8 +831,20 @@ export class Response extends Macroable {
|
|
|
699
831
|
this.status(404).send('Not Found')
|
|
700
832
|
return
|
|
701
833
|
}
|
|
834
|
+
|
|
702
835
|
const ct = contentType(basename(filePath))
|
|
703
836
|
if (ct) this.#headers['content-type'] = ct
|
|
837
|
+
|
|
838
|
+
// An ETag is a hash of the whole file, so asking for one asks for the file
|
|
839
|
+
// in memory. Without one, stream it: the body goes out as it is read and
|
|
840
|
+
// nothing bigger than a chunk is ever held.
|
|
841
|
+
if (!generateEtag && this.#streamBackend?.writeStreamBytes !== undefined) {
|
|
842
|
+
this.#headers['content-length'] = String(size)
|
|
843
|
+
await this.#pipe(createReadStream(filePath), errorCallback)
|
|
844
|
+
return
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const content = await readFile(filePath)
|
|
704
848
|
if (generateEtag) this.setEtag(content)
|
|
705
849
|
this.sendBuffer(content)
|
|
706
850
|
}
|
|
@@ -771,11 +915,78 @@ export class Response extends Macroable {
|
|
|
771
915
|
body: NodeJS.ReadableStream,
|
|
772
916
|
errorCallback?: (error: NodeJS.ErrnoException) => [string, number?],
|
|
773
917
|
): Promise<void> {
|
|
774
|
-
const draining = this.#
|
|
918
|
+
const draining = this.#pipe(body, errorCallback)
|
|
775
919
|
this.#pendingStream = draining
|
|
776
920
|
return draining
|
|
777
921
|
}
|
|
778
922
|
|
|
923
|
+
/**
|
|
924
|
+
* Send a readable as the body.
|
|
925
|
+
*
|
|
926
|
+
* Chunks go to the socket as they arrive when the host can carry them —
|
|
927
|
+
* nothing bigger than one chunk is ever held, so the file's size stops being
|
|
928
|
+
* the process's memory ceiling. A host with no binary stream backend (a unit
|
|
929
|
+
* test, a mock server) falls back to draining into memory, which is what
|
|
930
|
+
* this always used to do.
|
|
931
|
+
*/
|
|
932
|
+
async #pipe(
|
|
933
|
+
body: NodeJS.ReadableStream,
|
|
934
|
+
errorCallback?: (error: NodeJS.ErrnoException) => [string, number?],
|
|
935
|
+
): Promise<void> {
|
|
936
|
+
const backend = this.#streamBackend
|
|
937
|
+
const writeBytes = backend?.writeStreamBytes
|
|
938
|
+
if (backend === undefined || writeBytes === undefined) {
|
|
939
|
+
return this.#drain(body, errorCallback)
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const streamId = randomUUID()
|
|
943
|
+
if (!(await backend.registerStream(streamId))) {
|
|
944
|
+
// The id collided — vanishingly unlikely, but answering with a body
|
|
945
|
+
// nobody feeds would hang the client forever.
|
|
946
|
+
return this.#drain(body, errorCallback)
|
|
947
|
+
}
|
|
948
|
+
if (!this.#headers['content-type']) {
|
|
949
|
+
this.#headers['content-type'] = 'application/octet-stream'
|
|
950
|
+
}
|
|
951
|
+
this.#streamId = streamId
|
|
952
|
+
this.#finished = true
|
|
953
|
+
|
|
954
|
+
// Pump DETACHED, exactly as `sse()` does. What is awaited here is only the
|
|
955
|
+
// registration: the kernel awaits this before handing the response back to
|
|
956
|
+
// Rust, and Rust then looks the id up to attach the body. Awaiting the pump
|
|
957
|
+
// instead would close the stream before that lookup ever happened, and the
|
|
958
|
+
// client would get `E_STREAM_UNKNOWN` in place of the file.
|
|
959
|
+
this.#pumping = this.#pump(backend, writeBytes.bind(backend), streamId, body, errorCallback)
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/** Feed a registered stream until the source ends or the client leaves. */
|
|
963
|
+
async #pump(
|
|
964
|
+
backend: StreamBackend,
|
|
965
|
+
writeBytes: (id: string, chunk: Uint8Array) => Promise<boolean>,
|
|
966
|
+
streamId: string,
|
|
967
|
+
body: NodeJS.ReadableStream,
|
|
968
|
+
errorCallback?: (error: NodeJS.ErrnoException) => [string, number?],
|
|
969
|
+
): Promise<void> {
|
|
970
|
+
try {
|
|
971
|
+
for await (const chunk of body) {
|
|
972
|
+
const bytes = typeof chunk === 'string' ? Buffer.from(chunk) : chunk
|
|
973
|
+
// `false` means the client is gone. Stop reading the source rather
|
|
974
|
+
// than pumping a whole file into a closed socket.
|
|
975
|
+
if (!(await writeBytes(streamId, bytes))) break
|
|
976
|
+
}
|
|
977
|
+
} catch (err) {
|
|
978
|
+
// The headers are already out, so there is no status left to change: end
|
|
979
|
+
// the body and let the client see a short read. `errorCallback` is still
|
|
980
|
+
// consulted for whatever logging it does.
|
|
981
|
+
errorCallback?.(err as NodeJS.ErrnoException)
|
|
982
|
+
} finally {
|
|
983
|
+
// Never rethrows: this runs detached, and an unhandled rejection here
|
|
984
|
+
// would take the process down over one client's download.
|
|
985
|
+
await backend.closeStream(streamId).catch(() => {})
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** Read the whole body into memory. The fallback when nothing can stream. */
|
|
779
990
|
async #drain(
|
|
780
991
|
body: NodeJS.ReadableStream,
|
|
781
992
|
errorCallback?: (error: NodeJS.ErrnoException) => [string, number?],
|
|
@@ -991,6 +1202,64 @@ export class Response extends Macroable {
|
|
|
991
1202
|
return this.#finished
|
|
992
1203
|
}
|
|
993
1204
|
|
|
1205
|
+
// ─── State (AdonisJS getters) ──────────────────────────────
|
|
1206
|
+
//
|
|
1207
|
+
// Same questions AdonisJS answers on its response, under the same names. Ours
|
|
1208
|
+
// only had `isFinished()`, so a migrated `if (response.finished)` read
|
|
1209
|
+
// `undefined` and took the wrong branch without a word.
|
|
1210
|
+
|
|
1211
|
+
/** Whether the response has been written out. */
|
|
1212
|
+
get finished(): boolean {
|
|
1213
|
+
return this.#finished
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/** Whether a body has been set. */
|
|
1217
|
+
get hasContent(): boolean {
|
|
1218
|
+
return this.#body.length > 0
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
/** Whether the body is a stream still being drained. */
|
|
1222
|
+
get hasStream(): boolean {
|
|
1223
|
+
return this.#pendingStream !== undefined
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/** Whether a body of any kind is waiting to go out. */
|
|
1227
|
+
get hasLazyBody(): boolean {
|
|
1228
|
+
return this.hasContent || this.hasStream
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* Whether the headers have gone out.
|
|
1233
|
+
*
|
|
1234
|
+
* Ream serialises the whole response across the NAPI boundary in one step,
|
|
1235
|
+
* so headers and body leave together: this is true exactly when the response
|
|
1236
|
+
* is finished, rather than tracking a separate Node `headersSent`.
|
|
1237
|
+
*/
|
|
1238
|
+
get headersSent(): boolean {
|
|
1239
|
+
return this.#finished
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
/** Whether nothing has been sent yet — the window a middleware can still write in. */
|
|
1243
|
+
get isPending(): boolean {
|
|
1244
|
+
return !this.headersSent && !this.finished
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/**
|
|
1248
|
+
* Echo the caller's `x-request-id` back on the response (AdonisJS
|
|
1249
|
+
* `setRequestId`).
|
|
1250
|
+
*
|
|
1251
|
+
* Ream already READS that header into `ctx.id` — validating its shape and
|
|
1252
|
+
* generating one when it is missing — but never sent it back, so a caller
|
|
1253
|
+
* could not tie a response to the request id it issued. Nothing is echoed
|
|
1254
|
+
* when the client sent none: inventing one here would hand back an id the
|
|
1255
|
+
* caller never used.
|
|
1256
|
+
*/
|
|
1257
|
+
setRequestId(): this {
|
|
1258
|
+
const incoming = this.#request?.header('x-request-id')
|
|
1259
|
+
if (incoming) this.header('x-request-id', incoming)
|
|
1260
|
+
return this
|
|
1261
|
+
}
|
|
1262
|
+
|
|
994
1263
|
/** @internal Set body directly (used by redirect, exception handler). */
|
|
995
1264
|
setBody(body: string): void {
|
|
996
1265
|
this.#body = body
|
package/src/http/SseStream.ts
CHANGED
|
@@ -37,6 +37,16 @@
|
|
|
37
37
|
export interface StreamBackend {
|
|
38
38
|
registerStream(streamId: string): Promise<boolean>
|
|
39
39
|
writeStream(streamId: string, chunk: string): Promise<boolean>
|
|
40
|
+
/**
|
|
41
|
+
* Push a BINARY chunk, waiting for room rather than dropping it.
|
|
42
|
+
*
|
|
43
|
+
* `writeStream` is shaped for events: text, and a full buffer discards the
|
|
44
|
+
* frame — right when a missed event is a missed event, wrong when the client
|
|
45
|
+
* is reassembling a file and a missing frame is a corrupt download. Optional
|
|
46
|
+
* so a host that only serves SSE need not implement it; `response.stream()`
|
|
47
|
+
* falls back to buffering when it is absent.
|
|
48
|
+
*/
|
|
49
|
+
writeStreamBytes?(streamId: string, chunk: Uint8Array): Promise<boolean>
|
|
40
50
|
closeStream(streamId: string): Promise<boolean>
|
|
41
51
|
onStreamDisconnect(streamId: string, callback: () => void): void
|
|
42
52
|
}
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { Container } from './container/Container.js'
|
|
|
14
14
|
export { ContainerResolver } from './container/ContainerResolver.js'
|
|
15
15
|
export { ContextualBindingsBuilder } from './container/ContextualBindingsBuilder.js'
|
|
16
16
|
export type { Binding, ServiceFactory, ServiceScope, ServiceToken } from './container/types.js'
|
|
17
|
+
export { type DirectoriesNode, directories } from './directories.js'
|
|
17
18
|
export { Env, EnvValidationException } from './env/Env.js'
|
|
18
19
|
export { loadEnvFiles } from './env/loadEnvFiles.js'
|
|
19
20
|
export { Secret } from './env/Secret.js'
|
|
@@ -25,6 +26,13 @@ export {
|
|
|
25
26
|
type SchemaNode,
|
|
26
27
|
type StringOptions,
|
|
27
28
|
} from './env/schema.js'
|
|
29
|
+
export { MigrationRegistry } from './migrations/MigrationRegistry.js'
|
|
30
|
+
export type {
|
|
31
|
+
MigrationRunnerContract,
|
|
32
|
+
MigrationState,
|
|
33
|
+
MigrationStatusNode,
|
|
34
|
+
RegisteredMigrationSource,
|
|
35
|
+
} from './migrations/types.js'
|
|
28
36
|
|
|
29
37
|
// ─── HTTP (new — AdonisJS-compatible) ───────────────────────
|
|
30
38
|
|
|
@@ -164,8 +172,16 @@ export type {
|
|
|
164
172
|
RouteDefinition,
|
|
165
173
|
RouteHandler,
|
|
166
174
|
RouteHandlerFunction,
|
|
175
|
+
SignedUrlOptions,
|
|
176
|
+
} from './router/Router.js'
|
|
177
|
+
export {
|
|
178
|
+
GroupBuilder,
|
|
179
|
+
matchers,
|
|
180
|
+
OnRouteBuilder,
|
|
181
|
+
RouteBuilder,
|
|
182
|
+
Router,
|
|
183
|
+
UrlBuilder,
|
|
167
184
|
} from './router/Router.js'
|
|
168
|
-
export { GroupBuilder, matchers, OnRouteBuilder, RouteBuilder, Router } from './router/Router.js'
|
|
169
185
|
|
|
170
186
|
// ─── Middleware ──────────────────────────────────────────────
|
|
171
187
|
|
|
@@ -290,12 +306,22 @@ export { setServer } from './services/server.js'
|
|
|
290
306
|
// `@c9up/ream/events` subpath so importing the main barrel stays lazy.
|
|
291
307
|
export type { EventsConfig } from './events/config.js'
|
|
292
308
|
export { defineConfig as defineEventsConfig } from './events/config.js'
|
|
309
|
+
export type {
|
|
310
|
+
AsyncUnsubscribeFunction,
|
|
311
|
+
UnsubscribeFunction,
|
|
312
|
+
} from './events/Emitter.js'
|
|
293
313
|
export {
|
|
294
314
|
BaseEvent,
|
|
295
315
|
Emitter,
|
|
296
316
|
type EmitterResolver,
|
|
297
317
|
type ListenerClass,
|
|
298
318
|
} from './events/Emitter.js'
|
|
319
|
+
export {
|
|
320
|
+
type BufferedEvent,
|
|
321
|
+
type BufferedEventName,
|
|
322
|
+
type EventFinder,
|
|
323
|
+
EventsBuffer,
|
|
324
|
+
} from './events/EventsBuffer.js'
|
|
299
325
|
export type {
|
|
300
326
|
ErrorReporter,
|
|
301
327
|
LockBackend,
|