@kabyeon/nexusjs 0.6.7 → 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.
- package/README.md +9 -3
- package/dist/auth/index.js.map +1 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/drive/index.js.map +1 -1
- package/dist/drizzle/index.js.map +1 -1
- package/dist/events/index.js.map +1 -1
- package/dist/grpc/index.js.map +1 -1
- package/dist/health/index.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/limiter/index.js.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mail/index.js.map +1 -1
- package/dist/openapi/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/queue/index.js.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/schedule/index.js.map +1 -1
- package/dist/session/index.js.map +1 -1
- package/dist/shield/index.js.map +1 -1
- package/dist/tracing/index.js.map +1 -1
- package/dist/upload/index.js.map +1 -1
- package/dist/ws/index.js.map +1 -1
- package/package.json +1 -3
|
@@ -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:
|
|
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/limiter` — rate limiting.\n *\n * Two ways to apply limits:\n *\n * 1. **Global** via `LimiterModule.forRoot({ rules: [...] })`:\n * limits matched against request path / method.\n *\n * 2. **Per-route** via the `@RateLimit` decorator:\n *\n * ```ts\n * @Controller('/auth')\n * class AuthController {\n * @Post('/login')\n * @RateLimit({ points: 5, duration: '1m' })\n * login() {}\n * }\n * ```\n *\n * Key derivation: by default we use `c.req.header('x-forwarded-for')`\n * or the remote address. Decorator `key` option overrides with a\n * function (e.g. user ID, API key).\n *\n * Backends:\n * - `MemoryStorage` (default, single-process)\n * - `RedisStorage` (optional, multi-process / multi-pod)\n */\n\nimport \"reflect-metadata\";\nimport { METADATA_KEY } from \"../core/constants.js\";\n\n/** Identifier of the request — IP, user ID, API key, etc. */\nexport type RateLimitKey = string;\n\n/** Strategy used to count requests. */\nexport type RateLimitStrategy =\n\t| \"fixed-window\"\n\t| \"sliding-window\"\n\t| \"token-bucket\";\n\n/**\n * Numeric size of a window. Either a millisecond count or one of\n * `'1s'`, `'1m'`, `'1h'`, `'1d'` for convenience.\n */\nexport type DurationLike = number | `${number}${\"s\" | \"m\" | \"h\" | \"d\"}`;\n\n/** Result of a single rate-limit check. */\nexport interface RateLimitResult {\n\t/** Whether the request is allowed. */\n\tallowed: boolean;\n\t/** Remaining points in the current window. */\n\tremaining: number;\n\t/** Total points in the current window. */\n\tlimit: number;\n\t/** Unix-ms timestamp when the window resets. */\n\tresetAt: number;\n\t/** Number of seconds the client should wait (only when `allowed=false`). */\n\tretryAfter: number;\n}\n\n/** Storage backend for limiter state. */\nexport interface RateLimitStorage {\n\t/**\n\t * Consume `points` units for `key`, allowing at most `limit` units\n\t * per `durationMs` window. Returns the limit result.\n\t * Implementations must be atomic across concurrent callers.\n\t */\n\tconsume(\n\t\tkey: RateLimitKey,\n\t\tpoints: number,\n\t\tlimit: number,\n\t\tdurationMs: number,\n\t\tstrategy: RateLimitStrategy,\n\t): Promise<RateLimitResult>;\n\n\t/** Reset all state for a key. Useful in tests. */\n\treset(key: RateLimitKey): Promise<void>;\n}\n\n/** Per-rule configuration. */\nexport interface RateLimitRule {\n\t/** Path pattern. Glob: `*` matches a single segment, `**` any depth. */\n\tpath: string;\n\t/** HTTP methods to apply to; default = all. */\n\tmethods?: string[];\n\t/** Number of allowed requests per window. */\n\tpoints: number;\n\t/** Window size. */\n\tduration: DurationLike;\n\t/** Override key derivation. */\n\tkey?: (c: any) => string | undefined | Promise<string | undefined>;\n\t/** Bucket strategy. Default `'sliding-window'`. */\n\tstrategy?: RateLimitStrategy;\n\t/** Custom rejection response. */\n\treject?: (c: any, result: RateLimitResult) => Response | Promise<Response>;\n\t/** Skip when this returns true. */\n\tskip?: (c: any) => boolean | Promise<boolean>;\n}\n\n/** Top-level configuration. */\nexport interface LimiterConfig {\n\t/** Storage backend. Default: in-memory. */\n\tstorage?: RateLimitStorage;\n\t/** Global rules applied before the per-route ones. */\n\trules?: RateLimitRule[];\n\t/** Default key derivation when a rule omits one. Default: IP address. */\n\tdefaultKey?: (c: any) => string | undefined | Promise<string | undefined>;\n\t/** Default response when a request is rejected. */\n\tdefaultReject?: (\n\t\tc: any,\n\t\tresult: RateLimitResult,\n\t) => Response | Promise<Response>;\n}\n\nexport const LIMITER_RULE_KEY = Symbol.for(\"nexus:RateLimitRule\");\n\n/** Decorator: attach a per-route rate limit. */\nexport function RateLimit(\n\trule: RateLimitRule,\n): MethodDecorator & ClassDecorator {\n\treturn (\n\t\ttarget: any,\n\t\tpropertyKey?: string | symbol,\n\t\tdescriptor?: PropertyDescriptor,\n\t) => {\n\t\t// Class-level: applied to all routes of the controller.\n\t\tif (descriptor === undefined) {\n\t\t\tconst existing: RateLimitRule[] =\n\t\t\t\tReflect.getMetadata(LIMITER_RULE_KEY, target) ?? [];\n\t\t\texisting.push({ ...rule, path: \"**\" });\n\t\t\tReflect.defineMetadata(LIMITER_RULE_KEY, existing, target);\n\t\t\treturn target;\n\t\t}\n\t\t// Method-level: bound to the route.\n\t\tconst existing: RateLimitRule[] =\n\t\t\tReflect.getMetadata(LIMITER_RULE_KEY, target.constructor) ?? [];\n\t\texisting.push({ ...rule, path: propertyKey === undefined ? \"**\" : `**` });\n\t\tReflect.defineMetadata(LIMITER_RULE_KEY, existing, target.constructor);\n\t};\n}\n\n/** Read all `@RateLimit` rules from a controller or method. */\nexport function getLimiterRules(target: any): RateLimitRule[] {\n\treturn Reflect.getMetadata(LIMITER_RULE_KEY, target) ?? [];\n}\n\n/** Convert a `DurationLike` to milliseconds. */\nexport function durationToMs(d: DurationLike): number {\n\tif (typeof d === \"number\") return d;\n\tconst m = /^(\\d+)([smhd])$/.exec(d);\n\tif (!m) throw new Error(`Invalid duration: ${d}`);\n\tconst n = Number(m[1]);\n\tconst unit = m[2] as \"s\" | \"m\" | \"h\" | \"d\";\n\tconst mult: Record<typeof unit, number> = {\n\t\ts: 1000,\n\t\tm: 60_000,\n\t\th: 3_600_000,\n\t\td: 86_400_000,\n\t};\n\treturn n * mult[unit];\n}\n",
|
package/dist/logger/index.js.map
CHANGED
|
@@ -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:
|
|
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 * `Logger` — the user-facing logging interface.\n *\n * Logger is request-scoped via `AsyncLocalStorage`: any `logger.info(...)`\n * call inside a request automatically merges in fields set by\n * `logger.with({ requestId, userId, ... })`.\n *\n * Usage:\n * constructor(@Inject(Logger.TOKEN) private logger: Logger) {}\n *\n * this.logger.info({ userId: 'u-1' }, 'user signed in');\n * this.logger.error({ err }, 'failed to save');\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { Inject, Injectable } from \"../core/decorators/index.js\";\nimport type {\n\tLogLevel,\n\tLogRecord,\n\tLogTransport,\n\tLoggerOptions,\n\tLogContext,\n} from \"./types.js\";\nimport { PinoTransport, PrettyTransport, NullTransport } from \"./transports/index.js\";\n\nconst LEVEL_RANK: Record<LogLevel, number> = {\n\ttrace: 10,\n\tdebug: 20,\n\tinfo: 30,\n\twarn: 40,\n\terror: 50,\n\tfatal: 60,\n};\n\n@Injectable()\nexport class Logger {\n\t/** DI token — use with `@Inject(Logger.TOKEN)`. */\n\tstatic readonly TOKEN = Symbol.for(\"nexus:Logger\");\n\n\ttransports: LogTransport[];\n\tsilent: boolean;\n\tbase: Record<string, unknown>;\n\tlevel: LogLevel;\n\tals = new AsyncLocalStorage<LogContext>();\n\n\tconstructor(@Inject(\"LOGGER_OPTIONS\") options: LoggerOptions = {}) {\n\t\tthis.silent = options.silent ?? false;\n\t\tthis.base = options.base ?? {};\n\t\tthis.level = options.level ?? (process.env[\"NODE_ENV\"] === \"production\" ? \"info\" : \"debug\");\n\t\tif (options.transports && options.transports.length > 0) {\n\t\t\tthis.transports = options.transports;\n\t\t} else {\n\t\t\tconst pretty = options.pretty ?? process.env[\"NODE_ENV\"] !== \"production\";\n\t\t\tthis.transports = [\n\t\t\t\tpretty\n\t\t\t\t\t? new PrettyTransport(this.level, this.base)\n\t\t\t\t\t: new PinoTransport(this.level, this.base),\n\t\t\t];\n\t\t}\n\t}\n\n\t// ===========================================================================\n\t// Level methods\n\t// ===========================================================================\n\n\ttrace(meta: Record<string, unknown>, msg: string): void;\n\ttrace(msg: string): void;\n\ttrace(arg1: Record<string, unknown> | string, arg2?: string): void {\n\t\tthis.emit(\"trace\", arg1, arg2);\n\t}\n\n\tdebug(meta: Record<string, unknown>, msg: string): void;\n\tdebug(msg: string): void;\n\tdebug(arg1: Record<string, unknown> | string, arg2?: string): void {\n\t\tthis.emit(\"debug\", arg1, arg2);\n\t}\n\n\tinfo(meta: Record<string, unknown>, msg: string): void;\n\tinfo(msg: string): void;\n\tinfo(arg1: Record<string, unknown> | string, arg2?: string): void {\n\t\tthis.emit(\"info\", arg1, arg2);\n\t}\n\n\twarn(meta: Record<string, unknown>, msg: string): void;\n\twarn(msg: string): void;\n\twarn(arg1: Record<string, unknown> | string, arg2?: string): void {\n\t\tthis.emit(\"warn\", arg1, arg2);\n\t}\n\n\terror(meta: Record<string, unknown>, msg: string): void;\n\terror(msg: string): void;\n\terror(arg1: Record<string, unknown> | string, arg2?: string): void {\n\t\tthis.emit(\"error\", arg1, arg2);\n\t}\n\n\tfatal(meta: Record<string, unknown>, msg: string): void;\n\tfatal(msg: string): void;\n\tfatal(arg1: Record<string, unknown> | string, arg2?: string): void {\n\t\tthis.emit(\"fatal\", arg1, arg2);\n\t}\n\n\t// ===========================================================================\n\t// Context\n\t// ===========================================================================\n\n\t/**\n\t * Run `fn` inside a logger context — every log emitted during\n\t * `fn()` is tagged with `meta`.\n\t */\n\twith<T>(meta: LogContext, fn: () => T): T {\n\t\tconst prev = this.als.getStore() ?? {};\n\t\tconst next: LogContext = { ...prev, ...meta };\n\t\treturn this.als.run(next, fn);\n\t}\n\n\t/** Read the current request context (or empty object). */\n\tget context(): LogContext {\n\t\treturn this.als.getStore() ?? {};\n\t}\n\n\t// ===========================================================================\n\t// Child loggers\n\t// ===========================================================================\n\n\t/**\n\t * Derive a child logger that always merges `bindings` into every\n\t * record. Useful for service-scoped loggers.\n\t */\n\tchild(bindings: Record<string, unknown>): Logger {\n\t\tconst child = Object.create(Logger.prototype) as Logger;\n\t\tchild.transports = this.transports;\n\t\tchild.silent = this.silent;\n\t\tchild.base = { ...this.base, ...bindings };\n\t\tchild.level = this.level;\n\t\tchild.als = this.als;\n\t\treturn child;\n\t}\n\n\t// ===========================================================================\n\t// Lifecycle\n\t// ===========================================================================\n\n\t/** Wait for transports to finish loading (Pino is async). */\n\tasync ready(): Promise<void> {\n\t\tfor (const t of this.transports) {\n\t\t\tconst r = (t as { ready?: () => Promise<void> }).ready;\n\t\t\tif (r) await r.call(t);\n\t\t}\n\t}\n\n\t// ===========================================================================\n\t// Internal\n\t// ===========================================================================\n\n\tprivate emit(level: LogLevel, arg1: Record<string, unknown> | string, arg2?: string): void {\n\t\tif (this.silent) return;\n\t\tif (LEVEL_RANK[level] < LEVEL_RANK[this.level]) return;\n\n\t\tlet meta: Record<string, unknown> = {};\n\t\tlet msg: string;\n\t\tif (typeof arg1 === \"string\") {\n\t\t\tmsg = arg1;\n\t\t} else {\n\t\t\tmeta = arg1;\n\t\t\tmsg = arg2 ?? \"\";\n\t\t}\n\n\t\tconst ctx = this.als.getStore() ?? {};\n\t\tconst record: LogRecord = {\n\t\t\tlevel,\n\t\t\ttime: Date.now(),\n\t\t\tmsg,\n\t\t\t...this.base,\n\t\t\t...meta,\n\t\t\t...ctx,\n\t\t};\n\t\tfor (const t of this.transports) {\n\t\t\ttry {\n\t\t\t\tt.write(record);\n\t\t\t} catch {\n\t\t\t\t// never let a logging error crash the request\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Re-export NullTransport for tests.\nexport { NullTransport };",
|
package/dist/mail/index.js.map
CHANGED
|
@@ -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:
|
|
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/mail` — outbound email.\n *\n * const mail = new MailService({ transport: new SmtpTransport({ host: 'smtp.gmail.com' }) });\n * await mail.send({\n * from: 'no-reply@example.com',\n * to: 'user@example.com',\n * subject: 'Welcome',\n * html: '<h1>Hi</h1>',\n * });\n *\n * Transports:\n * - `SmtpTransport` — SMTP via nodemailer (peer dep)\n * - `FileTransport` — write .eml files to a directory (dev/test)\n * - `NullTransport` — drop everything (tests)\n *\n * mail.renderMjml('template', { name: 'Kim' }) // compile MJML to HTML\n */\nimport \"reflect-metadata\";\n\n/** A single recipient. */\nexport type MailAddress = string | { name?: string; address: string };\n\n/** A mail message. */\nexport interface MailMessage {\n\tfrom?: MailAddress;\n\tto: MailAddress | MailAddress[];\n\tcc?: MailAddress | MailAddress[];\n\tbcc?: MailAddress | MailAddress[];\n\treplyTo?: MailAddress;\n\tsubject: string;\n\ttext?: string;\n\thtml?: string;\n\t/** Attachments. */\n\tattachments?: MailAttachment[];\n\t/** Custom headers. */\n\theaders?: Record<string, string>;\n\t/** Priority: 1 (high), 3 (normal), 5 (low). */\n\tpriority?: \"high\" | \"normal\" | \"low\";\n}\n\nexport interface MailAttachment {\n\tfilename: string;\n\t/** File content (Buffer / string). */\n\tcontent: Buffer | string;\n\t/** MIME type. Default: 'application/octet-stream'. */\n\tcontentType?: string;\n\t/** Content-ID for inline images. */\n\tcid?: string;\n}\n\n/** Result returned by a transport after sending. */\nexport interface MailSendResult {\n\t/** Transport-specific ID (e.g. SMTP message-id). */\n\tid: string;\n\t/** When the message was sent (unix-ms). */\n\tsentAt: number;\n}\n\n/** Transport that knows how to deliver a message. */\nexport interface MailTransport {\n\treadonly kind: string;\n\tsend(msg: MailMessage): Promise<MailSendResult>;\n\tclose?(): Promise<void>;\n}\n\n/** Top-level config. */\nexport interface MailConfig {\n\t/** Transport backend. Default: NullTransport. */\n\ttransport?: MailTransport;\n\t/** Default `from` address. */\n\tdefaultFrom?: MailAddress;\n\t/** Whether to expose raw `nodemailer` (only if SmtpTransport is used). */\n\tdebug?: boolean;\n}\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:
|
|
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 * Scalar UI HTML — a single self-contained page that loads Scalar\n * from the jsDelivr CDN.\n *\n * The page mounts Scalar as a custom-element via `<script\n * id=\"api-reference\" data-url=\"...\">` and waits for the CDN script\n * to upgrade it.\n *\n * No assets are bundled with the framework. No build step required.\n */\n\nexport function scalarHtml(opts: { title: string; specUrl: string; theme?: \"default\" | \"dark\" | \"purple\" | \"alternate\" | \"moon\" | \"solarized\" | \"bluePlanet\" | \"saturn\" | \"kepler\" | \"mars\" | \"deepSpace\" | \"laserwave\" | \"none\" }): string {\n\tconst title = escapeHtml(opts.title);\n\tconst theme = opts.theme ?? \"default\";\n\treturn `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${title} — API Reference</title>\n <meta name=\"description\" content=\"API reference for ${title}, generated from OpenAPI.\" />\n <style>\n :root { color-scheme: light dark; }\n body { margin: 0; font-family: ui-sans-serif, system-ui, sans-serif; }\n </style>\n</head>\n<body>\n <script\n id=\"api-reference\"\n type=\"application/json\"\n data-url=\"${escapeHtml(opts.specUrl)}\"\n data-configuration='${escapeJsonForAttr(JSON.stringify({ theme, hideClientButton: true }))}'\n ></script>\n <script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.25.0\"></script>\n</body>\n</html>`;\n}\n\nfunction escapeHtml(s: string): string {\n\treturn s\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\n/**\n * Encode a string for use inside an HTML attribute value. We avoid\n * `"` so the value remains valid JSON for Scalar's parser.\n */\nfunction escapeJsonForAttr(s: string): string {\n\treturn s.replace(/'/g, \"'\").replace(/</g, \"<\");\n}",
|
package/dist/package.json
CHANGED
package/dist/queue/index.js.map
CHANGED
|
@@ -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:
|
|
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 * In-memory queue backend — for tests, single-instance dev, and the\n * `bunx nx dev` workflow when Redis isn't running yet.\n *\n * NOT for production. It does not persist across restarts, does not\n * distribute across workers, and silently drops jobs on crash.\n */\n\nimport type {\n\tQueueBackend,\n\tJobHandler,\n\tWorkerHandle,\n\tWorkerOptions,\n\tAddedJob,\n\tAddOptions,\n\tQueueEvent,\n\tQueueEventListener,\n\tJobContext,\n} from \"../types.js\";\n\ninterface PendingJob {\n\tjobId: string;\n\tname: string;\n\tdata: unknown;\n\toptions: AddOptions;\n\tresolveAt: number; // ms epoch\n}\n\n/** Per-job worker handle. */\nclass MemoryWorkerHandle implements WorkerHandle {\n\t#running = true;\n\t#handler: JobHandler;\n\t#context: { jobId: string; name: string };\n\tconstructor(name: string, handler: JobHandler) {\n\t\tthis.#handler = handler;\n\t\tthis.#context = { jobId: \"\", name };\n\t}\n\tget name() {\n\t\treturn this.#context.name;\n\t}\n\tasync close() {\n\t\tthis.#running = false;\n\t}\n\tasync pause() {\n\t\tthis.#running = false;\n\t}\n\tasync resume() {\n\t\tthis.#running = true;\n\t}\n\tisRunning() {\n\t\treturn this.#running;\n\t}\n}\n\nexport class MemoryQueueBackend implements QueueBackend {\n\treadonly name = \"memory\" as const;\n\t#queue: PendingJob[] = [];\n\t#handlers = new Map<string, JobHandler>();\n\t#workerOptions = new Map<string, WorkerOptions>();\n\t#listeners = new Set<QueueEventListener>();\n\t#tickHandle: ReturnType<typeof setInterval> | null = null;\n\t#inFlight = 0;\n\n\tconstructor() {\n\t\t// Tick every 100 ms to dispatch due jobs.\n\t\tthis.#tickHandle = setInterval(() => this.#tick(), 100);\n\t\t// Allow Node to exit if only this timer is running.\n\t\tif (\n\t\t\ttypeof (this.#tickHandle as { unref?: () => void }).unref === \"function\"\n\t\t) {\n\t\t\t(this.#tickHandle as { unref: () => void }).unref();\n\t\t}\n\t}\n\n\t// ===========================================================================\n\t// Producer\n\t// ===========================================================================\n\n\tasync add(\n\t\tname: string,\n\t\tdata: unknown,\n\t\toptions: AddOptions = {},\n\t): Promise<AddedJob> {\n\t\tconst jobId = `mem-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n\t\tconst delayMs = (options.delaySeconds ?? 0) * 1000;\n\t\tthis.#queue.push({\n\t\t\tjobId,\n\t\t\tname,\n\t\t\tdata,\n\t\t\toptions,\n\t\t\tresolveAt: Date.now() + delayMs,\n\t\t});\n\t\tthis.#emit({ kind: \"job:added\", jobId, name });\n\t\treturn { jobId, name };\n\t}\n\n\tasync addBatch(\n\t\tjobs: Array<{ name: string; data: unknown; options?: AddOptions }>,\n\t): Promise<AddedJob[]> {\n\t\treturn Promise.all(jobs.map((j) => this.add(j.name, j.data, j.options)));\n\t}\n\n\t// ===========================================================================\n\t// Worker\n\t// ===========================================================================\n\n\tasync process<T>(\n\t\tname: string,\n\t\thandler: JobHandler<T>,\n\t\toptions: WorkerOptions = {},\n\t): Promise<WorkerHandle> {\n\t\tthis.#handlers.set(name, handler as JobHandler);\n\t\tthis.#workerOptions.set(name, options);\n\t\tconst handle = new MemoryWorkerHandle(name, handler as JobHandler);\n\t\tthis.#emit({\n\t\t\tkind: \"worker:started\",\n\t\t\tname,\n\t\t\tconcurrency: options.concurrency ?? 1,\n\t\t});\n\t\treturn handle;\n\t}\n\n\t// ===========================================================================\n\t// Lifecycle\n\t// ===========================================================================\n\n\tasync drain(): Promise<void> {\n\t\twhile (this.#inFlight > 0 || this.#queue.length > 0) {\n\t\t\tawait new Promise((r) => setTimeout(r, 50));\n\t\t}\n\t}\n\n\tasync stop(): Promise<void> {\n\t\tfor (const name of this.#handlers.keys()) {\n\t\t\tthis.#emit({ kind: \"worker:stopped\", name });\n\t\t}\n\t\tif (this.#tickHandle) clearInterval(this.#tickHandle);\n\t\tthis.#tickHandle = null;\n\t}\n\n\t// ===========================================================================\n\t// Events\n\t// ===========================================================================\n\n\ton(listener: QueueEventListener): () => void {\n\t\tthis.#listeners.add(listener);\n\t\treturn () => this.#listeners.delete(listener);\n\t}\n\n\t// ===========================================================================\n\t// Internal\n\t// ===========================================================================\n\n\tasync #tick() {\n\t\tif (this.#queue.length === 0) return;\n\t\tconst now = Date.now();\n\t\tconst due = this.#queue.filter((j) => j.resolveAt <= now);\n\t\tfor (const job of due) {\n\t\t\tthis.#queue = this.#queue.filter((j) => j !== job);\n\t\t\tconst handler = this.#handlers.get(job.name);\n\t\t\tif (!handler) continue;\n\t\t\tconst options = this.#workerOptions.get(job.name) ?? {};\n\t\t\tconst concurrency = options.concurrency ?? 1;\n\t\t\tif (this.#inFlight >= concurrency) {\n\t\t\t\t// Re-queue at the front.\n\t\t\t\tthis.#queue.unshift(job);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvoid this.#runJob(job, handler, options);\n\t\t}\n\t}\n\n\tasync #runJob(job: PendingJob, handler: JobHandler, options: WorkerOptions) {\n\t\tthis.#inFlight++;\n\t\tconst ctx: JobContext = {\n\t\t\tjobId: job.jobId,\n\t\t\tattempts: 1,\n\t\t\tjob: { name: job.name, data: job.data },\n\t\t\tprefix: `[queue:${job.name}]`,\n\t\t};\n\t\tthis.#emit({\n\t\t\tkind: \"job:active\",\n\t\t\tjobId: job.jobId,\n\t\t\tname: job.name,\n\t\t\tattempts: 1,\n\t\t});\n\t\ttry {\n\t\t\tconst result = await handler(job.data, ctx);\n\t\t\tif (result && typeof result === \"object\" && \"status\" in result) {\n\t\t\t\tconst r = result as {\n\t\t\t\t\tstatus: string;\n\t\t\t\t\treturnvalue?: unknown;\n\t\t\t\t\terror?: Error;\n\t\t\t\t};\n\t\t\t\tif (r.status === \"failed\") {\n\t\t\t\t\tthis.#emit({\n\t\t\t\t\t\tkind: \"job:failed\",\n\t\t\t\t\t\tjobId: job.jobId,\n\t\t\t\t\t\tname: job.name,\n\t\t\t\t\t\terror: r.error ?? new Error(\"unknown\"),\n\t\t\t\t\t\twillRetry: false,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tthis.#emit({\n\t\t\t\t\t\tkind: \"job:completed\",\n\t\t\t\t\t\tjobId: job.jobId,\n\t\t\t\t\t\tname: job.name,\n\t\t\t\t\t\treturnvalue: r.returnvalue,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.#emit({\n\t\t\t\t\tkind: \"job:completed\",\n\t\t\t\t\tjobId: job.jobId,\n\t\t\t\t\tname: job.name,\n\t\t\t\t\treturnvalue: result,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst error = err instanceof Error ? err : new Error(String(err));\n\t\t\tconst willRetry = (job.options.attempts ?? 1) > 1;\n\t\t\tthis.#emit({\n\t\t\t\tkind: \"job:failed\",\n\t\t\t\tjobId: job.jobId,\n\t\t\t\tname: job.name,\n\t\t\t\terror,\n\t\t\t\twillRetry,\n\t\t\t});\n\t\t\tif (willRetry) {\n\t\t\t\tconst delayMs =\n\t\t\t\t\t(job.options.backoff?.delayMs ?? 1000) *\n\t\t\t\t\t(job.options.backoff?.type === \"exponential\" ? 2 ** ctx.attempts : 1);\n\t\t\t\tthis.#queue.push({\n\t\t\t\t\t...job,\n\t\t\t\t\tresolveAt: Date.now() + delayMs,\n\t\t\t\t});\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#inFlight--;\n\t\t}\n\t\t// Touch options to silence \"unused\" warnings in strict configs.\n\t\tvoid options;\n\t}\n\n\t#emit(event: QueueEvent) {\n\t\tfor (const l of this.#listeners) {\n\t\t\tvoid Promise.resolve(l(event));\n\t\t}\n\t}\n}\n",
|
package/dist/redis/index.js.map
CHANGED
|
@@ -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:
|
|
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 * Bun runtime adapter for `nexusjs/redis`.\n *\n * Uses the built-in `Bun.redis` client. No extra package needed.\n * The client is lazily opened on first use.\n */\n\nimport type {\n\tRedisClient,\n\tRedisConfig,\n\tRedisScanOptions,\n\tRedisScanResult,\n\tRedisSetOptions,\n\tRedisValue,\n} from \"../types.js\";\n\n/** The shape we need from `Bun.redis()`. */\ninterface BunRedisClient {\n\tget(key: string): Promise<string | null>;\n\tset(\n\t\tkey: string,\n\t\tvalue: string,\n\t\toptions?: { EX?: number; PX?: number; NX?: boolean; XX?: boolean },\n\t): Promise<\"OK\" | null>;\n\tdel(key: string): Promise<number>;\n\texists(key: string): Promise<number>;\n\tincr(key: string): Promise<number>;\n\tscan(cursor: number, options?: { MATCH?: string; COUNT?: number }): Promise<{\n\t\tcursor: number;\n\t\tkeys: string[];\n\t}>;\n\tclose(): void | Promise<void>;\n}\n\nexport class BunRedisAdapter implements RedisClient {\n\treadonly adapter = \"bun\" as const;\n\tprivate client: BunRedisClient | null = null;\n\tprivate readonly url: string;\n\tprivate readonly keyPrefix: string;\n\tprivate readonly defaultTtlSeconds: number;\n\n\tconstructor(config: RedisConfig = {}) {\n\t\tthis.url = config.url ?? process.env[\"REDIS_URL\"] ?? \"redis://localhost:6379\";\n\t\tthis.keyPrefix = config.keyPrefix ?? \"\";\n\t\tthis.defaultTtlSeconds = config.defaultTtlSeconds ?? 0;\n\t}\n\n\tprivate getClient(): BunRedisClient {\n\t\tif (this.client) return this.client;\n\t\t// Bun.redis is a global; cast through unknown so we can compile\n\t\t// in environments that don't have Bun's lib.d.ts loaded.\n\t\tconst bun = (globalThis as unknown as { Bun?: { redis: (url: string) => BunRedisClient } }).Bun;\n\t\tif (!bun || typeof bun.redis !== \"function\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"BunRedisAdapter can only be used in a Bun runtime. \" +\n\t\t\t\t\t\"On Node, use NodeRedisAdapter (install ioredis).\",\n\t\t\t);\n\t\t}\n\t\tthis.client = bun.redis(this.url);\n\t\treturn this.client!;\n\t}\n\n\tprivate k(key: string): string {\n\t\treturn this.keyPrefix + key;\n\t}\n\n\tasync get(key: string): Promise<RedisValue> {\n\t\treturn this.getClient().get(this.k(key));\n\t}\n\n\tasync set(key: string, value: string, options?: RedisSetOptions): Promise<void> {\n\t\tconst ex = options?.ex ?? this.defaultTtlSeconds ?? undefined;\n\t\tconst px = options?.px ?? undefined;\n\t\tconst bunOpts: { EX?: number; PX?: number; NX?: boolean; XX?: boolean } = {};\n\t\tif (ex) bunOpts.EX = ex;\n\t\tif (px) bunOpts.PX = px;\n\t\tif (options?.nx) bunOpts.NX = true;\n\t\tif (options?.xx) bunOpts.XX = true;\n\t\tawait this.getClient().set(this.k(key), value, bunOpts);\n\t}\n\n\tasync del(key: string): Promise<number> {\n\t\treturn this.getClient().del(this.k(key));\n\t}\n\n\tasync exists(key: string): Promise<boolean> {\n\t\treturn (await this.getClient().exists(this.k(key))) > 0;\n\t}\n\n\tasync incr(key: string, by = 1, options?: { ex?: number }): Promise<number> {\n\t\tconst fullKey = this.k(key);\n\t\tconst client = this.getClient();\n\t\tconst value = await client.incr(fullKey);\n\t\t// On the first increment (value === 1), apply the TTL if requested.\n\t\tif (options?.ex && value === by) {\n\t\t\tawait client.set(fullKey, String(value), { EX: options.ex });\n\t\t}\n\t\treturn value;\n\t}\n\n\tasync scan(options: RedisScanOptions = {}): Promise<RedisScanResult> {\n\t\tconst cursor = typeof options.cursor === \"number\" ? options.cursor : 0;\n\t\tconst res = await this.getClient().scan(cursor, {\n\t\t\tMATCH: options.match ?? \"*\",\n\t\t\tCOUNT: options.count ?? 100,\n\t\t});\n\t\treturn {\n\t\t\tcursor: res.cursor,\n\t\t\tkeys: (res.keys ?? []).map((k) =>\n\t\t\t\tthis.keyPrefix && k.startsWith(this.keyPrefix)\n\t\t\t\t\t? k.slice(this.keyPrefix.length)\n\t\t\t\t\t: k,\n\t\t\t),\n\t\t};\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.client) {\n\t\t\tawait this.client.close();\n\t\t\tthis.client = null;\n\t\t}\n\t}\n}\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:
|
|
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 * Cron expression parser + next-run calculator.\n *\n * Supports the standard 5-field crontab syntax, an optional 6-field\n * variant (with seconds), common aliases, and \"@every <duration>\".\n *\n * * * * * * every minute\n * 0 * * * * every hour\n * star-slash-15 every 15m\n * 0 9 * * 1-5 9am on weekdays\n * 0 0 1 * * first of the month\n * 30 14 1 4 * Apr 1st at 14:30\n * @yearly @annually 0 0 1 1 *\n * @monthly 0 0 1 * *\n * @weekly 0 0 * * 0\n * @daily @midnight 0 0 * * *\n * @hourly 0 * * * *\n * @every 1h30m every 90 minutes\n *\n * Field names (JAN, FEB, SUN, MON, ...) are accepted case-insensitively.\n */\n\nconst FIELD_RANGES: Array<[number, number]> = [\n\t[0, 59], // minute\n\t[0, 23], // hour\n\t[1, 31], // day of month\n\t[1, 12], // month\n\t[0, 7], // day of week (0 or 7 = Sunday)\n];\n\nconst FIELD_NAMES: Record<string, number> = {\n\tJAN: 1,\n\tFEB: 2,\n\tMAR: 3,\n\tAPR: 4,\n\tMAY: 5,\n\tJUN: 6,\n\tJUL: 7,\n\tAUG: 8,\n\tSEP: 9,\n\tOCT: 10,\n\tNOV: 11,\n\tDEC: 12,\n\tSUN: 0,\n\tMON: 1,\n\tTUE: 2,\n\tWED: 3,\n\tTHU: 4,\n\tFRI: 5,\n\tSAT: 6,\n};\n\nconst ALIASES: Record<string, string> = {\n\t\"@yearly\": \"0 0 1 1 *\",\n\t\"@annually\": \"0 0 1 1 *\",\n\t\"@monthly\": \"0 0 1 * *\",\n\t\"@weekly\": \"0 0 * * 0\",\n\t\"@daily\": \"0 0 * * *\",\n\t\"@midnight\": \"0 0 * * *\",\n\t\"@hourly\": \"0 * * * *\",\n};\n\n/** A single field expanded into a set of allowed numeric values. */\nexport class CronField {\n\treadonly values: Set<number>;\n\n\tconstructor(field: string, range: [number, number]) {\n\t\tthis.values = parseField(field, range);\n\t}\n\n\tcontains(n: number): boolean {\n\t\treturn this.values.has(n);\n\t}\n}\n\n/** A fully-parsed cron expression. */\nexport class CronExpression {\n\treadonly fields: CronField[]; // 5 or 6 entries\n\treadonly hasSeconds: boolean;\n\n\tconstructor(raw: string) {\n\t\tconst expanded = expandAlias(raw);\n\t\tconst every = expandEvery(expanded);\n\n\t\tif (every) {\n\t\t\t// \"@every Nd|Nh|Nm|Ns\" → uniform interval\n\t\t\tthis.hasSeconds = true;\n\t\t\tthis.fields = everyToFields(every);\n\t\t\treturn;\n\t\t}\n\n\t\tconst parts = expanded.trim().split(/\\s+/);\n\t\tif (parts.length === 5) {\n\t\t\tthis.hasSeconds = false;\n\t\t\tthis.fields = parts.map((p, i) => new CronField(p, FIELD_RANGES[i]!));\n\t\t} else if (parts.length === 6) {\n\t\t\tthis.hasSeconds = true;\n\t\t\tconst sec: [number, number] = [0, 59];\n\t\t\tthis.fields = [\n\t\t\t\tnew CronField(parts[0]!, sec),\n\t\t\t\t...parts.slice(1).map((p, i) => new CronField(p, FIELD_RANGES[i]!)),\n\t\t\t];\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid cron expression: \"${raw}\" (expected 5 or 6 fields, got ${parts.length})`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Return the next Date at or after `from` that matches this\n\t * expression. Returns null if no match is found within `maxYears`\n\t * (default 5) — which would indicate a misconfigured expression.\n\t */\n\tnext(from: Date, maxYears = 5): Date | null {\n\t\tconst cap = new Date(from.getTime() + maxYears * 365 * 24 * 60 * 60 * 1000);\n\t\tlet cur = new Date(from.getTime() + 1000);\n\t\tcur.setMilliseconds(0);\n\n\t\t// Brute-force: step minute-by-minute, but skip ahead when a\n\t\t// higher-order field doesn't match. For most crons this is\n\t\t// fast enough; for very sparse crons we use a forwarder.\n\t\tlet safety = 0;\n\t\twhile (cur <= cap) {\n\t\t\tif (this.matches(cur)) {\n\t\t\t\treturn cur;\n\t\t\t}\n\t\t\t// Fast-forward: if the month doesn't match, jump to next month.\n\t\t\tif (!this.fields[this.hasSeconds ? 4 : 3]!.contains(cur.getMonth() + 1)) {\n\t\t\t\tcur = new Date(cur.getFullYear(), cur.getMonth() + 1, 1, 0, 0, 0, 0);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// If day-of-month is restricted and current day doesn't match,\n\t\t\t// jump to next month at the first allowed day.\n\t\t\tconst domIdx = this.hasSeconds ? 3 : 2;\n\t\t\tconst domField = this.fields[domIdx]!;\n\t\t\tif (!domField.contains(cur.getDate())) {\n\t\t\t\t// Find the first allowed day in the set, or jump to next month.\n\t\t\t\tconst sortedDays = [...domField.values].sort((a, b) => a - b);\n\t\t\t\tconst nextDay = sortedDays.find((d) => d >= cur.getDate());\n\t\t\t\tif (nextDay !== undefined) {\n\t\t\t\t\tcur = new Date(\n\t\t\t\t\t\tcur.getFullYear(),\n\t\t\t\t\t\tcur.getMonth(),\n\t\t\t\t\t\tnextDay,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tcur = new Date(\n\t\t\t\t\t\tcur.getFullYear(),\n\t\t\t\t\t\tcur.getMonth() + 1,\n\t\t\t\t\t\tsortedDays[0]!,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// If the hour doesn't match, jump to next hour.\n\t\t\tif (!this.fields[this.hasSeconds ? 2 : 1]!.contains(cur.getHours())) {\n\t\t\t\tcur = new Date(\n\t\t\t\t\tcur.getFullYear(),\n\t\t\t\t\tcur.getMonth(),\n\t\t\t\t\tcur.getDate(),\n\t\t\t\t\tcur.getHours() + 1,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// If the minute doesn't match, jump to next minute.\n\t\t\tif (!this.fields[this.hasSeconds ? 1 : 0]!.contains(cur.getMinutes())) {\n\t\t\t\tcur = new Date(\n\t\t\t\t\tcur.getFullYear(),\n\t\t\t\t\tcur.getMonth(),\n\t\t\t\t\tcur.getDate(),\n\t\t\t\t\tcur.getHours(),\n\t\t\t\t\tcur.getMinutes() + 1,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// If seconds field exists and doesn't match, jump to next second.\n\t\t\tif (this.hasSeconds && !this.fields[0]!.contains(cur.getSeconds())) {\n\t\t\t\tcur = new Date(cur.getTime() + 1000);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Fallback: step 1 second (shouldn't normally hit).\n\t\t\tcur = new Date(cur.getTime() + 1000);\n\t\t\tif (++safety > 1_000_000) return null;\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate matches(d: Date): boolean {\n\t\tconst fields = this.hasSeconds\n\t\t\t? [\n\t\t\t\t\td.getSeconds(),\n\t\t\t\t\td.getMinutes(),\n\t\t\t\t\td.getHours(),\n\t\t\t\t\td.getDate(),\n\t\t\t\t\td.getMonth() + 1,\n\t\t\t\t\td.getDay(),\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\td.getMinutes(),\n\t\t\t\t\td.getHours(),\n\t\t\t\t\td.getDate(),\n\t\t\t\t\td.getMonth() + 1,\n\t\t\t\t\td.getDay(),\n\t\t\t\t];\n\t\t// Day-of-week in crontab: 0 = Sunday. Day-of-month is OR'd with\n\t\t// day-of-week when both are restricted (standard crontab behavior).\n\t\tconst domField = this.fields[this.hasSeconds ? 3 : 2]!;\n\t\tconst dowField = this.fields[this.hasSeconds ? 4 : 3]!;\n\t\tconst isWildDom = domField.values.size === FIELD_RANGES[2]![1]!;\n\t\tconst isWildDow = dowField.values.size === FIELD_RANGES[4]![1]! + 1;\n\t\tconst dayMatch =\n\t\t\tisWildDom || isWildDow\n\t\t\t\t? domField.contains(d.getDate()) || dowField.contains(d.getDay())\n\t\t\t\t: domField.contains(d.getDate()) || dowField.contains(d.getDay());\n\n\t\tfor (let i = 0; i < this.fields.length; i++) {\n\t\t\tif (i === (this.hasSeconds ? 3 : 2)) continue;\n\t\t\tif (!this.fields[i]!.contains(fields[i]!)) return false;\n\t\t}\n\t\treturn dayMatch;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction expandAlias(raw: string): string {\n\tconst trimmed = raw.trim();\n\tconst alias = ALIASES[trimmed.toLowerCase()];\n\treturn alias ?? trimmed;\n}\n\nfunction expandEvery(raw: string): number | null {\n\tconst m = /^@every\\s+(\\d+)\\s*(s|m|h|d)?$/i.exec(raw.trim());\n\tif (!m) return null;\n\tconst n = Number(m[1]);\n\tconst unit = (m[2] ?? \"s\").toLowerCase();\n\tswitch (unit) {\n\t\tcase \"s\":\n\t\t\treturn n * 1000;\n\t\tcase \"m\":\n\t\t\treturn n * 60 * 1000;\n\t\tcase \"h\":\n\t\t\treturn n * 60 * 60 * 1000;\n\t\tcase \"d\":\n\t\t\treturn n * 24 * 60 * 60 * 1000;\n\t}\n\treturn null;\n}\n\nfunction everyToFields(intervalMs: number): CronField[] {\n\t// Generate a 6-field expression that fires every `intervalMs`\n\t// starting from the next round minute.\n\tconst seconds = Math.floor(intervalMs / 1000);\n\tif (seconds < 60) {\n\t\t// fire every N seconds (only if it divides 60)\n\t\tif (60 % seconds === 0) {\n\t\t\treturn [\n\t\t\t\tnew CronField(`*/${seconds}`, [0, 59]),\n\t\t\t\tnew CronField(\"*\", [0, 59]),\n\t\t\t\tnew CronField(\"*\", [0, 23]),\n\t\t\t\tnew CronField(\"*\", [1, 31]),\n\t\t\t\tnew CronField(\"*\", [1, 12]),\n\t\t\t\tnew CronField(\"*\", [0, 7]),\n\t\t\t];\n\t\t}\n\t}\n\t// Otherwise just allow every minute; the registry layer handles\n\t// throttling via setInterval.\n\treturn [\n\t\tnew CronField(\"0\", [0, 59]),\n\t\tnew CronField(\"*\", [0, 59]),\n\t\tnew CronField(\"*\", [0, 23]),\n\t\tnew CronField(\"*\", [1, 31]),\n\t\tnew CronField(\"*\", [1, 12]),\n\t\tnew CronField(\"*\", [0, 7]),\n\t];\n}\n\nfunction parseField(field: string, range: [number, number]): Set<number> {\n\tconst out = new Set<number>();\n\tconst [lo, hi] = range;\n\tconst parts = field.split(\",\");\n\tfor (const partRaw of parts) {\n\t\tconst part = partRaw.trim();\n\t\t// step: e.g. \"*/2\" or \"0-30/2\"\n\t\tconst stepMatch = /^(.+?)\\/(\\d+)$/.exec(part);\n\t\tlet base = part;\n\t\tlet step = 1;\n\t\tif (stepMatch) {\n\t\t\tbase = stepMatch[1]!;\n\t\t\tstep = Number(stepMatch[2]);\n\t\t}\n\t\tlet start: number;\n\t\tlet end: number;\n\t\tif (base === \"*\") {\n\t\t\tstart = lo;\n\t\t\tend = hi;\n\t\t} else if (base.includes(\"-\")) {\n\t\t\tconst [a, b] = base.split(\"-\").map((s) => resolveValue(s, lo, hi));\n\t\t\tstart = a;\n\t\t\tend = b;\n\t\t} else {\n\t\t\tconst v = resolveValue(base, lo, hi);\n\t\t\tstart = v;\n\t\t\tend = stepMatch ? hi : v;\n\t\t}\n\t\tif (start > end) {\n\t\t\tthrow new Error(`Invalid range in cron field: \"${part}\"`);\n\t\t}\n\t\tfor (let i = start; i <= end; i += step) {\n\t\t\tout.add(i);\n\t\t}\n\t}\n\treturn out;\n}\n\nfunction resolveValue(token: string, lo: number, hi: number): number {\n\tconst t = token.trim();\n\tif (t === \"*\") return lo;\n\tconst named = FIELD_NAMES[t.toUpperCase()];\n\tif (named !== undefined) return named;\n\tconst n = Number(t);\n\tif (Number.isNaN(n)) {\n\t\tthrow new Error(`Invalid cron field value: \"${token}\"`);\n\t}\n\tif (n < lo || n > hi) {\n\t\tthrow new Error(`Cron value ${n} out of range [${lo}, ${hi}]`);\n\t}\n\treturn n;\n}\n\n/**\n * Parse a cron expression. Throws on invalid syntax. The returned\n * object can be queried for the next match (`expr.next(new Date())`).\n */\nexport function parseCron(expression: string): CronExpression {\n\treturn new CronExpression(expression);\n}\n\n/** Convenience: return the next Date matching `expression` after `from`. */\nexport function nextCron(\n\texpression: string,\n\tfrom: Date = new Date(),\n): Date | null {\n\treturn parseCron(expression).next(from);\n}\n",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"/**\n * `DrizzleSessionStorage` — session storage backed by any Drizzle database.\n *\n * import { DrizzleService } from 'nexusjs/drizzle';\n * import { SessionService } from 'nexusjs/session';\n *\n * const db = new DrizzleService({ dialect: 'postgres', connection: {... } });\n * await db.open();\n * await db.raw`CREATE TABLE IF NOT EXISTS nexus_sessions (...)`.execute();\n *\n * const storage = new DrizzleSessionStorage(db, 'nexus_sessions');\n * const session = new SessionService({ backend: 'drizzle', drizzle: { storage } });\n *\n * Schema (managed by the user / migrations):\n *\n * CREATE TABLE nexus_sessions (\n * id TEXT PRIMARY KEY,\n * user_id TEXT,\n * data JSONB,\n * created_at TIMESTAMP NOT NULL,\n * last_seen_at TIMESTAMP NOT NULL,\n * expires_at TIMESTAMP NOT NULL,\n * absolute_expires_at TIMESTAMP,\n * metadata JSONB\n * );\n */\nimport type { DrizzleService } from \"../../drizzle/drizzle.service.js\";\nimport type {\n\tSessionStorage,\n\tSessionRecord,\n\tSessionData,\n\tCreateSessionOptions,\n\tUpdateSessionOptions,\n\tSessionQuery,\n\tSessionMetadata,\n} from \"../types.js\";\n\nexport interface DrizzleSessionOptions {\n\t/** DrizzleService instance. */\n\tdb: DrizzleService;\n\t/** Table name. Default: 'nexus_sessions'. */\n\ttableName?: string;\n\t/** Column mapping. Override to match your schema. */\n\tcolumns?: {\n\t\tid?: string;\n\t\tuserId?: string;\n\t\tdata?: string;\n\t\tcreatedAt?: string;\n\t\tlastSeenAt?: string;\n\t\texpiresAt?: string;\n\t\tabsoluteExpiresAt?: string;\n\t\tmetadata?: string;\n\t};\n}\n\nexport class DrizzleSessionStorage implements SessionStorage {\n\treadonly name = \"database\" as const;\n\n\tprivate db: DrizzleService;\n\tprivate tableName: string;\n\tprivate cols: Required<NonNullable<DrizzleSessionOptions[\"columns\"]>>;\n\n\tconstructor(options: DrizzleSessionOptions) {\n\t\tthis.db = options.db;\n\t\tthis.tableName = options.tableName ?? \"nexus_sessions\";\n\t\tthis.cols = {\n\t\t\tid: options.columns?.id ?? \"id\",\n\t\t\tuserId: options.columns?.userId ?? \"user_id\",\n\t\t\tdata: options.columns?.data ?? \"data\",\n\t\t\tcreatedAt: options.columns?.createdAt ?? \"created_at\",\n\t\t\tlastSeenAt: options.columns?.lastSeenAt ?? \"last_seen_at\",\n\t\t\texpiresAt: options.columns?.expiresAt ?? \"expires_at\",\n\t\t\tabsoluteExpiresAt:\n\t\t\t\toptions.columns?.absoluteExpiresAt ?? \"absolute_expires_at\",\n\t\t\tmetadata: options.columns?.metadata ?? \"metadata\",\n\t\t};\n\t}\n\n\tasync create<T = SessionData>(\n\t\topts: CreateSessionOptions<T>,\n\t): Promise<SessionRecord<T>> {\n\t\tconst now = new Date();\n\t\tconst record: SessionRecord<T> = {\n\t\t\tid: opts.id ?? randomId(),\n\t\t\tuserId: null,\n\t\t\tdata: (opts.data ?? {}) as T,\n\t\t\tcreatedAt: now,\n\t\t\tlastSeenAt: now,\n\t\t\texpiresAt: new Date(\n\t\t\t\tnow.getTime() + (opts.ttlSeconds ?? 60 * 60 * 24 * 7) * 1000,\n\t\t\t),\n\t\t};\n\t\tif (opts.absoluteTtlSeconds) {\n\t\t\trecord.absoluteExpiresAt = new Date(\n\t\t\t\tnow.getTime() + opts.absoluteTtlSeconds * 1000,\n\t\t\t);\n\t\t}\n\t\tif (opts.metadata) record.metadata = opts.metadata;\n\n\t\tawait this.db.rawQuery(\n\t\t\t`INSERT INTO ${this.tableName} (${this.cols.id}, ${this.cols.userId}, ${this.cols.data}, ${this.cols.createdAt}, ${this.cols.lastSeenAt}, ${this.cols.expiresAt}, ${this.cols.absoluteExpiresAt}, ${this.cols.metadata})\n\t\t\t VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\t[\n\t\t\t\trecord.id,\n\t\t\t\trecord.userId,\n\t\t\t\tJSON.stringify(record.data),\n\t\t\t\ttoTimestamp(record.createdAt),\n\t\t\t\ttoTimestamp(record.lastSeenAt),\n\t\t\t\ttoTimestamp(record.expiresAt),\n\t\t\t\trecord.absoluteExpiresAt ? toTimestamp(record.absoluteExpiresAt) : null,\n\t\t\t\trecord.metadata ? JSON.stringify(record.metadata) : null,\n\t\t\t],\n\t\t);\n\t\treturn record;\n\t}\n\n\tasync read(id: string): Promise<SessionRecord | null> {\n\t\tconst rows = await this.db.rawQuery<SessionRow>(\n\t\t\t`SELECT * FROM ${this.tableName} WHERE ${this.cols.id} = ? LIMIT 1`,\n\t\t\t[id],\n\t\t);\n\t\treturn rows[0] ? this.rowToRecord(rows[0]) : null;\n\t}\n\n\tasync readMany(query: SessionQuery = {}): Promise<SessionRecord[]> {\n\t\tconst where: string[] = [];\n\t\tconst params: unknown[] = [];\n\t\tif (query.userId !== undefined) {\n\t\t\twhere.push(`${this.cols.userId} = ?`);\n\t\t\tparams.push(query.userId);\n\t\t}\n\t\tconst whereSql = where.length > 0 ? `WHERE ${where.join(\" AND \")}` : \"\";\n\t\tconst limit = query.limit ?? 1000;\n\t\tconst rows = await this.db.rawQuery<SessionRow>(\n\t\t\t`SELECT * FROM ${this.tableName} ${whereSql} ORDER BY ${this.cols.createdAt} DESC LIMIT ?`,\n\t\t\t[...params, limit],\n\t\t);\n\t\treturn rows.map((r) => this.rowToRecord(r));\n\t}\n\n\tasync update<T = SessionData>(\n\t\tid: string,\n\t\topts: UpdateSessionOptions<T>,\n\t): Promise<SessionRecord<T> | null> {\n\t\tconst sets: string[] = [];\n\t\tconst params: unknown[] = [];\n\t\tif (opts.dataPatch !== undefined) {\n\t\t\tsets.push(`${this.cols.data} = ?`);\n\t\t\tparams.push(JSON.stringify(opts.dataPatch));\n\t\t}\n\t\tif (opts.extendSeconds !== undefined) {\n\t\t\tsets.push(`${this.cols.expiresAt} = ?`);\n\t\t\tparams.push(\n\t\t\t\ttoTimestamp(new Date(Date.now() + opts.extendSeconds * 1000)),\n\t\t\t);\n\t\t}\n\t\tif (sets.length === 0)\n\t\t\treturn this.read(id) as Promise<SessionRecord<T> | null>;\n\t\tsets.push(`${this.cols.lastSeenAt} = ?`);\n\t\tparams.push(toTimestamp(new Date()));\n\t\tparams.push(id);\n\t\tawait this.db.rawQuery(\n\t\t\t`UPDATE ${this.tableName} SET ${sets.join(\", \")} WHERE ${this.cols.id} = ?`,\n\t\t\tparams,\n\t\t);\n\t\treturn this.read(id) as Promise<SessionRecord<T> | null>;\n\t}\n\n\tasync touch(id: string): Promise<SessionRecord | null> {\n\t\tawait this.db.rawQuery(\n\t\t\t`UPDATE ${this.tableName} SET ${this.cols.lastSeenAt} = ? WHERE ${this.cols.id} = ?`,\n\t\t\t[toTimestamp(new Date()), id],\n\t\t);\n\t\treturn this.read(id);\n\t}\n\n\tasync destroy(id: string): Promise<boolean> {\n\t\tconst existing = await this.read(id);\n\t\tif (!existing) return false;\n\t\tawait this.db.rawQuery(\n\t\t\t`DELETE FROM ${this.tableName} WHERE ${this.cols.id} = ?`,\n\t\t\t[id],\n\t\t);\n\t\treturn true;\n\t}\n\n\tasync destroyMany(query: SessionQuery = {}): Promise<number> {\n\t\tconst where: string[] = [];\n\t\tconst params: unknown[] = [];\n\t\tif (query.userId !== undefined) {\n\t\t\twhere.push(`${this.cols.userId} = ?`);\n\t\t\tparams.push(query.userId);\n\t\t}\n\t\tconst whereSql = where.length > 0 ? `WHERE ${where.join(\" AND \")}` : \"\";\n\t\tawait this.db.rawQuery(`DELETE FROM ${this.tableName} ${whereSql}`, params);\n\t\treturn 0; // not all dialects return rowCount; treat as unknown\n\t}\n\n\tasync gc(): Promise<number> {\n\t\tawait this.db.rawQuery(\n\t\t\t`DELETE FROM ${this.tableName} WHERE ${this.cols.expiresAt} < ?`,\n\t\t\t[toTimestamp(new Date())],\n\t\t);\n\t\treturn 0;\n\t}\n\n\tasync clear(): Promise<void> {\n\t\tawait this.db.rawQuery(`DELETE FROM ${this.tableName}`);\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// No background timer — the DB is the source of truth.\n\t}\n\n\tasync stop(): Promise<void> {\n\t\t// No background tasks to stop.\n\t}\n\n\t// ===========================================================================\n\t// Internal\n\t// ===========================================================================\n\n\tprivate rowToRecord(row: SessionRow): SessionRecord {\n\t\tconst r: SessionRecord = {\n\t\t\tid: String(row[this.cols.id]),\n\t\t\tuserId: row[this.cols.userId] as string | null,\n\t\t\tdata: parseJson(row[this.cols.data]) ?? {},\n\t\t\tcreatedAt: fromTimestamp(row[this.cols.createdAt]),\n\t\t\tlastSeenAt: fromTimestamp(row[this.cols.lastSeenAt]),\n\t\t\texpiresAt: fromTimestamp(row[this.cols.expiresAt]),\n\t\t};\n\t\tconst abs = row[this.cols.absoluteExpiresAt];\n\t\tif (abs) r.absoluteExpiresAt = fromTimestamp(abs);\n\t\tconst meta = row[this.cols.metadata];\n\t\tif (meta) r.metadata = parseJson(meta) as SessionMetadata;\n\t\treturn r;\n\t}\n}\n\ninterface SessionRow {\n\t[key: string]: unknown;\n}\n\nfunction toTimestamp(d: Date): string {\n\treturn d.toISOString();\n}\n\nfunction fromTimestamp(v: unknown): Date {\n\tif (v instanceof Date) return v;\n\tif (typeof v === \"string\") return new Date(v);\n\tif (typeof v === \"number\") return new Date(v);\n\treturn new Date();\n}\n\nfunction parseJson(v: unknown): any {\n\tif (typeof v !== \"string\") return v;\n\ttry {\n\t\treturn JSON.parse(v);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction randomId(bytes = 24): string {\n\tconst arr = new Uint8Array(bytes);\n\tcrypto.getRandomValues(arr);\n\treturn Buffer.from(arr).toString(\"base64url\");\n}\n",
|
|
6
6
|
"/**\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",
|
|
7
7
|
"/**\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",
|
|
8
|
-
"/**\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:
|
|
8
|
+
"/**\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",
|
|
9
9
|
"/**\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",
|
|
10
10
|
"/**\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",
|
|
11
11
|
"/**\n * In-memory session storage.\n *\n * LRU-evicting map for tests and single-instance dev. The same map is\n * also useful for mocking in unit tests — `create()` / `read()` /\n * `update()` / `destroy()` round-trip without any I/O.\n */\n\nimport type {\n\tSessionStorage,\n\tSessionRecord,\n\tSessionData,\n\tCreateSessionOptions,\n\tUpdateSessionOptions,\n\tSessionQuery,\n} from \"../types.js\";\n\nexport interface MemoryStorageOptions {\n\t/** GC interval in ms. Default: 60_000. */\n\tgcIntervalMs?: number;\n\t/** Max sessions in memory before evicting LRU. Default: 100_000. */\n\tmaxSessions?: number;\n}\n\nfunction randomId(bytes = 24): string {\n\tconst arr = new Uint8Array(bytes);\n\tcrypto.getRandomValues(arr);\n\treturn Buffer.from(arr).toString(\"base64url\");\n}\n\nexport class MemorySessionStorage implements SessionStorage {\n\treadonly name = \"memory\" as const;\n\t#sessions = new Map<string, SessionRecord>();\n\t#gcHandle: ReturnType<typeof setInterval> | null = null;\n\t#maxSessions: number;\n\t#gcIntervalMs: number;\n\n\tconstructor(options: MemoryStorageOptions = {}) {\n\t\tthis.#maxSessions = options.maxSessions ?? 100_000;\n\t\tthis.#gcIntervalMs = options.gcIntervalMs ?? 60_000;\n\t}\n\n\t/** Start the GC interval. Idempotent. */\n\tstart(): void {\n\t\tif (this.#gcHandle) return;\n\t\tthis.#gcHandle = setInterval(() => void this.gc(), this.#gcIntervalMs);\n\t\tconst h = this.#gcHandle as { unref?: () => void };\n\t\tif (typeof h.unref === \"function\") h.unref();\n\t}\n\n\tasync stop(): Promise<void> {\n\t\tif (this.#gcHandle) clearInterval(this.#gcHandle);\n\t\tthis.#gcHandle = null;\n\t}\n\n\t// ===========================================================================\n\t// SessionStorage API\n\t// ===========================================================================\n\n\tasync create<T = SessionData>(\n\t\topts: CreateSessionOptions<T>,\n\t): Promise<SessionRecord<T>> {\n\t\tconst now = new Date();\n\t\tconst ttl = (opts.ttlSeconds ?? 60 * 60 * 24 * 7) * 1000;\n\t\tconst record: SessionRecord<T> = {\n\t\t\tid: opts.id ?? randomId(),\n\t\t\tuserId: null,\n\t\t\tdata: (opts.data ?? {}) as T,\n\t\t\tcreatedAt: now,\n\t\t\tlastSeenAt: now,\n\t\t\texpiresAt: new Date(now.getTime() + ttl),\n\t\t};\n\t\tif (opts.absoluteTtlSeconds) {\n\t\t\trecord.absoluteExpiresAt = new Date(\n\t\t\t\tnow.getTime() + opts.absoluteTtlSeconds * 1000,\n\t\t\t);\n\t\t}\n\t\tif (opts.metadata) record.metadata = opts.metadata;\n\t\tthis.#sessions.set(record.id, record as unknown as SessionRecord);\n\t\tthis.#evictIfFull();\n\t\treturn record;\n\t}\n\n\tasync read(id: string): Promise<SessionRecord | null> {\n\t\tconst r = this.#sessions.get(id);\n\t\tif (!r) return null;\n\t\tif (this.#isExpired(r)) {\n\t\t\tthis.#sessions.delete(id);\n\t\t\treturn null;\n\t\t}\n\t\t// Touch — sliding expiry.\n\t\tconst now = new Date();\n\t\tr.lastSeenAt = now;\n\t\tr.expiresAt = new Date(\n\t\t\tnow.getTime() +\n\t\t\t\tMath.max(60_000, r.expiresAt.getTime() - r.lastSeenAt.getTime()),\n\t\t);\n\t\tthis.#sessions.set(id, r); // refresh LRU position\n\t\treturn r;\n\t}\n\n\tasync readMany(query: SessionQuery = {}): Promise<SessionRecord[]> {\n\t\tconst now = Date.now();\n\t\tconst list: SessionRecord[] = [];\n\t\tfor (const r of this.#sessions.values()) {\n\t\t\tif (this.#isExpired(r)) continue;\n\t\t\tif (query.userId !== undefined && r.userId !== query.userId) continue;\n\t\t\tif (query.metadata) {\n\t\t\t\tif (\n\t\t\t\t\tquery.metadata.ipAddress &&\n\t\t\t\t\tr.metadata?.ipAddress !== query.metadata.ipAddress\n\t\t\t\t)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (\n\t\t\t\t\tquery.metadata.userAgent &&\n\t\t\t\t\tr.metadata?.userAgent !== query.metadata.userAgent\n\t\t\t\t)\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlist.push(r);\n\t\t\tif (query.limit && list.length >= (query.offset ?? 0) + query.limit)\n\t\t\t\tbreak;\n\t\t}\n\t\tconst offset = query.offset ?? 0;\n\t\tconst limit = query.limit ?? list.length;\n\t\treturn list.slice(offset, offset + limit);\n\t}\n\n\tasync update<T = SessionData>(\n\t\tid: string,\n\t\topts: UpdateSessionOptions<T>,\n\t): Promise<SessionRecord<T> | null> {\n\t\tconst r = this.#sessions.get(id);\n\t\tif (!r) return null;\n\t\tif (this.#isExpired(r)) {\n\t\t\tthis.#sessions.delete(id);\n\t\t\treturn null;\n\t\t}\n\t\tif (opts.dataPatch) {\n\t\t\tr.data = { ...r.data, ...opts.dataPatch } as SessionData;\n\t\t}\n\t\tif (opts.extendSeconds !== undefined) {\n\t\t\tconst newExpiry = Date.now() + opts.extendSeconds * 1000;\n\t\t\tif (!r.absoluteExpiresAt || newExpiry < r.absoluteExpiresAt.getTime()) {\n\t\t\t\tr.expiresAt = new Date(newExpiry);\n\t\t\t}\n\t\t}\n\t\tr.lastSeenAt = new Date();\n\t\tthis.#sessions.set(id, r);\n\t\treturn r as unknown as SessionRecord<T>;\n\t}\n\n\tasync destroy(id: string): Promise<boolean> {\n\t\treturn this.#sessions.delete(id);\n\t}\n\n\tasync destroyMany(query: SessionQuery): Promise<number> {\n\t\tconst ids: string[] = [];\n\t\tfor (const [id, r] of this.#sessions) {\n\t\t\tif (query.userId !== undefined && r.userId !== query.userId) continue;\n\t\t\tids.push(id);\n\t\t}\n\t\tfor (const id of ids) this.#sessions.delete(id);\n\t\treturn ids.length;\n\t}\n\n\tasync touch(id: string): Promise<SessionRecord | null> {\n\t\treturn this.read(id);\n\t}\n\n\tasync gc(): Promise<number> {\n\t\tconst now = Date.now();\n\t\tlet removed = 0;\n\t\tfor (const [id, r] of this.#sessions) {\n\t\t\tif (this.#isExpired(r, now)) {\n\t\t\t\tthis.#sessions.delete(id);\n\t\t\t\tremoved++;\n\t\t\t}\n\t\t}\n\t\treturn removed;\n\t}\n\n\tasync clear(): Promise<void> {\n\t\tthis.#sessions.clear();\n\t}\n\n\t// ===========================================================================\n\t// Internal\n\t// ===========================================================================\n\n\t#isExpired(r: SessionRecord, now = Date.now()): boolean {\n\t\tif (r.expiresAt.getTime() <= now) return true;\n\t\tif (r.absoluteExpiresAt && r.absoluteExpiresAt.getTime() <= now)\n\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\t#evictIfFull(): void {\n\t\tif (this.#sessions.size <= this.#maxSessions) return;\n\t\t// LRU: delete the oldest-touched entry (Map preserves insertion order;\n\t\t// since we re-`set()` on touch, the first key is the least recently touched).\n\t\tconst oldest = this.#sessions.keys().next().value;\n\t\tif (oldest) this.#sessions.delete(oldest);\n\t}\n}\n",
|
package/dist/shield/index.js.map
CHANGED
|
@@ -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:
|
|
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/shield` — security middleware suite.\n *\n * Inspired by AdonisJS Shield. Provides:\n * - CSRF protection (synchronizer token pattern)\n * - Security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy)\n * - HSTS (Strict-Transport-Security)\n * - CSP (Content-Security-Policy) — optional\n * - XSS filter (browser-level, for legacy browsers)\n *\n * @Module({\n * imports: [\n * ShieldModule.forRoot({\n * csrf: { enabled: true },\n * hsts: { maxAge: 31_536_000, includeSubDomains: true },\n * csp: { directives: { defaultSrc: [\"'self'\"] } },\n * }),\n * ],\n * })\n * export class AppModule {}\n */\n\nimport \"reflect-metadata\";\nimport { randomBytes } from \"node:crypto\";\nimport { EncryptionService } from \"../crypto/encryption.js\";\n\n/** CSRF protection configuration. */\nexport interface CsrfConfig {\n\tenabled: boolean;\n\t/** Cookie name. Default: 'nexus-csrf'. */\n\tcookieName?: string;\n\t/** Header name expected from clients. Default: 'x-csrf-token'. */\n\theaderName?: string;\n\t/** Form field name. Default: '_csrf'. */\n\tfieldName?: string;\n\t/** Whether to require the token on GET. Default: false. */\n\tprotectGet?: boolean;\n\t/** Cookie attributes. */\n\tcookie?: {\n\t\tsameSite?: \"Strict\" | \"Lax\" | \"None\";\n\t\tsecure?: boolean;\n\t\thttpOnly?: boolean;\n\t\tpath?: string;\n\t};\n\t/** Methods that bypass CSRF check. Default: ['GET', 'HEAD', 'OPTIONS']. */\n\tignoreMethods?: string[];\n}\n\n/** HSTS configuration. */\nexport interface HstsConfig {\n\tmaxAge: number;\n\tincludeSubDomains?: boolean;\n\tpreload?: boolean;\n}\n\n/** CSP configuration. */\nexport interface CspConfig {\n\tdirectives: Record<string, string[]>;\n\treportOnly?: boolean;\n\treportUri?: string;\n}\n\n/** Top-level Shield config. */\nexport interface ShieldConfig {\n\tcsrf?: CsrfConfig | false;\n\thsts?: HstsConfig | false;\n\tcsp?: CspConfig | false;\n\txFrameOptions?: \"DENY\" | \"SAMEORIGIN\" | false;\n\txContentTypeOptions?: boolean;\n\treferrerPolicy?: string;\n\t/** Secret used to sign CSRF tokens. */\n\tsecret?: string;\n}\n\n/** CSRF token (synchronizer pattern). */\nexport interface CsrfToken {\n\t/** The token to embed in forms/headers. */\n\ttoken: string;\n\t/** A pre-formed <meta> tag. */\n\thtml: string;\n}\n\n/** Generate a random base64url string. */\nfunction randomToken(bytes = 24): string {\n\treturn randomBytes(bytes).toString(\"base64url\");\n}\n\n/**\n * Sign `value` with `secret` using EncryptionService.\n *\n * Returns the signed value in `<value>.<signature>` format. The\n * HMAC is HKDF-derived from the secret + purpose tag (\"csrf\"), so\n * a CSRF token can't be replayed as another-purpose token.\n */\nfunction sign(value: string, secret: string): string {\n\tconst sig = new EncryptionService(secret).signRaw(value, \"csrf\");\n\treturn `${value}.${sig}`;\n}\n\n/**\n * Verify a signed token. Returns the original value on success,\n * `null` on failure (tampered, wrong purpose, malformed).\n */\nfunction verify(signed: string, secret: string): string | null {\n\tconst lastDot = signed.lastIndexOf(\".\");\n\tif (lastDot < 1) return null;\n\tconst value = signed.slice(0, lastDot);\n\tconst sig = signed.slice(lastDot + 1);\n\tif (!new EncryptionService(secret).verifyRaw(value, sig, \"csrf\")) return null;\n\treturn value;\n}\n\nexport const ShieldInternals = {\n\tsign,\n\tverify,\n\trandomToken,\n};\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:
|
|
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",
|