@theokit/http 1.2.0 → 2.1.0-next.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 CHANGED
@@ -31,11 +31,16 @@ form — `@Body(schema)` — works without it.
31
31
  ## Quick start
32
32
 
33
33
  ```typescript
34
- import { Controller, Get, Post, Body, Param } from '@theokit/http'
34
+ import { Controller, Get, Post, Body, Param, Public } from '@theokit/http'
35
35
  import { z } from 'zod'
36
36
 
37
37
  // Convention: @Controller() on CatsController infers the prefix "api/cats".
38
38
  // Pass a string to override: @Controller('api/v2/cats').
39
+ //
40
+ // `@Public()` is the access decision "anyone may call this". Every route needs one — a guard or
41
+ // this — because a route that declares neither is refused with 403 rather than served. See
42
+ // "Every route declares who may call it" below.
43
+ @Public()
39
44
  @Controller()
40
45
  export class CatsController {
41
46
  @Get()
@@ -55,6 +60,40 @@ export class CatsController {
55
60
  }
56
61
  ```
57
62
 
63
+ ## Every route declares who may call it
64
+
65
+ A route says one of two things, and saying neither is not a third option:
66
+
67
+ | | how it is said |
68
+ |---|---|
69
+ | anyone may call it | `@Public()` — on the method, or on the class to cover every route under it |
70
+ | someone decides | `@UseGuards(SomeGuard)` — likewise on either |
71
+
72
+ A route that declares neither is **refused with 403** at dispatch, in every dispatcher this package
73
+ ships, and `theokit build` fails on it before that. The reason is that `guards: []` used to mean
74
+ both *"open on purpose"* and *"nobody said"*, so the dispatcher took the permissive reading — and a
75
+ route nobody thought about is the one that ships open.
76
+
77
+ `undeclaredRoutes: 'warn'` restores the old behaviour while you migrate, with one warning per route:
78
+
79
+ ```typescript
80
+ TheoApp.create({ controllers, undeclaredRoutes: 'warn' }) // also on createDecoratorHandler(...)
81
+ ```
82
+
83
+ Guards still run on a `@Public()` route — the decorator answers *who may call it*, not *what else
84
+ happens on the way in*. For "any signed-in caller", `theokit/server/auth` exports the guard rather
85
+ than leaving every app to write it:
86
+
87
+ ```typescript
88
+ import { createSessionManagerWeb, Authenticated } from 'theokit/server/auth'
89
+
90
+ const sessions = createSessionManagerWeb<{ userId: string }>({ secret: process.env.SESSION_SECRET! })
91
+
92
+ @Controller('api/tasks')
93
+ @UseGuards(Authenticated(sessions))
94
+ export class TasksController { … }
95
+ ```
96
+
58
97
  ## Validation — Zod is the single source of truth
59
98
 
60
99
  Pass the schema to `@Body` directly. The bridge validates the request against it and feeds the same
package/dist/app.d.ts CHANGED
@@ -1,49 +1,5 @@
1
1
  import { S as ServerHandle } from './types-CGthbcon.js';
2
-
3
- /**
4
- * Whether a route DECLARED an access decision, or nobody said (usetheokit/theokit#576).
5
- *
6
- * ## The ambiguity this removes
7
- *
8
- * `guards: []` meant two different things and the dispatcher could not tell them apart:
9
- *
10
- * - *this route is open on purpose*, and
11
- * - *nobody said*.
12
- *
13
- * Faced with both readings it took the permissive one and served. For controllers that was safe
14
- * only because a separate build gate (#514) refuses an undeclared controller route — which makes
15
- * least privilege a property of the PIPELINE rather than of the system. Anything reaching the
16
- * dispatcher without having run that build is served, and `@theokit/http` is published on its own,
17
- * so "without having run that build" is an ordinary way to use it.
18
- *
19
- * Agent routes had neither. They are auto-wired — the app never wrote them, so there is no file for
20
- * a reviewer to read — dispatched BEFORE controllers and file routes, and covered by no gate at
21
- * all. An agent authored through capabilities has no class, so it takes no `@UseGuards`, so
22
- * `guards` was `undefined`, so `?? []`, so served.
23
- *
24
- * The fix is not a new guard. It is making absence REPRESENTABLE, so a decision can be told from
25
- * its residue.
26
- */
27
- /** What a route says about who may call it. */
28
- type AccessDecision =
29
- /** Anyone may call it, on purpose. */
30
- 'public'
31
- /** At least one guard decides. */
32
- | 'guarded'
33
- /** Nobody said — the state that used to be indistinguishable from `'public'`. */
34
- | 'undeclared';
35
- /**
36
- * How an app answers a route that declared nothing.
37
- *
38
- * `'warn'` is the default and serves the request after saying so once, loudly, at mount. `'deny'`
39
- * refuses with 403.
40
- *
41
- * The default is `'warn'` and not `'deny'` because flipping it silently would break every app whose
42
- * agent endpoints are open today — which is precisely the population #576 is about, and breaking
43
- * them inside a patch is how a security improvement becomes an outage. It becomes `'deny'` in the
44
- * next major; `'deny'` is available now for anyone who wants the property before then.
45
- */
46
- type UndeclaredRoutePolicy = 'warn' | 'deny';
2
+ import { A as AccessDecision, U as UndeclaredRoutePolicy } from './route-access-Bq7eKXIc.js';
47
3
 
48
4
  /**
49
5
  * TheoApp — NestJS/Spring Boot-style application bootstrap.
@@ -142,12 +98,11 @@ interface TheoAppOptions {
142
98
  /**
143
99
  * What to do with a route that declared no access decision (#576).
144
100
  *
145
- * `'warn'` (default) serves it after saying so once at mount. `'deny'` refuses with 403.
101
+ * `'deny'` (default) refuses with 403. `'warn'` serves it after saying so once the migration
102
+ * escape for an app whose agent endpoints are open today, and nothing else.
146
103
  *
147
- * The default is not `'deny'` because flipping it here would break every app whose agent
148
- * endpoints are open today — the population #576 is about — inside a non-major release. It
149
- * becomes `'deny'` in the next major; `'deny'` is available now for anyone who wants the property
150
- * before then.
104
+ * The default was `'warn'` in 1.2.0 so the flip did not land inside a patch. This is the major
105
+ * that release bought.
151
106
  */
152
107
  undeclaredRoutes?: UndeclaredRoutePolicy;
153
108
  /**
package/dist/app.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  TheoApp
3
- } from "./chunk-NFCIOKAI.js";
4
- import "./chunk-X7Q5HPLK.js";
5
- import "./chunk-AJOJXZRY.js";
3
+ } from "./chunk-HN73QCHT.js";
4
+ import "./chunk-ERQT6ZDN.js";
5
+ import "./chunk-CRTEMKW2.js";
6
6
  import "./chunk-BSS3KPGY.js";
7
7
  import "./chunk-Z4QWC7IK.js";
8
8
  export {
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  CATCH_EXCEPTIONS,
3
3
  HttpException,
4
- getMeta
5
- } from "./chunk-AJOJXZRY.js";
4
+ getMeta,
5
+ httpExceptionToResponse
6
+ } from "./chunk-CRTEMKW2.js";
6
7
  import {
7
8
  resolveOrNew
8
9
  } from "./chunk-JP4J23TV.js";
@@ -37,12 +38,7 @@ function matchesException(exception, catchTypes) {
37
38
  __name(matchesException, "matchesException");
38
39
  function builtInResponse(exception) {
39
40
  if (exception instanceof HttpException) {
40
- return new Response(JSON.stringify(exception.toJSON()), {
41
- status: exception.statusCode,
42
- headers: {
43
- "content-type": "application/json"
44
- }
45
- });
41
+ return httpExceptionToResponse(exception);
46
42
  }
47
43
  return globalFallback(exception);
48
44
  }
@@ -68,4 +64,4 @@ __name(globalFallback, "globalFallback");
68
64
  export {
69
65
  runExceptionFilters
70
66
  };
71
- //# sourceMappingURL=chunk-HC47QXZV.js.map
67
+ //# sourceMappingURL=chunk-2HJYLH4O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bridge/exception-filter-chain.ts"],"mappings":";;;;;;;;;;;;;;AA0BA,eAAsBA,oBACpBC,WACAC,SACAC,SACAC,WAAuB;AAEvB,QAAMC,OAAsB;IAC1BC,YAAY,6BAAMH,SAAN;EACd;AAEA,aAAWI,cAAcL,SAAS;AAChC,UAAMM,aAAaC,QAAoBC,kBAAkBH,UAAAA,KAAe,CAAA;AACxE,QAAII,iBAAiBV,WAAWO,UAAAA,GAAa;AAC3C,YAAMI,SAASC,aAAaN,YAAYH,SAAAA;AACxC,UAAI;AACF,eAAO,MAAMQ,OAAOE,MAAMb,WAAWI,IAAAA;MACvC,SAASU,aAAa;AAEpBC,gBAAQC,MAAM,2CAA2CF,WAAAA;AACzD,eAAOG,eAAejB,SAAAA;MACxB;IACF;EACF;AAEA,SAAOkB,gBAAgBlB,SAAAA;AACzB;AAzBsBD;AA2BtB,SAASW,iBAAiBV,WAAoBO,YAAsB;AAClE,MAAIA,WAAWY,WAAW,EAAG,QAAO;AACpC,SAAOZ,WAAWa,KAAK,CAACC,SAASrB,qBAAqBqB,IAAAA;AACxD;AAHSX;AAKT,SAASQ,gBAAgBlB,WAAkB;AACzC,MAAIA,qBAAqBsB,eAAe;AAItC,WAAOC,wBAAwBvB,SAAAA;EACjC;AACA,SAAOiB,eAAejB,SAAAA;AACxB;AARSkB;AAUT,SAASD,eAAejB,WAAkB;AACxC,QAAMwB,UAAUxB,qBAAqByB,QAAQzB,UAAUwB,UAAU;AACjET,UAAQC,MAAM,wCAAwChB,SAAAA;AACtD,SAAO,IAAI0B,SACTC,KAAKC,UAAU;IAAEZ,OAAO;MAAEa,MAAM;MAAyBL;MAASM,YAAY;IAAI;EAAE,CAAA,GACpF;IAAEC,QAAQ;IAAKC,SAAS;MAAE,gBAAgB;IAAmB;EAAE,CAAA;AAEnE;AAPSf;","names":["runExceptionFilters","exception","filters","request","container","host","getRequest","FilterCtor","catchTypes","getMeta","CATCH_EXCEPTIONS","matchesException","filter","resolveOrNew","catch","filterError","console","error","globalFallback","builtInResponse","length","some","Type","HttpException","httpExceptionToResponse","message","Error","Response","JSON","stringify","code","statusCode","status","headers"]}
@@ -2,37 +2,6 @@ import {
2
2
  __name
3
3
  } from "./chunk-Z4QWC7IK.js";
4
4
 
5
- // src/metadata/keys.ts
6
- var CONTROLLER_PREFIX = /* @__PURE__ */ Symbol.for("theokit:http-decorators:controller-prefix");
7
- var ROUTE_METHODS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:route-methods");
8
- var ROUTE_PARAMS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:route-params");
9
- var ROUTE_STATUS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:route-status");
10
- var ROUTE_HEADERS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:route-headers");
11
- var ROUTE_REDIRECT = /* @__PURE__ */ Symbol.for("theokit:http-decorators:route-redirect");
12
- var USE_GUARDS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:use-guards");
13
- var USE_INTERCEPTORS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:use-interceptors");
14
- var USE_FILTERS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:use-filters");
15
- var CATCH_EXCEPTIONS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:catch-exceptions");
16
- var EXPOSE_AGENT = /* @__PURE__ */ Symbol.for("theokit:http-decorators:expose-agent");
17
-
18
- // src/metadata/storage.ts
19
- import "reflect-metadata";
20
- function setMeta(key, target, value, propertyKey) {
21
- if (propertyKey !== void 0) {
22
- Reflect.defineMetadata(key, value, target, propertyKey);
23
- } else {
24
- Reflect.defineMetadata(key, value, target);
25
- }
26
- }
27
- __name(setMeta, "setMeta");
28
- function getMeta(key, target, propertyKey) {
29
- if (propertyKey !== void 0) {
30
- return Reflect.getMetadata(key, target, propertyKey);
31
- }
32
- return Reflect.getMetadata(key, target);
33
- }
34
- __name(getMeta, "getMeta");
35
-
36
5
  // src/exceptions/http-exception.ts
37
6
  var STATUS_CODES = {
38
7
  400: "BAD_REQUEST",
@@ -64,6 +33,8 @@ var HttpException = class extends Error {
64
33
  statusCode;
65
34
  code;
66
35
  description;
36
+ /** Headers this refusal carries. Empty unless the thrower supplied some. See {@link HttpExceptionOptions.headers}. */
37
+ headers;
67
38
  constructor(message, statusCode, options) {
68
39
  super(message, options?.cause ? {
69
40
  cause: options.cause
@@ -72,6 +43,9 @@ var HttpException = class extends Error {
72
43
  this.statusCode = statusCode;
73
44
  this.code = STATUS_CODES[statusCode] ?? "INTERNAL_SERVER_ERROR";
74
45
  this.description = options?.description;
46
+ this.headers = Object.freeze({
47
+ ...options?.headers
48
+ });
75
49
  }
76
50
  toJSON() {
77
51
  return {
@@ -237,20 +211,53 @@ var HttpStatus = {
237
211
  GATEWAY_TIMEOUT: 504
238
212
  };
239
213
 
214
+ // src/exceptions/to-response.ts
215
+ function httpExceptionToResponse(exception) {
216
+ const headers = new Headers();
217
+ for (const [name, value] of Object.entries(exception.headers)) {
218
+ if (name.toLowerCase() === "content-type") continue;
219
+ headers.set(name, value);
220
+ }
221
+ headers.set("content-type", "application/json");
222
+ return new Response(JSON.stringify(exception.toJSON()), {
223
+ status: exception.statusCode,
224
+ headers
225
+ });
226
+ }
227
+ __name(httpExceptionToResponse, "httpExceptionToResponse");
228
+
229
+ // src/metadata/keys.ts
230
+ var CONTROLLER_PREFIX = /* @__PURE__ */ Symbol.for("theokit:http-decorators:controller-prefix");
231
+ var ROUTE_METHODS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:route-methods");
232
+ var ROUTE_PARAMS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:route-params");
233
+ var ROUTE_STATUS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:route-status");
234
+ var ROUTE_HEADERS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:route-headers");
235
+ var ROUTE_REDIRECT = /* @__PURE__ */ Symbol.for("theokit:http-decorators:route-redirect");
236
+ var USE_GUARDS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:use-guards");
237
+ var USE_INTERCEPTORS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:use-interceptors");
238
+ var USE_FILTERS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:use-filters");
239
+ var CATCH_EXCEPTIONS = /* @__PURE__ */ Symbol.for("theokit:http-decorators:catch-exceptions");
240
+ var EXPOSE_AGENT = /* @__PURE__ */ Symbol.for("theokit:http-decorators:expose-agent");
241
+
242
+ // src/metadata/storage.ts
243
+ import "reflect-metadata";
244
+ function setMeta(key, target, value, propertyKey) {
245
+ if (propertyKey !== void 0) {
246
+ Reflect.defineMetadata(key, value, target, propertyKey);
247
+ } else {
248
+ Reflect.defineMetadata(key, value, target);
249
+ }
250
+ }
251
+ __name(setMeta, "setMeta");
252
+ function getMeta(key, target, propertyKey) {
253
+ if (propertyKey !== void 0) {
254
+ return Reflect.getMetadata(key, target, propertyKey);
255
+ }
256
+ return Reflect.getMetadata(key, target);
257
+ }
258
+ __name(getMeta, "getMeta");
259
+
240
260
  export {
241
- CONTROLLER_PREFIX,
242
- ROUTE_METHODS,
243
- ROUTE_PARAMS,
244
- ROUTE_STATUS,
245
- ROUTE_HEADERS,
246
- ROUTE_REDIRECT,
247
- USE_GUARDS,
248
- USE_INTERCEPTORS,
249
- USE_FILTERS,
250
- CATCH_EXCEPTIONS,
251
- EXPOSE_AGENT,
252
- setMeta,
253
- getMeta,
254
261
  HttpException,
255
262
  BadRequestException,
256
263
  UnauthorizedException,
@@ -273,6 +280,20 @@ export {
273
280
  GatewayTimeoutException,
274
281
  HttpVersionNotSupportedException,
275
282
  TooManyRequestsException,
276
- HttpStatus
283
+ HttpStatus,
284
+ httpExceptionToResponse,
285
+ CONTROLLER_PREFIX,
286
+ ROUTE_METHODS,
287
+ ROUTE_PARAMS,
288
+ ROUTE_STATUS,
289
+ ROUTE_HEADERS,
290
+ ROUTE_REDIRECT,
291
+ USE_GUARDS,
292
+ USE_INTERCEPTORS,
293
+ USE_FILTERS,
294
+ CATCH_EXCEPTIONS,
295
+ EXPOSE_AGENT,
296
+ setMeta,
297
+ getMeta
277
298
  };
278
- //# sourceMappingURL=chunk-AJOJXZRY.js.map
299
+ //# sourceMappingURL=chunk-CRTEMKW2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/exceptions/http-exception.ts","../src/exceptions/to-response.ts","../src/metadata/keys.ts","../src/metadata/storage.ts"],"mappings":"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","names":["STATUS_CODES","HttpException","Error","statusCode","code","description","headers","message","options","cause","undefined","name","Object","freeze","toJSON","error","factory","status","defaultMsg","BadRequestException","UnauthorizedException","ForbiddenException","NotFoundException","MethodNotAllowedException","NotAcceptableException","RequestTimeoutException","ConflictException","GoneException","PreconditionFailedException","PayloadTooLargeException","UnsupportedMediaTypeException","ImATeapotException","UnprocessableEntityException","InternalServerErrorException","NotImplementedException","BadGatewayException","ServiceUnavailableException","GatewayTimeoutException","HttpVersionNotSupportedException","TooManyRequestsException","HttpStatus","OK","CREATED","ACCEPTED","NO_CONTENT","MOVED_PERMANENTLY","FOUND","NOT_MODIFIED","TEMPORARY_REDIRECT","PERMANENT_REDIRECT","BAD_REQUEST","UNAUTHORIZED","PAYMENT_REQUIRED","FORBIDDEN","NOT_FOUND","METHOD_NOT_ALLOWED","NOT_ACCEPTABLE","REQUEST_TIMEOUT","CONFLICT","GONE","PRECONDITION_FAILED","PAYLOAD_TOO_LARGE","UNSUPPORTED_MEDIA_TYPE","IM_A_TEAPOT","UNPROCESSABLE_ENTITY","TOO_MANY_REQUESTS","INTERNAL_SERVER_ERROR","NOT_IMPLEMENTED","BAD_GATEWAY","SERVICE_UNAVAILABLE","GATEWAY_TIMEOUT","httpExceptionToResponse","exception","headers","Headers","name","value","Object","entries","toLowerCase","set","Response","JSON","stringify","toJSON","status","statusCode","CONTROLLER_PREFIX","Symbol","for","ROUTE_METHODS","ROUTE_PARAMS","ROUTE_STATUS","ROUTE_HEADERS","ROUTE_REDIRECT","USE_GUARDS","USE_INTERCEPTORS","USE_FILTERS","CATCH_EXCEPTIONS","EXPOSE_AGENT","setMeta","key","target","value","propertyKey","undefined","Reflect","defineMetadata","getMeta","getMetadata"]}
@@ -0,0 +1,337 @@
1
+ import {
2
+ CONTROLLER_PREFIX,
3
+ EXPOSE_AGENT,
4
+ ForbiddenException,
5
+ ROUTE_HEADERS,
6
+ ROUTE_METHODS,
7
+ ROUTE_PARAMS,
8
+ ROUTE_REDIRECT,
9
+ ROUTE_STATUS,
10
+ USE_FILTERS,
11
+ USE_GUARDS,
12
+ USE_INTERCEPTORS,
13
+ getMeta,
14
+ httpExceptionToResponse
15
+ } from "./chunk-CRTEMKW2.js";
16
+ import {
17
+ __name
18
+ } from "./chunk-Z4QWC7IK.js";
19
+
20
+ // src/bridge/errors.ts
21
+ var HttpDecoratorsConfigError = class extends Error {
22
+ static {
23
+ __name(this, "HttpDecoratorsConfigError");
24
+ }
25
+ name = "HttpDecoratorsConfigError";
26
+ constructor(message) {
27
+ super(message);
28
+ }
29
+ };
30
+
31
+ // src/bridge/execution-context.ts
32
+ function createExecutionContext(request, controllerClass, methodName) {
33
+ const url = new URL(request.url);
34
+ return {
35
+ getRequest: /* @__PURE__ */ __name(() => request, "getRequest"),
36
+ getUrl: /* @__PURE__ */ __name(() => url, "getUrl"),
37
+ getClass: /* @__PURE__ */ __name(() => controllerClass, "getClass"),
38
+ getMethodName: /* @__PURE__ */ __name(() => methodName, "getMethodName")
39
+ };
40
+ }
41
+ __name(createExecutionContext, "createExecutionContext");
42
+
43
+ // src/bridge/guard-chain.ts
44
+ var constructGuard = /* @__PURE__ */ __name((Ctor) => new Ctor(), "constructGuard");
45
+ async function runGuards(guards, context, resolve = constructGuard) {
46
+ for (const GuardCtor of guards) {
47
+ const guard = resolve(GuardCtor);
48
+ if (!await guard.canActivate(context)) {
49
+ return httpExceptionToResponse(new ForbiddenException("Forbidden resource"));
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+ __name(runGuards, "runGuards");
55
+
56
+ // src/decorators/set-metadata.ts
57
+ import "reflect-metadata";
58
+ var decoratorKeyCounter = 0;
59
+ function createDecorator() {
60
+ const key = /* @__PURE__ */ Symbol.for(`theokit:custom:${++decoratorKeyCounter}`);
61
+ const decorator = /* @__PURE__ */ __name((value) => {
62
+ return (target, propertyKey) => {
63
+ const metaTarget = propertyKey !== void 0 ? target.constructor : target;
64
+ Reflect.defineMetadata(key, value, metaTarget, propertyKey);
65
+ };
66
+ }, "decorator");
67
+ decorator.key = key;
68
+ return decorator;
69
+ }
70
+ __name(createDecorator, "createDecorator");
71
+ function SetMetadata(metaKey, value) {
72
+ return (target, propertyKey) => {
73
+ const metaTarget = propertyKey !== void 0 ? target.constructor : target;
74
+ Reflect.defineMetadata(metaKey, value, metaTarget, propertyKey);
75
+ };
76
+ }
77
+ __name(SetMetadata, "SetMetadata");
78
+ var Reflector = class {
79
+ static {
80
+ __name(this, "Reflector");
81
+ }
82
+ /**
83
+ * Read metadata set by a typed decorator created via createDecorator<T>().
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * const Roles = createDecorator<string[]>()
88
+ * const reflector = new Reflector()
89
+ * const roles = reflector.get(Roles, handlerFn) // string[] | undefined
90
+ * ```
91
+ */
92
+ get(decorator, target, propertyKey) {
93
+ const key = decorator.key;
94
+ if (!key) return void 0;
95
+ if (propertyKey !== void 0) {
96
+ return Reflect.getMetadata(key, target, propertyKey);
97
+ }
98
+ return Reflect.getMetadata(key, target);
99
+ }
100
+ /**
101
+ * Read metadata set by @SetMetadata(key, value).
102
+ */
103
+ getByKey(key, target, propertyKey) {
104
+ if (propertyKey !== void 0) {
105
+ return Reflect.getMetadata(key, target, propertyKey);
106
+ }
107
+ return Reflect.getMetadata(key, target);
108
+ }
109
+ /**
110
+ * Read metadata checking method-level first, then class-level.
111
+ * Returns the first non-undefined value found.
112
+ *
113
+ * NestJS equivalent: `reflector.getAllAndOverride(ROLES_KEY, [context.getHandler(), context.getClass()])`
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * const Roles = createDecorator<string[]>()
118
+ * // In a guard:
119
+ * const roles = reflector.getAllAndOverride(Roles, context.getClass(), context.getMethodName())
120
+ * // Checks method-level @Roles first, falls back to class-level @Roles
121
+ * ```
122
+ */
123
+ getAllAndOverride(decorator, target, propertyKey) {
124
+ if (propertyKey !== void 0) {
125
+ const methodLevel = this.get(decorator, target, propertyKey);
126
+ if (methodLevel !== void 0) return methodLevel;
127
+ }
128
+ return this.get(decorator, target);
129
+ }
130
+ /**
131
+ * Read metadata checking method-level first, then class-level, by raw key.
132
+ * Returns the first non-undefined value found.
133
+ */
134
+ getAllAndOverrideByKey(key, target, propertyKey) {
135
+ if (propertyKey !== void 0) {
136
+ const methodLevel = this.getByKey(key, target, propertyKey);
137
+ if (methodLevel !== void 0) return methodLevel;
138
+ }
139
+ return this.getByKey(key, target);
140
+ }
141
+ /**
142
+ * Read metadata from both method-level and class-level, merging arrays.
143
+ * Returns all found values as a flat array.
144
+ *
145
+ * NestJS equivalent: `reflector.getAllAndMerge(ROLES_KEY, [context.getHandler(), context.getClass()])`
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * const Tags = createDecorator<string[]>()
150
+ *
151
+ * @Tags(['api'])
152
+ * @Controller('cats')
153
+ * class CatsCtrl {
154
+ * @Tags(['read'])
155
+ * @Get()
156
+ * findAll() {}
157
+ * }
158
+ *
159
+ * reflector.getAllAndMerge(Tags, CatsCtrl, 'findAll')
160
+ * // → ['read', 'api'] (method + class merged)
161
+ * ```
162
+ */
163
+ getAllAndMerge(decorator, target, propertyKey) {
164
+ const result = [];
165
+ if (propertyKey !== void 0) {
166
+ const methodLevel = this.get(decorator, target, propertyKey);
167
+ if (methodLevel !== void 0) {
168
+ if (Array.isArray(methodLevel)) result.push(...methodLevel);
169
+ else result.push(methodLevel);
170
+ }
171
+ }
172
+ const classLevel = this.get(decorator, target);
173
+ if (classLevel !== void 0) {
174
+ if (Array.isArray(classLevel)) result.push(...classLevel);
175
+ else result.push(classLevel);
176
+ }
177
+ return result;
178
+ }
179
+ };
180
+
181
+ // src/decorators/public.ts
182
+ var PUBLIC_ROUTE_METADATA = "theokit:public";
183
+ var Public = /* @__PURE__ */ __name(() => SetMetadata(PUBLIC_ROUTE_METADATA, true), "Public");
184
+
185
+ // src/route-access.ts
186
+ function classifyAccess(declared) {
187
+ if (declared.access !== void 0) return declared.access;
188
+ return declared.guards !== void 0 && declared.guards.length > 0 ? "guarded" : "undeclared";
189
+ }
190
+ __name(classifyAccess, "classifyAccess");
191
+ var declareOpen = /* @__PURE__ */ __name((kind) => kind === "agent" ? "`access: 'public'`" : "`@Public()`", "declareOpen");
192
+ function undeclaredRouteWarning(kind, route) {
193
+ return `[theokit] ${kind} route ${route} declares no access decision and is served to anyone because \`undeclaredRoutes: 'warn'\` is set. Declare one: attach a guard, or say it is open on purpose (${declareOpen(kind)}). Dropping the option refuses it with 403.`;
194
+ }
195
+ __name(undeclaredRouteWarning, "undeclaredRouteWarning");
196
+ function undeclaredRouteRefusal(kind, route) {
197
+ return `${kind === "agent" ? "Agent" : "Controller"} route ${route} declares no access decision, so it is refused. Attach a guard, or say it is open on purpose (${declareOpen(kind)}). \`undeclaredRoutes: 'warn'\` serves it with a warning while you migrate.`;
198
+ }
199
+ __name(undeclaredRouteRefusal, "undeclaredRouteRefusal");
200
+
201
+ // src/bridge/dto-zod.ts
202
+ function resolveDtoSchema(dtoClass) {
203
+ if (typeof dtoClass !== "function") return void 0;
204
+ const maybe = dtoClass.schema;
205
+ if (maybe !== null && maybe !== void 0 && typeof maybe.safeParse === "function") {
206
+ return maybe;
207
+ }
208
+ return void 0;
209
+ }
210
+ __name(resolveDtoSchema, "resolveDtoSchema");
211
+
212
+ // src/bridge/walk-metadata.ts
213
+ import "reflect-metadata";
214
+ function joinPath(prefix, path) {
215
+ return ("/" + prefix + "/" + path).replace(/\/+/g, "/").replace(/\/$/, "") || "/";
216
+ }
217
+ __name(joinPath, "joinPath");
218
+ function resolveBodySchema(paramEntries, ControllerClass, propertyKey) {
219
+ const bodyParam = paramEntries.find((p) => p.source === "body" && !p.key);
220
+ if (!bodyParam) return void 0;
221
+ if (bodyParam.schema) return bodyParam.schema;
222
+ const paramTypes = Reflect.getMetadata("design:paramtypes", ControllerClass.prototype, propertyKey) ?? [];
223
+ if (paramTypes.length > 0) {
224
+ return resolveDtoSchema(paramTypes[bodyParam.index]);
225
+ }
226
+ console.warn(`[@theokit/http] method ${String(propertyKey)} on ${ControllerClass.name}: @Body() without explicit schema and emitDecoratorMetadata is not active. Body will be passed raw (no validation). Fix: use @Body(zodSchema) for validation without metadata emission.`);
227
+ return void 0;
228
+ }
229
+ __name(resolveBodySchema, "resolveBodySchema");
230
+ function declaredAccess(ControllerClass, propertyKey, guards) {
231
+ const isPublic = Reflect.getMetadata(PUBLIC_ROUTE_METADATA, ControllerClass) === true || Reflect.getMetadata(PUBLIC_ROUTE_METADATA, ControllerClass, propertyKey) === true;
232
+ return classifyAccess({
233
+ access: isPublic ? "public" : void 0,
234
+ guards
235
+ });
236
+ }
237
+ __name(declaredAccess, "declaredAccess");
238
+ var walkCache = /* @__PURE__ */ new WeakMap();
239
+ function walkControllerMetadata(ControllerClass) {
240
+ const cached = walkCache.get(ControllerClass);
241
+ if (cached) return cached;
242
+ const controllerMeta = getMeta(CONTROLLER_PREFIX, ControllerClass);
243
+ if (!controllerMeta) {
244
+ throw new HttpDecoratorsConfigError(`Controller class ${ControllerClass.name} is missing @Controller() decorator. Add @Controller('prefix') to the class declaration.`);
245
+ }
246
+ const { prefix, host } = controllerMeta;
247
+ if (host) {
248
+ console.warn(`[@theokit/http] @Controller host '${host}' captured but enforcement deferred to v0.2.0`);
249
+ }
250
+ const methods = getMeta(ROUTE_METHODS, ControllerClass) ?? [];
251
+ const paramsMap = getMeta(ROUTE_PARAMS, ControllerClass) ?? /* @__PURE__ */ new Map();
252
+ const classGuards = getMeta(USE_GUARDS, ControllerClass) ?? [];
253
+ const classInterceptors = getMeta(USE_INTERCEPTORS, ControllerClass) ?? [];
254
+ const classFilters = getMeta(USE_FILTERS, ControllerClass) ?? [];
255
+ const result = methods.map((m) => {
256
+ const paramEntries = paramsMap.get(m.propertyKey) ?? [];
257
+ const bodySchema = resolveBodySchema(paramEntries, ControllerClass, m.propertyKey);
258
+ const methodGuards = getMeta(USE_GUARDS, ControllerClass, m.propertyKey) ?? [];
259
+ const methodInterceptors = getMeta(USE_INTERCEPTORS, ControllerClass, m.propertyKey) ?? [];
260
+ return {
261
+ verb: m.verb,
262
+ fullPath: joinPath(prefix, m.path),
263
+ propertyKey: m.propertyKey,
264
+ bodySchema,
265
+ paramEntries: [
266
+ ...paramEntries
267
+ ].sort((a, b) => a.index - b.index),
268
+ status: getMeta(ROUTE_STATUS, ControllerClass, m.propertyKey),
269
+ headers: getMeta(ROUTE_HEADERS, ControllerClass, m.propertyKey) ?? [],
270
+ redirect: getMeta(ROUTE_REDIRECT, ControllerClass, m.propertyKey),
271
+ guards: [
272
+ ...classGuards,
273
+ ...methodGuards
274
+ ],
275
+ interceptors: [
276
+ ...classInterceptors,
277
+ ...methodInterceptors
278
+ ],
279
+ filters: getMeta(USE_FILTERS, ControllerClass, m.propertyKey) ?? classFilters,
280
+ access: declaredAccess(ControllerClass, m.propertyKey, [
281
+ ...classGuards,
282
+ ...methodGuards
283
+ ])
284
+ };
285
+ });
286
+ const exposeEntries = getMeta(EXPOSE_AGENT, ControllerClass) ?? [];
287
+ const agentResults = exposeEntries.map((e) => {
288
+ const memberGuards = getMeta(USE_GUARDS, ControllerClass, e.propertyKey) ?? [];
289
+ const verb = "POST";
290
+ return {
291
+ verb,
292
+ fullPath: joinPath(prefix, String(e.propertyKey)),
293
+ propertyKey: e.propertyKey,
294
+ paramEntries: [],
295
+ headers: [],
296
+ guards: [
297
+ ...classGuards,
298
+ ...memberGuards
299
+ ],
300
+ interceptors: [],
301
+ filters: classFilters,
302
+ access: declaredAccess(ControllerClass, e.propertyKey, [
303
+ ...classGuards,
304
+ ...memberGuards
305
+ ]),
306
+ agent: {
307
+ module: e.agent,
308
+ opts: e.opts
309
+ }
310
+ };
311
+ });
312
+ const all = [
313
+ ...result,
314
+ ...agentResults
315
+ ];
316
+ walkCache.set(ControllerClass, all);
317
+ return all;
318
+ }
319
+ __name(walkControllerMetadata, "walkControllerMetadata");
320
+
321
+ export {
322
+ HttpDecoratorsConfigError,
323
+ createExecutionContext,
324
+ runGuards,
325
+ createDecorator,
326
+ SetMetadata,
327
+ Reflector,
328
+ PUBLIC_ROUTE_METADATA,
329
+ Public,
330
+ classifyAccess,
331
+ undeclaredRouteWarning,
332
+ undeclaredRouteRefusal,
333
+ resolveDtoSchema,
334
+ joinPath,
335
+ walkControllerMetadata
336
+ };
337
+ //# sourceMappingURL=chunk-ERQT6ZDN.js.map