@daloyjs/core 0.16.0 → 0.29.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/README.md +3 -3
- package/dist/adapters/bun.d.ts.map +1 -1
- package/dist/adapters/bun.js +38 -7
- package/dist/adapters/bun.js.map +1 -1
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +29 -7
- package/dist/adapters/node.js.map +1 -1
- package/dist/app.d.ts +412 -3
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +895 -29
- package/dist/app.js.map +1 -1
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +218 -1
- package/dist/cli.js.map +1 -1
- package/dist/combine.d.ts +97 -0
- package/dist/combine.d.ts.map +1 -0
- package/dist/combine.js +247 -0
- package/dist/combine.js.map +1 -0
- package/dist/compression.d.ts +127 -0
- package/dist/compression.d.ts.map +1 -0
- package/dist/compression.js +368 -0
- package/dist/compression.js.map +1 -0
- package/dist/config.d.ts +97 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +132 -0
- package/dist/config.js.map +1 -0
- package/dist/conn-info.d.ts +121 -0
- package/dist/conn-info.d.ts.map +1 -0
- package/dist/conn-info.js +145 -0
- package/dist/conn-info.js.map +1 -0
- package/dist/cookie.d.ts +112 -0
- package/dist/cookie.d.ts.map +1 -0
- package/dist/cookie.js +185 -0
- package/dist/cookie.js.map +1 -0
- package/dist/dependency.d.ts +47 -0
- package/dist/dependency.d.ts.map +1 -0
- package/dist/dependency.js +68 -0
- package/dist/dependency.js.map +1 -0
- package/dist/etag.d.ts +48 -0
- package/dist/etag.d.ts.map +1 -0
- package/dist/etag.js +117 -0
- package/dist/etag.js.map +1 -0
- package/dist/index.d.ts +41 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -5
- package/dist/index.js.map +1 -1
- package/dist/ip-restriction.d.ts +75 -0
- package/dist/ip-restriction.d.ts.map +1 -0
- package/dist/ip-restriction.js +203 -0
- package/dist/ip-restriction.js.map +1 -0
- package/dist/jwk.d.ts +82 -0
- package/dist/jwk.d.ts.map +1 -0
- package/dist/jwk.js +269 -0
- package/dist/jwk.js.map +1 -0
- package/dist/jwt.d.ts +103 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +437 -0
- package/dist/jwt.js.map +1 -0
- package/dist/load-shedding.d.ts +73 -0
- package/dist/load-shedding.d.ts.map +1 -0
- package/dist/load-shedding.js +171 -0
- package/dist/load-shedding.js.map +1 -0
- package/dist/middleware.d.ts +205 -2
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +399 -78
- package/dist/middleware.js.map +1 -1
- package/dist/multipart.d.ts +17 -0
- package/dist/multipart.d.ts.map +1 -1
- package/dist/multipart.js +117 -1
- package/dist/multipart.js.map +1 -1
- package/dist/openapi.d.ts +8 -0
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +18 -1
- package/dist/openapi.js.map +1 -1
- package/dist/security-schemes.d.ts +21 -5
- package/dist/security-schemes.d.ts.map +1 -1
- package/dist/security-schemes.js +32 -5
- package/dist/security-schemes.js.map +1 -1
- package/dist/security.d.ts +32 -0
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +79 -0
- package/dist/security.js.map +1 -1
- package/dist/session.d.ts +43 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +130 -87
- package/dist/session.js.map +1 -1
- package/dist/subdomains.d.ts +97 -0
- package/dist/subdomains.d.ts.map +1 -0
- package/dist/subdomains.js +157 -0
- package/dist/subdomains.js.map +1 -0
- package/dist/time-claims.d.ts +72 -0
- package/dist/time-claims.d.ts.map +1 -0
- package/dist/time-claims.js +88 -0
- package/dist/time-claims.js.map +1 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/websocket.d.ts +51 -34
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +162 -2
- package/dist/websocket.js.map +1 -1
- package/package.json +31 -3
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wave 7 (first focused slice): first-party `compression()` middleware with
|
|
3
|
+
* BREACH-aware safe defaults.
|
|
4
|
+
*
|
|
5
|
+
* Built on the web-standard `CompressionStream` API so the same line works
|
|
6
|
+
* on Node, Bun, Deno, Cloudflare Workers, Vercel Edge, and Fastly Compute.
|
|
7
|
+
* The middleware deliberately refuses to expose a configurable compression
|
|
8
|
+
* level — `CompressionStream` uses the runtime's documented default (gzip
|
|
9
|
+
* level 6 on Node / V8); a `level: 9` opt-in is rejected at construction
|
|
10
|
+
* because the bytes-saved-per-CPU-cycle tradeoff turns into a small-vCPU
|
|
11
|
+
* DoS amplifier under sustained load with single-digit-percent bytes-saved
|
|
12
|
+
* gains on typical JSON payloads.
|
|
13
|
+
*
|
|
14
|
+
* BREACH guards (CRIME-style cross-domain compression-oracle defenses) are
|
|
15
|
+
* always-on and not opt-out: the middleware refuses to compress responses
|
|
16
|
+
* that carry `Set-Cookie`, responses on requests that carry an
|
|
17
|
+
* `Authorization` header, responses on requests that carry a
|
|
18
|
+
* recognized session / CSRF cookie, and responses whose content type is
|
|
19
|
+
* already entropy-coded (images, video, audio, archives, fonts, wasm, pdf).
|
|
20
|
+
*
|
|
21
|
+
* @since 0.25.0
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Internal marker stamped on the hook so a future audit gate can confirm
|
|
25
|
+
* the BREACH-aware middleware is installed (Wave 9 audit-item parity).
|
|
26
|
+
*
|
|
27
|
+
* @since 0.25.0
|
|
28
|
+
*/
|
|
29
|
+
export const COMPRESSION_HOOK_MARKER = Symbol.for("daloy.compression.hook");
|
|
30
|
+
// Lowercased MIME-prefix deny-list. Each entry is matched as a leading
|
|
31
|
+
// prefix of the response's resolved content-type token (no parameters).
|
|
32
|
+
const ALWAYS_DENY_CONTENT_TYPES = Object.freeze([
|
|
33
|
+
"image/",
|
|
34
|
+
"video/",
|
|
35
|
+
"audio/",
|
|
36
|
+
"font/",
|
|
37
|
+
"application/zip",
|
|
38
|
+
"application/gzip",
|
|
39
|
+
"application/x-7z",
|
|
40
|
+
"application/x-gzip",
|
|
41
|
+
"application/x-bzip",
|
|
42
|
+
"application/x-xz",
|
|
43
|
+
"application/zstd",
|
|
44
|
+
"application/x-zstd",
|
|
45
|
+
"application/wasm",
|
|
46
|
+
"application/pdf",
|
|
47
|
+
"application/epub+zip",
|
|
48
|
+
]);
|
|
49
|
+
// Allow lossless override: `image/svg+xml` IS compressible (XML text).
|
|
50
|
+
const ALLOW_SUBTYPES = Object.freeze(["image/svg+xml"]);
|
|
51
|
+
// Cookie-name substring tokens that flag a request as security-state-bound.
|
|
52
|
+
const ALWAYS_AUTH_COOKIE_TOKENS = Object.freeze([
|
|
53
|
+
"session",
|
|
54
|
+
"csrf",
|
|
55
|
+
"xsrf",
|
|
56
|
+
"__host-",
|
|
57
|
+
"__secure-",
|
|
58
|
+
]);
|
|
59
|
+
function parseAcceptEncoding(header) {
|
|
60
|
+
if (!header)
|
|
61
|
+
return [];
|
|
62
|
+
const out = [];
|
|
63
|
+
for (const raw of header.split(",")) {
|
|
64
|
+
const part = raw.trim();
|
|
65
|
+
if (!part)
|
|
66
|
+
continue;
|
|
67
|
+
const [tokenRaw, ...paramsRaw] = part.split(";");
|
|
68
|
+
const token = (tokenRaw ?? "").trim().toLowerCase();
|
|
69
|
+
if (!token)
|
|
70
|
+
continue;
|
|
71
|
+
let q = 1;
|
|
72
|
+
for (const p of paramsRaw) {
|
|
73
|
+
const m = p.trim().match(/^q=([0-9.]+)$/i);
|
|
74
|
+
if (m) {
|
|
75
|
+
const parsed = Number.parseFloat(m[1]);
|
|
76
|
+
if (Number.isFinite(parsed) && parsed >= 0 && parsed <= 1)
|
|
77
|
+
q = parsed;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (token === "br" || token === "gzip" || token === "deflate") {
|
|
81
|
+
out.push({ encoding: token, q });
|
|
82
|
+
}
|
|
83
|
+
else if (token === "identity") {
|
|
84
|
+
out.push({ encoding: "identity", q });
|
|
85
|
+
}
|
|
86
|
+
else if (token === "*") {
|
|
87
|
+
out.push({ encoding: "*", q });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
function pickEncoding(accept, serverPreferred, runtimeSupported) {
|
|
93
|
+
if (accept.length === 0)
|
|
94
|
+
return null;
|
|
95
|
+
// Reject if `identity;q=0` AND no positive coded entries OR `*;q=0`.
|
|
96
|
+
// Per RFC 9110 §12.5.3, if the server cannot satisfy, it should send 406
|
|
97
|
+
// — we instead choose to skip compression (return null) which serves
|
|
98
|
+
// `identity` and lets upstream content-negotiation logic make any
|
|
99
|
+
// 406-vs-200 call.
|
|
100
|
+
// Build a map of encoding → highest q value the client expressed.
|
|
101
|
+
const explicit = new Map();
|
|
102
|
+
let starQ;
|
|
103
|
+
for (const a of accept) {
|
|
104
|
+
if (a.encoding === "*") {
|
|
105
|
+
starQ = a.q;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const prev = explicit.get(a.encoding);
|
|
109
|
+
if (prev === undefined || a.q > prev)
|
|
110
|
+
explicit.set(a.encoding, a.q);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const enc of serverPreferred) {
|
|
114
|
+
if (!runtimeSupported.has(enc))
|
|
115
|
+
continue;
|
|
116
|
+
const q = explicit.get(enc) ?? starQ;
|
|
117
|
+
if (q !== undefined && q > 0)
|
|
118
|
+
return enc;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
let cachedRuntimeSupport = null;
|
|
123
|
+
function detectRuntimeSupport() {
|
|
124
|
+
if (cachedRuntimeSupport)
|
|
125
|
+
return cachedRuntimeSupport;
|
|
126
|
+
const Stream = globalThis.CompressionStream;
|
|
127
|
+
const supported = new Set();
|
|
128
|
+
if (!Stream) {
|
|
129
|
+
cachedRuntimeSupport = supported;
|
|
130
|
+
return supported;
|
|
131
|
+
}
|
|
132
|
+
for (const enc of ["br", "gzip", "deflate"]) {
|
|
133
|
+
try {
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
135
|
+
const _ = new Stream(enc);
|
|
136
|
+
supported.add(enc);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// not supported on this runtime
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
cachedRuntimeSupport = supported;
|
|
143
|
+
return supported;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* @internal Reset cached runtime probe. Test-only.
|
|
147
|
+
* @since 0.25.0
|
|
148
|
+
*/
|
|
149
|
+
export function _resetCompressionRuntimeProbeForTests() {
|
|
150
|
+
cachedRuntimeSupport = null;
|
|
151
|
+
}
|
|
152
|
+
function isExcludedContentType(contentType, extraDeny) {
|
|
153
|
+
if (!contentType)
|
|
154
|
+
return false;
|
|
155
|
+
const token = contentType.split(";")[0].trim().toLowerCase();
|
|
156
|
+
if (!token)
|
|
157
|
+
return false;
|
|
158
|
+
for (const allow of ALLOW_SUBTYPES) {
|
|
159
|
+
if (token === allow)
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
for (const deny of ALWAYS_DENY_CONTENT_TYPES) {
|
|
163
|
+
if (token.startsWith(deny))
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
for (const deny of extraDeny) {
|
|
167
|
+
if (token.startsWith(deny))
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
function requestCarriesAuthCookie(cookieHeader, extra) {
|
|
173
|
+
if (!cookieHeader)
|
|
174
|
+
return false;
|
|
175
|
+
const lower = cookieHeader.toLowerCase();
|
|
176
|
+
for (const tok of ALWAYS_AUTH_COOKIE_TOKENS) {
|
|
177
|
+
if (lower.includes(tok))
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
for (const tok of extra) {
|
|
181
|
+
if (lower.includes(tok.toLowerCase()))
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
function appendVaryAcceptEncoding(headers) {
|
|
187
|
+
const existing = headers.get("vary");
|
|
188
|
+
if (!existing) {
|
|
189
|
+
headers.set("vary", "Accept-Encoding");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const parts = existing
|
|
193
|
+
.split(",")
|
|
194
|
+
.map((s) => s.trim())
|
|
195
|
+
.filter((s) => s.length > 0);
|
|
196
|
+
for (const p of parts) {
|
|
197
|
+
if (p === "*")
|
|
198
|
+
return;
|
|
199
|
+
if (p.toLowerCase() === "accept-encoding")
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
headers.set("vary", `${existing}, Accept-Encoding`);
|
|
203
|
+
}
|
|
204
|
+
function normalizeOptionTokens(values, optionName) {
|
|
205
|
+
if (!values)
|
|
206
|
+
return Object.freeze([]);
|
|
207
|
+
const normalized = [];
|
|
208
|
+
for (const value of values) {
|
|
209
|
+
if (typeof value !== "string") {
|
|
210
|
+
throw new TypeError(`compression(): \`${optionName}\` entries must be non-empty strings.`);
|
|
211
|
+
}
|
|
212
|
+
const token = value.trim().toLowerCase();
|
|
213
|
+
if (!token) {
|
|
214
|
+
throw new TypeError(`compression(): \`${optionName}\` entries must be non-empty strings.`);
|
|
215
|
+
}
|
|
216
|
+
normalized.push(token);
|
|
217
|
+
}
|
|
218
|
+
return Object.freeze(normalized);
|
|
219
|
+
}
|
|
220
|
+
async function compressBytes(bytes, encoding) {
|
|
221
|
+
const Stream = globalThis.CompressionStream;
|
|
222
|
+
const cs = new Stream(encoding);
|
|
223
|
+
const writer = cs.writable.getWriter();
|
|
224
|
+
// Intentionally do not `await writer.write(...)` separately from
|
|
225
|
+
// close — the stream queues the chunk synchronously and we want both
|
|
226
|
+
// operations to settle together for predictable backpressure on the
|
|
227
|
+
// tiny in-memory body we hand it.
|
|
228
|
+
void writer.write(bytes);
|
|
229
|
+
const closePromise = writer.close();
|
|
230
|
+
const reader = cs.readable.getReader();
|
|
231
|
+
const chunks = [];
|
|
232
|
+
let total = 0;
|
|
233
|
+
// eslint-disable-next-line no-constant-condition
|
|
234
|
+
while (true) {
|
|
235
|
+
const { done, value } = await reader.read();
|
|
236
|
+
if (done)
|
|
237
|
+
break;
|
|
238
|
+
if (value) {
|
|
239
|
+
chunks.push(value);
|
|
240
|
+
total += value.byteLength;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
await closePromise;
|
|
244
|
+
const out = new Uint8Array(total);
|
|
245
|
+
let offset = 0;
|
|
246
|
+
for (const c of chunks) {
|
|
247
|
+
out.set(c, offset);
|
|
248
|
+
offset += c.byteLength;
|
|
249
|
+
}
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* BREACH-aware response compression middleware.
|
|
254
|
+
*
|
|
255
|
+
* Picks `br` > `gzip` > `deflate` based on the client's `Accept-Encoding`
|
|
256
|
+
* header AND the runtime's `CompressionStream` support (probed once,
|
|
257
|
+
* cached). Skips compression when ANY of the following are true:
|
|
258
|
+
*
|
|
259
|
+
* - request method is not `GET` / `HEAD` (compressing responses on
|
|
260
|
+
* state-changing requests offers no caching win and trips BREACH
|
|
261
|
+
* oracles more easily);
|
|
262
|
+
* - request carried an `Authorization` header (auth-bound bodies);
|
|
263
|
+
* - request carried a recognized session / CSRF / `__Host-` /
|
|
264
|
+
* `__Secure-` cookie (auth-bound bodies);
|
|
265
|
+
* - response status is not `2xx`;
|
|
266
|
+
* - response already declares a `Content-Encoding`;
|
|
267
|
+
* - response declares a `Set-Cookie` (response is mutating auth state);
|
|
268
|
+
* - response body byte length is below `minimumSize` (default `1024`);
|
|
269
|
+
* - response `Content-Type` is in the always-on already-compressed
|
|
270
|
+
* deny-list (image/video/audio/archives/fonts/wasm/pdf, with
|
|
271
|
+
* `image/svg+xml` carved back in as compressible XML).
|
|
272
|
+
*
|
|
273
|
+
* When skipping, the middleware still appends `Vary: Accept-Encoding`
|
|
274
|
+
* to the response so cache keys remain content-negotiation-correct.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* import { App, compression } from "@daloyjs/core";
|
|
279
|
+
*
|
|
280
|
+
* const app = new App();
|
|
281
|
+
* app.use(compression());
|
|
282
|
+
* ```
|
|
283
|
+
*
|
|
284
|
+
* @since 0.25.0
|
|
285
|
+
*/
|
|
286
|
+
export function compression(opts = {}) {
|
|
287
|
+
if (Object.prototype.hasOwnProperty.call(opts, "compressLevel")) {
|
|
288
|
+
throw new TypeError("compression(): `compressLevel` is not configurable. CompressionStream uses the runtime default (typically 6); higher levels are a CPU-DoS amplifier with single-digit-percent bytes-saved gains.");
|
|
289
|
+
}
|
|
290
|
+
const minimumSize = opts.minimumSize === undefined ? 1024 : opts.minimumSize;
|
|
291
|
+
if (!Number.isFinite(minimumSize) ||
|
|
292
|
+
!Number.isInteger(minimumSize) ||
|
|
293
|
+
minimumSize < 0 ||
|
|
294
|
+
minimumSize > 2 ** 31 - 1) {
|
|
295
|
+
throw new TypeError("compression(): `minimumSize` must be a finite non-negative integer.");
|
|
296
|
+
}
|
|
297
|
+
const serverPreferred = opts.encodings && opts.encodings.length > 0
|
|
298
|
+
? Object.freeze([...opts.encodings])
|
|
299
|
+
: Object.freeze(["br", "gzip", "deflate"]);
|
|
300
|
+
// Validate the encodings list.
|
|
301
|
+
for (const enc of serverPreferred) {
|
|
302
|
+
if (enc !== "br" && enc !== "gzip" && enc !== "deflate") {
|
|
303
|
+
throw new TypeError(`compression(): unknown encoding ${JSON.stringify(enc)} — supported: br, gzip, deflate.`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const extraDeny = normalizeOptionTokens(opts.excludeContentTypes, "excludeContentTypes");
|
|
307
|
+
const extraAuthCookies = normalizeOptionTokens(opts.authCookieNames, "authCookieNames");
|
|
308
|
+
const hooks = {
|
|
309
|
+
async onSend(res, ctx) {
|
|
310
|
+
const req = ctx?.request;
|
|
311
|
+
if (!req)
|
|
312
|
+
return undefined;
|
|
313
|
+
appendVaryAcceptEncoding(res.headers);
|
|
314
|
+
const method = req.method;
|
|
315
|
+
if (method !== "GET" && method !== "HEAD")
|
|
316
|
+
return undefined;
|
|
317
|
+
if (res.status < 200 || res.status >= 300)
|
|
318
|
+
return undefined;
|
|
319
|
+
if (res.headers.has("content-encoding"))
|
|
320
|
+
return undefined;
|
|
321
|
+
if (res.headers.has("set-cookie"))
|
|
322
|
+
return undefined;
|
|
323
|
+
if (req.headers.has("authorization"))
|
|
324
|
+
return undefined;
|
|
325
|
+
if (requestCarriesAuthCookie(req.headers.get("cookie"), extraAuthCookies)) {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
if (isExcludedContentType(res.headers.get("content-type"), extraDeny)) {
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
const runtimeSupported = detectRuntimeSupport();
|
|
332
|
+
if (runtimeSupported.size === 0)
|
|
333
|
+
return undefined;
|
|
334
|
+
const accept = parseAcceptEncoding(req.headers.get("accept-encoding"));
|
|
335
|
+
const chosen = pickEncoding(accept, serverPreferred, runtimeSupported);
|
|
336
|
+
if (!chosen)
|
|
337
|
+
return undefined;
|
|
338
|
+
const original = new Uint8Array(await res.clone().arrayBuffer());
|
|
339
|
+
if (original.byteLength < minimumSize)
|
|
340
|
+
return undefined;
|
|
341
|
+
const compressed = await compressBytes(original, chosen);
|
|
342
|
+
if (compressed.byteLength >= original.byteLength) {
|
|
343
|
+
// Negative-compression-ratio guard: if the compressor produced a
|
|
344
|
+
// larger payload (very small / high-entropy bodies that slipped past
|
|
345
|
+
// the minimumSize check) skip rather than ship more bytes.
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
const headers = new Headers(res.headers);
|
|
349
|
+
headers.set("content-encoding", chosen);
|
|
350
|
+
headers.set("content-length", String(compressed.byteLength));
|
|
351
|
+
// Strong ETag computed upstream over the uncompressed body is now
|
|
352
|
+
// inconsistent with the wire bytes — downgrade to weak per RFC 9110.
|
|
353
|
+
const existingEtag = headers.get("etag");
|
|
354
|
+
if (existingEtag && !existingEtag.startsWith("W/")) {
|
|
355
|
+
headers.set("etag", `W/${existingEtag}`);
|
|
356
|
+
}
|
|
357
|
+
const responseBody = method === "HEAD" ? null : compressed;
|
|
358
|
+
return new Response(responseBody, {
|
|
359
|
+
status: res.status,
|
|
360
|
+
statusText: res.statusText,
|
|
361
|
+
headers,
|
|
362
|
+
});
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
hooks[COMPRESSION_HOOK_MARKER] = true;
|
|
366
|
+
return hooks;
|
|
367
|
+
}
|
|
368
|
+
//# sourceMappingURL=compression.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compression.js","sourceRoot":"","sources":["../src/compression.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wave 4 leftover: `defineConfig({ schema, source })`.
|
|
3
|
+
*
|
|
4
|
+
* Single boot-time helper that loads application configuration from a
|
|
5
|
+
* caller-chosen source (`process.env`, a file on disk, or an async secrets
|
|
6
|
+
* resolver), validates the merged object against a Standard-Schema
|
|
7
|
+
* validator, and aggregates **every** validation issue into a single
|
|
8
|
+
* structured error printed to stderr before the process exits.
|
|
9
|
+
*
|
|
10
|
+
* The point is to fail fast and loud: a misconfigured deployment should
|
|
11
|
+
* surface every missing/invalid key in one shot so operators do not have
|
|
12
|
+
* to redeploy four times to discover four different typos.
|
|
13
|
+
*
|
|
14
|
+
* @since 0.20.0
|
|
15
|
+
*/
|
|
16
|
+
import { type StandardSchemaV1 } from "./schema.js";
|
|
17
|
+
/**
|
|
18
|
+
* Aggregated validation failure thrown by {@link defineConfig}. Holds the
|
|
19
|
+
* full list of `{ key, message }` issues so callers can surface them in a
|
|
20
|
+
* dashboard, a startup probe, or a custom error renderer.
|
|
21
|
+
*/
|
|
22
|
+
export declare class ConfigValidationError extends Error {
|
|
23
|
+
readonly issues: ReadonlyArray<{
|
|
24
|
+
key: string;
|
|
25
|
+
message: string;
|
|
26
|
+
}>;
|
|
27
|
+
constructor(issues: ReadonlyArray<{
|
|
28
|
+
key: string;
|
|
29
|
+
message: string;
|
|
30
|
+
}>);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Source the raw object that gets handed to the schema validator. The
|
|
34
|
+
* built-in sources are intentionally narrow — anything more elaborate
|
|
35
|
+
* (Vault, Doppler, AWS Secrets Manager, ...) should arrive via the
|
|
36
|
+
* `"custom"` source with an async resolver.
|
|
37
|
+
*/
|
|
38
|
+
export type ConfigSource = "env" | {
|
|
39
|
+
kind: "env";
|
|
40
|
+
env: Record<string, string | undefined>;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "file";
|
|
43
|
+
path: string;
|
|
44
|
+
parse?: (text: string) => unknown;
|
|
45
|
+
} | {
|
|
46
|
+
kind: "object";
|
|
47
|
+
data: Record<string, unknown>;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "custom";
|
|
50
|
+
resolve: () => Promise<Record<string, unknown>>;
|
|
51
|
+
};
|
|
52
|
+
/** Options accepted by {@link defineConfig}. */
|
|
53
|
+
export interface DefineConfigOptions<S extends StandardSchemaV1> {
|
|
54
|
+
/** Standard-Schema validator (Zod, Valibot, ArkType, TypeBox, ...). */
|
|
55
|
+
schema: S;
|
|
56
|
+
/** Source of the raw input. Default `"env"` (reads from `process.env`). */
|
|
57
|
+
source?: ConfigSource;
|
|
58
|
+
/**
|
|
59
|
+
* Optional pre-validation transform. Useful for coercing string env
|
|
60
|
+
* values (`"true"` / `"42"`) to typed primitives before they hit the
|
|
61
|
+
* schema, or for renaming `FOO_BAR` → `fooBar`. Receives the raw
|
|
62
|
+
* source object and must return the object handed to the validator.
|
|
63
|
+
*/
|
|
64
|
+
transform?: (raw: Record<string, unknown>) => Record<string, unknown>;
|
|
65
|
+
/**
|
|
66
|
+
* Stream errors are written to. Defaults to `process.stderr`. Set to
|
|
67
|
+
* `false` to suppress the printed summary; the thrown
|
|
68
|
+
* {@link ConfigValidationError} still carries `issues`.
|
|
69
|
+
*/
|
|
70
|
+
stderr?: {
|
|
71
|
+
write: (chunk: string) => void;
|
|
72
|
+
} | false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Load and validate configuration at boot. Aggregates every Standard-Schema
|
|
76
|
+
* issue into a single {@link ConfigValidationError}, prints a human-readable
|
|
77
|
+
* summary to stderr, and only resolves with the typed config object when
|
|
78
|
+
* validation succeeded.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* import { z } from "zod";
|
|
83
|
+
* import { defineConfig } from "@daloyjs/core";
|
|
84
|
+
*
|
|
85
|
+
* const Config = z.object({
|
|
86
|
+
* PORT: z.coerce.number().int().min(1).max(65535),
|
|
87
|
+
* DATABASE_URL: z.string().url(),
|
|
88
|
+
* NODE_ENV: z.enum(["development", "production", "test"]),
|
|
89
|
+
* });
|
|
90
|
+
*
|
|
91
|
+
* export const config = await defineConfig({ schema: Config });
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @since 0.20.0
|
|
95
|
+
*/
|
|
96
|
+
export declare function defineConfig<S extends StandardSchemaV1>(opts: DefineConfigOptions<S>): Promise<StandardSchemaV1.InferOutput<S>>;
|
|
97
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAY,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE9D;;;;GAIG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;gBACrD,MAAM,EAAE,aAAa,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAUpE;AAED;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACpB,KAAK,GACL;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAAE,CAAC;AAExE,gDAAgD;AAChD,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,gBAAgB;IAC7D,uEAAuE;IACvE,MAAM,EAAE,CAAC,CAAC;IACV,2EAA2E;IAC3E,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,GAAG,KAAK,CAAC;CACrD;AA8CD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,YAAY,CAAC,CAAC,SAAS,gBAAgB,EAC3D,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC3B,OAAO,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAkC1C"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wave 4 leftover: `defineConfig({ schema, source })`.
|
|
3
|
+
*
|
|
4
|
+
* Single boot-time helper that loads application configuration from a
|
|
5
|
+
* caller-chosen source (`process.env`, a file on disk, or an async secrets
|
|
6
|
+
* resolver), validates the merged object against a Standard-Schema
|
|
7
|
+
* validator, and aggregates **every** validation issue into a single
|
|
8
|
+
* structured error printed to stderr before the process exits.
|
|
9
|
+
*
|
|
10
|
+
* The point is to fail fast and loud: a misconfigured deployment should
|
|
11
|
+
* surface every missing/invalid key in one shot so operators do not have
|
|
12
|
+
* to redeploy four times to discover four different typos.
|
|
13
|
+
*
|
|
14
|
+
* @since 0.20.0
|
|
15
|
+
*/
|
|
16
|
+
import { validate } from "./schema.js";
|
|
17
|
+
/**
|
|
18
|
+
* Aggregated validation failure thrown by {@link defineConfig}. Holds the
|
|
19
|
+
* full list of `{ key, message }` issues so callers can surface them in a
|
|
20
|
+
* dashboard, a startup probe, or a custom error renderer.
|
|
21
|
+
*/
|
|
22
|
+
export class ConfigValidationError extends Error {
|
|
23
|
+
issues;
|
|
24
|
+
constructor(issues) {
|
|
25
|
+
const summary = issues
|
|
26
|
+
.map((i) => ` - ${i.key || "<root>"}: ${i.message}`)
|
|
27
|
+
.join("\n");
|
|
28
|
+
super(`defineConfig(): configuration is invalid (${issues.length} issue${issues.length === 1 ? "" : "s"})\n${summary}`);
|
|
29
|
+
this.name = "ConfigValidationError";
|
|
30
|
+
this.issues = issues;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function readSource(source) {
|
|
34
|
+
if (source === "env") {
|
|
35
|
+
return readEnvObject((typeof process !== "undefined" ? process.env : {}));
|
|
36
|
+
}
|
|
37
|
+
if (source.kind === "env")
|
|
38
|
+
return readEnvObject(source.env);
|
|
39
|
+
if (source.kind === "object")
|
|
40
|
+
return { ...source.data };
|
|
41
|
+
if (source.kind === "file") {
|
|
42
|
+
const fs = await import("node:fs/promises");
|
|
43
|
+
const text = await fs.readFile(source.path, "utf8");
|
|
44
|
+
const parsed = (source.parse ?? JSON.parse)(text);
|
|
45
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
46
|
+
throw new ConfigValidationError([
|
|
47
|
+
{ key: source.path, message: "file did not parse to an object" },
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
return { ...parsed };
|
|
51
|
+
}
|
|
52
|
+
return { ...(await source.resolve()) };
|
|
53
|
+
}
|
|
54
|
+
function readEnvObject(env) {
|
|
55
|
+
const out = {};
|
|
56
|
+
for (const [key, value] of Object.entries(env)) {
|
|
57
|
+
if (value !== undefined)
|
|
58
|
+
out[key] = value;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function pathToKey(path) {
|
|
63
|
+
return path
|
|
64
|
+
.map((segment) => {
|
|
65
|
+
if (typeof segment === "object" && segment !== null && "key" in segment) {
|
|
66
|
+
return String(segment.key);
|
|
67
|
+
}
|
|
68
|
+
return String(segment);
|
|
69
|
+
})
|
|
70
|
+
.join(".");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Load and validate configuration at boot. Aggregates every Standard-Schema
|
|
74
|
+
* issue into a single {@link ConfigValidationError}, prints a human-readable
|
|
75
|
+
* summary to stderr, and only resolves with the typed config object when
|
|
76
|
+
* validation succeeded.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* import { z } from "zod";
|
|
81
|
+
* import { defineConfig } from "@daloyjs/core";
|
|
82
|
+
*
|
|
83
|
+
* const Config = z.object({
|
|
84
|
+
* PORT: z.coerce.number().int().min(1).max(65535),
|
|
85
|
+
* DATABASE_URL: z.string().url(),
|
|
86
|
+
* NODE_ENV: z.enum(["development", "production", "test"]),
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* export const config = await defineConfig({ schema: Config });
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* @since 0.20.0
|
|
93
|
+
*/
|
|
94
|
+
export async function defineConfig(opts) {
|
|
95
|
+
const stderr = opts.stderr === false
|
|
96
|
+
? null
|
|
97
|
+
: (opts.stderr ?? (typeof process !== "undefined" ? process.stderr : null));
|
|
98
|
+
let raw;
|
|
99
|
+
try {
|
|
100
|
+
raw = await readSource(opts.source ?? "env");
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
if (err instanceof ConfigValidationError) {
|
|
104
|
+
if (stderr)
|
|
105
|
+
stderr.write(`${err.message}\n`);
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
const wrapped = new ConfigValidationError([
|
|
109
|
+
{
|
|
110
|
+
key: "<source>",
|
|
111
|
+
message: err instanceof Error ? err.message : "failed to read source",
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
114
|
+
if (stderr)
|
|
115
|
+
stderr.write(`${wrapped.message}\n`);
|
|
116
|
+
throw wrapped;
|
|
117
|
+
}
|
|
118
|
+
const input = opts.transform ? opts.transform(raw) : raw;
|
|
119
|
+
const result = await validate(opts.schema, input);
|
|
120
|
+
if (result.issues) {
|
|
121
|
+
const issues = result.issues.map((issue) => ({
|
|
122
|
+
key: pathToKey(issue.path ?? []),
|
|
123
|
+
message: issue.message,
|
|
124
|
+
}));
|
|
125
|
+
const err = new ConfigValidationError(issues);
|
|
126
|
+
if (stderr)
|
|
127
|
+
stderr.write(`${err.message}\n`);
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
return result.value;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|