@theokit/http 0.5.3 → 0.6.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.
Files changed (43) hide show
  1. package/LICENSE +201 -0
  2. package/dist/app.d.ts +16 -0
  3. package/dist/app.js +5 -5
  4. package/dist/{chunk-AVSB6444.js → chunk-3PGQVQWG.js} +2 -2
  5. package/dist/{chunk-474GXFYG.js → chunk-6W4T4DPJ.js} +4 -4
  6. package/dist/chunk-7QVYU63E.js +7 -0
  7. package/dist/{chunk-UVV6THUP.js → chunk-ELCXHPAD.js} +3 -3
  8. package/dist/{chunk-H7V4WQAM.js → chunk-GQ2UH554.js} +127 -9
  9. package/dist/chunk-GQ2UH554.js.map +1 -0
  10. package/dist/{chunk-IHUNGY64.js → chunk-HLW7YKZE.js} +2 -2
  11. package/dist/{chunk-XPFP4PQO.js → chunk-MQAJWR3K.js} +2 -2
  12. package/dist/chunk-MQAJWR3K.js.map +1 -0
  13. package/dist/{chunk-5T6NAG62.js → chunk-QGB5YC4T.js} +3 -3
  14. package/dist/chunk-RC4V75DI.js +257 -0
  15. package/dist/chunk-RC4V75DI.js.map +1 -0
  16. package/dist/exception-filter-chain-O45FXGEB.js +10 -0
  17. package/dist/index.d.ts +280 -5
  18. package/dist/index.js +200 -22
  19. package/dist/index.js.map +1 -1
  20. package/dist/interceptor-chain-ELSL6KZT.js +9 -0
  21. package/dist/{middleware-consumer-ljxK1fU_.d.ts → middleware-consumer-DcaksawH.d.ts} +1 -1
  22. package/dist/runtime-node.js +2 -2
  23. package/dist/theokit-plugin.d.ts +1 -1
  24. package/dist/theokit-plugin.js +9 -169
  25. package/dist/theokit-plugin.js.map +1 -1
  26. package/package.json +24 -14
  27. package/dist/chunk-H7V4WQAM.js.map +0 -1
  28. package/dist/chunk-I2ADDC6C.js +0 -87
  29. package/dist/chunk-I2ADDC6C.js.map +0 -1
  30. package/dist/chunk-XPFP4PQO.js.map +0 -1
  31. package/dist/chunk-Z56OUB3Z.js +0 -19
  32. package/dist/exception-filter-chain-GU547TYA.js +0 -10
  33. package/dist/interceptor-chain-BEI3ROM7.js +0 -9
  34. package/dist/server.node-HU6QI5YI.js +0 -23373
  35. package/dist/server.node-HU6QI5YI.js.map +0 -1
  36. /package/dist/{chunk-AVSB6444.js.map → chunk-3PGQVQWG.js.map} +0 -0
  37. /package/dist/{chunk-474GXFYG.js.map → chunk-6W4T4DPJ.js.map} +0 -0
  38. /package/dist/{chunk-Z56OUB3Z.js.map → chunk-7QVYU63E.js.map} +0 -0
  39. /package/dist/{chunk-UVV6THUP.js.map → chunk-ELCXHPAD.js.map} +0 -0
  40. /package/dist/{chunk-IHUNGY64.js.map → chunk-HLW7YKZE.js.map} +0 -0
  41. /package/dist/{chunk-5T6NAG62.js.map → chunk-QGB5YC4T.js.map} +0 -0
  42. /package/dist/{exception-filter-chain-GU547TYA.js.map → exception-filter-chain-O45FXGEB.js.map} +0 -0
  43. /package/dist/{interceptor-chain-BEI3ROM7.js.map → interceptor-chain-ELSL6KZT.js.map} +0 -0
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { z } from 'zod';
2
- import { D as DiContainer, M as MiddlewareConsumerImpl } from './middleware-consumer-ljxK1fU_.js';
3
- export { a as MiddlewareConfigProxy, b as MiddlewareFn, N as NestMiddleware, R as ResolvedMiddleware, m as middlewareMatchesPath, r as resolveOrNew, c as runMiddleware } from './middleware-consumer-ljxK1fU_.js';
2
+ import { D as DiContainer, M as MiddlewareConsumerImpl } from './middleware-consumer-DcaksawH.js';
3
+ export { a as MiddlewareConfigProxy, b as MiddlewareFn, N as NestMiddleware, R as ResolvedMiddleware, m as middlewareMatchesPath, r as resolveOrNew, c as runMiddleware } from './middleware-consumer-DcaksawH.js';
4
4
  import { S as ServerHandle } from './types-CGthbcon.js';
5
5
  export { ReadinessCheck, TheoApp, TheoAppOptions } from './app.js';
6
+ import * as ReactTypes from 'react';
6
7
 
7
8
  interface ControllerOptions {
8
9
  host?: string;
@@ -354,6 +355,63 @@ declare function joinPath(prefix: string, path: string): string;
354
355
  */
355
356
  declare function walkControllerMetadata(ControllerClass: Function): WalkResult[];
356
357
 
358
+ /**
359
+ * SWC-powered module loader for controller files with parameter decorators.
360
+ *
361
+ * esbuild (used by tsx/Vite SSR) fundamentally cannot parse TypeScript
362
+ * parameter decorators (`@Body()`, `@Param()`, `@Query()`). This loader
363
+ * uses @swc/core to transform controller files with full decorator support
364
+ * (legacyDecorator + decoratorMetadata), then imports them via a temp .mjs
365
+ * file written in the SAME directory (preserving relative import resolution).
366
+ *
367
+ * Pattern: follows Next.js's approach (read tsconfig → configure SWC)
368
+ * but scoped to the http-decorators package, not the framework core.
369
+ *
370
+ * @see references/next.js/packages/next/src/build/swc/options.ts
371
+ */
372
+
373
+ interface SwcCore {
374
+ transformSync: (src: string, opts: unknown) => {
375
+ code: string;
376
+ };
377
+ }
378
+ /**
379
+ * Transform TypeScript controller SOURCE (with parameter decorators) into
380
+ * ESM code, emitting the `design:paramtypes` / decorator metadata that esbuild
381
+ * cannot produce. Pure code→code — no file I/O, no module load — so it is
382
+ * reusable both by {@link loadControllerWithSwc} (which then temp-writes +
383
+ * imports) and by a build-tool transform hook that returns `{ code }` directly.
384
+ *
385
+ * The `@swc/core` loader is injectable (`loadSwc`) for testability; it defaults
386
+ * to the cached singleton.
387
+ *
388
+ * @throws HttpDecoratorsConfigError when @swc/core is unavailable.
389
+ */
390
+ declare function transformControllerSource(source: string, filename: string, loadSwc?: () => Promise<SwcCore | null>): Promise<string>;
391
+ /**
392
+ * Load a TypeScript controller file using @swc/core for decorator support.
393
+ *
394
+ * Strategy:
395
+ * 1. Read source .ts file
396
+ * 2. Transform via {@link transformControllerSource} (legacyDecorator + metadata)
397
+ * 3. Write temp .mjs in SAME directory (relative imports resolve correctly)
398
+ * 4. Dynamic import() the .mjs — transitive .ts imports go through
399
+ * tsx/Vite's global hook (they don't have parameter decorators)
400
+ * 5. Cleanup temp file
401
+ */
402
+ declare function loadControllerWithSwc(absoluteFilePath: string): Promise<Record<string, unknown>>;
403
+ /**
404
+ * Scan a glob pattern for controller files and load them all via SWC.
405
+ * Returns an array of controller class constructors found.
406
+ */
407
+ declare function loadControllersFromGlob(rootDir: string, pattern: string): Promise<Function[]>;
408
+ /**
409
+ * Check if a function has @Controller metadata.
410
+ * Uses Symbol.for() global registry key — same Symbol instance across
411
+ * module boundaries (SWC-loaded controllers share the global registry).
412
+ */
413
+ declare function isControllerClass(fn: Function): boolean;
414
+
357
415
  interface RouteRegistration {
358
416
  verb: HttpVerb;
359
417
  fullPath: string;
@@ -377,6 +435,25 @@ interface CreateDecoratorServerOptions {
377
435
  container?: DiContainer;
378
436
  configure?: (consumer: MiddlewareConsumerImpl) => void;
379
437
  }
438
+ /**
439
+ * A pure Web-Standard controller handler: callable as `(request) => Response | null`
440
+ * plus a non-executing `matches(method, pathname)` route probe (so a host can gate
441
+ * — e.g. CSRF — before dispatch runs a handler).
442
+ */
443
+ interface DecoratorHandler {
444
+ (request: Request): Promise<Response | null>;
445
+ /** True when a controller route owns `method` + `pathname` (no handler executed). */
446
+ matches(method: string, pathname: string): boolean;
447
+ }
448
+ /**
449
+ * Build a pure Web-Standard request handler from decorated controller classes,
450
+ * WITHOUT binding a network listener. Returns a {@link DecoratorHandler} whose
451
+ * call returns `null` when no controller route matched — the caller decides the
452
+ * miss (a standalone server answers 404; a host middleware falls through to its
453
+ * own routing). This is the reusable dispatch seam consumed by the framework's
454
+ * controller dispatch (#122) so it never re-implements match/bind/validate.
455
+ */
456
+ declare function createDecoratorHandler(controllersOrOpts: Function[] | CreateDecoratorServerOptions): DecoratorHandler;
380
457
  declare function createDecoratorServer(controllersOrOpts: Function[] | CreateDecoratorServerOptions): ServerHandle;
381
458
 
382
459
  /**
@@ -998,10 +1075,10 @@ type InferResponse<D> = D extends {
998
1075
  response: infer R;
999
1076
  } ? R : unknown;
1000
1077
  interface TypedClient<M extends RouteMap> {
1001
- get<P extends string & keyof M>(path: P, opts?: {
1078
+ get<P extends string>(path: P, opts?: {
1002
1079
  query?: Record<string, string>;
1003
1080
  headers?: Record<string, string>;
1004
- }): Promise<InferResponse<M[`GET ${P}`] extends never ? M[P] : M[`GET ${P}`]>>;
1081
+ }): Promise<InferResponse<M[`GET ${P}`]>>;
1005
1082
  post<P extends string>(path: P, body?: InferBody<M[`POST ${P}`]>, opts?: {
1006
1083
  headers?: Record<string, string>;
1007
1084
  }): Promise<InferResponse<M[`POST ${P}`]>>;
@@ -1091,4 +1168,202 @@ declare function isSafePath(pathname: string): boolean;
1091
1168
  */
1092
1169
  declare function createStaticHandler(options?: StaticOptions): (request: Request) => Promise<Response | null>;
1093
1170
 
1094
- export { All, type ArgumentsHost, BadGatewayException, BadRequestException, Body, CATCH_EXCEPTIONS, CONTROLLER_PREFIX, type CanActivate, Catch, ConflictException, Controller, type ControllerMeta, type ControllerOptions, Delete, DiContainer, type ExceptionFilter, type ExecutionContext, ForbiddenException, GatewayTimeoutException, Get, GoneException, Head, Header, Headers, HostParam, HttpCode, HttpDecoratorsConfigError, HttpException, type HttpExceptionOptions, HttpStatus, type HttpStatusCode, type HttpVerb, HttpVersionNotSupportedException, ImATeapotException, type Interceptor, InternalServerErrorException, Ip, type MetadataKey, MethodNotAllowedException, MiddlewareConsumerImpl, NotAcceptableException, NotFoundException, NotImplementedException, Options, Param, type ParamEntry, type ParamSource, Patch, PayloadTooLargeException, Post, PreconditionFailedException, Put, Query, ROUTE_HEADERS, ROUTE_METHODS, ROUTE_PARAMS, ROUTE_REDIRECT, ROUTE_STATUS, Redirect, type RedirectMeta, Reflector, Req, RequestTimeoutException, Res, type RouteDefinition, type RouteMap, type RouteMethodEntry, type RouteRegistration, ServiceUnavailableException, Session, SetMetadata, SkipThrottle, type StaticOptions, Throttle, type ThrottleOptions, TooManyRequestsException, type TypedClient, TypedClientError, USE_FILTERS, USE_GUARDS, USE_INTERCEPTORS, UnauthorizedException, UnprocessableEntityException, UnsupportedMediaTypeException, UseFilters, UseGuards, UseInterceptors, type WalkResult, contract, createDecorator, createDecoratorServer, createExecutionContext, createStaticHandler, createTypedClient, getMeta, getMimeType, getThrottleOptions, isSafePath, isThrottleSkipped, joinPath, registerControllers, resolveDtoSchema, runExceptionFilters, runInterceptors, setMeta, walkControllerMetadata };
1171
+ interface TheoRequestContext {
1172
+ /** The raw Web Standard Request object. */
1173
+ request: Request;
1174
+ /** URL pathname (e.g., '/api/tasks'). */
1175
+ pathname: string;
1176
+ /** HTTP method (e.g., 'GET', 'POST'). */
1177
+ method: string;
1178
+ /** Route params extracted from URL (e.g., { id: '42' }). */
1179
+ params: Record<string, string>;
1180
+ /** Matched controller or agent class name (if resolved). */
1181
+ handler?: string;
1182
+ /** Request start time (ms). */
1183
+ startedAt: number;
1184
+ }
1185
+ /**
1186
+ * Get the current request context.
1187
+ *
1188
+ * @throws Error if called outside a request (e.g., at module load time).
1189
+ *
1190
+ * @example
1191
+ * ```typescript
1192
+ * import { getRequestContext } from '@theokit/http'
1193
+ *
1194
+ * class AuthGuard {
1195
+ * canActivate() {
1196
+ * const { request } = getRequestContext()
1197
+ * return request.headers.get('authorization') !== null
1198
+ * }
1199
+ * }
1200
+ * ```
1201
+ */
1202
+ declare function getRequestContext(): TheoRequestContext;
1203
+ /**
1204
+ * Try to get the current request context, or null if not in a request.
1205
+ * Useful for code that may run both inside and outside requests.
1206
+ */
1207
+ declare function tryGetRequestContext(): TheoRequestContext | null;
1208
+
1209
+ /**
1210
+ * Error digestion — converts any thrown value into a stable hash + context.
1211
+ *
1212
+ * Inspired by Next.js `create-error-handler.tsx`. Produces a deterministic
1213
+ * digest ID suitable for logging and client-safe error references without
1214
+ * leaking stack traces in production.
1215
+ *
1216
+ * Uses djb2 hash (sync, no crypto dependency) per ADR D3.
1217
+ */
1218
+ interface ErrorContext {
1219
+ route?: string;
1220
+ phase?: 'guard' | 'interceptor' | 'handler' | 'filter' | 'agent';
1221
+ source?: string;
1222
+ }
1223
+ interface DigestedError {
1224
+ digest: string;
1225
+ message: string;
1226
+ status: number;
1227
+ context: ErrorContext;
1228
+ stack?: string;
1229
+ }
1230
+ /**
1231
+ * Converts any thrown value into a structured {@link DigestedError}.
1232
+ *
1233
+ * - Sync (never async) — safe to call inside catch blocks.
1234
+ * - Stack trace stripped when `process.env.NODE_ENV === 'production'`.
1235
+ * - Preserves {@link HttpException} status codes.
1236
+ * - Handles non-Error throws (string, number, object).
1237
+ */
1238
+ declare function digestError(err: unknown, context?: ErrorContext): DigestedError;
1239
+
1240
+ /**
1241
+ * Component tree composition — recursive wrapping of file-convention
1242
+ * components (layout, page, loading, error, not-found) into a React
1243
+ * element tree with Suspense and error boundaries.
1244
+ *
1245
+ * Inspired by Next.js `create-component-tree.tsx`.
1246
+ *
1247
+ * React is loaded via dynamic `import('react')` because it is an
1248
+ * optional peerDep of @theokit/http (EC-2).
1249
+ */
1250
+
1251
+ interface RouteTree {
1252
+ layout?: ReactTypes.ComponentType<{
1253
+ children: ReactTypes.ReactNode;
1254
+ }>;
1255
+ page?: ReactTypes.ComponentType;
1256
+ loading?: ReactTypes.ComponentType;
1257
+ error?: ReactTypes.ComponentType;
1258
+ notFound?: ReactTypes.ComponentType;
1259
+ children?: Record<string, RouteTree>;
1260
+ }
1261
+ /**
1262
+ * Composes a {@link RouteTree} into a nested React element tree.
1263
+ *
1264
+ * Wrapping order (outermost → innermost):
1265
+ * layout → ErrorBoundary(error) → Suspense(loading) → page
1266
+ *
1267
+ * Returns `null` when no `page` component is found in the tree.
1268
+ *
1269
+ * @param tree - The route tree describing file conventions found.
1270
+ * @returns A React element or `null`.
1271
+ */
1272
+ declare function composeComponentTree(tree: RouteTree): Promise<ReactTypes.ReactElement | null>;
1273
+
1274
+ /**
1275
+ * Streaming SSR — renders a React element tree to a `ReadableStream<Uint8Array>`.
1276
+ *
1277
+ * Inspired by Next.js `stream-ops.ts`. Uses Web Standard `renderToReadableStream`
1278
+ * (works on Node 18+, Bun, Deno). Falls back to `renderToString` wrapped in a
1279
+ * ReadableStream when `renderToReadableStream` is not available (React 17 — EC-4).
1280
+ *
1281
+ * React is loaded via dynamic `import('react-dom/server')` because react-dom
1282
+ * is an optional peerDep of @theokit/http (ADR D2).
1283
+ */
1284
+
1285
+ interface StreamRenderOptions {
1286
+ /** React element to render */
1287
+ root: ReactTypes.ReactElement;
1288
+ /** Whether to wait for all Suspense to resolve (default: false — stream immediately) */
1289
+ waitForAll?: boolean;
1290
+ }
1291
+ interface StreamRenderResult {
1292
+ /** The HTML stream */
1293
+ stream: ReadableStream<Uint8Array>;
1294
+ /** Promise that resolves when all content has been flushed */
1295
+ allReady: Promise<void>;
1296
+ }
1297
+ /**
1298
+ * Renders a React element to a `ReadableStream<Uint8Array>` (Web Standard).
1299
+ *
1300
+ * 1. Tries `renderToReadableStream` first (React 18+ — Web Standard API).
1301
+ * 2. Falls back to `renderToString` wrapped in a ReadableStream when
1302
+ * `renderToReadableStream` is not available (React 17 compat — EC-4).
1303
+ * 3. Prepends `<!DOCTYPE html>` to the stream.
1304
+ *
1305
+ * **EC-7 — Streaming error handling:** In streaming mode, errors thrown inside
1306
+ * Suspense boundaries are caught by React's streaming error handler and result
1307
+ * in a client-side error boundary activation (the shell is already sent). In
1308
+ * string mode (`renderToString`), errors throw synchronously before any bytes
1309
+ * are sent, allowing a full 500 error page. Choose streaming when you want
1310
+ * progressive rendering; choose string mode when you want atomic error handling.
1311
+ */
1312
+ declare function renderToStream(options: StreamRenderOptions): Promise<StreamRenderResult>;
1313
+ /**
1314
+ * Converts a {@link StreamRenderResult} into a Web Standard `Response`.
1315
+ *
1316
+ * Convenience wrapper for use in request handlers:
1317
+ * ```ts
1318
+ * const result = await renderToStream({ root: <App /> })
1319
+ * return streamToResponse(result)
1320
+ * ```
1321
+ */
1322
+ declare function streamToResponse(result: StreamRenderResult): Response;
1323
+
1324
+ interface RevalidationSignal {
1325
+ kind: 'tag' | 'path';
1326
+ value: string;
1327
+ timestamp: number;
1328
+ }
1329
+ /**
1330
+ * Signal that a cache tag should be revalidated.
1331
+ *
1332
+ * Safe to call from any request handler (controller, agent, action).
1333
+ * Signals are accumulated per-request and consumed by the cache engine.
1334
+ *
1335
+ * @example
1336
+ * ```typescript
1337
+ * import { revalidateTag } from '@theokit/http'
1338
+ *
1339
+ * @Post()
1340
+ * async createTask(@Body(schema) body) {
1341
+ * const task = await db.tasks.create(body)
1342
+ * revalidateTag('tasks') // invalidate cached task lists
1343
+ * return task
1344
+ * }
1345
+ * ```
1346
+ */
1347
+ declare function revalidateTag(tag: string): void;
1348
+ /**
1349
+ * Signal that a cached path should be revalidated.
1350
+ *
1351
+ * @example
1352
+ * ```typescript
1353
+ * import { revalidatePath } from '@theokit/http'
1354
+ *
1355
+ * @Delete(':id')
1356
+ * async removeTask(@Param('id') id: string) {
1357
+ * await db.tasks.delete(id)
1358
+ * revalidatePath('/api/tasks')
1359
+ * }
1360
+ * ```
1361
+ */
1362
+ declare function revalidatePath(path: string): void;
1363
+ /**
1364
+ * Get all revalidation signals collected during the current request.
1365
+ * Called by the cache engine after the handler completes.
1366
+ */
1367
+ declare function getRevalidationSignals(): RevalidationSignal[];
1368
+
1369
+ export { All, type ArgumentsHost, BadGatewayException, BadRequestException, Body, CATCH_EXCEPTIONS, CONTROLLER_PREFIX, type CanActivate, Catch, ConflictException, Controller, type ControllerMeta, type ControllerOptions, type DecoratorHandler, Delete, DiContainer, type DigestedError, type ErrorContext, type ExceptionFilter, type ExecutionContext, ForbiddenException, GatewayTimeoutException, Get, GoneException, Head, Header, Headers, HostParam, HttpCode, HttpDecoratorsConfigError, HttpException, type HttpExceptionOptions, HttpStatus, type HttpStatusCode, type HttpVerb, HttpVersionNotSupportedException, ImATeapotException, type Interceptor, InternalServerErrorException, Ip, type MetadataKey, MethodNotAllowedException, MiddlewareConsumerImpl, NotAcceptableException, NotFoundException, NotImplementedException, Options, Param, type ParamEntry, type ParamSource, Patch, PayloadTooLargeException, Post, PreconditionFailedException, Put, Query, ROUTE_HEADERS, ROUTE_METHODS, ROUTE_PARAMS, ROUTE_REDIRECT, ROUTE_STATUS, Redirect, type RedirectMeta, Reflector, Req, RequestTimeoutException, Res, type RevalidationSignal, type RouteDefinition, type RouteMap, type RouteMethodEntry, type RouteRegistration, type RouteTree, ServiceUnavailableException, Session, SetMetadata, SkipThrottle, type StaticOptions, type StreamRenderOptions, type StreamRenderResult, type SwcCore, type TheoRequestContext, Throttle, type ThrottleOptions, TooManyRequestsException, type TypedClient, TypedClientError, USE_FILTERS, USE_GUARDS, USE_INTERCEPTORS, UnauthorizedException, UnprocessableEntityException, UnsupportedMediaTypeException, UseFilters, UseGuards, UseInterceptors, type WalkResult, composeComponentTree, contract, createDecorator, createDecoratorHandler, createDecoratorServer, createExecutionContext, createStaticHandler, createTypedClient, digestError, getMeta, getMimeType, getRequestContext, getRevalidationSignals, getThrottleOptions, isControllerClass, isSafePath, isThrottleSkipped, joinPath, loadControllerWithSwc, loadControllersFromGlob, registerControllers, renderToStream, resolveDtoSchema, revalidatePath, revalidateTag, runExceptionFilters, runInterceptors, setMeta, streamToResponse, transformControllerSource, tryGetRequestContext, walkControllerMetadata };
package/dist/index.js CHANGED
@@ -2,29 +2,37 @@ import {
2
2
  TheoApp,
3
3
  createStaticHandler,
4
4
  getMimeType,
5
- isSafePath
6
- } from "./chunk-H7V4WQAM.js";
5
+ getRequestContext,
6
+ isSafePath,
7
+ renderToStream,
8
+ streamToResponse,
9
+ tryGetRequestContext
10
+ } from "./chunk-GQ2UH554.js";
7
11
  import {
8
12
  MiddlewareConsumerImpl,
13
+ isControllerClass,
14
+ loadControllerWithSwc,
15
+ loadControllersFromGlob,
9
16
  middlewareMatchesPath,
10
- runMiddleware
11
- } from "./chunk-I2ADDC6C.js";
17
+ runMiddleware,
18
+ transformControllerSource
19
+ } from "./chunk-RC4V75DI.js";
12
20
  import {
13
21
  HttpDecoratorsConfigError,
14
22
  createExecutionContext,
15
23
  joinPath,
16
24
  resolveDtoSchema,
17
25
  walkControllerMetadata
18
- } from "./chunk-5T6NAG62.js";
26
+ } from "./chunk-QGB5YC4T.js";
19
27
  import {
20
28
  createNodeAdapter
21
- } from "./chunk-IHUNGY64.js";
29
+ } from "./chunk-HLW7YKZE.js";
22
30
  import {
23
31
  runInterceptors
24
- } from "./chunk-UVV6THUP.js";
32
+ } from "./chunk-ELCXHPAD.js";
25
33
  import {
26
34
  runExceptionFilters
27
- } from "./chunk-474GXFYG.js";
35
+ } from "./chunk-6W4T4DPJ.js";
28
36
  import {
29
37
  BadGatewayException,
30
38
  BadRequestException,
@@ -61,13 +69,13 @@ import {
61
69
  UnsupportedMediaTypeException,
62
70
  getMeta,
63
71
  setMeta
64
- } from "./chunk-AVSB6444.js";
72
+ } from "./chunk-3PGQVQWG.js";
65
73
  import {
66
74
  resolveOrNew
67
- } from "./chunk-XPFP4PQO.js";
75
+ } from "./chunk-MQAJWR3K.js";
68
76
  import {
69
77
  __name
70
- } from "./chunk-Z56OUB3Z.js";
78
+ } from "./chunk-7QVYU63E.js";
71
79
 
72
80
  // src/decorators/controller.ts
73
81
  function inferPrefix(className) {
@@ -415,7 +423,7 @@ __name(registerControllers, "registerControllers");
415
423
 
416
424
  // src/bridge/create-server.ts
417
425
  import "reflect-metadata";
418
- function createDecoratorServer(controllersOrOpts) {
426
+ function createDecoratorHandler(controllersOrOpts) {
419
427
  const { controllers, container, configure } = Array.isArray(controllersOrOpts) ? {
420
428
  controllers: controllersOrOpts,
421
429
  container: void 0,
@@ -448,9 +456,25 @@ function createDecoratorServer(controllersOrOpts) {
448
456
  if (aP !== bP) return aP ? 1 : -1;
449
457
  return 0;
450
458
  });
451
- const adapter = createNodeAdapter();
452
459
  const handler = /* @__PURE__ */ __name((request) => handleRequest(routes, request, container, middlewareEntries), "handler");
453
- return adapter.createServer(handler);
460
+ handler.matches = (method, pathname) => findRoute(routes, method.toUpperCase(), pathname) !== null;
461
+ return handler;
462
+ }
463
+ __name(createDecoratorHandler, "createDecoratorHandler");
464
+ function createDecoratorServer(controllersOrOpts) {
465
+ const handle = createDecoratorHandler(controllersOrOpts);
466
+ const adapter = createNodeAdapter();
467
+ return adapter.createServer(async (request) => {
468
+ const res = await handle(request);
469
+ if (res) return res;
470
+ const { pathname } = new URL(request.url);
471
+ return jsonResponse(404, {
472
+ error: {
473
+ code: "NOT_FOUND",
474
+ message: `No route for ${request.method.toUpperCase()} ${pathname}`
475
+ }
476
+ });
477
+ });
454
478
  }
455
479
  __name(createDecoratorServer, "createDecoratorServer");
456
480
  async function handleRequest(routes, request, container, middlewareEntries = []) {
@@ -458,14 +482,7 @@ async function handleRequest(routes, request, container, middlewareEntries = [])
458
482
  const method = request.method.toUpperCase();
459
483
  const pathname = url.pathname;
460
484
  const match = findRoute(routes, method, pathname);
461
- if (!match) {
462
- return jsonResponse(404, {
463
- error: {
464
- code: "NOT_FOUND",
465
- message: `No route for ${method} ${pathname}`
466
- }
467
- });
468
- }
485
+ if (!match) return null;
469
486
  const { walk, instance, params } = match;
470
487
  try {
471
488
  const mwResponse = await runMiddleware(middlewareEntries, request, pathname);
@@ -491,6 +508,7 @@ async function handleRequest(routes, request, container, middlewareEntries = [])
491
508
  }
492
509
  const handlerFn = instance[walk.propertyKey];
493
510
  const result = await runInterceptors(walk.interceptors, () => handlerFn.apply(instance, args), request, container);
511
+ if (result instanceof Response) return result;
494
512
  return buildResponse(result, walk, method);
495
513
  } catch (err) {
496
514
  return runExceptionFilters(err, walk.filters, request, container);
@@ -690,6 +708,152 @@ function contract(routes) {
690
708
  return routes;
691
709
  }
692
710
  __name(contract, "contract");
711
+
712
+ // src/error-digest.ts
713
+ function djb2(input) {
714
+ let hash = 5381;
715
+ for (let i = 0; i < input.length; i++) {
716
+ hash = (hash << 5) + hash + input.charCodeAt(i) | 0;
717
+ }
718
+ return (hash >>> 0).toString(16);
719
+ }
720
+ __name(djb2, "djb2");
721
+ function digestError(err, context = {}) {
722
+ const message = extractMessage(err);
723
+ const status = extractStatus(err);
724
+ const rawStack = extractStack(err);
725
+ const digest = djb2(message);
726
+ const isProduction = process.env.NODE_ENV === "production";
727
+ return {
728
+ digest,
729
+ message,
730
+ status,
731
+ context,
732
+ ...rawStack && !isProduction ? {
733
+ stack: rawStack
734
+ } : {}
735
+ };
736
+ }
737
+ __name(digestError, "digestError");
738
+ function extractMessage(err) {
739
+ if (err instanceof Error) {
740
+ return err.message;
741
+ }
742
+ if (typeof err === "string") {
743
+ return err;
744
+ }
745
+ if (typeof err === "number") {
746
+ return String(err);
747
+ }
748
+ try {
749
+ return JSON.stringify(err);
750
+ } catch {
751
+ return "Unknown error";
752
+ }
753
+ }
754
+ __name(extractMessage, "extractMessage");
755
+ function extractStatus(err) {
756
+ if (err instanceof HttpException) {
757
+ return err.statusCode;
758
+ }
759
+ return 500;
760
+ }
761
+ __name(extractStatus, "extractStatus");
762
+ function extractStack(err) {
763
+ if (err instanceof Error) {
764
+ return err.stack;
765
+ }
766
+ return void 0;
767
+ }
768
+ __name(extractStack, "extractStack");
769
+
770
+ // src/component-tree.ts
771
+ function createErrorBoundary(React, FallbackComponent) {
772
+ return class ErrorBoundary extends React.Component {
773
+ static {
774
+ __name(this, "ErrorBoundary");
775
+ }
776
+ constructor(props) {
777
+ super(props);
778
+ this.state = {
779
+ hasError: false
780
+ };
781
+ }
782
+ static getDerivedStateFromError() {
783
+ return {
784
+ hasError: true
785
+ };
786
+ }
787
+ render() {
788
+ if (this.state.hasError) {
789
+ return React.createElement(FallbackComponent);
790
+ }
791
+ return React.createElement(React.Fragment, null, this.props.children);
792
+ }
793
+ };
794
+ }
795
+ __name(createErrorBoundary, "createErrorBoundary");
796
+ async function composeComponentTree(tree) {
797
+ const React = await import("react");
798
+ return composeNode(React, tree);
799
+ }
800
+ __name(composeComponentTree, "composeComponentTree");
801
+ function composeNode(React, node) {
802
+ const { layout: Layout, page: Page, loading: Loading, error: ErrorFallback } = node;
803
+ if (!Page) {
804
+ return null;
805
+ }
806
+ let element = React.createElement(Page);
807
+ if (Loading) {
808
+ element = React.createElement(React.Suspense, {
809
+ fallback: React.createElement(Loading)
810
+ }, element);
811
+ }
812
+ if (ErrorFallback) {
813
+ const Boundary = createErrorBoundary(React, ErrorFallback);
814
+ element = React.createElement(Boundary, null, element);
815
+ }
816
+ if (Layout) {
817
+ element = React.createElement(Layout, null, element);
818
+ }
819
+ return element;
820
+ }
821
+ __name(composeNode, "composeNode");
822
+
823
+ // src/cache-signal.ts
824
+ function revalidateTag(tag) {
825
+ collectSignal({
826
+ kind: "tag",
827
+ value: tag,
828
+ timestamp: Date.now()
829
+ });
830
+ }
831
+ __name(revalidateTag, "revalidateTag");
832
+ function revalidatePath(path) {
833
+ collectSignal({
834
+ kind: "path",
835
+ value: path,
836
+ timestamp: Date.now()
837
+ });
838
+ }
839
+ __name(revalidatePath, "revalidatePath");
840
+ function getRevalidationSignals() {
841
+ const ctx = tryGetRequestContext();
842
+ if (!ctx) return [];
843
+ return ctx._revalidationSignals ?? [];
844
+ }
845
+ __name(getRevalidationSignals, "getRevalidationSignals");
846
+ function collectSignal(signal) {
847
+ const ctx = tryGetRequestContext();
848
+ if (!ctx) {
849
+ console.warn("[theokit] revalidateTag/revalidatePath called outside a request context. Signal ignored.");
850
+ return;
851
+ }
852
+ const extended = ctx;
853
+ extended._revalidationSignals ??= [];
854
+ extended._revalidationSignals.push(signal);
855
+ }
856
+ __name(collectSignal, "collectSignal");
693
857
  export {
694
858
  All,
695
859
  BadGatewayException,
@@ -757,26 +921,40 @@ export {
757
921
  UseFilters,
758
922
  UseGuards,
759
923
  UseInterceptors,
924
+ composeComponentTree,
760
925
  contract,
761
926
  createDecorator,
927
+ createDecoratorHandler,
762
928
  createDecoratorServer,
763
929
  createExecutionContext,
764
930
  createStaticHandler,
765
931
  createTypedClient,
932
+ digestError,
766
933
  getMeta,
767
934
  getMimeType,
935
+ getRequestContext,
936
+ getRevalidationSignals,
768
937
  getThrottleOptions,
938
+ isControllerClass,
769
939
  isSafePath,
770
940
  isThrottleSkipped,
771
941
  joinPath,
942
+ loadControllerWithSwc,
943
+ loadControllersFromGlob,
772
944
  middlewareMatchesPath,
773
945
  registerControllers,
946
+ renderToStream,
774
947
  resolveDtoSchema,
775
948
  resolveOrNew,
949
+ revalidatePath,
950
+ revalidateTag,
776
951
  runExceptionFilters,
777
952
  runInterceptors,
778
953
  runMiddleware,
779
954
  setMeta,
955
+ streamToResponse,
956
+ transformControllerSource,
957
+ tryGetRequestContext,
780
958
  walkControllerMetadata
781
959
  };
782
960
  //# sourceMappingURL=index.js.map