@dunx/http 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  // @bun
2
2
  import {
3
- HttpStatusCode
4
- } from "./chunk-x80f562w.js";
3
+ HttpStatusCode,
4
+ __decorateElement,
5
+ __decoratorMetadata,
6
+ __decoratorStart,
7
+ __runInitializers
8
+ } from "./chunk-5z96f3gr.js";
5
9
 
6
10
  // src/route/marker.ts
7
11
  var ROUTE = Symbol.for("dunx.route");
@@ -114,15 +118,166 @@ var discoverRoutes = (instance) => {
114
118
  }
115
119
  return routes;
116
120
  };
121
+ // src/inspect.ts
122
+ import {
123
+ collectModules,
124
+ dependenciesOf,
125
+ readControllers
126
+ } from "@dunx/core";
127
+
128
+ // src/ws/discover.ts
129
+ import {
130
+ AppError
131
+ } from "@dunx/core";
132
+
133
+ // src/ws/marker.ts
134
+ var HANDLER = Symbol.for("dunx.ws.handler");
135
+ var GATEWAY = Symbol.for("dunx.ws.gateway");
136
+ var HandlerKind = Object.freeze({
137
+ UPGRADE: "upgrade",
138
+ OPEN: "open",
139
+ MESSAGE: "message",
140
+ CLOSE: "close",
141
+ DRAIN: "drain",
142
+ PING: "ping",
143
+ PONG: "pong"
144
+ });
145
+ var markHandler = (target, meta2) => {
146
+ Object.defineProperty(target, HANDLER, { value: meta2, configurable: true });
147
+ };
148
+ var handlerMetaOf = (value) => typeof value === "function" ? value[HANDLER] : undefined;
149
+ var markGateway = (target, path) => {
150
+ Object.defineProperty(target, GATEWAY, { value: path, configurable: true });
151
+ };
152
+ var gatewayPathOf = (target) => target[GATEWAY] ?? "/";
153
+ var isGateway = (target) => target[GATEWAY] !== undefined;
154
+
155
+ // src/ws/discover.ts
156
+ var normalizePath = (path) => {
157
+ const joined = `/${path}`.replace(/\/{2,}/g, "/");
158
+ return joined.length > 1 ? joined.replace(/\/$/, "") : "/";
159
+ };
160
+ var eachHandler = (start) => {
161
+ const found = [];
162
+ const seen = new Set;
163
+ for (let proto = start;proto !== null && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) {
164
+ for (const [name, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(proto))) {
165
+ if (name === "constructor" || seen.has(name))
166
+ continue;
167
+ const meta2 = handlerMetaOf(descriptor.value);
168
+ if (!meta2)
169
+ continue;
170
+ seen.add(name);
171
+ found.push([name, meta2]);
172
+ }
173
+ }
174
+ return found;
175
+ };
176
+ var discoverGateway = (instance) => {
177
+ const klass = instance.constructor;
178
+ const members = instance;
179
+ return {
180
+ name: klass.name,
181
+ path: normalizePath(gatewayPathOf(klass)),
182
+ handlers: eachHandler(Object.getPrototypeOf(instance)).map(([name, meta2]) => ({
183
+ kind: meta2.kind,
184
+ event: meta2.event,
185
+ method: name,
186
+ invoke: members[name].bind(instance)
187
+ }))
188
+ };
189
+ };
190
+ var findHandlerMethod = (ctor) => eachHandler(ctor.prototype)[0]?.[0];
191
+ var classOf = (entry) => {
192
+ if (typeof entry === "function")
193
+ return { token: entry, ctor: entry };
194
+ return entry.provider.kind === "class" ? { token: entry.token, ctor: entry.provider.ctor } : undefined;
195
+ };
196
+ var discoverGateways = (modules, resolve) => {
197
+ const discovered = [];
198
+ for (const module of modules) {
199
+ for (const entry of module.options.providers ?? []) {
200
+ const candidate = classOf(entry);
201
+ if (!candidate)
202
+ continue;
203
+ if (isGateway(candidate.ctor)) {
204
+ discovered.push(discoverGateway(resolve(candidate.token)));
205
+ continue;
206
+ }
207
+ const orphan = findHandlerMethod(candidate.ctor);
208
+ if (orphan !== undefined) {
209
+ throw new AppError(`${candidate.ctor.name}.${orphan}() is a websocket handler, but ` + `${candidate.ctor.name} is not a gateway. Decorate the class with ` + "@Gateway(path), or drop the handler decorator.");
210
+ }
211
+ }
212
+ }
213
+ return discovered;
214
+ };
215
+
216
+ // src/inspect.ts
217
+ var vendorOf = (schema) => schema?.["~standard"]?.vendor;
218
+ var validatesIn = (options) => {
219
+ const body = vendorOf(options?.body);
220
+ const query = vendorOf(options?.query);
221
+ const params = vendorOf(options?.params);
222
+ return {
223
+ ...body === undefined ? {} : { body },
224
+ ...query === undefined ? {} : { query },
225
+ ...params === undefined ? {} : { params }
226
+ };
227
+ };
228
+ var rolesIn = (route) => {
229
+ const roles = route.meta?.get(ROLES.id);
230
+ if (roles === undefined || roles === null)
231
+ return null;
232
+ return (Array.isArray(roles) ? roles : [roles]).map(String);
233
+ };
234
+ var nodeFor = (route, module) => ({
235
+ method: route.method,
236
+ path: route.path,
237
+ controller: route.controller,
238
+ handler: route.handlerName,
239
+ module,
240
+ public: route.meta?.get(PUBLIC.id) === true,
241
+ roles: rolesIn(route),
242
+ guards: (route.guards ?? []).map((guard) => guard.name),
243
+ hidden: route.meta?.get(HIDDEN.id) === true,
244
+ validates: validatesIn(route.options),
245
+ status: route.options?.status ?? null,
246
+ responses: Object.keys(route.options?.response ?? {}).map(Number)
247
+ });
248
+ var routesOf = (root) => collectModules(root).flatMap((module) => readControllers(module).flatMap((controller) => {
249
+ const { prototype } = controller;
250
+ return discoverRoutes(Object.create(prototype)).map((route) => nodeFor(route, module.name));
251
+ }));
252
+ var gatewayFor = (ctor, module) => {
253
+ const { name, path, handlers } = discoverGateway(Object.create(ctor.prototype));
254
+ return {
255
+ name,
256
+ path,
257
+ module,
258
+ dependencies: dependenciesOf(ctor),
259
+ handlers: handlers.map((handler) => ({
260
+ kind: handler.kind,
261
+ event: handler.event ?? null,
262
+ method: handler.method
263
+ }))
264
+ };
265
+ };
266
+ var classOf2 = (entry) => {
267
+ if (typeof entry === "function")
268
+ return entry;
269
+ return entry.provider.kind === "class" ? entry.provider.ctor : undefined;
270
+ };
271
+ var gatewaysOf = (root) => collectModules(root).flatMap((module) => (module.options.providers ?? []).map(classOf2).filter((ctor) => ctor !== undefined).filter(isGateway).map((ctor) => gatewayFor(ctor, module.name)));
117
272
  // src/server/client-address.ts
118
- import { AppError } from "@dunx/core";
273
+ import { AppError as AppError2 } from "@dunx/core";
119
274
  var sources = new WeakMap;
120
275
 
121
276
  class ClientAddress {
122
277
  of(req) {
123
278
  const source = sources.get(this);
124
279
  if (!source) {
125
- throw new AppError("ClientAddress has no server yet. The address comes from the live Bun " + "server, so it is only available once listen() has run.");
280
+ throw new AppError2("ClientAddress has no server yet. The address comes from the live Bun " + "server, so it is only available once listen() has run.");
126
281
  }
127
282
  if (source.trustProxy) {
128
283
  const forwarded = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
@@ -199,8 +354,8 @@ var preflight = (options, methods) => {
199
354
  };
200
355
  };
201
356
  // src/server/errors.ts
202
- import { AppError as AppError2, ConsoleLogger } from "@dunx/core";
203
- class HttpError extends AppError2 {
357
+ import { AppError as AppError3, ConsoleLogger } from "@dunx/core";
358
+ class HttpError extends AppError3 {
204
359
  status;
205
360
  name = "HttpError";
206
361
  constructor(status, message, options) {
@@ -246,12 +401,12 @@ var errorMapper = (logger) => (error) => {
246
401
  var defaultErrorMapper = errorMapper(new ConsoleLogger);
247
402
  // src/server/factory.ts
248
403
  import {
249
- collectModules,
404
+ collectModules as collectModules2,
250
405
  AppError as AppError8,
251
406
  AppFactory,
252
407
  Logger as Logger3,
253
408
  provide,
254
- readControllers,
409
+ readControllers as readControllers2,
255
410
  RequestContext as RequestContext2
256
411
  } from "@dunx/core";
257
412
 
@@ -273,35 +428,11 @@ var decode = (message) => {
273
428
  };
274
429
 
275
430
  // src/ws/runtime.ts
276
- import { AppError as AppError3 } from "@dunx/core";
277
-
278
- // src/ws/marker.ts
279
- var HANDLER = Symbol.for("dunx.ws.handler");
280
- var GATEWAY = Symbol.for("dunx.ws.gateway");
281
- var HandlerKind = Object.freeze({
282
- UPGRADE: "upgrade",
283
- OPEN: "open",
284
- MESSAGE: "message",
285
- CLOSE: "close",
286
- DRAIN: "drain",
287
- PING: "ping",
288
- PONG: "pong"
289
- });
290
- var markHandler = (target, meta2) => {
291
- Object.defineProperty(target, HANDLER, { value: meta2, configurable: true });
292
- };
293
- var handlerMetaOf = (value) => typeof value === "function" ? value[HANDLER] : undefined;
294
- var markGateway = (target, path) => {
295
- Object.defineProperty(target, GATEWAY, { value: path, configurable: true });
296
- };
297
- var gatewayPathOf = (target) => target[GATEWAY] ?? "/";
298
- var isGateway = (target) => target[GATEWAY] !== undefined;
299
-
300
- // src/ws/runtime.ts
431
+ import { AppError as AppError4 } from "@dunx/core";
301
432
  var slotOf = (handler) => handler.kind === HandlerKind.MESSAGE && handler.event !== undefined ? `message ${JSON.stringify(handler.event)}` : handler.kind;
302
433
  var buildRuntime = (gateway) => {
303
434
  if (gateway.handlers.length === 0) {
304
- throw new AppError3(`${gateway.name} is registered as a gateway but declares no handlers. ` + "Add an @OnMessage/@OnOpen/... method, or drop the @Gateway decorator.");
435
+ throw new AppError4(`${gateway.name} is registered as a gateway but declares no handlers. ` + "Add an @OnMessage/@OnOpen/... method, or drop the @Gateway decorator.");
305
436
  }
306
437
  const owners = new Map;
307
438
  const events = new Map;
@@ -309,7 +440,7 @@ var buildRuntime = (gateway) => {
309
440
  const slot = slotOf(handler);
310
441
  const existing = owners.get(slot);
311
442
  if (existing) {
312
- throw new AppError3(`Handler collision in ${gateway.name}: ${slot} is claimed by ` + `${existing.method}() and by ${handler.method}(). One handler per event.`);
443
+ throw new AppError4(`Handler collision in ${gateway.name}: ${slot} is claimed by ` + `${existing.method}() and by ${handler.method}(). One handler per event.`);
313
444
  }
314
445
  owners.set(slot, handler);
315
446
  if (handler.kind === HandlerKind.MESSAGE && handler.event !== undefined) {
@@ -335,7 +466,7 @@ var buildGateways = (discovered) => {
335
466
  for (const gateway of discovered) {
336
467
  const existing = byPath.get(gateway.path);
337
468
  if (existing) {
338
- throw new AppError3(`Gateway path collision: ${gateway.path} is served by ${existing.name} ` + `and by ${gateway.name}. One gateway per path.`);
469
+ throw new AppError4(`Gateway path collision: ${gateway.path} is served by ${existing.name} ` + `and by ${gateway.name}. One gateway per path.`);
339
470
  }
340
471
  byPath.set(gateway.path, buildRuntime(gateway));
341
472
  }
@@ -468,70 +599,6 @@ var buildWebSocket = (discovered, options = {}) => {
468
599
  };
469
600
  };
470
601
 
471
- // src/ws/discover.ts
472
- import {
473
- AppError as AppError4
474
- } from "@dunx/core";
475
- var normalizePath = (path) => {
476
- const joined = `/${path}`.replace(/\/{2,}/g, "/");
477
- return joined.length > 1 ? joined.replace(/\/$/, "") : "/";
478
- };
479
- var eachHandler = (start) => {
480
- const found = [];
481
- const seen = new Set;
482
- for (let proto = start;proto !== null && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) {
483
- for (const [name, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(proto))) {
484
- if (name === "constructor" || seen.has(name))
485
- continue;
486
- const meta2 = handlerMetaOf(descriptor.value);
487
- if (!meta2)
488
- continue;
489
- seen.add(name);
490
- found.push([name, meta2]);
491
- }
492
- }
493
- return found;
494
- };
495
- var discoverGateway = (instance) => {
496
- const klass = instance.constructor;
497
- const members = instance;
498
- return {
499
- name: klass.name,
500
- path: normalizePath(gatewayPathOf(klass)),
501
- handlers: eachHandler(Object.getPrototypeOf(instance)).map(([name, meta2]) => ({
502
- kind: meta2.kind,
503
- event: meta2.event,
504
- method: name,
505
- invoke: members[name].bind(instance)
506
- }))
507
- };
508
- };
509
- var findHandlerMethod = (ctor) => eachHandler(ctor.prototype)[0]?.[0];
510
- var classOf = (entry) => {
511
- if (typeof entry === "function")
512
- return { token: entry, ctor: entry };
513
- return entry.provider.kind === "class" ? { token: entry.token, ctor: entry.provider.ctor } : undefined;
514
- };
515
- var discoverGateways = (modules, resolve) => {
516
- const discovered = [];
517
- for (const module of modules) {
518
- for (const entry of module.options.providers ?? []) {
519
- const candidate = classOf(entry);
520
- if (!candidate)
521
- continue;
522
- if (isGateway(candidate.ctor)) {
523
- discovered.push(discoverGateway(resolve(candidate.token)));
524
- continue;
525
- }
526
- const orphan = findHandlerMethod(candidate.ctor);
527
- if (orphan !== undefined) {
528
- throw new AppError4(`${candidate.ctor.name}.${orphan}() is a websocket handler, but ` + `${candidate.ctor.name} is not a gateway. Decorate the class with ` + "@Gateway(path), or drop the handler decorator.");
529
- }
530
- }
531
- }
532
- return discovered;
533
- };
534
-
535
602
  // src/ws/pubsub.ts
536
603
  import { AppError as AppError5 } from "@dunx/core";
537
604
 
@@ -726,6 +793,7 @@ class RequestLoggingMiddleware {
726
793
  #requestBody;
727
794
  #responseBody;
728
795
  #ignore;
796
+ #ignorePrefix;
729
797
  #correlateIgnored;
730
798
  #correlate;
731
799
  constructor(logger, context, options = {}) {
@@ -735,15 +803,23 @@ class RequestLoggingMiddleware {
735
803
  this.#requestBody = options.requestBody ?? false;
736
804
  this.#responseBody = options.responseBody ?? false;
737
805
  this.#ignore = new Set(options.ignore ?? []);
806
+ this.#ignorePrefix = options.ignorePrefix ?? [];
738
807
  this.#correlateIgnored = options.correlateIgnored ?? false;
739
808
  this.#correlate = options.correlate ?? true;
740
809
  }
810
+ #ignored(path) {
811
+ if (this.#ignore.size > 0 && this.#ignore.has(path))
812
+ return true;
813
+ if (this.#ignorePrefix.length === 0)
814
+ return false;
815
+ return this.#ignorePrefix.some((prefix) => path.startsWith(prefix));
816
+ }
741
817
  handle(req, ctx, next) {
742
818
  const url = req.url;
743
819
  const from = url.indexOf("/", url.indexOf("://") + 3);
744
820
  const mark = from === -1 ? -1 : url.indexOf("?", from);
745
821
  const path = from === -1 ? "/" : mark === -1 ? url.slice(from) : url.slice(from, mark);
746
- if (this.#ignore.size > 0 && this.#ignore.has(path)) {
822
+ if (this.#ignored(path)) {
747
823
  return this.#correlateIgnored ? this.#correlated(req, ctx, path, next) : next();
748
824
  }
749
825
  const started = Bun.nanoseconds();
@@ -1295,11 +1371,11 @@ class HttpFactory {
1295
1371
  exports: providers.map((entry) => typeof entry === "function" ? entry : entry.token)
1296
1372
  };
1297
1373
  const app = await AppFactory.create(scope, options.overrides ? { overrides: options.overrides } : {});
1298
- const modules = collectModules(scope);
1374
+ const modules = collectModules2(scope);
1299
1375
  const discovered = [];
1300
1376
  for (const module of modules) {
1301
1377
  const moduleMiddleware = module.options.middleware ?? [];
1302
- for (const controller of readControllers(module)) {
1378
+ for (const controller of readControllers2(module)) {
1303
1379
  const routes = discoverRoutes(app.get(controller, module.ref));
1304
1380
  if (routes.length === 0) {
1305
1381
  throw new AppError8(`${controller.name} is registered as a controller but declares no routes. ` + "Add a @Get/@Post/... method, or move it to providers.");
@@ -1319,6 +1395,129 @@ class HttpFactory {
1319
1395
  return new HttpApplication(app, discovered, options, root, websocket);
1320
1396
  }
1321
1397
  }
1398
+ // src/static/files.ts
1399
+ import { join, normalize, resolve } from "path";
1400
+
1401
+ // src/static/options.ts
1402
+ class StaticOptions {
1403
+ root;
1404
+ path;
1405
+ maxAge;
1406
+ immutable;
1407
+ constructor(init) {
1408
+ this.root = init.root;
1409
+ this.path = normalizePrefix(init.path ?? "/");
1410
+ this.maxAge = init.maxAge ?? 60;
1411
+ this.immutable = init.immutable ?? (() => false);
1412
+ }
1413
+ }
1414
+ Object.defineProperty(StaticOptions, Symbol.for("dunx.deps"), {
1415
+ value: () => [{ unresolved: "init: StaticOptionsInit" }]
1416
+ });
1417
+ var normalizePrefix = (path) => {
1418
+ const trimmed = path.split("/").filter(Boolean).join("/");
1419
+ return trimmed === "" ? "/" : `/${trimmed}`;
1420
+ };
1421
+
1422
+ // src/static/files.ts
1423
+ class StaticFiles {
1424
+ #options;
1425
+ #root;
1426
+ #prefix;
1427
+ constructor(options) {
1428
+ this.#options = options;
1429
+ this.#root = resolve(options.root);
1430
+ this.#prefix = options.path === "/" ? "/" : `${options.path}/`;
1431
+ }
1432
+ resolvePath(pathname) {
1433
+ const relative = pathname.startsWith(this.#prefix) ? pathname.slice(this.#prefix.length) : pathname.slice(this.#options.path.length);
1434
+ let decoded;
1435
+ try {
1436
+ decoded = decodeURIComponent(relative);
1437
+ } catch {
1438
+ return;
1439
+ }
1440
+ if (decoded.includes("\x00"))
1441
+ return;
1442
+ const candidate = resolve(join(this.#root, normalize(decoded)));
1443
+ if (candidate !== this.#root && !candidate.startsWith(`${this.#root}/`)) {
1444
+ return;
1445
+ }
1446
+ return candidate;
1447
+ }
1448
+ #cacheControl(pathname) {
1449
+ const { immutable, maxAge } = this.#options;
1450
+ return immutable(pathname) ? "public, max-age=31536000, immutable" : `public, max-age=${maxAge}`;
1451
+ }
1452
+ async handle(req, _ctx, next) {
1453
+ const { pathname } = new URL(req.url);
1454
+ if (pathname !== this.#options.path && !pathname.startsWith(this.#prefix)) {
1455
+ return next();
1456
+ }
1457
+ if (req.method !== "GET" && req.method !== "HEAD")
1458
+ return next();
1459
+ const path = this.resolvePath(pathname);
1460
+ if (path === undefined)
1461
+ return next();
1462
+ const file = Bun.file(path);
1463
+ if (!await file.exists())
1464
+ return next();
1465
+ return new Response(file, {
1466
+ headers: {
1467
+ "cache-control": this.#cacheControl(pathname),
1468
+ ...file.type === "" ? { "content-type": "application/octet-stream" } : {},
1469
+ "x-content-type-options": "nosniff"
1470
+ }
1471
+ });
1472
+ }
1473
+ }
1474
+ Object.defineProperty(StaticFiles, Symbol.for("dunx.deps"), {
1475
+ value: () => [StaticOptions]
1476
+ });
1477
+ // src/static/module.ts
1478
+ import {
1479
+ Module,
1480
+ provide as provide2
1481
+ } from "@dunx/core";
1482
+ var files = () => provide2(StaticFiles, {
1483
+ useFactory: (options) => new StaticFiles(options),
1484
+ inject: [StaticOptions]
1485
+ });
1486
+ var _dec = [
1487
+ Module({})
1488
+ ];
1489
+ var _init = __decoratorStart(undefined);
1490
+
1491
+ class StaticModule {
1492
+ static forRoot(init) {
1493
+ return {
1494
+ module: StaticModule,
1495
+ exports: [StaticOptions, StaticFiles],
1496
+ providers: [
1497
+ provide2(StaticOptions, { useValue: new StaticOptions(init) }),
1498
+ files()
1499
+ ]
1500
+ };
1501
+ }
1502
+ static forRootAsync(config) {
1503
+ return {
1504
+ module: StaticModule,
1505
+ ...config.imports && { imports: config.imports },
1506
+ exports: [StaticOptions, StaticFiles],
1507
+ providers: [
1508
+ provide2(StaticOptions, {
1509
+ useFactory: async (...deps) => new StaticOptions(await config.useFactory(...deps)),
1510
+ inject: config.inject ?? []
1511
+ }),
1512
+ files()
1513
+ ]
1514
+ };
1515
+ }
1516
+ }
1517
+ StaticModule = __decorateElement(_init, 0, "StaticModule", _dec, StaticModule);
1518
+ __runInitializers(_init, 1, StaticModule);
1519
+ __decoratorMetadata(_init, StaticModule);
1520
+ let _StaticModule = StaticModule;
1322
1521
  // src/ws/decorators.ts
1323
1522
  var Gateway = (path = "/") => (target) => {
1324
1523
  markGateway(target, path);
@@ -1435,7 +1634,9 @@ export {
1435
1634
  withUpgradeRoutes,
1436
1635
  withCors,
1437
1636
  toErrorMapper,
1637
+ routesOf,
1438
1638
  preflight,
1639
+ normalizePrefix,
1439
1640
  normalizePath,
1440
1641
  metaOf,
1441
1642
  metaKey,
@@ -1445,6 +1646,7 @@ export {
1445
1646
  isGateway,
1446
1647
  isErrorFilter,
1447
1648
  guardsOf,
1649
+ gatewaysOf,
1448
1650
  errorMapper,
1449
1651
  encodeRelay,
1450
1652
  encode,
@@ -1466,6 +1668,9 @@ export {
1466
1668
  ValidationError,
1467
1669
  UseGuards,
1468
1670
  UNMATCHED,
1671
+ StaticOptions,
1672
+ StaticModule,
1673
+ StaticFiles,
1469
1674
  Roles,
1470
1675
  RequestLoggingMiddleware,
1471
1676
  RedisRelay,
@@ -1499,5 +1704,5 @@ export {
1499
1704
  ApiHidden
1500
1705
  };
1501
1706
 
1502
- //# debugId=B4FE7D323451AFAC64756E2164756E21
1707
+ //# debugId=70611CF4C37807EE64756E2164756E21
1503
1708
  //# sourceMappingURL=index.js.map