@kabyeon/nexusjs 0.6.6 → 0.6.8

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.
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
5
5
  "/**\n * Metadata keys used by reflect-metadata for storing decorator data.\n *\n * These constants are the contract between decorators and the framework\n * core (DI container, router, validator).\n */\nexport const METADATA_KEY = {\n\t/** Marks a class as a Nest-style controller, stores route prefix. */\n\tCONTROLLER: \"nexus:controller\",\n\n\t/** Marks a class as an injectable provider. */\n\tINJECTABLE: \"nexus:injectable\",\n\n\t/** Marks a class as a repository. */\n\tREPOSITORY: \"nexus:repository\",\n\n\t/** Marks a class as a module. Stores module options. */\n\tMODULE: \"nexus:module\",\n\n\t/** HTTP method routes registered on a controller (Get/Post/...). */\n\tROUTES: \"nexus:routes\",\n\n\t/** Method parameter type metadata (body/query/param/headers/ctx). */\n\tPARAMS: \"nexus:params\",\n\n\t/** Validation schema per method (Zod schema or class). */\n\tVALIDATE: \"nexus:validate\",\n\n\t/** Class-level design:paramtypes (built-in). */\n\tPARAMTYPES: \"design:paramtypes\",\n\n\t/** Class-level design:type (built-in). */\n\tTYPE: \"design:type\",\n\n\t/** Class-level design:returntype (built-in). */\n\tRETURNTYPE: \"design:returntype\",\n\n\t/** Provider token to inject for a parameter (for custom tokens). */\n\tINJECT: \"nexus:inject\",\n} as const;\n\nexport type MetadataKey = (typeof METADATA_KEY)[keyof typeof METADATA_KEY];\n\n/** Available parameter decorator locations. */\nexport const PARAM_TYPES = {\n\tREQUEST: 0,\n\tRESPONSE: 1,\n\tNEXT: 2,\n\tBODY: 3,\n\tQUERY: 4,\n\tPARAM: 5,\n\tHEADERS: 6,\n\tCTX: 7,\n\tUSER: 8,\n} as const;\n\nexport type ParamType = (typeof PARAM_TYPES)[keyof typeof PARAM_TYPES];\n\n/** HTTP methods supported by the router. */\nexport const HTTP_METHODS = [\n\t\"GET\",\n\t\"POST\",\n\t\"PUT\",\n\t\"DELETE\",\n\t\"PATCH\",\n\t\"OPTIONS\",\n\t\"HEAD\",\n] as const;\nexport type HttpMethod = (typeof HTTP_METHODS)[number];\n",
6
6
  "/**\n * @Controller decorator.\n *\n * Marks a class as a controller and registers a route prefix.\n * Routes inside the controller class are decorated with @Get/@Post/etc.\n *\n * @example\n * ```ts\n * @Controller('/users')\n * class UserController {\n * @Get('/')\n * list() { ... }\n * }\n * ```\n */\nimport \"reflect-metadata\";\nimport { METADATA_KEY } from \"../constants.js\";\nimport type { ControllerMetadata } from \"../di/tokens.js\";\n\nexport function Controller(prefix: string = \"/\"): ClassDecorator {\n\treturn (target: object) => {\n\t\tconst normalized = normalizePrefix(prefix);\n\t\tconst meta: ControllerMetadata = { prefix: normalized };\n\t\tReflect.defineMetadata(METADATA_KEY.CONTROLLER, meta, target);\n\t};\n}\n\nexport function getControllerMetadata(target: any): ControllerMetadata {\n\treturn (\n\t\tReflect.getMetadata(METADATA_KEY.CONTROLLER, target) ?? { prefix: \"/\" }\n\t);\n}\n\nexport function isController(target: any): boolean {\n\treturn Reflect.hasMetadata(METADATA_KEY.CONTROLLER, target);\n}\n\n/**\n * Normalize a prefix so we can safely concatenate it with handler paths.\n * - Empty string becomes '/'.\n * - Trailing slashes are trimmed (we re-add them on the join).\n * - No leading slash is added; the router always joins with `/`.\n */\nfunction normalizePrefix(prefix: string): string {\n\tif (!prefix) return \"\";\n\tif (prefix !== \"/\" && prefix.endsWith(\"/\")) {\n\t\treturn prefix.slice(0, -1);\n\t}\n\treturn prefix;\n}\n",
7
- "/**\n * HTTP method decorators.\n *\n * `@Get`, `@Post`, `@Put`, `@Delete`, `@Patch`, `@Options`, `@Head` mark a\n * controller method as a route handler. The path argument is appended to\n * the controller's prefix.\n *\n * @example\n * ```ts\n * @Controller('/users')\n * class UserController {\n * @Get('/')\n * list() {}\n *\n * @Post('/')\n * create(@Body() body: CreateUserDto) {}\n * }\n * ```\n */\nimport \"reflect-metadata\";\nimport { HTTP_METHODS, METADATA_KEY, type HttpMethod } from \"../constants.js\";\nimport type { RouteMetadata } from \"../di/tokens.js\";\n\nfunction defineRoute(method: HttpMethod, path: string): MethodDecorator {\n\treturn (\n\t\ttarget: object,\n\t\tpropertyKey: string | symbol,\n\t\tdescriptor: PropertyDescriptor,\n\t) => {\n\t\tconst routes: RouteMetadata[] =\n\t\t\tReflect.getMetadata(METADATA_KEY.ROUTES, target.constructor) ?? [];\n\n\t\troutes.push({\n\t\t\tmethod,\n\t\t\tpath: normalizePath(path),\n\t\t\tpropertyKey,\n\t\t\thandler: descriptor.value,\n\t\t});\n\n\t\tReflect.defineMetadata(METADATA_KEY.ROUTES, routes, target.constructor);\n\t};\n}\n\nfunction normalizePath(path: string): string {\n\tif (!path || path === \"/\") return \"/\";\n\treturn path.startsWith(\"/\") ? path : `/${path}`;\n}\n\nexport const Get = (path: string = \"/\") => defineRoute(\"GET\", path);\nexport const Post = (path: string = \"/\") => defineRoute(\"POST\", path);\nexport const Put = (path: string = \"/\") => defineRoute(\"PUT\", path);\nexport const Delete = (path: string = \"/\") => defineRoute(\"DELETE\", path);\nexport const Patch = (path: string = \"/\") => defineRoute(\"PATCH\", path);\nexport const Options = (path: string = \"/\") => defineRoute(\"OPTIONS\", path);\nexport const Head = (path: string = \"/\") => defineRoute(\"HEAD\", path);\n\nexport function getRoutes(target: any): RouteMetadata[] {\n\treturn Reflect.getMetadata(METADATA_KEY.ROUTES, target) ?? [];\n}\n\nexport { HTTP_METHODS };\nexport type { RouteMetadata };\n",
7
+ "/**\n * HTTP method decorators.\n *\n * `@Get`, `@Post`, `@Put`, `@Delete`, `@Patch`, `@Options`, `@Head` mark a\n * controller method as a route handler. The path argument is appended to\n * the controller's prefix.\n *\n * @example\n * ```ts\n * @Controller('/users')\n * class UserController {\n * @Get('/')\n * list() {}\n *\n * @Post('/')\n * create(@Body() body: CreateUserDto) {}\n * }\n * ```\n */\nimport \"reflect-metadata\";\nimport { HTTP_METHODS, METADATA_KEY, type HttpMethod } from \"../constants.js\";\nimport type { RouteMetadata } from \"../di/tokens.js\";\n\nfunction defineRoute(method: HttpMethod, path: string): MethodDecorator {\n\treturn (\n\t\ttarget: object,\n\t\tpropertyKey: string | symbol,\n\t\tdescriptor: TypedPropertyDescriptor<any>,\n\t) => {\n\t\tconst routes: RouteMetadata[] =\n\t\t\tReflect.getMetadata(METADATA_KEY.ROUTES, target.constructor) ?? [];\n\n\t\troutes.push({\n\t\t\tmethod,\n\t\t\tpath: normalizePath(path),\n\t\t\tpropertyKey,\n\t\t\thandler: descriptor.value,\n\t\t});\n\n\t\tReflect.defineMetadata(METADATA_KEY.ROUTES, routes, target.constructor);\n\t};\n}\n\nfunction normalizePath(path: string): string {\n\tif (!path || path === \"/\") return \"/\";\n\treturn path.startsWith(\"/\") ? path : `/${path}`;\n}\n\nexport const Get = (path: string = \"/\") => defineRoute(\"GET\", path);\nexport const Post = (path: string = \"/\") => defineRoute(\"POST\", path);\nexport const Put = (path: string = \"/\") => defineRoute(\"PUT\", path);\nexport const Delete = (path: string = \"/\") => defineRoute(\"DELETE\", path);\nexport const Patch = (path: string = \"/\") => defineRoute(\"PATCH\", path);\nexport const Options = (path: string = \"/\") => defineRoute(\"OPTIONS\", path);\nexport const Head = (path: string = \"/\") => defineRoute(\"HEAD\", path);\n\nexport function getRoutes(target: any): RouteMetadata[] {\n\treturn Reflect.getMetadata(METADATA_KEY.ROUTES, target) ?? [];\n}\n\nexport { HTTP_METHODS };\nexport type { RouteMetadata };\n",
8
8
  "/**\n * Parameter decorators.\n *\n * These mark a controller method argument as a source of request data:\n * - `@Req()` → Hono context\n * - `@Res()` → Response helper\n * - `@Next()` → next() callback (for middleware-style handlers)\n * - `@Body()` → request body (parsed)\n * - `@Query('key')` → a single query param, or full query object\n * - `@Param('key')` → a single path param, or full params object\n * - `@Headers('k')` → a single header, or full headers object\n * - `@Ctx()` → Hono context (alias for @Req)\n * - `@User()` → authenticated user (resolved via auth provider)\n *\n * The metadata is read by the router at mount time to build the\n * handler invocation list.\n */\nimport \"reflect-metadata\";\nimport { METADATA_KEY, PARAM_TYPES } from \"../constants.js\";\nimport type { ParamMetadata } from \"../di/tokens.js\";\n\nexport function createParamDecorator(\n\ttype: number,\n\tdata?: string | object,\n): ParameterDecorator {\n\treturn (\n\t\ttarget: object,\n\t\tpropertyKey: string | symbol | undefined,\n\t\tparameterIndex: number,\n\t) => {\n\t\t// Method parameter: target is the prototype, propertyKey is the method name.\n\t\t// Constructor parameter: target is the class, propertyKey is undefined.\n\t\tif (propertyKey !== undefined) {\n\t\t\tconst params: ParamMetadata[] =\n\t\t\t\tReflect.getMetadata(METADATA_KEY.PARAMS, target, propertyKey) ?? [];\n\t\t\tparams.push({\n\t\t\t\tindex: parameterIndex,\n\t\t\t\ttype,\n\t\t\t\tname: typeof data === \"string\" ? data : undefined,\n\t\t\t\tdata: typeof data === \"object\" ? data : undefined,\n\t\t\t});\n\t\t\tReflect.defineMetadata(METADATA_KEY.PARAMS, params, target, propertyKey);\n\t\t} else {\n\t\t\tconst params: ParamMetadata[] =\n\t\t\t\tReflect.getMetadata(METADATA_KEY.PARAMS, target) ?? [];\n\t\t\tparams.push({\n\t\t\t\tindex: parameterIndex,\n\t\t\t\ttype,\n\t\t\t\tname: typeof data === \"string\" ? data : undefined,\n\t\t\t\tdata: typeof data === \"object\" ? data : undefined,\n\t\t\t});\n\t\t\tReflect.defineMetadata(METADATA_KEY.PARAMS, params, target);\n\t\t}\n\t};\n}\n\nexport const Req = () => createParamDecorator(PARAM_TYPES.REQUEST);\nexport const Res = () => createParamDecorator(PARAM_TYPES.RESPONSE);\nexport const Next = () => createParamDecorator(PARAM_TYPES.NEXT);\nexport const Body = (key?: string) =>\n\tcreateParamDecorator(PARAM_TYPES.BODY, key);\nexport const Query = (key?: string) =>\n\tcreateParamDecorator(PARAM_TYPES.QUERY, key);\nexport const Param = (key?: string) =>\n\tcreateParamDecorator(PARAM_TYPES.PARAM, key);\nexport const Headers = (key?: string) =>\n\tcreateParamDecorator(PARAM_TYPES.HEADERS, key);\nexport const Ctx = () => createParamDecorator(PARAM_TYPES.CTX);\nexport const User = () => createParamDecorator(PARAM_TYPES.USER);\n\nexport function getParamMetadata(\n\ttarget: any,\n\tpropertyKey: string | symbol,\n): ParamMetadata[] {\n\treturn Reflect.getMetadata(METADATA_KEY.PARAMS, target, propertyKey) ?? [];\n}\n\nexport { PARAM_TYPES };\n",
9
9
  "/**\n * @Validate decorator.\n *\n * Attaches Zod schemas (or class validators) to a route handler. Each\n * schema is run against the corresponding request part before the handler\n * executes; failed validation throws or returns a 400 response.\n *\n * @example\n * ```ts\n * const UserSchema = z.object({ name: z.string(), email: z.email() });\n *\n * @Post('/')\n * @Validate({ body: UserSchema })\n * create(@Body() body: z.infer<typeof UserSchema>) { ... }\n * ```\n */\nimport \"reflect-metadata\";\nimport { METADATA_KEY } from \"../constants.js\";\nimport type { ValidationMetadata } from \"../di/tokens.js\";\n\nexport function Validate(options: ValidationMetadata): MethodDecorator {\n\treturn (\n\t\ttarget: object,\n\t\tpropertyKey: string | symbol,\n\t\tdescriptor: PropertyDescriptor,\n\t) => {\n\t\tReflect.defineMetadata(\n\t\t\tMETADATA_KEY.VALIDATE,\n\t\t\toptions,\n\t\t\ttarget.constructor,\n\t\t\tpropertyKey,\n\t\t);\n\t};\n}\n\nexport function getValidationMetadata(\n\ttarget: any,\n\tpropertyKey: string | symbol,\n): ValidationMetadata | undefined {\n\treturn Reflect.getMetadata(METADATA_KEY.VALIDATE, target, propertyKey);\n}\n",
10
10
  "/**\n * `TracingService` — the framework's distributed-tracing primitive.\n *\n * Design:\n * 1. The OpenTelemetry API (`@opentelemetry/api`) is the only\n * required dependency; it's ~7kb and provides the no-op default\n * tracer when the SDK is not configured.\n * 2. The OTel SDK is **lazy-loaded**: only when the user calls\n * `TracingModule.forRoot(...)` is `@opentelemetry/sdk-node` and\n * the configured exporter imported. This keeps the bundle small\n * for users who don't trace.\n * 3. Without `forRoot()`, the service returns no-op spans. They have\n * valid `traceId` / `spanId` (the OTel no-op span id format) so\n * log lines and error reports don't need to special-case \"not\n * configured\".\n *\n * Public API:\n * - `startSpan(name, options?)` — create a new active span\n * - `withSpan(name, fn, options?)` — run `fn` inside a span, return its result\n * - `getCurrentTraceId()` / `getCurrentSpanId()` — read the active context\n * - `extractContext(headers)` / `injectContext(headers)` — W3C trace context\n * - `getSpans()` — read the in-memory span recorder (always available,\n * used for tests and for the `console` exporter)\n * - `reset()` — clear the in-memory recorder\n *\n * The service is registered in the DI container as a singleton. The\n * framework does **not** call `trace.getTracer` until something\n * actually starts a span.\n */\n\nimport {\n\tcontext as otelContext,\n\tpropagation,\n\tSpanStatusCode,\n\tSpanKind,\n\ttrace,\n\ttype Context,\n\ttype Span as OtelSpan,\n\ttype Tracer,\n} from \"@opentelemetry/api\";\nimport type {\n\tActiveSpan,\n\tFinishedSpan,\n\tSpanContext,\n\tSpanOptions,\n\tSpanStatus,\n} from \"./types.js\";\n\n/* ------------------------------------------------------------------ *\n * In-memory recorder\n * ------------------------------------------------------------------ */\n\nexport class InMemorySpanRecorder {\n\tprivate finished: FinishedSpan[] = [];\n\tprivate nextEventCounter = 0;\n\n\t/** Append a finished span. */\n\trecord(span: FinishedSpan): void {\n\t\tthis.finished.push(span);\n\t\tthis.nextEventCounter++;\n\t}\n\n\t/** Return all finished spans (most recent last). */\n\tgetAll(): FinishedSpan[] {\n\t\treturn this.finished;\n\t}\n\n\t/** Return only the spans whose `name` matches. */\n\tfindByName(name: string): FinishedSpan[] {\n\t\treturn this.finished.filter((s) => s.name === name);\n\t}\n\n\t/** Clear the recorder. */\n\tclear(): void {\n\t\tthis.finished = [];\n\t\tthis.nextEventCounter = 0;\n\t}\n\n\t/** Number of recorded spans. */\n\tget size(): number {\n\t\treturn this.finished.length;\n\t}\n}\n\n/* ------------------------------------------------------------------ *\n * ActiveSpan wrapper around OTel span\n * ------------------------------------------------------------------ */\n\nclass OtelActiveSpan implements ActiveSpan {\n\tconstructor(\n\t\tpublic readonly name: string,\n\t\tpublic readonly traceId: string,\n\t\tpublic readonly spanId: string,\n\t\tpublic readonly isRecording: boolean,\n\t\tprivate readonly otelSpan: OtelSpan,\n\t) {}\n\n\tsetAttribute(key: string, value: string | number | boolean): void {\n\t\tthis.otelSpan.setAttribute(key, value);\n\t}\n\n\tsetAttributes(attributes: Record<string, string | number | boolean>): void {\n\t\tthis.otelSpan.setAttributes(attributes);\n\t}\n\n\taddEvent(name: string, attributes?: Record<string, unknown>): void {\n\t\tthis.otelSpan.addEvent(name, attributes as never);\n\t}\n\n\trecordException(err: unknown): void {\n\t\tif (err instanceof Error) {\n\t\t\tthis.otelSpan.recordException(err);\n\t\t} else {\n\t\t\tthis.otelSpan.recordException(new Error(String(err)));\n\t\t}\n\t}\n\n\tsetStatus(status: \"ok\" | \"error\" | \"unset\", description?: string): void {\n\t\tconst code =\n\t\t\tstatus === \"ok\"\n\t\t\t\t? SpanStatusCode.OK\n\t\t\t\t: status === \"error\"\n\t\t\t\t\t? SpanStatusCode.ERROR\n\t\t\t\t\t: SpanStatusCode.UNSET;\n\t\tthis.otelSpan.setStatus({ code, message: description });\n\t}\n\n\tend(): void {\n\t\tthis.otelSpan.end();\n\t}\n}\n\n/* ------------------------------------------------------------------ *\n * TracingService\n * ------------------------------------------------------------------ */\n\nexport const TRACING_SERVICE_TOKEN = Symbol.for(\"nexus:TracingService\");\n\n/**\n * Global registry of the active `TracingService` instance.\n * The framework's `TracingModule.forRoot()` calls `setTracingService()`.\n * Decorators that need a `TracingService` (e.g. `@Trace()`) call\n * `getTracingService()` to look it up without DI plumbing.\n */\nlet _current: TracingService | undefined;\n\nexport function setTracingService(service: TracingService): void {\n\t_current = service;\n}\n\nexport function getTracingService(): TracingService | undefined {\n\treturn _current;\n}\n\nexport class TracingService {\n\treadonly tracer: Tracer;\n\tprivate readonly recorder = new InMemorySpanRecorder();\n\tprivate sdkStop?: () => Promise<void>;\n\tprivate initialized = false;\n\n\tconstructor() {\n\t\t// Default OTel tracer: \"nexusjs\". Even with no SDK, this returns\n\t\t// a no-op tracer that produces no-op spans — never throws.\n\t\tthis.tracer = trace.getTracer(\"nexusjs\", \"0.4.0\");\n\t}\n\n\t/** True if the SDK has been started (i.e. `forRoot()` was called). */\n\tget isInitialized(): boolean {\n\t\treturn this.initialized;\n\t}\n\n\t/** Read all finished spans recorded so far. */\n\tgetSpans(): FinishedSpan[] {\n\t\treturn this.recorder.getAll();\n\t}\n\n\t/** Find spans by name. */\n\tfindSpans(name: string): FinishedSpan[] {\n\t\treturn this.recorder.findByName(name);\n\t}\n\n\t/** Clear the in-memory recorder (and the SDK's batch, if any). */\n\tclearSpans(): void {\n\t\tthis.recorder.clear();\n\t}\n\n\t/* ---------------- span lifecycle ---------------- */\n\n\tstartSpan(name: string, options: SpanOptions = {}): ActiveSpan {\n\t\tconst kind = toOtelKind(options.kind ?? \"internal\");\n\t\tconst otelSpan = this.tracer.startSpan(name, {\n\t\t\tkind,\n\t\t\tattributes: options.attributes as never,\n\t\t\tstartTime: options.startTime,\n\t\t});\n\n\t\tconst ctx = otelSpan.spanContext();\n\t\treturn new OtelActiveSpan(\n\t\t\tname,\n\t\t\tctx.traceId,\n\t\t\tctx.spanId,\n\t\t\totelSpan.isRecording(),\n\t\t\totelSpan,\n\t\t);\n\t}\n\n\t/** Run `fn` inside a new span. Returns the result of `fn`. */\n\tasync withSpan<T>(\n\t\tname: string,\n\t\tfn: (span: ActiveSpan) => Promise<T> | T,\n\t\toptions: SpanOptions = {},\n\t): Promise<T> {\n\t\tconst span = this.startSpan(name, options);\n\t\tconst ctx = trace.setSpan(otelContext.active(), (span as OtelActiveSpan)[\"otelSpan\"] as OtelSpan);\n\t\ttry {\n\t\t\tconst result = await otelContext.with(ctx, () => fn(span));\n\t\t\tif (span.isRecording) span.setStatus(\"ok\");\n\t\t\treturn result;\n\t\t} catch (err) {\n\t\t\tif (span.isRecording) {\n\t\t\t\tspan.recordException(err);\n\t\t\t\tspan.setStatus(\"error\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\tthrow err;\n\t\t} finally {\n\t\t\tspan.end();\n\t\t}\n\t}\n\n\t/** Synchronous version of `withSpan`. */\n\twithSpanSync<T>(name: string, fn: (span: ActiveSpan) => T, options: SpanOptions = {}): T {\n\t\tconst span = this.startSpan(name, options);\n\t\ttry {\n\t\t\tconst result = fn(span);\n\t\t\tif (span.isRecording) span.setStatus(\"ok\");\n\t\t\treturn result;\n\t\t} catch (err) {\n\t\t\tif (span.isRecording) {\n\t\t\t\tspan.recordException(err);\n\t\t\t\tspan.setStatus(\"error\", err instanceof Error ? err.message : String(err));\n\t\t\t}\n\t\t\tthrow err;\n\t\t} finally {\n\t\t\tspan.end();\n\t\t}\n\t}\n\n\t/* ---------------- context propagation ---------------- */\n\n\t/** Get the trace id of the active span, or `undefined`. */\n\tgetCurrentTraceId(): string | undefined {\n\t\tconst ctx = trace.getSpan(otelContext.active())?.spanContext();\n\t\treturn ctx?.traceId;\n\t}\n\n\t/** Get the span id of the active span, or `undefined`. */\n\tgetCurrentSpanId(): string | undefined {\n\t\tconst ctx = trace.getSpan(otelContext.active())?.spanContext();\n\t\treturn ctx?.spanId;\n\t}\n\n\t/** Get the current OTel `Context`. */\n\tgetCurrentContext(): Context {\n\t\treturn otelContext.active();\n\t}\n\n\t/**\n\t * Extract a span context from incoming HTTP headers.\n\t * Reads `traceparent` (W3C) and `x-b3-*` (B3 single) when present.\n\t * Returns `undefined` if no recognizable header is found.\n\t */\n\textractContext(headers: Record<string, string | string[] | undefined>): Context {\n\t\tconst flat = flattenHeaders(headers);\n\t\treturn propagation.extract(otelContext.active(), flat);\n\t}\n\n\t/**\n\t * Inject the active span context into outgoing HTTP headers.\n\t * Writes `traceparent` (W3C) by default.\n\t */\n\tinjectContext(headers: Record<string, string> = {}): Record<string, string> {\n\t\tconst out: Record<string, string> = { ...headers };\n\t\tpropagation.inject(otelContext.active(), out);\n\t\treturn out;\n\t}\n\n\t/* ---------------- SDK bootstrap (called by module) ---------------- */\n\n\t/**\n\t * Initialize the OpenTelemetry SDK with the given configuration.\n\t * This is called by `TracingModule.forRoot()`. It's idempotent.\n\t */\n\tasync startSdk(config: import(\"./types.js\").TracingConfig): Promise<void> {\n\t\tif (this.initialized) return;\n\n\t\tconst serviceName = config.serviceName ?? process.env.OTEL_SERVICE_NAME ?? \"nexusjs\";\n\t\tconst endpoint = config.endpoint ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? \"http://localhost:4318\";\n\t\tconst sampleRatio = config.sampleRatio ?? 1.0;\n\n\t\t// Lazy import the SDK so apps that don't use tracing don't pay the cost.\n\t\t// The SDK packages are optional peer dependencies; we resolve them\n\t\t// dynamically so users who don't trace don't need them.\n\t\tlet NodeSDK: any, OTLPTraceExporter: any, Resource: any, SemanticResourceAttributes: any;\n\t\ttry {\n\t\t\t// @ts-ignore - optional peer dep\n\t\t\t({ NodeSDK } = await import(\"@opentelemetry/sdk-node\"));\n\t\t\t// @ts-ignore - optional peer dep\n\t\t\t({ OTLPTraceExporter } = await import(\"@opentelemetry/exporter-trace-otlp-http\"));\n\t\t\t// @ts-ignore - optional peer dep\n\t\t\t({ Resource } = await import(\"@opentelemetry/resources\"));\n\t\t\tconst semconv = await import(\"@opentelemetry/semantic-conventions\");\n\t\t\tSemanticResourceAttributes = (semconv as any).SemanticResourceAttributes ?? semconv;\n\t\t} catch (err) {\n\t\t\tthrow new Error(\n\t\t\t\t\"TracingModule.forRoot() requires the OTel SDK packages. \" +\n\t\t\t\t\t\"Install with: bun add @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources @opentelemetry/semantic-conventions\",\n\t\t\t);\n\t\t}\n\n\t\t// Build the resource\n\t\tconst resourceAttrs: Record<string, string> = {\n\t\t\t[SemanticResourceAttributes.SERVICE_NAME ?? \"service.name\"]: serviceName,\n\t\t\t[SemanticResourceAttributes.SERVICE_VERSION ?? \"service.version\"]:\n\t\t\t\tconfig.serviceVersion ?? \"0.0.0\",\n\t\t\t[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT ?? \"deployment.environment\"]:\n\t\t\t\tconfig.environment ?? process.env.NODE_ENV ?? \"development\",\n\t\t\t...config.resourceAttributes,\n\t\t};\n\t\tconst resource = new Resource(resourceAttrs);\n\n\t\t// Pick the exporter\n\t\tlet traceExporter: any;\n\t\tif (config.exporter === \"console\" || config.exporter === \"memory\" || !config.exporter) {\n\t\t\t// Console exporter: print to stdout. (Bun has no in-process\n\t\t\t// console exporter, so we use a simple OTel-compatible one\n\t\t\t// via the in-memory recorder.)\n\t\t\ttraceExporter = undefined; // We use the recorder for the in-memory case\n\t\t} else {\n\t\t\ttraceExporter = new OTLPTraceExporter({ url: `${endpoint.replace(/\\/$/, \"\")}/v1/traces` });\n\t\t}\n\n\t\t// Build the SDK\n\t\tconst sdk = new NodeSDK({\n\t\t\tresource,\n\t\t\ttraceExporter,\n\t\t\tsampler: {\n\t\t\t\t// Custom simple ratio sampler to avoid pulling the full sampler package.\n\t\t\t\tshouldSample: () => ({\n\t\t\t\t\tdecision: Math.random() < sampleRatio ? 1 : 0,\n\t\t\t\t}),\n\t\t\t\ttoString: () => `RatioSampler(${sampleRatio})`,\n\t\t\t},\n\t\t});\n\t\tsdk.start();\n\n\t\tthis.sdkStop = async () => {\n\t\t\ttry {\n\t\t\t\tawait sdk.shutdown();\n\t\t\t} catch {\n\t\t\t\t/* ignore shutdown errors */\n\t\t\t}\n\t\t};\n\n\t\tthis.initialized = true;\n\t}\n\n\t/** Stop the SDK. Called on process exit / app shutdown. */\n\tasync stopSdk(): Promise<void> {\n\t\tif (this.sdkStop) {\n\t\t\tawait this.sdkStop();\n\t\t\tthis.sdkStop = undefined;\n\t\t}\n\t\tthis.initialized = false;\n\t}\n}\n\n/* ------------------------------------------------------------------ *\n * Helpers\n * ------------------------------------------------------------------ */\n\nfunction toOtelKind(kind: NonNullable<SpanOptions[\"kind\"]>): SpanKind {\n\tswitch (kind) {\n\t\tcase \"server\":\n\t\t\treturn SpanKind.SERVER;\n\t\tcase \"client\":\n\t\t\treturn SpanKind.CLIENT;\n\t\tcase \"producer\":\n\t\t\treturn SpanKind.PRODUCER;\n\t\tcase \"consumer\":\n\t\t\treturn SpanKind.CONSUMER;\n\t\tdefault:\n\t\t\treturn SpanKind.INTERNAL;\n\t}\n}\n\nfunction flattenHeaders(\n\theaders: Record<string, string | string[] | undefined>,\n): Record<string, string> {\n\tconst out: Record<string, string> = {};\n\tfor (const [k, v] of Object.entries(headers)) {\n\t\tif (v === undefined) continue;\n\t\tout[k.toLowerCase()] = Array.isArray(v) ? v.join(\",\") : v;\n\t}\n\treturn out;\n}\n\n/** Re-export for downstream type users. */\nexport type { SpanContext, SpanStatus };\n",
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
5
5
  "/**\n * Metadata keys used by reflect-metadata for storing decorator data.\n *\n * These constants are the contract between decorators and the framework\n * core (DI container, router, validator).\n */\nexport const METADATA_KEY = {\n\t/** Marks a class as a Nest-style controller, stores route prefix. */\n\tCONTROLLER: \"nexus:controller\",\n\n\t/** Marks a class as an injectable provider. */\n\tINJECTABLE: \"nexus:injectable\",\n\n\t/** Marks a class as a repository. */\n\tREPOSITORY: \"nexus:repository\",\n\n\t/** Marks a class as a module. Stores module options. */\n\tMODULE: \"nexus:module\",\n\n\t/** HTTP method routes registered on a controller (Get/Post/...). */\n\tROUTES: \"nexus:routes\",\n\n\t/** Method parameter type metadata (body/query/param/headers/ctx). */\n\tPARAMS: \"nexus:params\",\n\n\t/** Validation schema per method (Zod schema or class). */\n\tVALIDATE: \"nexus:validate\",\n\n\t/** Class-level design:paramtypes (built-in). */\n\tPARAMTYPES: \"design:paramtypes\",\n\n\t/** Class-level design:type (built-in). */\n\tTYPE: \"design:type\",\n\n\t/** Class-level design:returntype (built-in). */\n\tRETURNTYPE: \"design:returntype\",\n\n\t/** Provider token to inject for a parameter (for custom tokens). */\n\tINJECT: \"nexus:inject\",\n} as const;\n\nexport type MetadataKey = (typeof METADATA_KEY)[keyof typeof METADATA_KEY];\n\n/** Available parameter decorator locations. */\nexport const PARAM_TYPES = {\n\tREQUEST: 0,\n\tRESPONSE: 1,\n\tNEXT: 2,\n\tBODY: 3,\n\tQUERY: 4,\n\tPARAM: 5,\n\tHEADERS: 6,\n\tCTX: 7,\n\tUSER: 8,\n} as const;\n\nexport type ParamType = (typeof PARAM_TYPES)[keyof typeof PARAM_TYPES];\n\n/** HTTP methods supported by the router. */\nexport const HTTP_METHODS = [\n\t\"GET\",\n\t\"POST\",\n\t\"PUT\",\n\t\"DELETE\",\n\t\"PATCH\",\n\t\"OPTIONS\",\n\t\"HEAD\",\n] as const;\nexport type HttpMethod = (typeof HTTP_METHODS)[number];\n",
6
6
  "/**\n * @Controller decorator.\n *\n * Marks a class as a controller and registers a route prefix.\n * Routes inside the controller class are decorated with @Get/@Post/etc.\n *\n * @example\n * ```ts\n * @Controller('/users')\n * class UserController {\n * @Get('/')\n * list() { ... }\n * }\n * ```\n */\nimport \"reflect-metadata\";\nimport { METADATA_KEY } from \"../constants.js\";\nimport type { ControllerMetadata } from \"../di/tokens.js\";\n\nexport function Controller(prefix: string = \"/\"): ClassDecorator {\n\treturn (target: object) => {\n\t\tconst normalized = normalizePrefix(prefix);\n\t\tconst meta: ControllerMetadata = { prefix: normalized };\n\t\tReflect.defineMetadata(METADATA_KEY.CONTROLLER, meta, target);\n\t};\n}\n\nexport function getControllerMetadata(target: any): ControllerMetadata {\n\treturn (\n\t\tReflect.getMetadata(METADATA_KEY.CONTROLLER, target) ?? { prefix: \"/\" }\n\t);\n}\n\nexport function isController(target: any): boolean {\n\treturn Reflect.hasMetadata(METADATA_KEY.CONTROLLER, target);\n}\n\n/**\n * Normalize a prefix so we can safely concatenate it with handler paths.\n * - Empty string becomes '/'.\n * - Trailing slashes are trimmed (we re-add them on the join).\n * - No leading slash is added; the router always joins with `/`.\n */\nfunction normalizePrefix(prefix: string): string {\n\tif (!prefix) return \"\";\n\tif (prefix !== \"/\" && prefix.endsWith(\"/\")) {\n\t\treturn prefix.slice(0, -1);\n\t}\n\treturn prefix;\n}\n",
7
- "/**\n * HTTP method decorators.\n *\n * `@Get`, `@Post`, `@Put`, `@Delete`, `@Patch`, `@Options`, `@Head` mark a\n * controller method as a route handler. The path argument is appended to\n * the controller's prefix.\n *\n * @example\n * ```ts\n * @Controller('/users')\n * class UserController {\n * @Get('/')\n * list() {}\n *\n * @Post('/')\n * create(@Body() body: CreateUserDto) {}\n * }\n * ```\n */\nimport \"reflect-metadata\";\nimport { HTTP_METHODS, METADATA_KEY, type HttpMethod } from \"../constants.js\";\nimport type { RouteMetadata } from \"../di/tokens.js\";\n\nfunction defineRoute(method: HttpMethod, path: string): MethodDecorator {\n\treturn (\n\t\ttarget: object,\n\t\tpropertyKey: string | symbol,\n\t\tdescriptor: PropertyDescriptor,\n\t) => {\n\t\tconst routes: RouteMetadata[] =\n\t\t\tReflect.getMetadata(METADATA_KEY.ROUTES, target.constructor) ?? [];\n\n\t\troutes.push({\n\t\t\tmethod,\n\t\t\tpath: normalizePath(path),\n\t\t\tpropertyKey,\n\t\t\thandler: descriptor.value,\n\t\t});\n\n\t\tReflect.defineMetadata(METADATA_KEY.ROUTES, routes, target.constructor);\n\t};\n}\n\nfunction normalizePath(path: string): string {\n\tif (!path || path === \"/\") return \"/\";\n\treturn path.startsWith(\"/\") ? path : `/${path}`;\n}\n\nexport const Get = (path: string = \"/\") => defineRoute(\"GET\", path);\nexport const Post = (path: string = \"/\") => defineRoute(\"POST\", path);\nexport const Put = (path: string = \"/\") => defineRoute(\"PUT\", path);\nexport const Delete = (path: string = \"/\") => defineRoute(\"DELETE\", path);\nexport const Patch = (path: string = \"/\") => defineRoute(\"PATCH\", path);\nexport const Options = (path: string = \"/\") => defineRoute(\"OPTIONS\", path);\nexport const Head = (path: string = \"/\") => defineRoute(\"HEAD\", path);\n\nexport function getRoutes(target: any): RouteMetadata[] {\n\treturn Reflect.getMetadata(METADATA_KEY.ROUTES, target) ?? [];\n}\n\nexport { HTTP_METHODS };\nexport type { RouteMetadata };\n",
7
+ "/**\n * HTTP method decorators.\n *\n * `@Get`, `@Post`, `@Put`, `@Delete`, `@Patch`, `@Options`, `@Head` mark a\n * controller method as a route handler. The path argument is appended to\n * the controller's prefix.\n *\n * @example\n * ```ts\n * @Controller('/users')\n * class UserController {\n * @Get('/')\n * list() {}\n *\n * @Post('/')\n * create(@Body() body: CreateUserDto) {}\n * }\n * ```\n */\nimport \"reflect-metadata\";\nimport { HTTP_METHODS, METADATA_KEY, type HttpMethod } from \"../constants.js\";\nimport type { RouteMetadata } from \"../di/tokens.js\";\n\nfunction defineRoute(method: HttpMethod, path: string): MethodDecorator {\n\treturn (\n\t\ttarget: object,\n\t\tpropertyKey: string | symbol,\n\t\tdescriptor: TypedPropertyDescriptor<any>,\n\t) => {\n\t\tconst routes: RouteMetadata[] =\n\t\t\tReflect.getMetadata(METADATA_KEY.ROUTES, target.constructor) ?? [];\n\n\t\troutes.push({\n\t\t\tmethod,\n\t\t\tpath: normalizePath(path),\n\t\t\tpropertyKey,\n\t\t\thandler: descriptor.value,\n\t\t});\n\n\t\tReflect.defineMetadata(METADATA_KEY.ROUTES, routes, target.constructor);\n\t};\n}\n\nfunction normalizePath(path: string): string {\n\tif (!path || path === \"/\") return \"/\";\n\treturn path.startsWith(\"/\") ? path : `/${path}`;\n}\n\nexport const Get = (path: string = \"/\") => defineRoute(\"GET\", path);\nexport const Post = (path: string = \"/\") => defineRoute(\"POST\", path);\nexport const Put = (path: string = \"/\") => defineRoute(\"PUT\", path);\nexport const Delete = (path: string = \"/\") => defineRoute(\"DELETE\", path);\nexport const Patch = (path: string = \"/\") => defineRoute(\"PATCH\", path);\nexport const Options = (path: string = \"/\") => defineRoute(\"OPTIONS\", path);\nexport const Head = (path: string = \"/\") => defineRoute(\"HEAD\", path);\n\nexport function getRoutes(target: any): RouteMetadata[] {\n\treturn Reflect.getMetadata(METADATA_KEY.ROUTES, target) ?? [];\n}\n\nexport { HTTP_METHODS };\nexport type { RouteMetadata };\n",
8
8
  "/**\n * Parameter decorators.\n *\n * These mark a controller method argument as a source of request data:\n * - `@Req()` → Hono context\n * - `@Res()` → Response helper\n * - `@Next()` → next() callback (for middleware-style handlers)\n * - `@Body()` → request body (parsed)\n * - `@Query('key')` → a single query param, or full query object\n * - `@Param('key')` → a single path param, or full params object\n * - `@Headers('k')` → a single header, or full headers object\n * - `@Ctx()` → Hono context (alias for @Req)\n * - `@User()` → authenticated user (resolved via auth provider)\n *\n * The metadata is read by the router at mount time to build the\n * handler invocation list.\n */\nimport \"reflect-metadata\";\nimport { METADATA_KEY, PARAM_TYPES } from \"../constants.js\";\nimport type { ParamMetadata } from \"../di/tokens.js\";\n\nexport function createParamDecorator(\n\ttype: number,\n\tdata?: string | object,\n): ParameterDecorator {\n\treturn (\n\t\ttarget: object,\n\t\tpropertyKey: string | symbol | undefined,\n\t\tparameterIndex: number,\n\t) => {\n\t\t// Method parameter: target is the prototype, propertyKey is the method name.\n\t\t// Constructor parameter: target is the class, propertyKey is undefined.\n\t\tif (propertyKey !== undefined) {\n\t\t\tconst params: ParamMetadata[] =\n\t\t\t\tReflect.getMetadata(METADATA_KEY.PARAMS, target, propertyKey) ?? [];\n\t\t\tparams.push({\n\t\t\t\tindex: parameterIndex,\n\t\t\t\ttype,\n\t\t\t\tname: typeof data === \"string\" ? data : undefined,\n\t\t\t\tdata: typeof data === \"object\" ? data : undefined,\n\t\t\t});\n\t\t\tReflect.defineMetadata(METADATA_KEY.PARAMS, params, target, propertyKey);\n\t\t} else {\n\t\t\tconst params: ParamMetadata[] =\n\t\t\t\tReflect.getMetadata(METADATA_KEY.PARAMS, target) ?? [];\n\t\t\tparams.push({\n\t\t\t\tindex: parameterIndex,\n\t\t\t\ttype,\n\t\t\t\tname: typeof data === \"string\" ? data : undefined,\n\t\t\t\tdata: typeof data === \"object\" ? data : undefined,\n\t\t\t});\n\t\t\tReflect.defineMetadata(METADATA_KEY.PARAMS, params, target);\n\t\t}\n\t};\n}\n\nexport const Req = () => createParamDecorator(PARAM_TYPES.REQUEST);\nexport const Res = () => createParamDecorator(PARAM_TYPES.RESPONSE);\nexport const Next = () => createParamDecorator(PARAM_TYPES.NEXT);\nexport const Body = (key?: string) =>\n\tcreateParamDecorator(PARAM_TYPES.BODY, key);\nexport const Query = (key?: string) =>\n\tcreateParamDecorator(PARAM_TYPES.QUERY, key);\nexport const Param = (key?: string) =>\n\tcreateParamDecorator(PARAM_TYPES.PARAM, key);\nexport const Headers = (key?: string) =>\n\tcreateParamDecorator(PARAM_TYPES.HEADERS, key);\nexport const Ctx = () => createParamDecorator(PARAM_TYPES.CTX);\nexport const User = () => createParamDecorator(PARAM_TYPES.USER);\n\nexport function getParamMetadata(\n\ttarget: any,\n\tpropertyKey: string | symbol,\n): ParamMetadata[] {\n\treturn Reflect.getMetadata(METADATA_KEY.PARAMS, target, propertyKey) ?? [];\n}\n\nexport { PARAM_TYPES };\n",
9
9
  "/**\n * @Validate decorator.\n *\n * Attaches Zod schemas (or class validators) to a route handler. Each\n * schema is run against the corresponding request part before the handler\n * executes; failed validation throws or returns a 400 response.\n *\n * @example\n * ```ts\n * const UserSchema = z.object({ name: z.string(), email: z.email() });\n *\n * @Post('/')\n * @Validate({ body: UserSchema })\n * create(@Body() body: z.infer<typeof UserSchema>) { ... }\n * ```\n */\nimport \"reflect-metadata\";\nimport { METADATA_KEY } from \"../constants.js\";\nimport type { ValidationMetadata } from \"../di/tokens.js\";\n\nexport function Validate(options: ValidationMetadata): MethodDecorator {\n\treturn (\n\t\ttarget: object,\n\t\tpropertyKey: string | symbol,\n\t\tdescriptor: PropertyDescriptor,\n\t) => {\n\t\tReflect.defineMetadata(\n\t\t\tMETADATA_KEY.VALIDATE,\n\t\t\toptions,\n\t\t\ttarget.constructor,\n\t\t\tpropertyKey,\n\t\t);\n\t};\n}\n\nexport function getValidationMetadata(\n\ttarget: any,\n\tpropertyKey: string | symbol,\n): ValidationMetadata | undefined {\n\treturn Reflect.getMetadata(METADATA_KEY.VALIDATE, target, propertyKey);\n}\n",
10
10
  "/**\n * `nexusjs/upload` — file upload helper.\n *\n * @Module({\n * imports: [\n * UploadModule.forRoot({\n * maxFileSize: 10 * 1024 * 1024, // 10MB per file\n * maxFiles: 5,\n * allowedMimeTypes: ['image/*', 'application/pdf'],\n * storage: 'memory', // or 'drive' (uses nexus/drive)\n * }),\n * ],\n * })\n *\n * @Post('/avatars')\n * @Upload('avatar') // form field name\n * async upload(@UploadedFile('avatar') file: UploadedFile) {\n * return { size: file.size, type: file.contentType };\n * }\n *\n * @Post('/photos')\n * async multi(@UploadedFiles('photos') files: UploadedFile[]) {\n * return files.length;\n * }\n */\n\nimport \"reflect-metadata\";\nimport { METADATA_KEY } from \"../core/constants.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A file that has been parsed from `multipart/form-data`.\n *\n * The framework reads the entire body into memory (with a hard cap\n * enforced by `maxFileSize`) and exposes it as a `Buffer`. For very\n * large files (gigabytes), use a streaming approach instead — the\n * middleware leaves `stream` populated when the file is too large\n * to buffer.\n */\nexport interface UploadedFile {\n\t/** Form field name (e.g. \"avatar\", \"photos\"). */\n\tfieldName: string;\n\t/** Filename from the client (e.g. \"my-photo.png\"). */\n\tfilename: string;\n\t/** MIME type from the client. */\n\tcontentType: string;\n\t/** Encoding (typically '7bit'). */\n\tencoding: string;\n\t/** File content. */\n\tbuffer: Buffer;\n\t/** Convenience: same as `buffer.length`. */\n\tsize: number;\n}\n\n/** Top-level config. */\nexport interface UploadConfig {\n\t/** Max bytes per file. Default: 10 MB. */\n\tmaxFileSize?: number;\n\t/** Max number of files per request. Default: 5. */\n\tmaxFiles?: number;\n\t/**\n\t * Allowed MIME types. Supports `*` wildcards:\n\t * 'image/*' — any image type\n\t * 'application/pdf'\n\t * 'video/mp4'\n\t * Default: '*' (any).\n\t */\n\tallowedMimeTypes?: string[];\n\t/**\n\t * Where parsed files are stored in memory. Default: 'memory'.\n\t * The decorator reads from this storage on each access.\n\t */\n\tstorage?: \"memory\";\n\t/**\n\t * When set, parsed files are also pushed to the configured\n\t * `nexusjs/drive` storage under this prefix. The drive is\n\t * resolved by the DI token string.\n\t */\n\tdriveToken?: string;\n\tdrivePrefix?: string;\n\t/** When using drive storage: keep the original filename. Default: false (UUID). */\n\tpreserveFilename?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Decorator payload\n// ---------------------------------------------------------------------------\n\nexport interface UploadOptions {\n\t/** Form field name. Default: property name. */\n\tname?: string;\n\t/** Per-field max size (overrides the global default). */\n\tmaxSize?: number;\n\t/** Per-field MIME-type filter. */\n\tmimeTypes?: string[];\n\t/** Per-field max files (for multi-file). Default: 1. */\n\tmaxFiles?: number;\n\t/** Whether the field is required. Default: true. */\n\trequired?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Validation errors\n// ---------------------------------------------------------------------------\n\nexport class UploadError extends Error {\n\treadonly status = 400;\n\treadonly code: string;\n\treadonly field: string;\n\tconstructor(code: string, field: string, message: string) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t\tthis.field = field;\n\t\tthis.name = \"UploadError\";\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Middleware storage key (per-request)\n// ---------------------------------------------------------------------------\n\n/** Key under which the multipart middleware stores parsed files. */\nexport const UPLOAD_STORAGE_KEY = \"nexus:upload:files\";\n\n/** Internal metadata key (decorator). */\nexport const UPLOAD_META = \"nexus:upload:options\";\n\nexport { METADATA_KEY };\n",
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
5
5
  "/**\n * Metadata keys used by reflect-metadata for storing decorator data.\n *\n * These constants are the contract between decorators and the framework\n * core (DI container, router, validator).\n */\nexport const METADATA_KEY = {\n\t/** Marks a class as a Nest-style controller, stores route prefix. */\n\tCONTROLLER: \"nexus:controller\",\n\n\t/** Marks a class as an injectable provider. */\n\tINJECTABLE: \"nexus:injectable\",\n\n\t/** Marks a class as a repository. */\n\tREPOSITORY: \"nexus:repository\",\n\n\t/** Marks a class as a module. Stores module options. */\n\tMODULE: \"nexus:module\",\n\n\t/** HTTP method routes registered on a controller (Get/Post/...). */\n\tROUTES: \"nexus:routes\",\n\n\t/** Method parameter type metadata (body/query/param/headers/ctx). */\n\tPARAMS: \"nexus:params\",\n\n\t/** Validation schema per method (Zod schema or class). */\n\tVALIDATE: \"nexus:validate\",\n\n\t/** Class-level design:paramtypes (built-in). */\n\tPARAMTYPES: \"design:paramtypes\",\n\n\t/** Class-level design:type (built-in). */\n\tTYPE: \"design:type\",\n\n\t/** Class-level design:returntype (built-in). */\n\tRETURNTYPE: \"design:returntype\",\n\n\t/** Provider token to inject for a parameter (for custom tokens). */\n\tINJECT: \"nexus:inject\",\n} as const;\n\nexport type MetadataKey = (typeof METADATA_KEY)[keyof typeof METADATA_KEY];\n\n/** Available parameter decorator locations. */\nexport const PARAM_TYPES = {\n\tREQUEST: 0,\n\tRESPONSE: 1,\n\tNEXT: 2,\n\tBODY: 3,\n\tQUERY: 4,\n\tPARAM: 5,\n\tHEADERS: 6,\n\tCTX: 7,\n\tUSER: 8,\n} as const;\n\nexport type ParamType = (typeof PARAM_TYPES)[keyof typeof PARAM_TYPES];\n\n/** HTTP methods supported by the router. */\nexport const HTTP_METHODS = [\n\t\"GET\",\n\t\"POST\",\n\t\"PUT\",\n\t\"DELETE\",\n\t\"PATCH\",\n\t\"OPTIONS\",\n\t\"HEAD\",\n] as const;\nexport type HttpMethod = (typeof HTTP_METHODS)[number];\n",
6
6
  "/**\n * @Controller decorator.\n *\n * Marks a class as a controller and registers a route prefix.\n * Routes inside the controller class are decorated with @Get/@Post/etc.\n *\n * @example\n * ```ts\n * @Controller('/users')\n * class UserController {\n * @Get('/')\n * list() { ... }\n * }\n * ```\n */\nimport \"reflect-metadata\";\nimport { METADATA_KEY } from \"../constants.js\";\nimport type { ControllerMetadata } from \"../di/tokens.js\";\n\nexport function Controller(prefix: string = \"/\"): ClassDecorator {\n\treturn (target: object) => {\n\t\tconst normalized = normalizePrefix(prefix);\n\t\tconst meta: ControllerMetadata = { prefix: normalized };\n\t\tReflect.defineMetadata(METADATA_KEY.CONTROLLER, meta, target);\n\t};\n}\n\nexport function getControllerMetadata(target: any): ControllerMetadata {\n\treturn (\n\t\tReflect.getMetadata(METADATA_KEY.CONTROLLER, target) ?? { prefix: \"/\" }\n\t);\n}\n\nexport function isController(target: any): boolean {\n\treturn Reflect.hasMetadata(METADATA_KEY.CONTROLLER, target);\n}\n\n/**\n * Normalize a prefix so we can safely concatenate it with handler paths.\n * - Empty string becomes '/'.\n * - Trailing slashes are trimmed (we re-add them on the join).\n * - No leading slash is added; the router always joins with `/`.\n */\nfunction normalizePrefix(prefix: string): string {\n\tif (!prefix) return \"\";\n\tif (prefix !== \"/\" && prefix.endsWith(\"/\")) {\n\t\treturn prefix.slice(0, -1);\n\t}\n\treturn prefix;\n}\n",
7
- "/**\n * HTTP method decorators.\n *\n * `@Get`, `@Post`, `@Put`, `@Delete`, `@Patch`, `@Options`, `@Head` mark a\n * controller method as a route handler. The path argument is appended to\n * the controller's prefix.\n *\n * @example\n * ```ts\n * @Controller('/users')\n * class UserController {\n * @Get('/')\n * list() {}\n *\n * @Post('/')\n * create(@Body() body: CreateUserDto) {}\n * }\n * ```\n */\nimport \"reflect-metadata\";\nimport { HTTP_METHODS, METADATA_KEY, type HttpMethod } from \"../constants.js\";\nimport type { RouteMetadata } from \"../di/tokens.js\";\n\nfunction defineRoute(method: HttpMethod, path: string): MethodDecorator {\n\treturn (\n\t\ttarget: object,\n\t\tpropertyKey: string | symbol,\n\t\tdescriptor: PropertyDescriptor,\n\t) => {\n\t\tconst routes: RouteMetadata[] =\n\t\t\tReflect.getMetadata(METADATA_KEY.ROUTES, target.constructor) ?? [];\n\n\t\troutes.push({\n\t\t\tmethod,\n\t\t\tpath: normalizePath(path),\n\t\t\tpropertyKey,\n\t\t\thandler: descriptor.value,\n\t\t});\n\n\t\tReflect.defineMetadata(METADATA_KEY.ROUTES, routes, target.constructor);\n\t};\n}\n\nfunction normalizePath(path: string): string {\n\tif (!path || path === \"/\") return \"/\";\n\treturn path.startsWith(\"/\") ? path : `/${path}`;\n}\n\nexport const Get = (path: string = \"/\") => defineRoute(\"GET\", path);\nexport const Post = (path: string = \"/\") => defineRoute(\"POST\", path);\nexport const Put = (path: string = \"/\") => defineRoute(\"PUT\", path);\nexport const Delete = (path: string = \"/\") => defineRoute(\"DELETE\", path);\nexport const Patch = (path: string = \"/\") => defineRoute(\"PATCH\", path);\nexport const Options = (path: string = \"/\") => defineRoute(\"OPTIONS\", path);\nexport const Head = (path: string = \"/\") => defineRoute(\"HEAD\", path);\n\nexport function getRoutes(target: any): RouteMetadata[] {\n\treturn Reflect.getMetadata(METADATA_KEY.ROUTES, target) ?? [];\n}\n\nexport { HTTP_METHODS };\nexport type { RouteMetadata };\n",
7
+ "/**\n * HTTP method decorators.\n *\n * `@Get`, `@Post`, `@Put`, `@Delete`, `@Patch`, `@Options`, `@Head` mark a\n * controller method as a route handler. The path argument is appended to\n * the controller's prefix.\n *\n * @example\n * ```ts\n * @Controller('/users')\n * class UserController {\n * @Get('/')\n * list() {}\n *\n * @Post('/')\n * create(@Body() body: CreateUserDto) {}\n * }\n * ```\n */\nimport \"reflect-metadata\";\nimport { HTTP_METHODS, METADATA_KEY, type HttpMethod } from \"../constants.js\";\nimport type { RouteMetadata } from \"../di/tokens.js\";\n\nfunction defineRoute(method: HttpMethod, path: string): MethodDecorator {\n\treturn (\n\t\ttarget: object,\n\t\tpropertyKey: string | symbol,\n\t\tdescriptor: TypedPropertyDescriptor<any>,\n\t) => {\n\t\tconst routes: RouteMetadata[] =\n\t\t\tReflect.getMetadata(METADATA_KEY.ROUTES, target.constructor) ?? [];\n\n\t\troutes.push({\n\t\t\tmethod,\n\t\t\tpath: normalizePath(path),\n\t\t\tpropertyKey,\n\t\t\thandler: descriptor.value,\n\t\t});\n\n\t\tReflect.defineMetadata(METADATA_KEY.ROUTES, routes, target.constructor);\n\t};\n}\n\nfunction normalizePath(path: string): string {\n\tif (!path || path === \"/\") return \"/\";\n\treturn path.startsWith(\"/\") ? path : `/${path}`;\n}\n\nexport const Get = (path: string = \"/\") => defineRoute(\"GET\", path);\nexport const Post = (path: string = \"/\") => defineRoute(\"POST\", path);\nexport const Put = (path: string = \"/\") => defineRoute(\"PUT\", path);\nexport const Delete = (path: string = \"/\") => defineRoute(\"DELETE\", path);\nexport const Patch = (path: string = \"/\") => defineRoute(\"PATCH\", path);\nexport const Options = (path: string = \"/\") => defineRoute(\"OPTIONS\", path);\nexport const Head = (path: string = \"/\") => defineRoute(\"HEAD\", path);\n\nexport function getRoutes(target: any): RouteMetadata[] {\n\treturn Reflect.getMetadata(METADATA_KEY.ROUTES, target) ?? [];\n}\n\nexport { HTTP_METHODS };\nexport type { RouteMetadata };\n",
8
8
  "/**\n * Parameter decorators.\n *\n * These mark a controller method argument as a source of request data:\n * - `@Req()` → Hono context\n * - `@Res()` → Response helper\n * - `@Next()` → next() callback (for middleware-style handlers)\n * - `@Body()` → request body (parsed)\n * - `@Query('key')` → a single query param, or full query object\n * - `@Param('key')` → a single path param, or full params object\n * - `@Headers('k')` → a single header, or full headers object\n * - `@Ctx()` → Hono context (alias for @Req)\n * - `@User()` → authenticated user (resolved via auth provider)\n *\n * The metadata is read by the router at mount time to build the\n * handler invocation list.\n */\nimport \"reflect-metadata\";\nimport { METADATA_KEY, PARAM_TYPES } from \"../constants.js\";\nimport type { ParamMetadata } from \"../di/tokens.js\";\n\nexport function createParamDecorator(\n\ttype: number,\n\tdata?: string | object,\n): ParameterDecorator {\n\treturn (\n\t\ttarget: object,\n\t\tpropertyKey: string | symbol | undefined,\n\t\tparameterIndex: number,\n\t) => {\n\t\t// Method parameter: target is the prototype, propertyKey is the method name.\n\t\t// Constructor parameter: target is the class, propertyKey is undefined.\n\t\tif (propertyKey !== undefined) {\n\t\t\tconst params: ParamMetadata[] =\n\t\t\t\tReflect.getMetadata(METADATA_KEY.PARAMS, target, propertyKey) ?? [];\n\t\t\tparams.push({\n\t\t\t\tindex: parameterIndex,\n\t\t\t\ttype,\n\t\t\t\tname: typeof data === \"string\" ? data : undefined,\n\t\t\t\tdata: typeof data === \"object\" ? data : undefined,\n\t\t\t});\n\t\t\tReflect.defineMetadata(METADATA_KEY.PARAMS, params, target, propertyKey);\n\t\t} else {\n\t\t\tconst params: ParamMetadata[] =\n\t\t\t\tReflect.getMetadata(METADATA_KEY.PARAMS, target) ?? [];\n\t\t\tparams.push({\n\t\t\t\tindex: parameterIndex,\n\t\t\t\ttype,\n\t\t\t\tname: typeof data === \"string\" ? data : undefined,\n\t\t\t\tdata: typeof data === \"object\" ? data : undefined,\n\t\t\t});\n\t\t\tReflect.defineMetadata(METADATA_KEY.PARAMS, params, target);\n\t\t}\n\t};\n}\n\nexport const Req = () => createParamDecorator(PARAM_TYPES.REQUEST);\nexport const Res = () => createParamDecorator(PARAM_TYPES.RESPONSE);\nexport const Next = () => createParamDecorator(PARAM_TYPES.NEXT);\nexport const Body = (key?: string) =>\n\tcreateParamDecorator(PARAM_TYPES.BODY, key);\nexport const Query = (key?: string) =>\n\tcreateParamDecorator(PARAM_TYPES.QUERY, key);\nexport const Param = (key?: string) =>\n\tcreateParamDecorator(PARAM_TYPES.PARAM, key);\nexport const Headers = (key?: string) =>\n\tcreateParamDecorator(PARAM_TYPES.HEADERS, key);\nexport const Ctx = () => createParamDecorator(PARAM_TYPES.CTX);\nexport const User = () => createParamDecorator(PARAM_TYPES.USER);\n\nexport function getParamMetadata(\n\ttarget: any,\n\tpropertyKey: string | symbol,\n): ParamMetadata[] {\n\treturn Reflect.getMetadata(METADATA_KEY.PARAMS, target, propertyKey) ?? [];\n}\n\nexport { PARAM_TYPES };\n",
9
9
  "/**\n * @Validate decorator.\n *\n * Attaches Zod schemas (or class validators) to a route handler. Each\n * schema is run against the corresponding request part before the handler\n * executes; failed validation throws or returns a 400 response.\n *\n * @example\n * ```ts\n * const UserSchema = z.object({ name: z.string(), email: z.email() });\n *\n * @Post('/')\n * @Validate({ body: UserSchema })\n * create(@Body() body: z.infer<typeof UserSchema>) { ... }\n * ```\n */\nimport \"reflect-metadata\";\nimport { METADATA_KEY } from \"../constants.js\";\nimport type { ValidationMetadata } from \"../di/tokens.js\";\n\nexport function Validate(options: ValidationMetadata): MethodDecorator {\n\treturn (\n\t\ttarget: object,\n\t\tpropertyKey: string | symbol,\n\t\tdescriptor: PropertyDescriptor,\n\t) => {\n\t\tReflect.defineMetadata(\n\t\t\tMETADATA_KEY.VALIDATE,\n\t\t\toptions,\n\t\t\ttarget.constructor,\n\t\t\tpropertyKey,\n\t\t);\n\t};\n}\n\nexport function getValidationMetadata(\n\ttarget: any,\n\tpropertyKey: string | symbol,\n): ValidationMetadata | undefined {\n\treturn Reflect.getMetadata(METADATA_KEY.VALIDATE, target, propertyKey);\n}\n",
10
10
  "/**\n * `WebSocketService` — connection registry, rooms, broadcasting.\n *\n * The service is the central state for all open WebSocket\n * connections. It is registered in the DI container as a singleton\n * and consumed by `@WebSocketGateway` classes.\n *\n * Features:\n * - **Connection registry** — `getConnections()` returns all\n * currently-open clients.\n * - **Per-connection lookup** — `getConnection(id)`.\n * - **Rooms** — `joinRoom`, `leaveRoom`, `broadcastToRoom`. Rooms\n * are auto-cleaned when empty.\n * - **Broadcast** — `broadcast(data)` sends to all open clients.\n * - **Targeted send** — `sendTo(id, data)`.\n * - **Lifecycle tracking** — `onConnect` / `onDisconnect` callbacks\n * for metrics, audit logging, etc.\n */\n\nimport type { WebSocketClient, WsMessage, WsClientData } from \"./types.js\";\nimport { WebSocketClientImpl } from \"./client.js\";\n\nexport const WEBSOCKET_SERVICE_TOKEN = Symbol.for(\"nexus:WebSocketService\");\n\nexport class WebSocketService {\n\t/** id -> client */\n\tprivate clients = new Map<string, WebSocketClientImpl>();\n\t/** room -> Set<id> */\n\tprivate rooms = new Map<string, Set<string>>();\n\t/** Lifecycle callbacks. */\n\tprivate onConnectListeners: Array<(c: WebSocketClient) => void> = [];\n\tprivate onDisconnectListeners: Array<(c: WebSocketClient) => void> = [];\n\n\t/* ---------------- registry ---------------- */\n\n\t/** Register a new client. Returns the framework id. */\n\tregister(client: WebSocketClientImpl): string {\n\t\tthis.clients.set(client.id, client);\n\t\tfor (const cb of this.onConnectListeners) {\n\t\t\ttry {\n\t\t\t\tcb(client);\n\t\t\t} catch {\n\t\t\t\t/* ignore listener errors */\n\t\t\t}\n\t\t}\n\t\treturn client.id;\n\t}\n\n\t/** Remove a client from the registry. Auto-cleans empty rooms. */\n\tunregister(client: WebSocketClientImpl): void {\n\t\tthis.clients.delete(client.id);\n\t\tfor (const room of client.rooms) {\n\t\t\tconst set = this.rooms.get(room);\n\t\t\tif (set) {\n\t\t\t\tset.delete(client.id);\n\t\t\t\tif (set.size === 0) this.rooms.delete(room);\n\t\t\t}\n\t\t}\n\t\tfor (const cb of this.onDisconnectListeners) {\n\t\t\ttry {\n\t\t\t\tcb(client);\n\t\t\t} catch {\n\t\t\t\t/* ignore listener errors */\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Number of currently-open connections. */\n\tget size(): number {\n\t\treturn this.clients.size;\n\t}\n\n\t/** Get all open connections. */\n\tgetConnections(): WebSocketClient[] {\n\t\treturn [...this.clients.values()];\n\t}\n\n\t/** Get a connection by id. */\n\tgetConnection(id: string): WebSocketClient | undefined {\n\t\treturn this.clients.get(id);\n\t}\n\n\t/* ---------------- rooms ---------------- */\n\n\t/** Join a room. */\n\tjoinRoom(client: WebSocketClient, room: string): void {\n\t\tconst impl = client as WebSocketClientImpl;\n\t\timpl.joinRoom(room);\n\t\tlet set = this.rooms.get(room);\n\t\tif (!set) {\n\t\t\tset = new Set();\n\t\t\tthis.rooms.set(room, set);\n\t\t}\n\t\tset.add(client.id);\n\t}\n\n\t/** Leave a room. */\n\tleaveRoom(client: WebSocketClient, room: string): void {\n\t\tconst impl = client as WebSocketClientImpl;\n\t\timpl.leaveRoom(room);\n\t\tconst set = this.rooms.get(room);\n\t\tif (set) {\n\t\t\tset.delete(client.id);\n\t\t\tif (set.size === 0) this.rooms.delete(room);\n\t\t}\n\t}\n\n\t/** Leave all rooms. */\n\tleaveAllRooms(client: WebSocketClient): void {\n\t\tconst impl = client as WebSocketClientImpl;\n\t\tfor (const room of impl.rooms) {\n\t\t\tconst set = this.rooms.get(room);\n\t\t\tif (set) {\n\t\t\t\tset.delete(client.id);\n\t\t\t\tif (set.size === 0) this.rooms.delete(room);\n\t\t\t}\n\t\t}\n\t\timpl.rooms.clear();\n\t}\n\n\t/** All client ids in a room. */\n\tgetRoomMembers(room: string): WebSocketClient[] {\n\t\tconst ids = this.rooms.get(room);\n\t\tif (!ids) return [];\n\t\tconst out: WebSocketClient[] = [];\n\t\tfor (const id of ids) {\n\t\t\tconst c = this.clients.get(id);\n\t\t\tif (c) out.push(c);\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** All room names currently in use. */\n\tgetRooms(): string[] {\n\t\treturn [...this.rooms.keys()];\n\t}\n\n\t/** Whether a room has any members. */\n\thasRoom(room: string): boolean {\n\t\tconst set = this.rooms.get(room);\n\t\treturn !!set && set.size > 0;\n\t}\n\n\t/* ---------------- broadcast ---------------- */\n\n\t/** Send to all open clients. */\n\tbroadcast(data: WsMessage, filter?: (client: WebSocketClient) => boolean): void {\n\t\tfor (const client of this.clients.values()) {\n\t\t\tif (filter && !filter(client)) continue;\n\t\t\ttry {\n\t\t\t\tclient.send(data);\n\t\t\t} catch {\n\t\t\t\t/* ignore send errors */\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Send to all clients in a room. */\n\tbroadcastToRoom(room: string, data: WsMessage): void {\n\t\tconst ids = this.rooms.get(room);\n\t\tif (!ids) return;\n\t\tfor (const id of ids) {\n\t\t\tconst client = this.clients.get(id);\n\t\t\tif (!client) continue;\n\t\t\ttry {\n\t\t\t\tclient.send(data);\n\t\t\t} catch {\n\t\t\t\t/* ignore send errors */\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Send to a single client by id. */\n\tsendTo(id: string, data: WsMessage): boolean {\n\t\tconst client = this.clients.get(id);\n\t\tif (!client) return false;\n\t\ttry {\n\t\t\tclient.send(data);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/** Close all connections (graceful shutdown). */\n\tcloseAll(code = 1001, reason = \"Server shutting down\"): void {\n\t\tfor (const client of this.clients.values()) {\n\t\t\ttry {\n\t\t\t\tclient.close(code, reason);\n\t\t\t} catch {\n\t\t\t\t/* ignore */\n\t\t\t}\n\t\t}\n\t}\n\n\t/* ---------------- lifecycle listeners ---------------- */\n\n\t/** Register a callback for new connections. */\n\tonConnect(cb: (client: WebSocketClient) => void): void {\n\t\tthis.onConnectListeners.push(cb);\n\t}\n\n\t/** Register a callback for closed connections. */\n\tonDisconnect(cb: (client: WebSocketClient) => void): void {\n\t\tthis.onDisconnectListeners.push(cb);\n\t}\n}\n",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kabyeon/nexusjs",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "Bun Native Fullstack Framework - NestJS structure + Adonis productivity + Hono edge performance",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -166,8 +166,6 @@
166
166
  },
167
167
  "scripts": {
168
168
  "build": "bun run build.ts",
169
- "dev": "bun --hot src/app/main.ts",
170
- "start": "bun src/app/main.ts",
171
169
  "test": "vitest run",
172
170
  "test:watch": "vitest --watch",
173
171
  "typecheck": "tsc --noEmit",