@forinda/kickjs 5.6.0 → 5.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapter-0r2uRCD3.d.mts → adapter-CoJp35rz.d.mts} +2 -2
- package/dist/{adapter-0r2uRCD3.d.mts.map → adapter-CoJp35rz.d.mts.map} +1 -1
- package/dist/{app-module-CzlIIjO3.d.mts → app-module-BE7hAASB.d.mts} +2 -2
- package/dist/{app-module-CzlIIjO3.d.mts.map → app-module-BE7hAASB.d.mts.map} +1 -1
- package/dist/{application-YJZ2frWX.mjs → application-Bg_-CIPK.mjs} +3 -3
- package/dist/{application-YJZ2frWX.mjs.map → application-Bg_-CIPK.mjs.map} +1 -1
- package/dist/{application-CaEYca9E.d.mts → application-DmekGKp7.d.mts} +5 -5
- package/dist/{application-CaEYca9E.d.mts.map → application-DmekGKp7.d.mts.map} +1 -1
- package/dist/{bootstrap-Db8k2-21.d.mts → bootstrap-CwP2GGu0.d.mts} +2 -2
- package/dist/{bootstrap-Db8k2-21.d.mts.map → bootstrap-CwP2GGu0.d.mts.map} +1 -1
- package/dist/{bootstrap-UCZoqbbW.mjs → bootstrap-D86s_Aac.mjs} +3 -3
- package/dist/{bootstrap-UCZoqbbW.mjs.map → bootstrap-D86s_Aac.mjs.map} +1 -1
- package/dist/config/index.d.mts +1 -1
- package/dist/config/index.mjs +2 -2
- package/dist/{config-D6VaNpwo.mjs → config-BVvCHSX2.mjs} +3 -3
- package/dist/{config-D6VaNpwo.mjs.map → config-BVvCHSX2.mjs.map} +1 -1
- package/dist/{container-D0v17gF8.mjs → container-COmj1vM3.mjs} +3 -3
- package/dist/{container-D0v17gF8.mjs.map → container-COmj1vM3.mjs.map} +1 -1
- package/dist/{context-CbX_T0XC.mjs → context-CvJQzbBF.mjs} +3 -3
- package/dist/{context-CbX_T0XC.mjs.map → context-CvJQzbBF.mjs.map} +1 -1
- package/dist/{context-decorator-BgdDd-6X.d.mts → context-decorator-BYf8HdMl.d.mts} +2 -2
- package/dist/{context-decorator-BgdDd-6X.d.mts.map → context-decorator-BYf8HdMl.d.mts.map} +1 -1
- package/dist/core/adapter.d.mts +1 -1
- package/dist/core/adapter.mjs +1 -1
- package/dist/core/app-module.d.mts +1 -1
- package/dist/core/app-module.mjs +1 -1
- package/dist/core/cache.mjs +1 -1
- package/dist/core/container.mjs +2 -2
- package/dist/core/cron.mjs +2 -2
- package/dist/core/decorators.d.mts +2 -2
- package/dist/core/decorators.mjs +2 -2
- package/dist/core/decorators.mjs.map +1 -1
- package/dist/core/errors.mjs +1 -1
- package/dist/core/interfaces.mjs +1 -1
- package/dist/core/logger.mjs +1 -1
- package/dist/core/path.mjs +1 -1
- package/dist/core/plugin.d.mts +1 -1
- package/dist/core/plugin.mjs +1 -1
- package/dist/core/reactivity.mjs +1 -1
- package/dist/{core-DvTHNOay.mjs → core-CJKmg8QR.mjs} +3 -3
- package/dist/{core-DvTHNOay.mjs.map → core-CJKmg8QR.mjs.map} +1 -1
- package/dist/{decorators-J5mC7D-6.d.mts → decorators-ZcjsV1O0.d.mts} +40 -13
- package/dist/decorators-ZcjsV1O0.d.mts.map +1 -0
- package/dist/{env-yTMEMpV6.mjs → env-DfB1WNRf.mjs} +3 -3
- package/dist/{env-yTMEMpV6.mjs.map → env-DfB1WNRf.mjs.map} +1 -1
- package/dist/http/application.d.mts +1 -1
- package/dist/http/application.mjs +2 -2
- package/dist/http/bootstrap.d.mts +1 -1
- package/dist/http/bootstrap.mjs +2 -2
- package/dist/http/context.mjs +2 -2
- package/dist/http/middleware/cors.mjs +1 -1
- package/dist/http/middleware/csrf.mjs +1 -1
- package/dist/http/middleware/error-handler.mjs +2 -2
- package/dist/http/middleware/helmet.mjs +1 -1
- package/dist/http/middleware/rate-limit.mjs +1 -1
- package/dist/http/middleware/request-id.mjs +1 -1
- package/dist/http/middleware/request-logger.mjs +2 -2
- package/dist/http/middleware/session.mjs +1 -1
- package/dist/http/middleware/spa.d.mts +1 -1
- package/dist/http/middleware/spa.mjs +2 -2
- package/dist/http/middleware/upload.d.mts +1 -1
- package/dist/http/middleware/upload.mjs +1 -1
- package/dist/http/middleware/validate.mjs +2 -2
- package/dist/http/middleware/views.d.mts +1 -1
- package/dist/http/middleware/views.mjs +2 -2
- package/dist/http/query/index.mjs +2 -2
- package/dist/http/router-builder.d.mts +1 -1
- package/dist/http/router-builder.mjs +2 -2
- package/dist/{index-D-Wb23e6.d.mts → index-CkKLXZHg.d.mts} +5 -5
- package/dist/{index-D-Wb23e6.d.mts.map → index-CkKLXZHg.d.mts.map} +1 -1
- package/dist/{index-BbtF8kIr.d.mts → index-oB1CDq5i.d.mts} +2 -2
- package/dist/{index-BbtF8kIr.d.mts.map → index-oB1CDq5i.d.mts.map} +1 -1
- package/dist/index.d.mts +14 -14
- package/dist/index.mjs +2 -2
- package/dist/{metadata-IHiynOCv.mjs → metadata-D9F5Lm-z.mjs} +2 -2
- package/dist/{metadata-IHiynOCv.mjs.map → metadata-D9F5Lm-z.mjs.map} +1 -1
- package/dist/{plugin-JMkG7soG.d.mts → plugin-NMHVmDRF.d.mts} +4 -4
- package/dist/{plugin-JMkG7soG.d.mts.map → plugin-NMHVmDRF.d.mts.map} +1 -1
- package/dist/{query-CQU-Hyiv.mjs → query-Di7R8qNX.mjs} +2 -2
- package/dist/{query-CQU-Hyiv.mjs.map → query-Di7R8qNX.mjs.map} +1 -1
- package/dist/{router-builder-CaQ629tw.d.mts → router-builder-BfCCeQB-.d.mts} +2 -2
- package/dist/{router-builder-CaQ629tw.d.mts.map → router-builder-BfCCeQB-.d.mts.map} +1 -1
- package/dist/{spa-CreyQc4N.d.mts → spa-DIXtRBj-.d.mts} +2 -2
- package/dist/{spa-CreyQc4N.d.mts.map → spa-DIXtRBj-.d.mts.map} +1 -1
- package/dist/{upload-tGMrlhEB.d.mts → upload-DqRwWhX0.d.mts} +2 -2
- package/dist/{upload-tGMrlhEB.d.mts.map → upload-DqRwWhX0.d.mts.map} +1 -1
- package/dist/{views-BMhrZsH9.d.mts → views-DDT9vghc.d.mts} +3 -3
- package/dist/{views-BMhrZsH9.d.mts.map → views-DDT9vghc.d.mts.map} +1 -1
- package/package.json +2 -2
- package/dist/decorators-J5mC7D-6.d.mts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-
|
|
1
|
+
{"version":3,"file":"config-BVvCHSX2.mjs","names":[],"sources":["../src/config/config-service.ts"],"sourcesContent":["import type { z } from 'zod'\nimport { Service } from '../core/decorators'\nimport type { EnvKey } from '../core/decorators'\nimport { loadEnv, reloadEnv } from './env'\n\n/**\n * Constraint that locks the `key` argument to known `EnvKey` literals\n * once `kick typegen` has populated `KickEnv`. Mirrors the same trick\n * used by the `@Value` decorator: when `EnvKey` resolves to `never`\n * (no schema yet), accept any string for back-compat; once typegen\n * has run, require a known key.\n */\ntype ConfigKey<K extends string> = [EnvKey] extends [never] ? K : K & EnvKey\n\n/**\n * Resolve the return type for a `ConfigService.get(key)` call. When\n * `KickEnv` is populated and the key is a known one, returns the\n * schema-inferred type. Otherwise falls back to the caller-provided\n * generic `T` (default `any`) so existing call sites keep working.\n */\ntype ConfigValue<K extends string, T> = K extends EnvKey ? KickEnv[K] : T\n\n/**\n * Injectable service for accessing typed environment configuration.\n *\n * Once `kick typegen` has run against your `src/env.ts`, both `get()`\n * and `getAll()` are fully typed against the schema with **no extra\n * setup** — you just inject `ConfigService` and call it:\n *\n * ```ts\n * @Service()\n * class DatabaseService {\n * @Autowired() private readonly config!: ConfigService\n *\n * connect() {\n * const url = this.config.get('DATABASE_URL') // typed string\n * const port = this.config.get('PORT') // typed number\n * // const bad = this.config.get('NOPE') // tsc error\n * }\n * }\n * ```\n *\n * Without typegen the previous `<T = any>` generic still works, so\n * legacy code calling `config.get<string>('DATABASE_URL')` keeps\n * compiling.\n */\n@Service()\nexport class ConfigService {\n /**\n * Read the current parsed env on every access. We deliberately do **not**\n * snapshot in the constructor: if `ConfigService` is instantiated before\n * the user's `defineEnv()` schema has been registered (e.g. DI wires it\n * eagerly, or HMR's `reloadEnv()` cleared the cache between rebuilds),\n * an eager snapshot would freeze the base shape and never see the\n * extended values — even though `@Value()` (which reads lazily through\n * `Container._envResolver`) would resolve them correctly.\n *\n * `loadEnv()` is sticky: once an extended schema has been parsed it\n * keeps returning that schema's values, so this getter is cheap (one\n * cached lookup) and always consistent with `@Value`.\n */\n private get env(): Record<string, any> {\n return loadEnv() as Record<string, any>\n }\n\n /**\n * Get an env variable by key.\n *\n * - When `kick typegen` has run, `key` is constrained to known\n * `KickEnv` keys and the return type is inferred from the schema.\n * - When `KickEnv` is empty (no typegen run yet), any string is\n * accepted and `T` falls back to the explicit generic for\n * back-compat.\n */\n get<K extends string, T = any>(key: ConfigKey<K>): ConfigValue<K, T> {\n return this.env[key] as ConfigValue<K, T>\n }\n\n /**\n * Get all env config as a readonly object. When `KickEnv` is\n * populated by typegen, returns `Readonly<KickEnv>` so the caller\n * gets autocomplete on every key.\n */\n getAll(): [EnvKey] extends [never] ? Readonly<Record<string, any>> : Readonly<KickEnv> {\n return Object.freeze({ ...this.env }) as any\n }\n\n /** Reload env from .env file (for HMR / file-watch scenarios) */\n reload(): void {\n reloadEnv()\n // Touch loadEnv() so the resolver + cache are repopulated immediately.\n loadEnv()\n }\n\n isProduction(): boolean {\n return this.env.NODE_ENV === 'production'\n }\n\n isDevelopment(): boolean {\n return this.env.NODE_ENV === 'development'\n }\n\n isTest(): boolean {\n return this.env.NODE_ENV === 'test'\n }\n}\n\n/**\n * Typed config service interface — provides autocomplete for env keys.\n * Use with `createConfigService()` for full type safety.\n */\nexport interface TypedConfigService<TEnv extends Record<string, any>> {\n get<K extends string & keyof TEnv>(key: K): TEnv[K]\n getAll(): Readonly<TEnv>\n reload(): void\n isProduction(): boolean\n isDevelopment(): boolean\n isTest(): boolean\n}\n\n/**\n * Create a typed ConfigService class bound to a specific Zod env schema.\n * The returned class is DI-injectable via `@Service()` and provides\n * fully typed `get()` with key autocomplete.\n *\n * @example\n * ```ts\n * const envSchema = defineEnv((base) =>\n * base.extend({\n * DATABASE_URL: z.string().url(),\n * JWT_SECRET: z.string().min(32),\n * })\n * )\n *\n * // Create a typed, injectable config service\n * export const AppConfigService = createConfigService(envSchema)\n * export type AppConfigService = InstanceType<typeof AppConfigService>\n *\n * // In a controller or service — inject it:\n * @Controller()\n * class UserController {\n * constructor(@Inject(AppConfigService) private config: AppConfigService) {\n * const dbUrl = config.get('DATABASE_URL') // string — autocompletes!\n * const bad = config.get('NOPE') // TS error\n * }\n * }\n * ```\n */\nexport function createConfigService<T extends z.ZodObject<any>>(schema: T) {\n type TEnv = z.infer<T>\n\n @Service()\n class SchemaConfigService implements TypedConfigService<TEnv> {\n /** See note on `ConfigService.env` — same eager-snapshot hazard. */\n private get env(): TEnv {\n return loadEnv(schema)\n }\n\n get<K extends string & keyof TEnv>(key: K): TEnv[K] {\n return this.env[key]\n }\n\n getAll(): Readonly<TEnv> {\n return Object.freeze({ ...this.env })\n }\n\n reload(): void {\n reloadEnv()\n loadEnv(schema)\n }\n\n isProduction(): boolean {\n return this.env.NODE_ENV === 'production'\n }\n\n isDevelopment(): boolean {\n return this.env.NODE_ENV === 'development'\n }\n\n isTest(): boolean {\n return this.env.NODE_ENV === 'test'\n }\n }\n\n return SchemaConfigService as {\n new (): TypedConfigService<TEnv>\n }\n}\n"],"mappings":";;;;;;;;;;uhBA+CO,IAAA,cAAA,KAAoB,CAczB,IAAY,KAA2B,CACrC,OAAO,SAAS,CAYlB,IAA+B,IAAsC,CACnE,OAAO,KAAK,IAAI,KAQlB,QAAuF,CACrF,OAAO,OAAO,OAAO,CAAE,GAAG,KAAK,IAAK,CAAC,CAIvC,QAAe,CACb,WAAW,CAEX,SAAS,CAGX,cAAwB,CACtB,OAAO,KAAK,IAAI,WAAa,aAG/B,eAAyB,CACvB,OAAO,KAAK,IAAI,WAAa,cAG/B,QAAkB,CAChB,OAAO,KAAK,IAAI,WAAa,mCAzDhC,SAAS,CAAA,CAAA,cAAA,CAsGV,SAAgB,oBAAgD,OAAW,CAGzE,IAAA,oBAAA,KAC8D,CAE5D,IAAY,KAAY,CACtB,OAAO,QAAQ,OAAO,CAGxB,IAAmC,IAAiB,CAClD,OAAO,KAAK,IAAI,KAGlB,QAAyB,CACvB,OAAO,OAAO,OAAO,CAAE,GAAG,KAAK,IAAK,CAAC,CAGvC,QAAe,CACb,WAAW,CACX,QAAQ,OAAO,CAGjB,cAAwB,CACtB,OAAO,KAAK,IAAI,WAAa,aAG/B,eAAyB,CACvB,OAAO,KAAK,IAAI,WAAa,cAG/B,QAAkB,CAChB,OAAO,KAAK,IAAI,WAAa,SAIjC,uCAjCC,SAAS,CAAA,CAAA,oBAAA,CAiCH"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.
|
|
2
|
+
* @forinda/kickjs v5.7.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{METADATA}from"./core/interfaces.mjs";import{createLogger}from"./core/logger.mjs";import{i as getMetaRecord,n as getClassMetaOrUndefined,o as getMethodMetaOrUndefined,r as getMetaMap,t as getClassMeta}from"./metadata-IHiynOCv.mjs";import{createRequire}from"node:module";import"reflect-metadata";import{existsSync,readFileSync,readdirSync}from"node:fs";import{isAbsolute,join,resolve,sep}from"node:path";var __require=createRequire(import.meta.url);const INJECTION_TOKEN=Symbol.for(`@kickjs/InjectionToken`);function createToken(name){return Object.freeze({[INJECTION_TOKEN]:!0,name})}function isInjectionToken(value){return typeof value==`object`&&!!value&&value[INJECTION_TOKEN]===!0}function groupAssetKeys(namespace,paths,opts={}){let strategy=opts.strategy??`auto`;if(strategy===`with-extension`)return{pairs:paths.map(rel=>({rel,key:`${namespace}/${rel}`})),collisionGroupsResolved:0};if(strategy===`strip`)return{pairs:paths.map(rel=>({rel,key:`${namespace}/${stripExt(rel)}`})),collisionGroupsResolved:0};let groups=new Map;for(let rel of paths){let strippedKey=`${namespace}/${stripExt(rel)}`,arr=groups.get(strippedKey);arr?arr.push(rel):groups.set(strippedKey,[rel])}let collisionGroupsResolved=0,keyByRel=new Map;for(let[strippedKey,rels]of groups)if(rels.length===1)keyByRel.set(rels[0],strippedKey);else{collisionGroupsResolved+=1;for(let rel of rels)keyByRel.set(rel,`${namespace}/${rel}`)}return{pairs:paths.map(rel=>({rel,key:keyByRel.get(rel)??`${namespace}/${rel}`})),collisionGroupsResolved}}function stripExt(path){let lastSlash=path.lastIndexOf(`/`),filename=lastSlash>=0?path.slice(lastSlash+1):path,lastDot=filename.lastIndexOf(`.`);return lastDot<=0?path:(lastSlash>=0?path.slice(0,lastSlash+1):``)+filename.slice(0,lastDot)}const ASSET_MANIFEST_VERSION=1,ASSETS=createToken(`kick/assets/Map`);var UnknownAssetError=class extends Error{namespace;key;constructor(namespace,key){super(`Unknown asset '${namespace}/${key}'. Make sure the namespace exists in kick.config.ts assetMap and that the file is present in the source directory.`),this.name=`UnknownAssetError`,this.namespace=namespace,this.key=key}};let manifestCache;function resolveAsset(namespace,key){let resolved=loadManifest();if(!resolved)throw new UnknownAssetError(namespace,key);let entry=resolved.manifest.entries[`${namespace}/${key}`];if(!entry)throw new UnknownAssetError(namespace,key);return isAbsolute(entry)?entry:resolve(resolved.root,entry)}function clearAssetCache(){manifestCache=void 0}function loadManifest(){return process.env.NODE_ENV===`production`?(manifestCache===void 0&&(manifestCache=discoverManifest()),manifestCache):discoverManifest()}function discoverManifest(){let envRoot=process.env.KICK_ASSETS_ROOT;if(envRoot){let fromEnv=readBuiltManifest(envRoot);if(fromEnv)return fromEnv}let cwd=process.cwd(),config=loadConfigSync(cwd),configOutDir=config?.build?.outDir;if(configOutDir&&!STANDARD_OUT_DIRS.includes(configOutDir)){let found=readBuiltManifest(resolve(cwd,configOutDir));if(found)return found}for(let candidate of STANDARD_OUT_DIRS){let found=readBuiltManifest(join(cwd,candidate));if(found)return found}return synthesiseDevManifest(cwd,config)}const STANDARD_OUT_DIRS=[`dist`,`build`,`out`];function readBuiltManifest(dir){let path=join(dir,`.kickjs-assets.json`);if(!existsSync(path))return null;try{let raw=readFileSync(path,`utf-8`),parsed=JSON.parse(raw);return parsed.version!==1||!parsed.entries||typeof parsed.entries!=`object`?null:{manifest:parsed,root:resolve(dir)}}catch{return null}}function synthesiseDevManifest(cwd,preloadedConfig){let config=preloadedConfig===void 0?loadConfigSync(cwd):preloadedConfig;if(!config?.assetMap)return null;let entries={};for(let[namespace,entry]of Object.entries(config.assetMap)){if(!entry||typeof entry.src!=`string`)continue;let srcAbs=resolve(cwd,entry.src);if(!existsSync(srcAbs))continue;let collected=[];walkSync(srcAbs,srcAbs,(relPath,absPath)=>{entry.glob&&!matchesGlobLite(relPath,entry.glob,namespace)||collected.push({rel:relPath,abs:absPath})}),collected.sort((a,b)=>a.rel.localeCompare(b.rel));let{pairs,collisionGroupsResolved}=groupAssetKeys(namespace,collected.map(c=>c.rel),{strategy:entry.keys??`auto`}),absByRel=new Map(collected.map(c=>[c.rel,c.abs]));for(let{rel,key}of pairs){let abs=absByRel.get(rel);abs&&(entries[key]=abs)}collisionGroupsResolved>0&&process.env.NODE_ENV!==`production`&&console.log(`[kickjs/assets] '${namespace}': ${collisionGroupsResolved} basename collision(s) auto-resolved by keeping extensions (set assetMap.${namespace}.keys to 'strip' or 'with-extension' to override).`)}return{manifest:{version:1,entries},root:cwd}}const warnedGlobs=new Set;function matchesGlobLite(relPath,pattern,namespace){if(pattern===`**/*`||pattern===`**`)return!0;let singleExt=/^\*\*\/\*\.(\w+)$/.exec(pattern);if(singleExt)return relPath.endsWith(`.${singleExt[1]}`);let braceExt=/^\*\*\/\*\.\{([^}]+)\}$/.exec(pattern);if(braceExt)return braceExt[1].split(`,`).map(e=>e.trim()).some(ext=>relPath.endsWith(`.${ext}`));let tag=`${namespace}::${pattern}`;return warnedGlobs.has(tag)||(warnedGlobs.add(tag),console.warn(`[kickjs/assets] Dev-mode glob '${pattern}' on '${namespace}' isn't recognised by the lite matcher. Falling back to '**/*'. Run 'kick build:assets' to use the full glob engine.`)),!0}function walkSync(root,dir,onFile){let entries;try{entries=readdirSync(dir,{withFileTypes:!0})}catch{return}for(let entry of entries){if(entry.name===`node_modules`||entry.name.startsWith(`.`))continue;let full=join(dir,entry.name);entry.isDirectory()?walkSync(root,full,onFile):entry.isFile()&&onFile(full.slice(root.length+1).split(sep).join(`/`),full)}}function loadConfigSync(cwd){let jsonPath=join(cwd,`kick.config.json`);if(existsSync(jsonPath))try{return JSON.parse(readFileSync(jsonPath,`utf-8`))}catch{return null}for(let filename of[`kick.config.cjs`,`kick.config.js`]){let path=join(cwd,filename);if(existsSync(path))try{let{createRequire}=__require(`node:module`),mod=createRequire(join(cwd,`package.json`))(path);return mod.default??mod??null}catch{continue}}return null}const PROMISE_DETECTION_KEYS=new Set([`then`]),PASSTHROUGH_KEYS=new Set([`toString`,`valueOf`,`constructor`,`prototype`,`name`,`length`,`asymmetricMatch`,`nodeType`,Symbol.toPrimitive,Symbol.toStringTag,Symbol.iterator,Symbol.asyncIterator]);function createNamespaceProxy(parts){return new Proxy(function(){},{apply(){if(parts.length<2)throw new UnknownAssetError(parts[0]??``,``);let[namespace,...rest]=parts;return resolveAsset(namespace,rest.join(`/`))},get(t,prop){if(!PROMISE_DETECTION_KEYS.has(prop)){if(PASSTHROUGH_KEYS.has(prop))return Reflect.get(t,prop);if(typeof prop!=`symbol`)return createNamespaceProxy([...parts,prop])}}})}function createAssetProxy(){return new Proxy({},{get(t,prop){if(!PROMISE_DETECTION_KEYS.has(prop)){if(PASSTHROUGH_KEYS.has(prop))return Reflect.get(t,prop);if(typeof prop!=`symbol`)return createNamespaceProxy([prop])}}})}const assets=createAssetProxy();function useAssets(){return assets}const log=createLogger(`Container`);function createReg(fields){return{kind:`unknown`,resolveCount:0,postConstructStatus:`skipped`,dependencies:[],...fields}}function tokenName(token){return typeof token==`symbol`?token.toString():isInjectionToken(token)?token.name:token?.name||String(token)}function getPersistentStore(){return globalThis.__kickjs_persistent||(globalThis.__kickjs_persistent={factories:[],instances:[],resolvedInstances:new Map}),globalThis.__kickjs_persistent}var Container=class Container{static instance;registrations=new Map;resolving=new Set;changeListeners=new Set;pendingChanges=[];notifyTimer=null;static DEBOUNCE_MS=50;static _onReady=null;static _onReset=null;static _envResolver=null;static _requestStoreProvider=null;static getInstance(){return Container.instance||=new Container,Container._onReady&&=(Container._onReady(Container.instance),null),Container.instance}static reset(){Container.instance=new Container,Container._onReset?.(Container.instance);let store=getPersistentStore();for(let{token,factory,scope}of store.factories)if(!Container.instance.has(token)){let name=tokenName(token),existing=store.resolvedInstances.get(name);Container.instance.registrations.set(token,createReg({target:Object,scope,factory,kind:`factory`,instance:existing,resolveCount:+!!existing,postConstructStatus:existing?`completed`:`skipped`}))}for(let{token,instance}of store.instances)Container.instance.has(token)||Container.instance.registrations.set(token,createReg({target:instance.constructor,scope:`singleton`,instance,kind:`instance`,postConstructStatus:`completed`,resolveCount:1}))}static create(){return new Container}register(token,target,scope=`singleton`){let kind=getClassMeta(METADATA.CLASS_KIND,target,`unknown`),reg=createReg({target,scope,kind,dependencies:this.extractDependencies(target)});this.registrations.set(token,reg),typeof token==`function`&&token.name&&this.registrations.set(`__hmr__${token.name}`,reg),this.emit(tokenName(token),`registered`,kind)}registerFactory(token,factory,scope=`singleton`){let store=getPersistentStore(),name=tokenName(token),factoryUnchanged=store.factories.find(r=>tokenName(r.token)===name)?.factory===factory,existingInstance=factoryUnchanged?store.resolvedInstances.get(name):void 0;factoryUnchanged||store.resolvedInstances.delete(name),this.registrations.set(token,createReg({target:Object,scope,factory,kind:`factory`,instance:existingInstance,resolveCount:+!!existingInstance,postConstructStatus:existingInstance?`completed`:`skipped`}));let idx=store.factories.findIndex(r=>tokenName(r.token)===name);idx>=0?store.factories[idx]={token,factory,scope}:store.factories.push({token,factory,scope}),this.emit(name,`registered`,`factory`)}registerInstance(token,instance){let store=getPersistentStore(),name=tokenName(token);this.registrations.set(token,createReg({target:instance.constructor,scope:`singleton`,instance,kind:`instance`,postConstructStatus:`completed`,resolveCount:1,firstResolvedAt:Date.now(),lastResolvedAt:Date.now()})),store.resolvedInstances.set(name,instance);let idx=store.instances.findIndex(r=>tokenName(r.token)===name);idx>=0?store.instances[idx]={token,instance}:store.instances.push({token,instance}),this.emit(name,`registered`,`instance`)}has(token){return this.registrations.has(token)}getRegistrations(){let entries=[];for(let[token,reg]of this.registrations)entries.push({token:tokenName(token),scope:reg.scope,kind:reg.kind,instantiated:reg.scope===`singleton`?reg.instance!==void 0:reg.resolveCount>0,resolveCount:reg.resolveCount,lastResolvedAt:reg.lastResolvedAt,firstResolvedAt:reg.firstResolvedAt,resolveDurationMs:reg.resolveDurationMs,postConstructStatus:reg.postConstructStatus,dependencies:reg.dependencies});return entries}resolve(token){let reg=this.registrations.get(token);if(!reg&&typeof token==`function`&&token.name&&(reg=this.registrations.get(`__hmr__${token.name}`)),!reg)throw Error(`No binding found for: ${tokenName(token)}`);if(reg.scope===`singleton`&®.instance!==void 0)return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(tokenName(token),`resolved`,reg.kind),reg.instance;if(reg.scope===`request`){if(!Container._requestStoreProvider)throw Error(`Cannot resolve REQUEST-scoped "${tokenName(token)}": no request store provider configured. Ensure requestScopeMiddleware() is in your middleware pipeline.`);let store=Container._requestStoreProvider();if(!store)throw Error(`Cannot resolve REQUEST-scoped "${tokenName(token)}" outside an HTTP request context.`);if(store.values.has(token))return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(tokenName(token),`resolved`,reg.kind),store.values.get(token);if(store.instances.has(token))return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(tokenName(token),`resolved`,reg.kind),store.instances.get(token);if(reg.factory){let start=performance.now(),instance=reg.factory();return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),store.instances.set(token,instance),this.emit(tokenName(token),`resolved`,reg.kind),instance}let start=performance.now(),instance=this.createInstance(reg);return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),store.instances.set(token,instance),this.emit(tokenName(token),`resolved`,reg.kind),instance}if(reg.factory){let start=performance.now(),instance=reg.factory();return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),reg.scope===`singleton`&&(reg.instance=instance,getPersistentStore().resolvedInstances.set(tokenName(token),instance)),this.emit(tokenName(token),`resolved`,reg.kind),instance}if(this.resolving.has(token)){let chain=[...this.resolving].map(tokenName);throw chain.push(tokenName(token)),Error(`Circular dependency detected: ${chain.join(` -> `)}`)}this.resolving.add(token);try{let start=performance.now(),instance=this.createInstance(reg);return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),reg.scope===`singleton`&&(reg.instance=instance),this.emit(tokenName(token),`resolved`,reg.kind),instance}finally{this.resolving.delete(token)}}bootstrap(){}onChange(callback){return this.changeListeners.add(callback),()=>this.changeListeners.delete(callback)}invalidate(token){let name=tokenName(token),reg=this.registrations.get(token)??this.registrations.get(`__hmr__${name}`);if(reg&&!getPersistentStore().resolvedInstances.has(name)){reg.instance=void 0,reg.resolveCount=0,reg.postConstructStatus=`skipped`,this.emit(name,`invalidated`,reg.kind);for(let[depToken,depReg]of this.registrations)depReg.dependencies.includes(name)&&this.invalidate(depToken)}}emit(token,event,kind){let existing=this.pendingChanges.find(p=>p.token===token&&p.event===event);existing?existing.timestamp=Date.now():this.pendingChanges.push({token,event,kind,timestamp:Date.now()}),this.notifyTimer&&clearTimeout(this.notifyTimer),this.notifyTimer=setTimeout(()=>{let batch=this.pendingChanges.splice(0);if(batch.length!==0)for(let listener of this.changeListeners)try{listener(batch)}catch{}},Container.DEBOUNCE_MS)}flushChanges(){this.notifyTimer&&=(clearTimeout(this.notifyTimer),null);let batch=this.pendingChanges.splice(0);if(batch.length!==0)for(let listener of this.changeListeners)try{listener(batch)}catch{}}createInstance(reg){let args=getClassMeta(METADATA.PARAM_TYPES,reg.target,[]).map((paramType,index)=>{let token=getMetaRecord(METADATA.INJECT,reg.target)[index]||paramType;if(reg.scope===`singleton`){let depReg=this.registrations.get(token);if(depReg&&depReg.scope===`request`)throw Error(`Cannot inject REQUEST-scoped "${tokenName(token)}" into SINGLETON "${tokenName(reg.target)}". Singletons outlive requests. Use TRANSIENT or REQUEST scope for the parent.`)}return this.resolve(token)}),instance=new reg.target(...args);this.injectProperties(instance,reg.target);let postConstruct=getClassMetaOrUndefined(METADATA.POST_CONSTRUCT,reg.target.prototype);if(postConstruct&&typeof instance[postConstruct]==`function`)try{let result=instance[postConstruct]();result&&typeof result.then==`function`?(reg.postConstructStatus=`pending`,result.then(()=>{reg.postConstructStatus=`completed`}).catch(()=>{reg.postConstructStatus=`failed`}),log.warn(`@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() returned a Promise but the container is synchronous. The async operation will not be awaited.`)):reg.postConstructStatus=`completed`}catch(err){reg.postConstructStatus=`failed`,log.error(err,`@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`)}return instance}extractDependencies(target){let paramTypes=getClassMeta(METADATA.PARAM_TYPES,target,[]),injectTokens=getMetaRecord(METADATA.INJECT,target),ctorDeps=paramTypes.map((type,index)=>tokenName(injectTokens[index]||type)),autowiredProps=getMetaMap(METADATA.AUTOWIRED,target.prototype),propDeps=[];for(let[prop,token]of autowiredProps){let resolvedToken=token||getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE,target.prototype,prop);resolvedToken&&propDeps.push(tokenName(resolvedToken))}return[...new Set([...ctorDeps,...propDeps])]}injectProperties(instance,target){let autowiredProps=getMetaMap(METADATA.AUTOWIRED,target.prototype);for(let[prop,token]of autowiredProps){let resolvedToken=token||getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE,target.prototype,prop);resolvedToken&&Object.defineProperty(instance,prop,{get:()=>this.resolve(resolvedToken),enumerable:!0,configurable:!0})}let valueProps=getMetaMap(METADATA.VALUE,target.prototype);for(let[prop,config]of valueProps)Object.defineProperty(instance,prop,{get(){if(Container._envResolver){let val=Container._envResolver(config.envKey);if(val!==void 0)return val}let val=process.env[config.envKey];if(val!==void 0)return val;if(config.defaultValue!==void 0)return config.defaultValue;throw Error(`@Value('${config.envKey}'): Environment variable "${config.envKey}" is not set and no default was provided.`)},enumerable:!0,configurable:!0});let assetProps=getMetaMap(METADATA.ASSET,target.prototype);if(assetProps.size>0)for(let[prop,config]of assetProps){let slashIdx=config.assetKey.indexOf(`/`);if(slashIdx===-1)throw Error(`@Asset('${config.assetKey}'): asset key must include a '/' separator (e.g. 'mails/welcome').`);let namespace=config.assetKey.slice(0,slashIdx),key=config.assetKey.slice(slashIdx+1);Object.defineProperty(instance,prop,{get(){return resolveAsset(namespace,key)},enumerable:!0,configurable:!0})}}};export{UnknownAssetError as a,resolveAsset as c,stripExt as d,INJECTION_TOKEN as f,__require as h,ASSET_MANIFEST_VERSION as i,useAssets as l,isInjectionToken as m,tokenName as n,assets as o,createToken as p,ASSETS as r,clearAssetCache as s,Container as t,groupAssetKeys as u};
|
|
12
|
-
//# sourceMappingURL=container-
|
|
11
|
+
import{METADATA}from"./core/interfaces.mjs";import{createLogger}from"./core/logger.mjs";import{i as getMetaRecord,n as getClassMetaOrUndefined,o as getMethodMetaOrUndefined,r as getMetaMap,t as getClassMeta}from"./metadata-D9F5Lm-z.mjs";import{createRequire}from"node:module";import"reflect-metadata";import{existsSync,readFileSync,readdirSync}from"node:fs";import{isAbsolute,join,resolve,sep}from"node:path";var __require=createRequire(import.meta.url);const INJECTION_TOKEN=Symbol.for(`@kickjs/InjectionToken`);function createToken(name){return Object.freeze({[INJECTION_TOKEN]:!0,name})}function isInjectionToken(value){return typeof value==`object`&&!!value&&value[INJECTION_TOKEN]===!0}function groupAssetKeys(namespace,paths,opts={}){let strategy=opts.strategy??`auto`;if(strategy===`with-extension`)return{pairs:paths.map(rel=>({rel,key:`${namespace}/${rel}`})),collisionGroupsResolved:0};if(strategy===`strip`)return{pairs:paths.map(rel=>({rel,key:`${namespace}/${stripExt(rel)}`})),collisionGroupsResolved:0};let groups=new Map;for(let rel of paths){let strippedKey=`${namespace}/${stripExt(rel)}`,arr=groups.get(strippedKey);arr?arr.push(rel):groups.set(strippedKey,[rel])}let collisionGroupsResolved=0,keyByRel=new Map;for(let[strippedKey,rels]of groups)if(rels.length===1)keyByRel.set(rels[0],strippedKey);else{collisionGroupsResolved+=1;for(let rel of rels)keyByRel.set(rel,`${namespace}/${rel}`)}return{pairs:paths.map(rel=>({rel,key:keyByRel.get(rel)??`${namespace}/${rel}`})),collisionGroupsResolved}}function stripExt(path){let lastSlash=path.lastIndexOf(`/`),filename=lastSlash>=0?path.slice(lastSlash+1):path,lastDot=filename.lastIndexOf(`.`);return lastDot<=0?path:(lastSlash>=0?path.slice(0,lastSlash+1):``)+filename.slice(0,lastDot)}const ASSET_MANIFEST_VERSION=1,ASSETS=createToken(`kick/assets/Map`);var UnknownAssetError=class extends Error{namespace;key;constructor(namespace,key){super(`Unknown asset '${namespace}/${key}'. Make sure the namespace exists in kick.config.ts assetMap and that the file is present in the source directory.`),this.name=`UnknownAssetError`,this.namespace=namespace,this.key=key}};let manifestCache;function resolveAsset(namespace,key){let resolved=loadManifest();if(!resolved)throw new UnknownAssetError(namespace,key);let entry=resolved.manifest.entries[`${namespace}/${key}`];if(!entry)throw new UnknownAssetError(namespace,key);return isAbsolute(entry)?entry:resolve(resolved.root,entry)}function clearAssetCache(){manifestCache=void 0}function loadManifest(){return process.env.NODE_ENV===`production`?(manifestCache===void 0&&(manifestCache=discoverManifest()),manifestCache):discoverManifest()}function discoverManifest(){let envRoot=process.env.KICK_ASSETS_ROOT;if(envRoot){let fromEnv=readBuiltManifest(envRoot);if(fromEnv)return fromEnv}let cwd=process.cwd(),config=loadConfigSync(cwd),configOutDir=config?.build?.outDir;if(configOutDir&&!STANDARD_OUT_DIRS.includes(configOutDir)){let found=readBuiltManifest(resolve(cwd,configOutDir));if(found)return found}for(let candidate of STANDARD_OUT_DIRS){let found=readBuiltManifest(join(cwd,candidate));if(found)return found}return synthesiseDevManifest(cwd,config)}const STANDARD_OUT_DIRS=[`dist`,`build`,`out`];function readBuiltManifest(dir){let path=join(dir,`.kickjs-assets.json`);if(!existsSync(path))return null;try{let raw=readFileSync(path,`utf-8`),parsed=JSON.parse(raw);return parsed.version!==1||!parsed.entries||typeof parsed.entries!=`object`?null:{manifest:parsed,root:resolve(dir)}}catch{return null}}function synthesiseDevManifest(cwd,preloadedConfig){let config=preloadedConfig===void 0?loadConfigSync(cwd):preloadedConfig;if(!config?.assetMap)return null;let entries={};for(let[namespace,entry]of Object.entries(config.assetMap)){if(!entry||typeof entry.src!=`string`)continue;let srcAbs=resolve(cwd,entry.src);if(!existsSync(srcAbs))continue;let collected=[];walkSync(srcAbs,srcAbs,(relPath,absPath)=>{entry.glob&&!matchesGlobLite(relPath,entry.glob,namespace)||collected.push({rel:relPath,abs:absPath})}),collected.sort((a,b)=>a.rel.localeCompare(b.rel));let{pairs,collisionGroupsResolved}=groupAssetKeys(namespace,collected.map(c=>c.rel),{strategy:entry.keys??`auto`}),absByRel=new Map(collected.map(c=>[c.rel,c.abs]));for(let{rel,key}of pairs){let abs=absByRel.get(rel);abs&&(entries[key]=abs)}collisionGroupsResolved>0&&process.env.NODE_ENV!==`production`&&console.log(`[kickjs/assets] '${namespace}': ${collisionGroupsResolved} basename collision(s) auto-resolved by keeping extensions (set assetMap.${namespace}.keys to 'strip' or 'with-extension' to override).`)}return{manifest:{version:1,entries},root:cwd}}const warnedGlobs=new Set;function matchesGlobLite(relPath,pattern,namespace){if(pattern===`**/*`||pattern===`**`)return!0;let singleExt=/^\*\*\/\*\.(\w+)$/.exec(pattern);if(singleExt)return relPath.endsWith(`.${singleExt[1]}`);let braceExt=/^\*\*\/\*\.\{([^}]+)\}$/.exec(pattern);if(braceExt)return braceExt[1].split(`,`).map(e=>e.trim()).some(ext=>relPath.endsWith(`.${ext}`));let tag=`${namespace}::${pattern}`;return warnedGlobs.has(tag)||(warnedGlobs.add(tag),console.warn(`[kickjs/assets] Dev-mode glob '${pattern}' on '${namespace}' isn't recognised by the lite matcher. Falling back to '**/*'. Run 'kick build:assets' to use the full glob engine.`)),!0}function walkSync(root,dir,onFile){let entries;try{entries=readdirSync(dir,{withFileTypes:!0})}catch{return}for(let entry of entries){if(entry.name===`node_modules`||entry.name.startsWith(`.`))continue;let full=join(dir,entry.name);entry.isDirectory()?walkSync(root,full,onFile):entry.isFile()&&onFile(full.slice(root.length+1).split(sep).join(`/`),full)}}function loadConfigSync(cwd){let jsonPath=join(cwd,`kick.config.json`);if(existsSync(jsonPath))try{return JSON.parse(readFileSync(jsonPath,`utf-8`))}catch{return null}for(let filename of[`kick.config.cjs`,`kick.config.js`]){let path=join(cwd,filename);if(existsSync(path))try{let{createRequire}=__require(`node:module`),mod=createRequire(join(cwd,`package.json`))(path);return mod.default??mod??null}catch{continue}}return null}const PROMISE_DETECTION_KEYS=new Set([`then`]),PASSTHROUGH_KEYS=new Set([`toString`,`valueOf`,`constructor`,`prototype`,`name`,`length`,`asymmetricMatch`,`nodeType`,Symbol.toPrimitive,Symbol.toStringTag,Symbol.iterator,Symbol.asyncIterator]);function createNamespaceProxy(parts){return new Proxy(function(){},{apply(){if(parts.length<2)throw new UnknownAssetError(parts[0]??``,``);let[namespace,...rest]=parts;return resolveAsset(namespace,rest.join(`/`))},get(t,prop){if(!PROMISE_DETECTION_KEYS.has(prop)){if(PASSTHROUGH_KEYS.has(prop))return Reflect.get(t,prop);if(typeof prop!=`symbol`)return createNamespaceProxy([...parts,prop])}}})}function createAssetProxy(){return new Proxy({},{get(t,prop){if(!PROMISE_DETECTION_KEYS.has(prop)){if(PASSTHROUGH_KEYS.has(prop))return Reflect.get(t,prop);if(typeof prop!=`symbol`)return createNamespaceProxy([prop])}}})}const assets=createAssetProxy();function useAssets(){return assets}const log=createLogger(`Container`);function createReg(fields){return{kind:`unknown`,resolveCount:0,postConstructStatus:`skipped`,dependencies:[],...fields}}function tokenName(token){return typeof token==`symbol`?token.toString():isInjectionToken(token)?token.name:token?.name||String(token)}function getPersistentStore(){return globalThis.__kickjs_persistent||(globalThis.__kickjs_persistent={factories:[],instances:[],resolvedInstances:new Map}),globalThis.__kickjs_persistent}var Container=class Container{static instance;registrations=new Map;resolving=new Set;changeListeners=new Set;pendingChanges=[];notifyTimer=null;static DEBOUNCE_MS=50;static _onReady=null;static _onReset=null;static _envResolver=null;static _requestStoreProvider=null;static getInstance(){return Container.instance||=new Container,Container._onReady&&=(Container._onReady(Container.instance),null),Container.instance}static reset(){Container.instance=new Container,Container._onReset?.(Container.instance);let store=getPersistentStore();for(let{token,factory,scope}of store.factories)if(!Container.instance.has(token)){let name=tokenName(token),existing=store.resolvedInstances.get(name);Container.instance.registrations.set(token,createReg({target:Object,scope,factory,kind:`factory`,instance:existing,resolveCount:+!!existing,postConstructStatus:existing?`completed`:`skipped`}))}for(let{token,instance}of store.instances)Container.instance.has(token)||Container.instance.registrations.set(token,createReg({target:instance.constructor,scope:`singleton`,instance,kind:`instance`,postConstructStatus:`completed`,resolveCount:1}))}static create(){return new Container}register(token,target,scope=`singleton`){let kind=getClassMeta(METADATA.CLASS_KIND,target,`unknown`),reg=createReg({target,scope,kind,dependencies:this.extractDependencies(target)});this.registrations.set(token,reg),typeof token==`function`&&token.name&&this.registrations.set(`__hmr__${token.name}`,reg),this.emit(tokenName(token),`registered`,kind)}registerFactory(token,factory,scope=`singleton`){let store=getPersistentStore(),name=tokenName(token),factoryUnchanged=store.factories.find(r=>tokenName(r.token)===name)?.factory===factory,existingInstance=factoryUnchanged?store.resolvedInstances.get(name):void 0;factoryUnchanged||store.resolvedInstances.delete(name),this.registrations.set(token,createReg({target:Object,scope,factory,kind:`factory`,instance:existingInstance,resolveCount:+!!existingInstance,postConstructStatus:existingInstance?`completed`:`skipped`}));let idx=store.factories.findIndex(r=>tokenName(r.token)===name);idx>=0?store.factories[idx]={token,factory,scope}:store.factories.push({token,factory,scope}),this.emit(name,`registered`,`factory`)}registerInstance(token,instance){let store=getPersistentStore(),name=tokenName(token);this.registrations.set(token,createReg({target:instance.constructor,scope:`singleton`,instance,kind:`instance`,postConstructStatus:`completed`,resolveCount:1,firstResolvedAt:Date.now(),lastResolvedAt:Date.now()})),store.resolvedInstances.set(name,instance);let idx=store.instances.findIndex(r=>tokenName(r.token)===name);idx>=0?store.instances[idx]={token,instance}:store.instances.push({token,instance}),this.emit(name,`registered`,`instance`)}has(token){return this.registrations.has(token)}getRegistrations(){let entries=[];for(let[token,reg]of this.registrations)entries.push({token:tokenName(token),scope:reg.scope,kind:reg.kind,instantiated:reg.scope===`singleton`?reg.instance!==void 0:reg.resolveCount>0,resolveCount:reg.resolveCount,lastResolvedAt:reg.lastResolvedAt,firstResolvedAt:reg.firstResolvedAt,resolveDurationMs:reg.resolveDurationMs,postConstructStatus:reg.postConstructStatus,dependencies:reg.dependencies});return entries}resolve(token){let reg=this.registrations.get(token);if(!reg&&typeof token==`function`&&token.name&&(reg=this.registrations.get(`__hmr__${token.name}`)),!reg)throw Error(`No binding found for: ${tokenName(token)}`);if(reg.scope===`singleton`&®.instance!==void 0)return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(tokenName(token),`resolved`,reg.kind),reg.instance;if(reg.scope===`request`){if(!Container._requestStoreProvider)throw Error(`Cannot resolve REQUEST-scoped "${tokenName(token)}": no request store provider configured. Ensure requestScopeMiddleware() is in your middleware pipeline.`);let store=Container._requestStoreProvider();if(!store)throw Error(`Cannot resolve REQUEST-scoped "${tokenName(token)}" outside an HTTP request context.`);if(store.values.has(token))return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(tokenName(token),`resolved`,reg.kind),store.values.get(token);if(store.instances.has(token))return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(tokenName(token),`resolved`,reg.kind),store.instances.get(token);if(reg.factory){let start=performance.now(),instance=reg.factory();return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),store.instances.set(token,instance),this.emit(tokenName(token),`resolved`,reg.kind),instance}let start=performance.now(),instance=this.createInstance(reg);return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),store.instances.set(token,instance),this.emit(tokenName(token),`resolved`,reg.kind),instance}if(reg.factory){let start=performance.now(),instance=reg.factory();return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),reg.scope===`singleton`&&(reg.instance=instance,getPersistentStore().resolvedInstances.set(tokenName(token),instance)),this.emit(tokenName(token),`resolved`,reg.kind),instance}if(this.resolving.has(token)){let chain=[...this.resolving].map(tokenName);throw chain.push(tokenName(token)),Error(`Circular dependency detected: ${chain.join(` -> `)}`)}this.resolving.add(token);try{let start=performance.now(),instance=this.createInstance(reg);return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),reg.scope===`singleton`&&(reg.instance=instance),this.emit(tokenName(token),`resolved`,reg.kind),instance}finally{this.resolving.delete(token)}}bootstrap(){}onChange(callback){return this.changeListeners.add(callback),()=>this.changeListeners.delete(callback)}invalidate(token){let name=tokenName(token),reg=this.registrations.get(token)??this.registrations.get(`__hmr__${name}`);if(reg&&!getPersistentStore().resolvedInstances.has(name)){reg.instance=void 0,reg.resolveCount=0,reg.postConstructStatus=`skipped`,this.emit(name,`invalidated`,reg.kind);for(let[depToken,depReg]of this.registrations)depReg.dependencies.includes(name)&&this.invalidate(depToken)}}emit(token,event,kind){let existing=this.pendingChanges.find(p=>p.token===token&&p.event===event);existing?existing.timestamp=Date.now():this.pendingChanges.push({token,event,kind,timestamp:Date.now()}),this.notifyTimer&&clearTimeout(this.notifyTimer),this.notifyTimer=setTimeout(()=>{let batch=this.pendingChanges.splice(0);if(batch.length!==0)for(let listener of this.changeListeners)try{listener(batch)}catch{}},Container.DEBOUNCE_MS)}flushChanges(){this.notifyTimer&&=(clearTimeout(this.notifyTimer),null);let batch=this.pendingChanges.splice(0);if(batch.length!==0)for(let listener of this.changeListeners)try{listener(batch)}catch{}}createInstance(reg){let args=getClassMeta(METADATA.PARAM_TYPES,reg.target,[]).map((paramType,index)=>{let token=getMetaRecord(METADATA.INJECT,reg.target)[index]||paramType;if(reg.scope===`singleton`){let depReg=this.registrations.get(token);if(depReg&&depReg.scope===`request`)throw Error(`Cannot inject REQUEST-scoped "${tokenName(token)}" into SINGLETON "${tokenName(reg.target)}". Singletons outlive requests. Use TRANSIENT or REQUEST scope for the parent.`)}return this.resolve(token)}),instance=new reg.target(...args);this.injectProperties(instance,reg.target);let postConstruct=getClassMetaOrUndefined(METADATA.POST_CONSTRUCT,reg.target.prototype);if(postConstruct&&typeof instance[postConstruct]==`function`)try{let result=instance[postConstruct]();result&&typeof result.then==`function`?(reg.postConstructStatus=`pending`,result.then(()=>{reg.postConstructStatus=`completed`}).catch(()=>{reg.postConstructStatus=`failed`}),log.warn(`@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() returned a Promise but the container is synchronous. The async operation will not be awaited.`)):reg.postConstructStatus=`completed`}catch(err){reg.postConstructStatus=`failed`,log.error(err,`@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`)}return instance}extractDependencies(target){let paramTypes=getClassMeta(METADATA.PARAM_TYPES,target,[]),injectTokens=getMetaRecord(METADATA.INJECT,target),ctorDeps=paramTypes.map((type,index)=>tokenName(injectTokens[index]||type)),autowiredProps=getMetaMap(METADATA.AUTOWIRED,target.prototype),propDeps=[];for(let[prop,token]of autowiredProps){let resolvedToken=token||getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE,target.prototype,prop);resolvedToken&&propDeps.push(tokenName(resolvedToken))}return[...new Set([...ctorDeps,...propDeps])]}injectProperties(instance,target){let autowiredProps=getMetaMap(METADATA.AUTOWIRED,target.prototype);for(let[prop,token]of autowiredProps){let resolvedToken=token||getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE,target.prototype,prop);resolvedToken&&Object.defineProperty(instance,prop,{get:()=>this.resolve(resolvedToken),enumerable:!0,configurable:!0})}let valueProps=getMetaMap(METADATA.VALUE,target.prototype);for(let[prop,config]of valueProps)Object.defineProperty(instance,prop,{get(){if(Container._envResolver){let val=Container._envResolver(config.envKey);if(val!==void 0)return val}let val=process.env[config.envKey];if(val!==void 0)return val;if(config.defaultValue!==void 0)return config.defaultValue;throw Error(`@Value('${config.envKey}'): Environment variable "${config.envKey}" is not set and no default was provided.`)},enumerable:!0,configurable:!0});let assetProps=getMetaMap(METADATA.ASSET,target.prototype);if(assetProps.size>0)for(let[prop,config]of assetProps){let slashIdx=config.assetKey.indexOf(`/`);if(slashIdx===-1)throw Error(`@Asset('${config.assetKey}'): asset key must include a '/' separator (e.g. 'mails/welcome').`);let namespace=config.assetKey.slice(0,slashIdx),key=config.assetKey.slice(slashIdx+1);Object.defineProperty(instance,prop,{get(){return resolveAsset(namespace,key)},enumerable:!0,configurable:!0})}}};export{UnknownAssetError as a,resolveAsset as c,stripExt as d,INJECTION_TOKEN as f,__require as h,ASSET_MANIFEST_VERSION as i,useAssets as l,isInjectionToken as m,tokenName as n,assets as o,createToken as p,ASSETS as r,clearAssetCache as s,Container as t,groupAssetKeys as u};
|
|
12
|
+
//# sourceMappingURL=container-COmj1vM3.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container-D0v17gF8.mjs","names":[],"sources":["../src/core/token.ts","../src/core/asset-keys.ts","../src/core/assets.ts","../src/core/container.ts"],"sourcesContent":["/**\n * Collision-safe injection tokens with phantom type parameters.\n *\n * `createToken<T>(name)` is the recommended way to declare any DI token\n * that isn't a class. Each call returns a unique frozen object — even if\n * two files write `createToken<X>('Logger')`, they produce two distinct\n * references that the container treats as different tokens. Collisions\n * become impossible by construction, not by convention.\n *\n * The phantom type parameter `T` lets `container.resolve(token)` infer\n * the correct return type without any codegen.\n *\n * ## Hardening layers\n *\n * KickJS has four DI token kinds, listed from safest to riskiest:\n *\n * 1. **Class identity** — `container.resolve(UserService)`. Reference\n * equality, never collides. Type-safe via `Constructor<T>` overload.\n * 2. **`createToken<T>(name)`** — frozen object identity, never collides.\n * Type-safe via the `InjectionToken<T>` overload.\n * 3. **Symbol** — `Symbol('foo')` is unique per call but `Symbol.for('foo')`\n * is interned and CAN collide. Discouraged.\n * 4. **Raw string `@Inject('foo')`** — high collision risk, untyped.\n * Reserved for legacy code and runtime-computed token names.\n *\n * Prefer (1) and (2). Use (4) only when you have to.\n *\n * @example\n * ```ts\n * // src/tokens.ts\n * export const DATABASE_URL = createToken<string>('config.database.url')\n * export const FEATURE_FLAGS = createToken<FeatureFlags>('app.features')\n *\n * // bootstrap\n * container.registerInstance(DATABASE_URL, process.env.DATABASE_URL!)\n *\n * // anywhere else\n * const url = container.resolve(DATABASE_URL) // typed as `string`\n * ```\n *\n * @module @forinda/kickjs/core/token\n */\n\n/**\n * Symbol marker stamped on every InjectionToken so the container can\n * detect them at runtime regardless of which package created them.\n *\n * Uses `Symbol.for()` so cross-package isolation (different copies of\n * `@forinda/kickjs` loaded under different module identities) still\n * shares the same marker.\n */\nexport const INJECTION_TOKEN: unique symbol = Symbol.for('@kickjs/InjectionToken') as never\n\n/**\n * A type-safe DI token. Each `createToken()` call returns a unique frozen\n * object identified by reference, not by the `name` string. Two\n * `createToken<X>('foo')` calls in different files produce two distinct\n * tokens that the container treats independently.\n *\n * The phantom `_type` parameter is never assigned at runtime — it exists\n * solely to thread the type through `container.resolve(token)`.\n */\nexport interface InjectionToken<T> {\n readonly [INJECTION_TOKEN]: true\n /** Descriptive name used in error messages, logs, and devtools */\n readonly name: string\n /** Phantom — never assigned at runtime */\n readonly _type?: T\n}\n\n/**\n * Create a collision-safe injection token.\n *\n * The returned token is a frozen object identified by reference, not by\n * the `name` string. The `name` is used only for error messages, logs,\n * and debugging.\n */\nexport function createToken<T>(name: string): InjectionToken<T> {\n return Object.freeze({\n [INJECTION_TOKEN]: true as const,\n name,\n })\n}\n\n/** Type guard — returns `true` if `value` is an InjectionToken */\nexport function isInjectionToken(value: unknown): value is InjectionToken<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[INJECTION_TOKEN] === true\n )\n}\n","// Shared asset-key strategy — used by the build pipeline, the\n// runtime dev resolver, and the typegen emitter so all three layers\n// agree on what the manifest looks like.\n//\n// Three strategies:\n//\n// - `'strip'` — drop the final extension. `pages/index.pug` →\n// `pages/index`. Best for namespaces where every basename is\n// unique (mail templates, pdf invoices). Shortest autocomplete.\n//\n// - `'with-extension'` — keep the extension. `pages/index.pug` →\n// `pages/index.pug`. Required when extension siblings coexist.\n//\n// - `'auto'` (default) — strip when safe, keep extensions when not.\n// The grouping pass collects files by stripped basename; groups\n// of one keep the stripped key, groups of more than one keep the\n// full path. Backward compatible for projects with no\n// extension-siblings; lossless for projects that have them.\n//\n// The function returns the path / key pairs in walk order (input\n// order preserved) so callers can iterate without re-sorting. Sort\n// in the caller if a stable manifest order is needed.\n\nexport type AssetKeyStrategy = 'auto' | 'strip' | 'with-extension'\n\nexport interface AssetKeyPair {\n /** Source-relative path the file was discovered at. */\n rel: string\n /** Final logical key for the manifest, including the namespace prefix. */\n key: string\n}\n\nexport interface GroupAssetKeysOptions {\n /**\n * Strategy applied to this namespace. Default `'auto'`.\n *\n * Adopters override per-namespace via `assetMap.<ns>.keys` in\n * kick.config.ts when they want uniform behaviour (e.g. always\n * include the extension regardless of collisions).\n */\n strategy?: AssetKeyStrategy\n}\n\nexport interface GroupAssetKeysResult {\n pairs: AssetKeyPair[]\n /**\n * Number of collision groups resolved by keeping extensions.\n * Surfaces as an informational log (\"auto-resolved N collisions\")\n * so adopters know whether they're relying on auto-mode or not.\n * Always 0 for `'strip'` (overwrites) and `'with-extension'`\n * (extensions always preserved, no collision possible).\n */\n collisionGroupsResolved: number\n}\n\n/**\n * Compute final manifest keys for a namespace's discovered paths,\n * given the chosen strategy. Pure function — no fs / config / globals\n * involved.\n *\n * @param namespace assetMap key — e.g. `'mails'`, `'pages'`.\n * @param paths source-relative file paths (POSIX separators) discovered\n * under the namespace's `src` directory.\n * @param opts grouping options.\n */\nexport function groupAssetKeys(\n namespace: string,\n paths: readonly string[],\n opts: GroupAssetKeysOptions = {},\n): GroupAssetKeysResult {\n const strategy = opts.strategy ?? 'auto'\n\n if (strategy === 'with-extension') {\n return {\n pairs: paths.map((rel) => ({ rel, key: `${namespace}/${rel}` })),\n collisionGroupsResolved: 0,\n }\n }\n\n if (strategy === 'strip') {\n // Strict strip — last-walk-order wins on collision; no\n // collision tracking here because the docs document this as the\n // adopter's choice. The caller (build / runtime / typegen) can\n // still warn if it wants; this function just shapes keys.\n return {\n pairs: paths.map((rel) => ({ rel, key: `${namespace}/${stripExt(rel)}` })),\n collisionGroupsResolved: 0,\n }\n }\n\n // strategy === 'auto' — group by stripped basename, keep\n // extensions only on collision groups.\n const groups = new Map<string, string[]>()\n for (const rel of paths) {\n const strippedKey = `${namespace}/${stripExt(rel)}`\n const arr = groups.get(strippedKey)\n if (arr) {\n arr.push(rel)\n } else {\n groups.set(strippedKey, [rel])\n }\n }\n\n let collisionGroupsResolved = 0\n // Build a per-rel decision table so we preserve input ordering in\n // the output (manifest writers rely on stable ordering).\n const keyByRel = new Map<string, string>()\n for (const [strippedKey, rels] of groups) {\n if (rels.length === 1) {\n keyByRel.set(rels[0], strippedKey)\n } else {\n collisionGroupsResolved += 1\n for (const rel of rels) {\n keyByRel.set(rel, `${namespace}/${rel}`)\n }\n }\n }\n\n return {\n pairs: paths.map((rel) => ({\n rel,\n key: keyByRel.get(rel) ?? `${namespace}/${rel}`,\n })),\n collisionGroupsResolved,\n }\n}\n\n/** Strip the final extension from a path. `'mails/welcome.ejs'` → `'mails/welcome'`. */\nexport function stripExt(path: string): string {\n // Inline the logic instead of importing extname so this module\n // stays usable in pure-browser contexts. Same algorithm: find the\n // last dot AFTER the last separator, slice up to it.\n const lastSlash = path.lastIndexOf('/')\n const filename = lastSlash >= 0 ? path.slice(lastSlash + 1) : path\n const lastDot = filename.lastIndexOf('.')\n if (lastDot <= 0) return path // no ext, or hidden file like `.env`\n const dirPart = lastSlash >= 0 ? path.slice(0, lastSlash + 1) : ''\n return dirPart + filename.slice(0, lastDot)\n}\n","/**\n * Asset manager runtime (assets-plan.md PR 3).\n *\n * Three accessor surfaces over one resolver engine:\n *\n * - `assets.x.y()` — Proxy ambient, autocomplete-friendly default.\n * - `useAssets()` — hook returning the same Proxy; mockable per-call.\n * - `resolveAsset(ns, k)` — string resolver for dynamic dispatch.\n *\n * Resolution path:\n *\n * 1. Load the build-time manifest (`<outDir>/.kickjs-assets.json`)\n * when present. Manifest existence = \"we're running from a built\n * dist\". Fast path; mapped paths returned verbatim.\n * 2. Otherwise (dev / test) walk the assetMap src directories from\n * `kick.config.{json,js,cjs}` and synthesise the same manifest\n * shape in memory. First call pays the walk cost; subsequent\n * calls hit the cache.\n * 3. `KICK_ASSETS_ROOT` env var overrides everything — the resolver\n * treats the path as the manifest directory and skips discovery.\n *\n * @module @forinda/kickjs/core/assets\n */\n\nimport { existsSync, readFileSync, readdirSync } from 'node:fs'\nimport { isAbsolute, join, resolve, sep } from 'node:path'\nimport { createToken, type InjectionToken } from './token'\nimport { groupAssetKeys } from './asset-keys'\n\nexport const ASSET_MANIFEST_VERSION = 1 as const\n\ninterface AssetManifest {\n version: typeof ASSET_MANIFEST_VERSION\n entries: Record<string, string>\n}\n\ninterface ResolvedManifest {\n manifest: AssetManifest\n root: string\n}\n\n/**\n * Augmentable interface — `kick typegen` (assets-plan.md PR 4) emits\n * a `declare global` augmentation listing every namespace + key for\n * the project's `assetMap`. Adopters who haven't run typegen yet\n * still get a runtime-correct Proxy; only typed autocomplete waits\n * on the augmentation.\n */\nexport interface KickAssets {}\n\n/**\n * DI token for the `assets` Proxy. Adopter code that wants to\n * receive assets via constructor injection registers via\n * `container.registerInstance(ASSETS, useAssets())` in `bootstrap()`\n * and injects with `@Inject(ASSETS)`.\n */\nexport const ASSETS: InjectionToken<KickAssets> = createToken<KickAssets>('kick/assets/Map')\n\nexport class UnknownAssetError extends Error {\n readonly namespace: string\n readonly key: string\n constructor(namespace: string, key: string) {\n super(\n `Unknown asset '${namespace}/${key}'. ` +\n `Make sure the namespace exists in kick.config.ts assetMap and that the file is present in the source directory.`,\n )\n this.name = 'UnknownAssetError'\n this.namespace = namespace\n this.key = key\n }\n}\n\nlet manifestCache: ResolvedManifest | null | undefined = undefined\n\nexport function resolveAsset(namespace: string, key: string): string {\n const resolved = loadManifest()\n if (!resolved) {\n throw new UnknownAssetError(namespace, key)\n }\n const entry = resolved.manifest.entries[`${namespace}/${key}`]\n if (!entry) {\n throw new UnknownAssetError(namespace, key)\n }\n return isAbsolute(entry) ? entry : resolve(resolved.root, entry)\n}\n\n/** Reset the manifest cache. Tests use this; production code shouldn't need it. */\nexport function clearAssetCache(): void {\n manifestCache = undefined\n}\n\nfunction loadManifest(): ResolvedManifest | null {\n // In development we deliberately skip the cache so adding or removing\n // a template file (e.g. dropping `welcome-back.ejs` into\n // `src/templates/mails/`) takes effect without a server restart. The\n // synthesise-dev-manifest path walks ~10 files; the cost is negligible\n // compared to the user-visible surprise of \"I added a file but\n // assets.x.y() throws\". Production keeps the cache because the built\n // manifest under `dist/.kickjs-assets.json` doesn't change at runtime.\n if (process.env.NODE_ENV !== 'production') {\n return discoverManifest()\n }\n if (manifestCache !== undefined) return manifestCache\n manifestCache = discoverManifest()\n return manifestCache\n}\n\nfunction discoverManifest(): ResolvedManifest | null {\n const envRoot = process.env.KICK_ASSETS_ROOT\n if (envRoot) {\n const fromEnv = readBuiltManifest(envRoot)\n if (fromEnv) return fromEnv\n }\n\n const cwd = process.cwd()\n\n // Honour `kick.config.build.outDir` BEFORE the standard probe list\n // so adopters with non-default Vite outputs (e.g. `output/`,\n // `target/`, anything else) don't have to set KICK_ASSETS_ROOT\n // every time. Cached config load — same loader the dev fallback\n // uses. Skip when outDir matches one of the standard candidates;\n // it'd be probed redundantly otherwise.\n const config = loadConfigSync(cwd)\n const configOutDir = config?.build?.outDir\n if (configOutDir && !(STANDARD_OUT_DIRS as readonly string[]).includes(configOutDir)) {\n const found = readBuiltManifest(resolve(cwd, configOutDir))\n if (found) return found\n }\n\n for (const candidate of STANDARD_OUT_DIRS) {\n const found = readBuiltManifest(join(cwd, candidate))\n if (found) return found\n }\n\n return synthesiseDevManifest(cwd, config)\n}\n\nconst STANDARD_OUT_DIRS = ['dist', 'build', 'out'] as const\n\nfunction readBuiltManifest(dir: string): ResolvedManifest | null {\n const path = join(dir, '.kickjs-assets.json')\n if (!existsSync(path)) return null\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw) as Partial<AssetManifest>\n if (parsed.version !== ASSET_MANIFEST_VERSION) return null\n if (!parsed.entries || typeof parsed.entries !== 'object') return null\n return { manifest: parsed as AssetManifest, root: resolve(dir) }\n } catch {\n return null\n }\n}\n\ninterface AssetMapConfigShape {\n assetMap?: Record<\n string,\n {\n src: string\n dest?: string\n glob?: string\n keys?: 'auto' | 'strip' | 'with-extension'\n }\n >\n build?: { outDir?: string }\n}\n\nfunction synthesiseDevManifest(\n cwd: string,\n preloadedConfig?: AssetMapConfigShape | null,\n): ResolvedManifest | null {\n const config = preloadedConfig === undefined ? loadConfigSync(cwd) : preloadedConfig\n if (!config?.assetMap) return null\n\n const entries: Record<string, string> = {}\n // Walk order is filesystem-dependent on Linux/macOS, so we collect\n // every match per-namespace first, then sort + feed `groupAssetKeys`\n // for stable manifest output. Build pipeline does the same — both\n // layers agree on the resulting key set.\n for (const [namespace, entry] of Object.entries(config.assetMap)) {\n if (!entry || typeof entry.src !== 'string') continue\n const srcAbs = resolve(cwd, entry.src)\n if (!existsSync(srcAbs)) continue\n\n const collected: Array<{ rel: string; abs: string }> = []\n walkSync(srcAbs, srcAbs, (relPath, absPath) => {\n if (entry.glob && !matchesGlobLite(relPath, entry.glob, namespace)) return\n collected.push({ rel: relPath, abs: absPath })\n })\n collected.sort((a, b) => a.rel.localeCompare(b.rel))\n\n const { pairs, collisionGroupsResolved } = groupAssetKeys(\n namespace,\n collected.map((c) => c.rel),\n { strategy: entry.keys ?? 'auto' },\n )\n const absByRel = new Map(collected.map((c) => [c.rel, c.abs]))\n for (const { rel, key } of pairs) {\n const abs = absByRel.get(rel)\n if (abs) entries[key] = abs\n }\n if (collisionGroupsResolved > 0 && process.env.NODE_ENV !== 'production') {\n // Dev-only nudge — production runs read the build manifest where\n // the collision was already resolved at build time.\n console.log(\n `[kickjs/assets] '${namespace}': ${collisionGroupsResolved} basename collision(s) auto-resolved by keeping extensions ` +\n `(set assetMap.${namespace}.keys to 'strip' or 'with-extension' to override).`,\n )\n }\n }\n\n return { manifest: { version: ASSET_MANIFEST_VERSION, entries }, root: cwd }\n}\n\nconst warnedGlobs = new Set<string>()\n\n/**\n * Tiny glob matcher used only by dev manifest synthesis. Covers the\n * common assetMap glob patterns (`**\\/*`, `**\\/*.ext`,\n * `**\\/*.{a,b}`). Anything exotic prints a one-time warning + falls\n * through to `**\\/*` semantics rather than silently dropping files.\n *\n * The build pipeline (`packages/cli/src/asset-manager/build.ts`) uses\n * the full `glob` package; adopters who hit the dev-mode warning\n * should run `kick build:assets` to produce a real manifest.\n */\nfunction matchesGlobLite(relPath: string, pattern: string, namespace: string): boolean {\n if (pattern === '**/*' || pattern === '**') return true\n const singleExt = /^\\*\\*\\/\\*\\.(\\w+)$/.exec(pattern)\n if (singleExt) return relPath.endsWith(`.${singleExt[1]}`)\n const braceExt = /^\\*\\*\\/\\*\\.\\{([^}]+)\\}$/.exec(pattern)\n if (braceExt) {\n const exts = braceExt[1].split(',').map((e) => e.trim())\n return exts.some((ext) => relPath.endsWith(`.${ext}`))\n }\n const tag = `${namespace}::${pattern}`\n if (!warnedGlobs.has(tag)) {\n warnedGlobs.add(tag)\n console.warn(\n `[kickjs/assets] Dev-mode glob '${pattern}' on '${namespace}' isn't recognised by the lite matcher. ` +\n `Falling back to '**/*'. Run 'kick build:assets' to use the full glob engine.`,\n )\n }\n return true\n}\n\nfunction walkSync(root: string, dir: string, onFile: (rel: string, abs: string) => void): void {\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n const full = join(dir, entry.name)\n if (entry.isDirectory()) {\n walkSync(root, full, onFile)\n } else if (entry.isFile()) {\n const rel = full\n .slice(root.length + 1)\n .split(sep)\n .join('/')\n onFile(rel, full)\n }\n }\n}\n\n/**\n * Sync `kick.config.{json,cjs,js}` loader for dev manifest\n * synthesis. Avoids dynamic `import()` (would force every assets\n * lookup to be async). TypeScript configs (`kick.config.ts`) are\n * skipped here — adopters who use them should run `kick build:assets`\n * to produce a real manifest the resolver can read.\n */\nfunction loadConfigSync(cwd: string): AssetMapConfigShape | null {\n const jsonPath = join(cwd, 'kick.config.json')\n if (existsSync(jsonPath)) {\n try {\n return JSON.parse(readFileSync(jsonPath, 'utf-8')) as AssetMapConfigShape\n } catch {\n return null\n }\n }\n for (const filename of ['kick.config.cjs', 'kick.config.js']) {\n const path = join(cwd, filename)\n if (!existsSync(path)) continue\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { createRequire } = require('node:module') as typeof import('node:module')\n const req = createRequire(join(cwd, 'package.json'))\n const mod = req(path) as { default?: AssetMapConfigShape } & AssetMapConfigShape\n return mod.default ?? mod ?? null\n } catch {\n continue\n }\n }\n return null\n}\n\n/**\n * Keys we MUST return `undefined` for: returning anything truthy\n * here causes Promise resolution to treat the Proxy as a thenable\n * and `await` it (resolving to whatever `then.call(...)` produces).\n * Two-element list deliberately — every other coercion-related key\n * goes through {@link PASSTHROUGH_KEYS} below so `String(assets)`,\n * template literals, and `util.inspect` keep working.\n */\nconst PROMISE_DETECTION_KEYS: ReadonlySet<string | symbol> = new Set<string | symbol>(['then'])\n\n/**\n * Keys we let fall through to the proxy target (a function-shaped\n * object). The default `Object.prototype` implementations of\n * `toString` / `valueOf` etc. are correct for these — returning\n * `undefined` here would break `String(assets)` and template\n * literals (`${assets}`) because `Symbol.toPrimitive` falling through\n * to a missing `toString` throws. Adopters that hit `assets.toString`\n * get `'function () {}'` back, which is fine — the contract is\n * \"asset access returns paths\"; everything else is incidental.\n */\nconst PASSTHROUGH_KEYS: ReadonlySet<string | symbol> = new Set<string | symbol>([\n 'toString',\n 'valueOf',\n 'constructor',\n 'prototype',\n 'name',\n 'length',\n 'asymmetricMatch',\n 'nodeType',\n Symbol.toPrimitive,\n Symbol.toStringTag,\n Symbol.iterator,\n Symbol.asyncIterator,\n])\n\nfunction createNamespaceProxy(parts: readonly string[]): unknown {\n const target = function () {} as unknown as Record<string, unknown>\n return new Proxy(target, {\n apply() {\n if (parts.length < 2) {\n throw new UnknownAssetError(parts[0] ?? '', '')\n }\n const [namespace, ...rest] = parts\n return resolveAsset(namespace, rest.join('/'))\n },\n get(t, prop) {\n if (PROMISE_DETECTION_KEYS.has(prop)) return undefined\n if (PASSTHROUGH_KEYS.has(prop)) return Reflect.get(t, prop)\n if (typeof prop === 'symbol') return undefined\n return createNamespaceProxy([...parts, prop])\n },\n })\n}\n\nfunction createAssetProxy(): KickAssets {\n return new Proxy({} as Record<string, unknown>, {\n get(t, prop) {\n if (PROMISE_DETECTION_KEYS.has(prop)) return undefined\n if (PASSTHROUGH_KEYS.has(prop)) return Reflect.get(t, prop)\n if (typeof prop === 'symbol') return undefined\n return createNamespaceProxy([prop])\n },\n }) as unknown as KickAssets\n}\n\n/**\n * Variant A — the ambient Proxy. Default accessor for static call\n * sites where the namespace + key are literals known at compile time.\n *\n * @example\n * ```ts\n * import { assets } from '@forinda/kickjs'\n * const path = assets.mails.welcome()\n * ```\n */\nexport const assets: KickAssets = createAssetProxy()\n\n/**\n * Variant B — the hook accessor. Returns the same Proxy as `assets`;\n * factory shape exists for testability + DI. Mock via\n * `vi.mock('@forinda/kickjs', () => ({ useAssets: () => fakeAssets }))`.\n *\n * @example\n * ```ts\n * import { useAssets } from '@forinda/kickjs'\n * class MailService {\n * private assets = useAssets()\n * send(name: string) {\n * return ejs.renderFile(this.assets.mails[name](), {})\n * }\n * }\n * ```\n */\nexport function useAssets(): KickAssets {\n return assets\n}\n","import 'reflect-metadata'\nimport {\n Constructor,\n Scope,\n METADATA,\n type ClassKind,\n type PostConstructStatus,\n} from './interfaces'\nimport { resolveAsset } from './assets'\nimport { createLogger } from './logger'\nimport {\n getClassMeta,\n getClassMetaOrUndefined,\n getMetaMap,\n getMetaRecord,\n getMethodMetaOrUndefined,\n} from './metadata'\nimport { isInjectionToken, type InjectionToken } from './token'\n\nconst log = createLogger('Container')\n\n/** Internal registration entry that tracks how a dependency should be resolved */\ninterface Registration {\n target: Constructor\n scope: Scope\n instance?: any\n factory?: () => any\n // Observability fields\n kind: ClassKind\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n}\n\n/** Create a Registration with observability defaults */\nfunction createReg(\n fields: Partial<Registration> & Pick<Registration, 'target' | 'scope'>,\n): Registration {\n return {\n kind: 'unknown',\n resolveCount: 0,\n postConstructStatus: 'skipped',\n dependencies: [],\n ...fields,\n }\n}\n\n/** Format a token for display in error messages */\nexport function tokenName(token: any): string {\n if (typeof token === 'symbol') return token.toString()\n if (isInjectionToken(token)) return token.name\n return token?.name || String(token)\n}\n\n// ── Persistent Store ────────────────────────────────────────────────────\n// globalThis-based storage that survives BOTH Container.reset() AND Vite's\n// ssrLoadModule() re-evaluation. When Vite re-evaluates a module, the\n// Container class gets a new identity (new statics). But globalThis persists.\n//\n// This is how DB connections, Redis clients, WsAdapter instances, and any\n// other stateful bindings survive HMR without being dropped.\n//\n// Pattern from: React Router (globalThis for state), Vinxi (globalThis.app)\n\n// ── Change Event System ─────────────────────────────────────────────────\n// Batched (50ms debounce) so `kick g module` creating 10+ files emits ONE\n// notification, not 10. Any adapter can subscribe: Swagger, DevTools, WS, etc.\n\n/** A single container change event */\nexport interface ContainerChangeEvent {\n token: string\n event: 'registered' | 'resolved' | 'invalidated'\n kind?: ClassKind\n timestamp: number\n}\n\n/** Listener that receives batched change events */\nexport type ContainerChangeListener = (changes: ContainerChangeEvent[]) => void\n\n/**\n * Type-safe registry of string DI tokens. Augmented by `kick typegen` —\n * the generated `.kickjs/types/registry.d.ts` adds entries like:\n *\n * ```ts\n * declare module '@forinda/kickjs' {\n * interface KickJsRegistry {\n * UserService: import('../../src/modules/users/user.service').UserService\n * }\n * }\n * ```\n *\n * Once typegen has run, `container.resolve('UserService')` returns the\n * correct type instead of `any`. Class-token resolution\n * (`container.resolve(UserService)`) is already type-safe via the\n * `Constructor<T>` overload and does not need typegen.\n */\nexport interface KickJsRegistry {}\n\ninterface PersistentStore {\n factories: Array<{ token: any; factory: () => any; scope: Scope }>\n instances: Array<{ token: any; instance: any }>\n /** Resolved instances keyed by string name — survives class identity changes */\n resolvedInstances: Map<string, any>\n}\n\nfunction getPersistentStore(): PersistentStore {\n if (!(globalThis as any).__kickjs_persistent) {\n ;(globalThis as any).__kickjs_persistent = {\n factories: [],\n instances: [],\n resolvedInstances: new Map(),\n } satisfies PersistentStore\n }\n return (globalThis as any).__kickjs_persistent\n}\n\n/**\n * Inversion-of-Control (IoC) container that manages dependency registration,\n * resolution, and lifecycle. Implements the Singleton pattern so all parts of\n * the application share a single container instance.\n *\n * Supports constructor injection, property injection (@Autowired),\n * factory registrations, and lifecycle hooks (@PostConstruct).\n *\n * Persistent state (DB connections, Redis, etc.) is stored on globalThis so\n * it survives both Container.reset() and Vite module re-evaluation.\n */\nexport class Container {\n private static instance: Container\n private registrations = new Map<any, Registration>()\n private resolving = new Set<any>()\n\n // ── Reactive change tracking ──────────────────────────────────────\n private changeListeners = new Set<ContainerChangeListener>()\n private pendingChanges: ContainerChangeEvent[] = []\n private notifyTimer: ReturnType<typeof setTimeout> | null = null\n private static readonly DEBOUNCE_MS = 50\n\n /** Callback set by the decorators module to flush pending registrations */\n static _onReady: ((container: Container) => void) | null = null\n /** Callback invoked on reset so decorators can update their container reference */\n static _onReset: ((container: Container) => void) | null = null\n /**\n * Environment resolver for @Value decorator. Set by the unified\n * `@forinda/kickjs` config layer to return Zod-validated, type-coerced\n * env values instead of raw process.env strings.\n */\n static _envResolver: ((key: string) => any) | null = null\n /**\n * Request store provider for REQUEST-scoped DI. Set by the HTTP layer\n * inside `@forinda/kickjs` to return the current request's\n * AsyncLocalStorage store. Returns { instances: Map, values: Map } or\n * null if outside a request.\n */\n static _requestStoreProvider:\n | (() => { instances: Map<any, any>; values: Map<any, any> } | null)\n | null = null\n\n static getInstance(): Container {\n if (!Container.instance) {\n Container.instance = new Container()\n }\n // Flush any decorator registrations that queued before the container existed\n if (Container._onReady) {\n Container._onReady(Container.instance)\n Container._onReady = null\n }\n return Container.instance\n }\n\n /**\n * Resets the container by replacing the singleton with a fresh instance.\n * Persistent registrations (DB, Redis, etc.) are replayed from globalThis\n * so they survive both reset() and Vite module re-evaluation.\n */\n static reset(): void {\n Container.instance = new Container()\n // Notify decorators so they update their container reference\n Container._onReset?.(Container.instance)\n // Replay persistent registrations from globalThis store\n const store = getPersistentStore()\n for (const { token, factory, scope } of store.factories) {\n if (!Container.instance.has(token)) {\n // Check if we have a previously resolved instance for this token\n const name = tokenName(token)\n const existing = store.resolvedInstances.get(name)\n Container.instance.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existing, // Reuse resolved instance if available\n resolveCount: existing ? 1 : 0,\n postConstructStatus: existing ? 'completed' : 'skipped',\n }),\n )\n }\n }\n for (const { token, instance } of store.instances) {\n if (!Container.instance.has(token)) {\n Container.instance.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n }),\n )\n }\n }\n }\n\n /**\n * Create an isolated container instance (not the global singleton).\n * Useful for concurrent tests that must not share DI state.\n *\n * @example\n * ```ts\n * const container = Container.create()\n * container.register(UserService, UserService)\n * const svc = container.resolve(UserService)\n * ```\n */\n static create(): Container {\n return new Container()\n }\n\n /** Register a class constructor under the given token */\n register(token: any, target: Constructor, scope: Scope = Scope.SINGLETON): void {\n const kind = getClassMeta<ClassKind>(METADATA.CLASS_KIND, target, 'unknown')\n const dependencies = this.extractDependencies(target)\n const reg = createReg({ target, scope, kind, dependencies })\n this.registrations.set(token, reg)\n // Store a name-based fallback so HMR class re-creation (new identity)\n // can still resolve by the original class name.\n if (typeof token === 'function' && token.name) {\n this.registrations.set(`__hmr__${token.name}`, reg)\n }\n this.emit(tokenName(token), 'registered', kind)\n }\n\n /** Register a factory function under the given token */\n registerFactory(token: any, factory: () => any, scope: Scope = Scope.SINGLETON): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n // Check if we already have a resolved instance from a previous HMR cycle.\n // Only reuse if the factory hasn't changed (same reference). When a new\n // factory is provided (e.g. swapping implementations), clear the stale\n // instance so the new factory runs on next resolve.\n const existingEntry = store.factories.find((r) => tokenName(r.token) === name)\n const factoryUnchanged = existingEntry?.factory === factory\n const existingInstance = factoryUnchanged ? store.resolvedInstances.get(name) : undefined\n\n // Clear stale resolved instance when factory changes\n if (!factoryUnchanged) {\n store.resolvedInstances.delete(name)\n }\n\n this.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existingInstance,\n resolveCount: existingInstance ? 1 : 0,\n postConstructStatus: existingInstance ? 'completed' : 'skipped',\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation\n const idx = store.factories.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.factories[idx] = { token, factory, scope }\n } else {\n store.factories.push({ token, factory, scope })\n }\n this.emit(name, 'registered', 'factory')\n }\n\n /** Register a pre-constructed singleton instance */\n registerInstance(token: any, instance: any): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n\n this.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n firstResolvedAt: Date.now(),\n lastResolvedAt: Date.now(),\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation\n store.resolvedInstances.set(name, instance)\n const idx = store.instances.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.instances[idx] = { token, instance }\n } else {\n store.instances.push({ token, instance })\n }\n this.emit(name, 'registered', 'instance')\n }\n\n /** Check if a binding exists for the given token */\n has(token: any): boolean {\n return this.registrations.has(token)\n }\n\n /** Return a snapshot of all registered tokens with their scope and instantiation status */\n getRegistrations(): Array<{\n token: string\n scope: string\n kind: ClassKind\n instantiated: boolean\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n }> {\n const entries: Array<ReturnType<Container['getRegistrations']>[number]> = []\n for (const [token, reg] of this.registrations) {\n entries.push({\n token: tokenName(token),\n scope: reg.scope,\n kind: reg.kind,\n // Singletons check cached instance; transients check resolveCount\n instantiated:\n reg.scope === Scope.SINGLETON ? reg.instance !== undefined : reg.resolveCount > 0,\n resolveCount: reg.resolveCount,\n lastResolvedAt: reg.lastResolvedAt,\n firstResolvedAt: reg.firstResolvedAt,\n resolveDurationMs: reg.resolveDurationMs,\n postConstructStatus: reg.postConstructStatus,\n dependencies: reg.dependencies,\n })\n }\n return entries\n }\n\n /**\n * Resolve a dependency by its token.\n *\n * Four overloads, in order of preference:\n * - **`InjectionToken<T>`** (from `createToken<T>(name)`) → returns `T`.\n * Collision-safe by construction; no codegen required.\n * - **Class constructor** → returns an instance of that class. Always\n * type-safe; no codegen required.\n * - **String token in `KickJsRegistry`** (populated by `kick typegen`) →\n * returns the augmented type.\n * - **Anything else** → returns `any`. Use this only for legacy code or\n * string tokens that haven't been registered with typegen yet.\n */\n resolve<T>(token: InjectionToken<T>): T\n resolve<T>(token: Constructor<T>): T\n resolve<K extends keyof KickJsRegistry & string>(token: K): KickJsRegistry[K]\n resolve<T = any>(token: any): T\n resolve<T = any>(token: any): T {\n let reg = this.registrations.get(token)\n // HMR fallback: when Vite re-evaluates a module, decorated classes get new\n // identity. Try resolving by class name if the primary token lookup fails.\n if (!reg && typeof token === 'function' && token.name) {\n reg = this.registrations.get(`__hmr__${token.name}`)\n }\n if (!reg) {\n throw new Error(`No binding found for: ${tokenName(token)}`)\n }\n\n if (reg.scope === Scope.SINGLETON && reg.instance !== undefined) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(tokenName(token), 'resolved', reg.kind)\n return reg.instance\n }\n\n // REQUEST scope: one instance per HTTP request, cached in AsyncLocalStorage\n if (reg.scope === Scope.REQUEST) {\n if (!Container._requestStoreProvider) {\n throw new Error(\n `Cannot resolve REQUEST-scoped \"${tokenName(token)}\": no request store provider configured. ` +\n `Ensure requestScopeMiddleware() is in your middleware pipeline.`,\n )\n }\n const store = Container._requestStoreProvider()\n if (!store) {\n throw new Error(\n `Cannot resolve REQUEST-scoped \"${tokenName(token)}\" outside an HTTP request context.`,\n )\n }\n // Check for pre-registered request values (user, tenant, etc.)\n if (store.values.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(tokenName(token), 'resolved', reg.kind)\n return store.values.get(token) as T\n }\n // Check per-request instance cache\n if (store.instances.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(tokenName(token), 'resolved', reg.kind)\n return store.instances.get(token) as T\n }\n // Factory-registered binding: invoke the factory once per request\n // and cache the result alongside class-instantiated REQUEST tokens.\n // Without this branch, registerFactory(token, fn, Scope.REQUEST)\n // silently falls through to createInstance(reg) which `new`s the\n // factory's placeholder `target: Object` and returns an empty {}\n // — the factory function never runs.\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(tokenName(token), 'resolved', reg.kind)\n return instance as T\n }\n // Create instance and cache for this request only\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(tokenName(token), 'resolved', reg.kind)\n return instance as T\n }\n\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n // Persist resolved factory instances so they survive module re-evaluation\n getPersistentStore().resolvedInstances.set(tokenName(token), instance)\n }\n this.emit(tokenName(token), 'resolved', reg.kind)\n return instance\n }\n\n if (this.resolving.has(token)) {\n const chain = [...this.resolving].map(tokenName)\n chain.push(tokenName(token))\n throw new Error(`Circular dependency detected: ${chain.join(' -> ')}`)\n }\n this.resolving.add(token)\n\n try {\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n }\n this.emit(tokenName(token), 'resolved', reg.kind)\n return instance\n } finally {\n this.resolving.delete(token)\n }\n }\n\n /** Lifecycle hook called during Application.setup() after module registration */\n bootstrap(): void {\n // Reserved for future use — adapters and modules register via\n // container.register(), registerFactory(), and registerInstance().\n }\n\n // ── Reactive Change API ─────────────────────────────────────────────\n\n /**\n * Subscribe to container changes. Returns an unsubscribe function.\n *\n * Changes are batched (50ms debounce) so bulk operations like\n * `kick g module` creating 10+ files emit ONE batch, not 10 events.\n *\n * Any adapter can subscribe:\n * - SwaggerAdapter: re-build spec when controllers change\n * - DevToolsAdapter: push SSE events to dashboard\n * - WsAdapter: re-discover namespaces when controllers change\n * - OtelAdapter: emit tracing spans for DI resolution\n * - Custom user adapters\n *\n * @example\n * ```ts\n * const unsub = container.onChange((changes) => {\n * for (const c of changes) {\n * console.log(`${c.event}: ${c.token} (${c.kind})`)\n * }\n * })\n * // later:\n * unsub()\n * ```\n */\n onChange(callback: ContainerChangeListener): () => void {\n this.changeListeners.add(callback)\n return () => this.changeListeners.delete(callback)\n }\n\n /**\n * Invalidate a specific registration. Clears the cached instance so the\n * next resolve() creates a fresh one. Also invalidates dependents\n * (anything that injected this token) by walking the dependency graph.\n *\n * Called by the Vite HMR plugin when a module file changes.\n * Skips persistent registrations (DB connections, etc.).\n */\n invalidate(token: any): void {\n const name = tokenName(token)\n // Try both the token itself and the HMR fallback name\n const reg = this.registrations.get(token) ?? this.registrations.get(`__hmr__${name}`)\n if (!reg) return\n\n // Don't invalidate persistent registrations (DB, Redis, etc.)\n const store = getPersistentStore()\n if (store.resolvedInstances.has(name)) return\n\n // Clear cached instance — next resolve() will re-create\n reg.instance = undefined\n reg.resolveCount = 0\n reg.postConstructStatus = 'skipped'\n this.emit(name, 'invalidated', reg.kind)\n\n // Walk dependency graph — invalidate anything that injected this token\n for (const [depToken, depReg] of this.registrations) {\n if (depReg.dependencies.includes(name)) {\n this.invalidate(depToken)\n }\n }\n }\n\n /** Batch-emit a change event (debounced, flushed after 50ms of quiet) */\n private emit(token: string, event: ContainerChangeEvent['event'], kind?: ClassKind): void {\n // Dedupe within the pending batch: a high-throughput TRANSIENT\n // token that resolves 1000× per request would otherwise queue\n // 1000 identical 'resolved' events. The downstream listener only\n // cares that the token's state changed, so collapse to one entry\n // (the earliest timestamp wins; the latest update is reflected\n // in resolveCount/lastResolvedAt on the registration itself,\n // which the listener reads at notify time).\n const existing = this.pendingChanges.find((p) => p.token === token && p.event === event)\n if (existing) {\n existing.timestamp = Date.now()\n } else {\n this.pendingChanges.push({ token, event, kind, timestamp: Date.now() })\n }\n\n if (this.notifyTimer) clearTimeout(this.notifyTimer)\n this.notifyTimer = setTimeout(() => {\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }, Container.DEBOUNCE_MS)\n }\n\n /** Flush pending change events immediately (useful in tests) */\n flushChanges(): void {\n if (this.notifyTimer) {\n clearTimeout(this.notifyTimer)\n this.notifyTimer = null\n }\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }\n\n private createInstance(reg: Registration): any {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, reg.target, [])\n\n const args = paramTypes.map((paramType, index) => {\n // Check for @Inject token override on constructor parameter\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, reg.target)\n const token = injectTokens[index] || paramType\n // Scope validation: SINGLETON cannot inject REQUEST-scoped dependencies\n if (reg.scope === Scope.SINGLETON) {\n const depReg = this.registrations.get(token)\n if (depReg && depReg.scope === Scope.REQUEST) {\n throw new Error(\n `Cannot inject REQUEST-scoped \"${tokenName(token)}\" into SINGLETON \"${tokenName(reg.target)}\". ` +\n `Singletons outlive requests. Use TRANSIENT or REQUEST scope for the parent.`,\n )\n }\n }\n return this.resolve(token)\n })\n\n const instance = new reg.target(...args)\n\n // Property injection via @Autowired\n this.injectProperties(instance, reg.target)\n\n // @PostConstruct lifecycle hook\n const postConstruct = getClassMetaOrUndefined<string | symbol>(\n METADATA.POST_CONSTRUCT,\n reg.target.prototype,\n )\n if (postConstruct && typeof instance[postConstruct] === 'function') {\n try {\n const result = instance[postConstruct]()\n if (result && typeof result.then === 'function') {\n reg.postConstructStatus = 'pending'\n ;(result as Promise<void>)\n .then(() => {\n reg.postConstructStatus = 'completed'\n })\n .catch(() => {\n reg.postConstructStatus = 'failed'\n })\n log.warn(\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() returned a Promise ` +\n `but the container is synchronous. The async operation will not be awaited.`,\n )\n } else {\n reg.postConstructStatus = 'completed'\n }\n } catch (err) {\n reg.postConstructStatus = 'failed'\n log.error(\n err,\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`,\n )\n }\n }\n\n return instance\n }\n\n /**\n * Extract every dependency token name a class declares — both\n * constructor params (`@Inject` + emitted `design:paramtypes`) and\n * `@Autowired` properties (read lazily at injection time, but\n * captured here so devtools surfaces a complete dependency graph).\n *\n * Without the property branch, classes that rely entirely on\n * `@Autowired` show `dependencies: []` in the introspection panel\n * even though they have real DI deps — see /_debug topology.\n */\n private extractDependencies(target: Constructor): string[] {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, target, [])\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, target)\n const ctorDeps = paramTypes.map((type, index) => {\n const token = injectTokens[index] || type\n return tokenName(token)\n })\n\n // @Autowired properties live on `target.prototype`. Token may be\n // explicit (`@Autowired('SOME_TOKEN')`) or fall back to the\n // emitted `design:type` metadata of the property.\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, target.prototype)\n const propDeps: string[] = []\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, target.prototype, prop)\n if (resolvedToken) propDeps.push(tokenName(resolvedToken))\n }\n\n // De-dupe — a class can declare the same token in both surfaces.\n return [...new Set([...ctorDeps, ...propDeps])]\n }\n\n private injectProperties(instance: any, target: Constructor): void {\n // @Autowired — lazy DI property injection\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, target.prototype)\n\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, target.prototype, prop)\n if (resolvedToken) {\n Object.defineProperty(instance, prop, {\n get: () => this.resolve(resolvedToken),\n enumerable: true,\n configurable: true,\n })\n }\n }\n\n // @Value — lazy environment variable injection\n const valueProps = getMetaMap<string, { envKey: string; defaultValue?: any }>(\n METADATA.VALUE,\n target.prototype,\n )\n\n for (const [prop, config] of valueProps) {\n Object.defineProperty(instance, prop, {\n get() {\n // Use the registered env resolver if available (wired by the\n // config layer in @forinda/kickjs). Returns Zod-validated,\n // type-coerced values (e.g. PORT as number).\n if (Container._envResolver) {\n const val = Container._envResolver(config.envKey)\n if (val !== undefined) return val\n }\n\n // Fallback to raw process.env for apps that haven't called loadEnv()\n const val = process.env[config.envKey]\n if (val !== undefined) return val\n if (config.defaultValue !== undefined) return config.defaultValue\n throw new Error(\n `@Value('${config.envKey}'): Environment variable \"${config.envKey}\" is not set and no default was provided.`,\n )\n },\n enumerable: true,\n configurable: true,\n })\n }\n\n // @Asset — lazy asset path injection (assets-plan.md). Same lazy-\n // getter pattern as @Value: resolve on every property access via\n // the asset manager's cached resolver, not at class instantiation.\n // Splits the key on the FIRST '/' so `mails/orders/confirmation`\n // becomes namespace=`mails` + key=`orders/confirmation`.\n const assetProps = getMetaMap<string, { assetKey: string }>(METADATA.ASSET, target.prototype)\n\n if (assetProps.size > 0) {\n for (const [prop, config] of assetProps) {\n const slashIdx = config.assetKey.indexOf('/')\n if (slashIdx === -1) {\n throw new Error(\n `@Asset('${config.assetKey}'): asset key must include a '/' separator (e.g. 'mails/welcome').`,\n )\n }\n const namespace = config.assetKey.slice(0, slashIdx)\n const key = config.assetKey.slice(slashIdx + 1)\n Object.defineProperty(instance, prop, {\n get() {\n return resolveAsset(namespace, key)\n },\n enumerable: true,\n configurable: true,\n })\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;scAmDA,MAAa,gBAAiC,OAAO,IAAI,yBAAyB,CA0BlF,SAAgB,YAAe,KAAiC,CAC9D,OAAO,OAAO,OAAO,EAClB,iBAAkB,GACnB,KACD,CAAC,CAIJ,SAAgB,iBAAiB,MAAkD,CACjF,OACE,OAAO,OAAU,YACjB,OACC,MAAkC,mBAAqB,GCxB5D,SAAgB,eACd,UACA,MACA,KAA8B,EAAE,CACV,CACtB,IAAM,SAAW,KAAK,UAAY,OAElC,GAAI,WAAa,iBACf,MAAO,CACL,MAAO,MAAM,IAAK,MAAS,CAAE,IAAK,IAAK,GAAG,UAAU,GAAG,MAAO,EAAE,CAChE,wBAAyB,EAC1B,CAGH,GAAI,WAAa,QAKf,MAAO,CACL,MAAO,MAAM,IAAK,MAAS,CAAE,IAAK,IAAK,GAAG,UAAU,GAAG,SAAS,IAAI,GAAI,EAAE,CAC1E,wBAAyB,EAC1B,CAKH,IAAM,OAAS,IAAI,IACnB,IAAK,IAAM,OAAO,MAAO,CACvB,IAAM,YAAc,GAAG,UAAU,GAAG,SAAS,IAAI,GAC3C,IAAM,OAAO,IAAI,YAAY,CAC/B,IACF,IAAI,KAAK,IAAI,CAEb,OAAO,IAAI,YAAa,CAAC,IAAI,CAAC,CAIlC,IAAI,wBAA0B,EAGxB,SAAW,IAAI,IACrB,IAAK,GAAM,CAAC,YAAa,QAAS,OAChC,GAAI,KAAK,SAAW,EAClB,SAAS,IAAI,KAAK,GAAI,YAAY,KAC7B,CACL,yBAA2B,EAC3B,IAAK,IAAM,OAAO,KAChB,SAAS,IAAI,IAAK,GAAG,UAAU,GAAG,MAAM,CAK9C,MAAO,CACL,MAAO,MAAM,IAAK,MAAS,CACzB,IACA,IAAK,SAAS,IAAI,IAAI,EAAI,GAAG,UAAU,GAAG,MAC3C,EAAE,CACH,wBACD,CAIH,SAAgB,SAAS,KAAsB,CAI7C,IAAM,UAAY,KAAK,YAAY,IAAI,CACjC,SAAW,WAAa,EAAI,KAAK,MAAM,UAAY,EAAE,CAAG,KACxD,QAAU,SAAS,YAAY,IAAI,CAGzC,OAFI,SAAW,EAAU,MACT,WAAa,EAAI,KAAK,MAAM,EAAG,UAAY,EAAE,CAAG,IAC/C,SAAS,MAAM,EAAG,QAAQ,CC5G7C,MAAa,uBAAyB,EA2BzB,OAAqC,YAAwB,kBAAkB,CAE5F,IAAa,kBAAb,cAAuC,KAAM,CAC3C,UACA,IACA,YAAY,UAAmB,IAAa,CAC1C,MACE,kBAAkB,UAAU,GAAG,IAAI,oHAEpC,CACD,KAAK,KAAO,oBACZ,KAAK,UAAY,UACjB,KAAK,IAAM,MAIf,IAAI,cAEJ,SAAgB,aAAa,UAAmB,IAAqB,CACnE,IAAM,SAAW,cAAc,CAC/B,GAAI,CAAC,SACH,MAAM,IAAI,kBAAkB,UAAW,IAAI,CAE7C,IAAM,MAAQ,SAAS,SAAS,QAAQ,GAAG,UAAU,GAAG,OACxD,GAAI,CAAC,MACH,MAAM,IAAI,kBAAkB,UAAW,IAAI,CAE7C,OAAO,WAAW,MAAM,CAAG,MAAQ,QAAQ,SAAS,KAAM,MAAM,CAIlE,SAAgB,iBAAwB,CACtC,cAAgB,IAAA,GAGlB,SAAS,cAAwC,CAa/C,OALI,QAAQ,IAAI,WAAa,cAGzB,gBAAkB,IAAA,KACtB,cAAgB,kBAAkB,EADM,eAF/B,kBAAkB,CAO7B,SAAS,kBAA4C,CACnD,IAAM,QAAU,QAAQ,IAAI,iBAC5B,GAAI,QAAS,CACX,IAAM,QAAU,kBAAkB,QAAQ,CAC1C,GAAI,QAAS,OAAO,QAGtB,IAAM,IAAM,QAAQ,KAAK,CAQnB,OAAS,eAAe,IAAI,CAC5B,aAAe,QAAQ,OAAO,OACpC,GAAI,cAAgB,CAAE,kBAAwC,SAAS,aAAa,CAAE,CACpF,IAAM,MAAQ,kBAAkB,QAAQ,IAAK,aAAa,CAAC,CAC3D,GAAI,MAAO,OAAO,MAGpB,IAAK,IAAM,aAAa,kBAAmB,CACzC,IAAM,MAAQ,kBAAkB,KAAK,IAAK,UAAU,CAAC,CACrD,GAAI,MAAO,OAAO,MAGpB,OAAO,sBAAsB,IAAK,OAAO,CAG3C,MAAM,kBAAoB,CAAC,OAAQ,QAAS,MAAM,CAElD,SAAS,kBAAkB,IAAsC,CAC/D,IAAM,KAAO,KAAK,IAAK,sBAAsB,CAC7C,GAAI,CAAC,WAAW,KAAK,CAAE,OAAO,KAC9B,GAAI,CACF,IAAM,IAAM,aAAa,KAAM,QAAQ,CACjC,OAAS,KAAK,MAAM,IAAI,CAG9B,OAFI,OAAO,UAAA,GACP,CAAC,OAAO,SAAW,OAAO,OAAO,SAAY,SAAiB,KAC3D,CAAE,SAAU,OAAyB,KAAM,QAAQ,IAAI,CAAE,MAC1D,CACN,OAAO,MAiBX,SAAS,sBACP,IACA,gBACyB,CACzB,IAAM,OAAS,kBAAoB,IAAA,GAAY,eAAe,IAAI,CAAG,gBACrE,GAAI,CAAC,QAAQ,SAAU,OAAO,KAE9B,IAAM,QAAkC,EAAE,CAK1C,IAAK,GAAM,CAAC,UAAW,SAAU,OAAO,QAAQ,OAAO,SAAS,CAAE,CAChE,GAAI,CAAC,OAAS,OAAO,MAAM,KAAQ,SAAU,SAC7C,IAAM,OAAS,QAAQ,IAAK,MAAM,IAAI,CACtC,GAAI,CAAC,WAAW,OAAO,CAAE,SAEzB,IAAM,UAAiD,EAAE,CACzD,SAAS,OAAQ,QAAS,QAAS,UAAY,CACzC,MAAM,MAAQ,CAAC,gBAAgB,QAAS,MAAM,KAAM,UAAU,EAClE,UAAU,KAAK,CAAE,IAAK,QAAS,IAAK,QAAS,CAAC,EAC9C,CACF,UAAU,MAAM,EAAG,IAAM,EAAE,IAAI,cAAc,EAAE,IAAI,CAAC,CAEpD,GAAM,CAAE,MAAO,yBAA4B,eACzC,UACA,UAAU,IAAK,GAAM,EAAE,IAAI,CAC3B,CAAE,SAAU,MAAM,MAAQ,OAAQ,CACnC,CACK,SAAW,IAAI,IAAI,UAAU,IAAK,GAAM,CAAC,EAAE,IAAK,EAAE,IAAI,CAAC,CAAC,CAC9D,IAAK,GAAM,CAAE,IAAK,OAAS,MAAO,CAChC,IAAM,IAAM,SAAS,IAAI,IAAI,CACzB,MAAK,QAAQ,KAAO,KAEtB,wBAA0B,GAAK,QAAQ,IAAI,WAAa,cAG1D,QAAQ,IACN,oBAAoB,UAAU,KAAK,wBAAwB,2EACxC,UAAU,oDAC9B,CAIL,MAAO,CAAE,SAAU,CAAE,QAAA,EAAiC,QAAS,CAAE,KAAM,IAAK,CAG9E,MAAM,YAAc,IAAI,IAYxB,SAAS,gBAAgB,QAAiB,QAAiB,UAA4B,CACrF,GAAI,UAAY,QAAU,UAAY,KAAM,MAAO,GACnD,IAAM,UAAY,oBAAoB,KAAK,QAAQ,CACnD,GAAI,UAAW,OAAO,QAAQ,SAAS,IAAI,UAAU,KAAK,CAC1D,IAAM,SAAW,0BAA0B,KAAK,QAAQ,CACxD,GAAI,SAEF,OADa,SAAS,GAAG,MAAM,IAAI,CAAC,IAAK,GAAM,EAAE,MAAM,CAC5C,CAAC,KAAM,KAAQ,QAAQ,SAAS,IAAI,MAAM,CAAC,CAExD,IAAM,IAAM,GAAG,UAAU,IAAI,UAQ7B,OAPK,YAAY,IAAI,IAAI,GACvB,YAAY,IAAI,IAAI,CACpB,QAAQ,KACN,kCAAkC,QAAQ,QAAQ,UAAU,sHAE7D,EAEI,GAGT,SAAS,SAAS,KAAc,IAAa,OAAkD,CAC7F,IAAI,QACJ,GAAI,CACF,QAAU,YAAY,IAAK,CAAE,cAAe,GAAM,CAAC,MAC7C,CACN,OAEF,IAAK,IAAM,SAAS,QAAS,CAC3B,GAAI,MAAM,OAAS,gBAAkB,MAAM,KAAK,WAAW,IAAI,CAAE,SACjE,IAAM,KAAO,KAAK,IAAK,MAAM,KAAK,CAC9B,MAAM,aAAa,CACrB,SAAS,KAAM,KAAM,OAAO,CACnB,MAAM,QAAQ,EAKvB,OAJY,KACT,MAAM,KAAK,OAAS,EAAE,CACtB,MAAM,IAAI,CACV,KAAK,IACE,CAAE,KAAK,EAYvB,SAAS,eAAe,IAAyC,CAC/D,IAAM,SAAW,KAAK,IAAK,mBAAmB,CAC9C,GAAI,WAAW,SAAS,CACtB,GAAI,CACF,OAAO,KAAK,MAAM,aAAa,SAAU,QAAQ,CAAC,MAC5C,CACN,OAAO,KAGX,IAAK,IAAM,WAAY,CAAC,kBAAmB,iBAAiB,CAAE,CAC5D,IAAM,KAAO,KAAK,IAAK,SAAS,CAC3B,cAAW,KAAK,CACrB,GAAI,CAEF,GAAM,CAAE,eAAA,UAA0B,cAAc,CAE1C,IADM,cAAc,KAAK,IAAK,eAAe,CACpC,CAAC,KAAK,CACrB,OAAO,IAAI,SAAW,KAAO,UACvB,CACN,UAGJ,OAAO,KAWT,MAAM,uBAAuD,IAAI,IAAqB,CAAC,OAAO,CAAC,CAYzF,iBAAiD,IAAI,IAAqB,CAC9E,WACA,UACA,cACA,YACA,OACA,SACA,kBACA,WACA,OAAO,YACP,OAAO,YACP,OAAO,SACP,OAAO,cACR,CAAC,CAEF,SAAS,qBAAqB,MAAmC,CAE/D,OAAO,IAAI,MAAM,UADU,GACF,CACvB,OAAQ,CACN,GAAI,MAAM,OAAS,EACjB,MAAM,IAAI,kBAAkB,MAAM,IAAM,GAAI,GAAG,CAEjD,GAAM,CAAC,UAAW,GAAG,MAAQ,MAC7B,OAAO,aAAa,UAAW,KAAK,KAAK,IAAI,CAAC,EAEhD,IAAI,EAAG,KAAM,CACP,2BAAuB,IAAI,KAAK,CACpC,IAAI,iBAAiB,IAAI,KAAK,CAAE,OAAO,QAAQ,IAAI,EAAG,KAAK,CACvD,UAAO,MAAS,SACpB,OAAO,qBAAqB,CAAC,GAAG,MAAO,KAAK,CAAC,GAEhD,CAAC,CAGJ,SAAS,kBAA+B,CACtC,OAAO,IAAI,MAAM,EAAE,CAA6B,CAC9C,IAAI,EAAG,KAAM,CACP,2BAAuB,IAAI,KAAK,CACpC,IAAI,iBAAiB,IAAI,KAAK,CAAE,OAAO,QAAQ,IAAI,EAAG,KAAK,CACvD,UAAO,MAAS,SACpB,OAAO,qBAAqB,CAAC,KAAK,CAAC,GAEtC,CAAC,CAaJ,MAAa,OAAqB,kBAAkB,CAkBpD,SAAgB,WAAwB,CACtC,OAAO,OCtXT,MAAM,IAAM,aAAa,YAAY,CAmBrC,SAAS,UACP,OACc,CACd,MAAO,CACL,KAAM,UACN,aAAc,EACd,oBAAqB,UACrB,aAAc,EAAE,CAChB,GAAG,OACJ,CAIH,SAAgB,UAAU,MAAoB,CAG5C,OAFI,OAAO,OAAU,SAAiB,MAAM,UAAU,CAClD,iBAAiB,MAAM,CAAS,MAAM,KACnC,OAAO,MAAQ,OAAO,MAAM,CAsDrC,SAAS,oBAAsC,CAQ7C,OAPM,WAAmB,sBACrB,WAAmB,oBAAsB,CACzC,UAAW,EAAE,CACb,UAAW,EAAE,CACb,kBAAmB,IAAI,IACxB,EAEK,WAAmB,oBAc7B,IAAa,UAAb,MAAa,SAAU,CACrB,OAAe,SACf,cAAwB,IAAI,IAC5B,UAAoB,IAAI,IAGxB,gBAA0B,IAAI,IAC9B,eAAiD,EAAE,CACnD,YAA4D,KAC5D,OAAwB,YAAc,GAGtC,OAAO,SAAoD,KAE3D,OAAO,SAAoD,KAM3D,OAAO,aAA8C,KAOrD,OAAO,sBAEI,KAEX,OAAO,aAAyB,CAS9B,MARA,CACE,UAAU,WAAW,IAAI,UAG3B,AAEE,UAAU,YADV,UAAU,SAAS,UAAU,SAAS,CACjB,MAEhB,UAAU,SAQnB,OAAO,OAAc,CACnB,UAAU,SAAW,IAAI,UAEzB,UAAU,WAAW,UAAU,SAAS,CAExC,IAAM,MAAQ,oBAAoB,CAClC,IAAK,GAAM,CAAE,MAAO,QAAS,SAAW,MAAM,UAC5C,GAAI,CAAC,UAAU,SAAS,IAAI,MAAM,CAAE,CAElC,IAAM,KAAO,UAAU,MAAM,CACvB,SAAW,MAAM,kBAAkB,IAAI,KAAK,CAClD,UAAU,SAAS,cAAc,IAC/B,MACA,UAAU,CACR,OAAQ,OACR,MACA,QACA,KAAM,UACN,SAAU,SACV,aAAc,YACd,oBAAqB,SAAW,YAAc,UAC/C,CAAC,CACH,CAGL,IAAK,GAAM,CAAE,MAAO,YAAc,MAAM,UACjC,UAAU,SAAS,IAAI,MAAM,EAChC,UAAU,SAAS,cAAc,IAC/B,MACA,UAAU,CACR,OAAQ,SAAS,YACjB,MAAA,YACA,SACA,KAAM,WACN,oBAAqB,YACrB,aAAc,EACf,CAAC,CACH,CAgBP,OAAO,QAAoB,CACzB,OAAO,IAAI,UAIb,SAAS,MAAY,OAAqB,MAAA,YAAsC,CAC9E,IAAM,KAAO,aAAwB,SAAS,WAAY,OAAQ,UAAU,CAEtE,IAAM,UAAU,CAAE,OAAQ,MAAO,KAAM,aADxB,KAAK,oBAAoB,OACW,CAAE,CAAC,CAC5D,KAAK,cAAc,IAAI,MAAO,IAAI,CAG9B,OAAO,OAAU,YAAc,MAAM,MACvC,KAAK,cAAc,IAAI,UAAU,MAAM,OAAQ,IAAI,CAErD,KAAK,KAAK,UAAU,MAAM,CAAE,aAAc,KAAK,CAIjD,gBAAgB,MAAY,QAAoB,MAAA,YAAsC,CACpF,IAAM,MAAQ,oBAAoB,CAC5B,KAAO,UAAU,MAAM,CAMvB,iBADgB,MAAM,UAAU,KAAM,GAAM,UAAU,EAAE,MAAM,GAAK,KACnC,EAAE,UAAY,QAC9C,iBAAmB,iBAAmB,MAAM,kBAAkB,IAAI,KAAK,CAAG,IAAA,GAG3E,kBACH,MAAM,kBAAkB,OAAO,KAAK,CAGtC,KAAK,cAAc,IACjB,MACA,UAAU,CACR,OAAQ,OACR,MACA,QACA,KAAM,UACN,SAAU,iBACV,aAAc,oBACd,oBAAqB,iBAAmB,YAAc,UACvD,CAAC,CACH,CAED,IAAM,IAAM,MAAM,UAAU,UAAW,GAAM,UAAU,EAAE,MAAM,GAAK,KAAK,CACrE,KAAO,EACT,MAAM,UAAU,KAAO,CAAE,MAAO,QAAS,MAAO,CAEhD,MAAM,UAAU,KAAK,CAAE,MAAO,QAAS,MAAO,CAAC,CAEjD,KAAK,KAAK,KAAM,aAAc,UAAU,CAI1C,iBAAiB,MAAY,SAAqB,CAChD,IAAM,MAAQ,oBAAoB,CAC5B,KAAO,UAAU,MAAM,CAE7B,KAAK,cAAc,IACjB,MACA,UAAU,CACR,OAAQ,SAAS,YACjB,MAAA,YACA,SACA,KAAM,WACN,oBAAqB,YACrB,aAAc,EACd,gBAAiB,KAAK,KAAK,CAC3B,eAAgB,KAAK,KAAK,CAC3B,CAAC,CACH,CAED,MAAM,kBAAkB,IAAI,KAAM,SAAS,CAC3C,IAAM,IAAM,MAAM,UAAU,UAAW,GAAM,UAAU,EAAE,MAAM,GAAK,KAAK,CACrE,KAAO,EACT,MAAM,UAAU,KAAO,CAAE,MAAO,SAAU,CAE1C,MAAM,UAAU,KAAK,CAAE,MAAO,SAAU,CAAC,CAE3C,KAAK,KAAK,KAAM,aAAc,WAAW,CAI3C,IAAI,MAAqB,CACvB,OAAO,KAAK,cAAc,IAAI,MAAM,CAItC,kBAWG,CACD,IAAM,QAAoE,EAAE,CAC5E,IAAK,GAAM,CAAC,MAAO,OAAQ,KAAK,cAC9B,QAAQ,KAAK,CACX,MAAO,UAAU,MAAM,CACvB,MAAO,IAAI,MACX,KAAM,IAAI,KAEV,aACE,IAAI,QAAA,YAA4B,IAAI,WAAa,IAAA,GAAY,IAAI,aAAe,EAClF,aAAc,IAAI,aAClB,eAAgB,IAAI,eACpB,gBAAiB,IAAI,gBACrB,kBAAmB,IAAI,kBACvB,oBAAqB,IAAI,oBACzB,aAAc,IAAI,aACnB,CAAC,CAEJ,OAAO,QAoBT,QAAiB,MAAe,CAC9B,IAAI,IAAM,KAAK,cAAc,IAAI,MAAM,CAMvC,GAHI,CAAC,KAAO,OAAO,OAAU,YAAc,MAAM,OAC/C,IAAM,KAAK,cAAc,IAAI,UAAU,MAAM,OAAO,EAElD,CAAC,IACH,MAAU,MAAM,yBAAyB,UAAU,MAAM,GAAG,CAG9D,GAAI,IAAI,QAAA,aAA6B,IAAI,WAAa,IAAA,GAIpD,MAHA,KAAI,eACJ,IAAI,eAAiB,KAAK,KAAK,CAC/B,KAAK,KAAK,UAAU,MAAM,CAAE,WAAY,IAAI,KAAK,CAC1C,IAAI,SAIb,GAAI,IAAI,QAAA,UAAyB,CAC/B,GAAI,CAAC,UAAU,sBACb,MAAU,MACR,kCAAkC,UAAU,MAAM,CAAC,0GAEpD,CAEH,IAAM,MAAQ,UAAU,uBAAuB,CAC/C,GAAI,CAAC,MACH,MAAU,MACR,kCAAkC,UAAU,MAAM,CAAC,oCACpD,CAGH,GAAI,MAAM,OAAO,IAAI,MAAM,CAIzB,MAHA,KAAI,eACJ,IAAI,eAAiB,KAAK,KAAK,CAC/B,KAAK,KAAK,UAAU,MAAM,CAAE,WAAY,IAAI,KAAK,CAC1C,MAAM,OAAO,IAAI,MAAM,CAGhC,GAAI,MAAM,UAAU,IAAI,MAAM,CAI5B,MAHA,KAAI,eACJ,IAAI,eAAiB,KAAK,KAAK,CAC/B,KAAK,KAAK,UAAU,MAAM,CAAE,WAAY,IAAI,KAAK,CAC1C,MAAM,UAAU,IAAI,MAAM,CAQnC,GAAI,IAAI,QAAS,CACf,IAAM,MAAQ,YAAY,KAAK,CACzB,SAAW,IAAI,SAAS,CAO9B,MANA,KAAI,kBAAoB,YAAY,KAAK,CAAG,MAC5C,IAAI,eACJ,IAAI,eAAiB,KAAK,KAAK,CAC/B,AAA0B,IAAI,kBAAkB,KAAK,KAAK,CAC1D,MAAM,UAAU,IAAI,MAAO,SAAS,CACpC,KAAK,KAAK,UAAU,MAAM,CAAE,WAAY,IAAI,KAAK,CAC1C,SAGT,IAAM,MAAQ,YAAY,KAAK,CACzB,SAAW,KAAK,eAAe,IAAI,CAOzC,MANA,KAAI,kBAAoB,YAAY,KAAK,CAAG,MAC5C,IAAI,eACJ,IAAI,eAAiB,KAAK,KAAK,CAC/B,AAA0B,IAAI,kBAAkB,KAAK,KAAK,CAC1D,MAAM,UAAU,IAAI,MAAO,SAAS,CACpC,KAAK,KAAK,UAAU,MAAM,CAAE,WAAY,IAAI,KAAK,CAC1C,SAGT,GAAI,IAAI,QAAS,CACf,IAAM,MAAQ,YAAY,KAAK,CACzB,SAAW,IAAI,SAAS,CAW9B,MAVA,KAAI,kBAAoB,YAAY,KAAK,CAAG,MAC5C,IAAI,eACJ,IAAI,eAAiB,KAAK,KAAK,CAC/B,AAA0B,IAAI,kBAAkB,KAAK,KAAK,CACtD,IAAI,QAAA,cACN,IAAI,SAAW,SAEf,oBAAoB,CAAC,kBAAkB,IAAI,UAAU,MAAM,CAAE,SAAS,EAExE,KAAK,KAAK,UAAU,MAAM,CAAE,WAAY,IAAI,KAAK,CAC1C,SAGT,GAAI,KAAK,UAAU,IAAI,MAAM,CAAE,CAC7B,IAAM,MAAQ,CAAC,GAAG,KAAK,UAAU,CAAC,IAAI,UAAU,CAEhD,MADA,MAAM,KAAK,UAAU,MAAM,CAAC,CAClB,MAAM,iCAAiC,MAAM,KAAK,OAAO,GAAG,CAExE,KAAK,UAAU,IAAI,MAAM,CAEzB,GAAI,CACF,IAAM,MAAQ,YAAY,KAAK,CACzB,SAAW,KAAK,eAAe,IAAI,CASzC,MARA,KAAI,kBAAoB,YAAY,KAAK,CAAG,MAC5C,IAAI,eACJ,IAAI,eAAiB,KAAK,KAAK,CAC/B,AAA0B,IAAI,kBAAkB,KAAK,KAAK,CACtD,IAAI,QAAA,cACN,IAAI,SAAW,UAEjB,KAAK,KAAK,UAAU,MAAM,CAAE,WAAY,IAAI,KAAK,CAC1C,gBACC,CACR,KAAK,UAAU,OAAO,MAAM,EAKhC,WAAkB,EA+BlB,SAAS,SAA+C,CAEtD,OADA,KAAK,gBAAgB,IAAI,SAAS,KACrB,KAAK,gBAAgB,OAAO,SAAS,CAWpD,WAAW,MAAkB,CAC3B,IAAM,KAAO,UAAU,MAAM,CAEvB,IAAM,KAAK,cAAc,IAAI,MAAM,EAAI,KAAK,cAAc,IAAI,UAAU,OAAO,CAChF,QAGS,qBACL,CAAC,kBAAkB,IAAI,KAAK,CAMrC,CAHA,IAAI,SAAW,IAAA,GACf,IAAI,aAAe,EACnB,IAAI,oBAAsB,UAC1B,KAAK,KAAK,KAAM,cAAe,IAAI,KAAK,CAGxC,IAAK,GAAM,CAAC,SAAU,UAAW,KAAK,cAChC,OAAO,aAAa,SAAS,KAAK,EACpC,KAAK,WAAW,SAAS,EAM/B,KAAa,MAAe,MAAsC,KAAwB,CAQxF,IAAM,SAAW,KAAK,eAAe,KAAM,GAAM,EAAE,QAAU,OAAS,EAAE,QAAU,MAAM,CACpF,SACF,SAAS,UAAY,KAAK,KAAK,CAE/B,KAAK,eAAe,KAAK,CAAE,MAAO,MAAO,KAAM,UAAW,KAAK,KAAK,CAAE,CAAC,CAGrE,KAAK,aAAa,aAAa,KAAK,YAAY,CACpD,KAAK,YAAc,eAAiB,CAClC,IAAM,MAAQ,KAAK,eAAe,OAAO,EAAE,CACvC,SAAM,SAAW,EACrB,IAAK,IAAM,YAAY,KAAK,gBAC1B,GAAI,CACF,SAAS,MAAM,MACT,IAIT,UAAU,YAAY,CAI3B,cAAqB,CACnB,AAEE,KAAK,eADL,aAAa,KAAK,YAAY,CACX,MAErB,IAAM,MAAQ,KAAK,eAAe,OAAO,EAAE,CACvC,SAAM,SAAW,EACrB,IAAK,IAAM,YAAY,KAAK,gBAC1B,GAAI,CACF,SAAS,MAAM,MACT,GAMZ,eAAuB,IAAwB,CAG7C,IAAM,KAFa,aAA4B,SAAS,YAAa,IAAI,OAAQ,EAAE,CAE5D,CAAC,KAAK,UAAW,QAAU,CAGhD,IAAM,MADe,cAAmB,SAAS,OAAQ,IAAI,OACnC,CAAC,QAAU,UAErC,GAAI,IAAI,QAAA,YAA2B,CACjC,IAAM,OAAS,KAAK,cAAc,IAAI,MAAM,CAC5C,GAAI,QAAU,OAAO,QAAA,UACnB,MAAU,MACR,iCAAiC,UAAU,MAAM,CAAC,oBAAoB,UAAU,IAAI,OAAO,CAAC,gFAE7F,CAGL,OAAO,KAAK,QAAQ,MAAM,EAC1B,CAEI,SAAW,IAAI,IAAI,OAAO,GAAG,KAAK,CAGxC,KAAK,iBAAiB,SAAU,IAAI,OAAO,CAG3C,IAAM,cAAgB,wBACpB,SAAS,eACT,IAAI,OAAO,UACZ,CACD,GAAI,eAAiB,OAAO,SAAS,gBAAmB,WACtD,GAAI,CACF,IAAM,OAAS,SAAS,gBAAgB,CACpC,QAAU,OAAO,OAAO,MAAS,YACnC,IAAI,oBAAsB,UACxB,OACC,SAAW,CACV,IAAI,oBAAsB,aAC1B,CACD,UAAY,CACX,IAAI,oBAAsB,UAC1B,CACJ,IAAI,KACF,qBAAqB,UAAU,IAAI,OAAO,CAAC,GAAG,OAAO,cAAc,CAAC,kGAErE,EAED,IAAI,oBAAsB,kBAErB,IAAK,CACZ,IAAI,oBAAsB,SAC1B,IAAI,MACF,IACA,qBAAqB,UAAU,IAAI,OAAO,CAAC,GAAG,OAAO,cAAc,CAAC,mBACrE,CAIL,OAAO,SAaT,oBAA4B,OAA+B,CACzD,IAAM,WAAa,aAA4B,SAAS,YAAa,OAAQ,EAAE,CAAC,CAC1E,aAAe,cAAmB,SAAS,OAAQ,OAAO,CAC1D,SAAW,WAAW,KAAK,KAAM,QAE9B,UADO,aAAa,QAAU,KACd,CACvB,CAKI,eAAiB,WAAwB,SAAS,UAAW,OAAO,UAAU,CAC9E,SAAqB,EAAE,CAC7B,IAAK,GAAM,CAAC,KAAM,SAAU,eAAgB,CAC1C,IAAM,cACJ,OAAS,yBAAyB,SAAS,cAAe,OAAO,UAAW,KAAK,CAC/E,eAAe,SAAS,KAAK,UAAU,cAAc,CAAC,CAI5D,MAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,SAAU,GAAG,SAAS,CAAC,CAAC,CAGjD,iBAAyB,SAAe,OAA2B,CAEjE,IAAM,eAAiB,WAAwB,SAAS,UAAW,OAAO,UAAU,CAEpF,IAAK,GAAM,CAAC,KAAM,SAAU,eAAgB,CAC1C,IAAM,cACJ,OAAS,yBAAyB,SAAS,cAAe,OAAO,UAAW,KAAK,CAC/E,eACF,OAAO,eAAe,SAAU,KAAM,CACpC,QAAW,KAAK,QAAQ,cAAc,CACtC,WAAY,GACZ,aAAc,GACf,CAAC,CAKN,IAAM,WAAa,WACjB,SAAS,MACT,OAAO,UACR,CAED,IAAK,GAAM,CAAC,KAAM,UAAW,WAC3B,OAAO,eAAe,SAAU,KAAM,CACpC,KAAM,CAIJ,GAAI,UAAU,aAAc,CAC1B,IAAM,IAAM,UAAU,aAAa,OAAO,OAAO,CACjD,GAAI,MAAQ,IAAA,GAAW,OAAO,IAIhC,IAAM,IAAM,QAAQ,IAAI,OAAO,QAC/B,GAAI,MAAQ,IAAA,GAAW,OAAO,IAC9B,GAAI,OAAO,eAAiB,IAAA,GAAW,OAAO,OAAO,aACrD,MAAU,MACR,WAAW,OAAO,OAAO,4BAA4B,OAAO,OAAO,2CACpE,EAEH,WAAY,GACZ,aAAc,GACf,CAAC,CAQJ,IAAM,WAAa,WAAyC,SAAS,MAAO,OAAO,UAAU,CAE7F,GAAI,WAAW,KAAO,EACpB,IAAK,GAAM,CAAC,KAAM,UAAW,WAAY,CACvC,IAAM,SAAW,OAAO,SAAS,QAAQ,IAAI,CAC7C,GAAI,WAAa,GACf,MAAU,MACR,WAAW,OAAO,SAAS,oEAC5B,CAEH,IAAM,UAAY,OAAO,SAAS,MAAM,EAAG,SAAS,CAC9C,IAAM,OAAO,SAAS,MAAM,SAAW,EAAE,CAC/C,OAAO,eAAe,SAAU,KAAM,CACpC,KAAM,CACJ,OAAO,aAAa,UAAW,IAAI,EAErC,WAAY,GACZ,aAAc,GACf,CAAC"}
|
|
1
|
+
{"version":3,"file":"container-COmj1vM3.mjs","names":[],"sources":["../src/core/token.ts","../src/core/asset-keys.ts","../src/core/assets.ts","../src/core/container.ts"],"sourcesContent":["/**\n * Collision-safe injection tokens with phantom type parameters.\n *\n * `createToken<T>(name)` is the recommended way to declare any DI token\n * that isn't a class. Each call returns a unique frozen object — even if\n * two files write `createToken<X>('Logger')`, they produce two distinct\n * references that the container treats as different tokens. Collisions\n * become impossible by construction, not by convention.\n *\n * The phantom type parameter `T` lets `container.resolve(token)` infer\n * the correct return type without any codegen.\n *\n * ## Hardening layers\n *\n * KickJS has four DI token kinds, listed from safest to riskiest:\n *\n * 1. **Class identity** — `container.resolve(UserService)`. Reference\n * equality, never collides. Type-safe via `Constructor<T>` overload.\n * 2. **`createToken<T>(name)`** — frozen object identity, never collides.\n * Type-safe via the `InjectionToken<T>` overload.\n * 3. **Symbol** — `Symbol('foo')` is unique per call but `Symbol.for('foo')`\n * is interned and CAN collide. Discouraged.\n * 4. **Raw string `@Inject('foo')`** — high collision risk, untyped.\n * Reserved for legacy code and runtime-computed token names.\n *\n * Prefer (1) and (2). Use (4) only when you have to.\n *\n * @example\n * ```ts\n * // src/tokens.ts\n * export const DATABASE_URL = createToken<string>('config.database.url')\n * export const FEATURE_FLAGS = createToken<FeatureFlags>('app.features')\n *\n * // bootstrap\n * container.registerInstance(DATABASE_URL, process.env.DATABASE_URL!)\n *\n * // anywhere else\n * const url = container.resolve(DATABASE_URL) // typed as `string`\n * ```\n *\n * @module @forinda/kickjs/core/token\n */\n\n/**\n * Symbol marker stamped on every InjectionToken so the container can\n * detect them at runtime regardless of which package created them.\n *\n * Uses `Symbol.for()` so cross-package isolation (different copies of\n * `@forinda/kickjs` loaded under different module identities) still\n * shares the same marker.\n */\nexport const INJECTION_TOKEN: unique symbol = Symbol.for('@kickjs/InjectionToken') as never\n\n/**\n * A type-safe DI token. Each `createToken()` call returns a unique frozen\n * object identified by reference, not by the `name` string. Two\n * `createToken<X>('foo')` calls in different files produce two distinct\n * tokens that the container treats independently.\n *\n * The phantom `_type` parameter is never assigned at runtime — it exists\n * solely to thread the type through `container.resolve(token)`.\n */\nexport interface InjectionToken<T> {\n readonly [INJECTION_TOKEN]: true\n /** Descriptive name used in error messages, logs, and devtools */\n readonly name: string\n /** Phantom — never assigned at runtime */\n readonly _type?: T\n}\n\n/**\n * Create a collision-safe injection token.\n *\n * The returned token is a frozen object identified by reference, not by\n * the `name` string. The `name` is used only for error messages, logs,\n * and debugging.\n */\nexport function createToken<T>(name: string): InjectionToken<T> {\n return Object.freeze({\n [INJECTION_TOKEN]: true as const,\n name,\n })\n}\n\n/** Type guard — returns `true` if `value` is an InjectionToken */\nexport function isInjectionToken(value: unknown): value is InjectionToken<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[INJECTION_TOKEN] === true\n )\n}\n","// Shared asset-key strategy — used by the build pipeline, the\n// runtime dev resolver, and the typegen emitter so all three layers\n// agree on what the manifest looks like.\n//\n// Three strategies:\n//\n// - `'strip'` — drop the final extension. `pages/index.pug` →\n// `pages/index`. Best for namespaces where every basename is\n// unique (mail templates, pdf invoices). Shortest autocomplete.\n//\n// - `'with-extension'` — keep the extension. `pages/index.pug` →\n// `pages/index.pug`. Required when extension siblings coexist.\n//\n// - `'auto'` (default) — strip when safe, keep extensions when not.\n// The grouping pass collects files by stripped basename; groups\n// of one keep the stripped key, groups of more than one keep the\n// full path. Backward compatible for projects with no\n// extension-siblings; lossless for projects that have them.\n//\n// The function returns the path / key pairs in walk order (input\n// order preserved) so callers can iterate without re-sorting. Sort\n// in the caller if a stable manifest order is needed.\n\nexport type AssetKeyStrategy = 'auto' | 'strip' | 'with-extension'\n\nexport interface AssetKeyPair {\n /** Source-relative path the file was discovered at. */\n rel: string\n /** Final logical key for the manifest, including the namespace prefix. */\n key: string\n}\n\nexport interface GroupAssetKeysOptions {\n /**\n * Strategy applied to this namespace. Default `'auto'`.\n *\n * Adopters override per-namespace via `assetMap.<ns>.keys` in\n * kick.config.ts when they want uniform behaviour (e.g. always\n * include the extension regardless of collisions).\n */\n strategy?: AssetKeyStrategy\n}\n\nexport interface GroupAssetKeysResult {\n pairs: AssetKeyPair[]\n /**\n * Number of collision groups resolved by keeping extensions.\n * Surfaces as an informational log (\"auto-resolved N collisions\")\n * so adopters know whether they're relying on auto-mode or not.\n * Always 0 for `'strip'` (overwrites) and `'with-extension'`\n * (extensions always preserved, no collision possible).\n */\n collisionGroupsResolved: number\n}\n\n/**\n * Compute final manifest keys for a namespace's discovered paths,\n * given the chosen strategy. Pure function — no fs / config / globals\n * involved.\n *\n * @param namespace assetMap key — e.g. `'mails'`, `'pages'`.\n * @param paths source-relative file paths (POSIX separators) discovered\n * under the namespace's `src` directory.\n * @param opts grouping options.\n */\nexport function groupAssetKeys(\n namespace: string,\n paths: readonly string[],\n opts: GroupAssetKeysOptions = {},\n): GroupAssetKeysResult {\n const strategy = opts.strategy ?? 'auto'\n\n if (strategy === 'with-extension') {\n return {\n pairs: paths.map((rel) => ({ rel, key: `${namespace}/${rel}` })),\n collisionGroupsResolved: 0,\n }\n }\n\n if (strategy === 'strip') {\n // Strict strip — last-walk-order wins on collision; no\n // collision tracking here because the docs document this as the\n // adopter's choice. The caller (build / runtime / typegen) can\n // still warn if it wants; this function just shapes keys.\n return {\n pairs: paths.map((rel) => ({ rel, key: `${namespace}/${stripExt(rel)}` })),\n collisionGroupsResolved: 0,\n }\n }\n\n // strategy === 'auto' — group by stripped basename, keep\n // extensions only on collision groups.\n const groups = new Map<string, string[]>()\n for (const rel of paths) {\n const strippedKey = `${namespace}/${stripExt(rel)}`\n const arr = groups.get(strippedKey)\n if (arr) {\n arr.push(rel)\n } else {\n groups.set(strippedKey, [rel])\n }\n }\n\n let collisionGroupsResolved = 0\n // Build a per-rel decision table so we preserve input ordering in\n // the output (manifest writers rely on stable ordering).\n const keyByRel = new Map<string, string>()\n for (const [strippedKey, rels] of groups) {\n if (rels.length === 1) {\n keyByRel.set(rels[0], strippedKey)\n } else {\n collisionGroupsResolved += 1\n for (const rel of rels) {\n keyByRel.set(rel, `${namespace}/${rel}`)\n }\n }\n }\n\n return {\n pairs: paths.map((rel) => ({\n rel,\n key: keyByRel.get(rel) ?? `${namespace}/${rel}`,\n })),\n collisionGroupsResolved,\n }\n}\n\n/** Strip the final extension from a path. `'mails/welcome.ejs'` → `'mails/welcome'`. */\nexport function stripExt(path: string): string {\n // Inline the logic instead of importing extname so this module\n // stays usable in pure-browser contexts. Same algorithm: find the\n // last dot AFTER the last separator, slice up to it.\n const lastSlash = path.lastIndexOf('/')\n const filename = lastSlash >= 0 ? path.slice(lastSlash + 1) : path\n const lastDot = filename.lastIndexOf('.')\n if (lastDot <= 0) return path // no ext, or hidden file like `.env`\n const dirPart = lastSlash >= 0 ? path.slice(0, lastSlash + 1) : ''\n return dirPart + filename.slice(0, lastDot)\n}\n","/**\n * Asset manager runtime (assets-plan.md PR 3).\n *\n * Three accessor surfaces over one resolver engine:\n *\n * - `assets.x.y()` — Proxy ambient, autocomplete-friendly default.\n * - `useAssets()` — hook returning the same Proxy; mockable per-call.\n * - `resolveAsset(ns, k)` — string resolver for dynamic dispatch.\n *\n * Resolution path:\n *\n * 1. Load the build-time manifest (`<outDir>/.kickjs-assets.json`)\n * when present. Manifest existence = \"we're running from a built\n * dist\". Fast path; mapped paths returned verbatim.\n * 2. Otherwise (dev / test) walk the assetMap src directories from\n * `kick.config.{json,js,cjs}` and synthesise the same manifest\n * shape in memory. First call pays the walk cost; subsequent\n * calls hit the cache.\n * 3. `KICK_ASSETS_ROOT` env var overrides everything — the resolver\n * treats the path as the manifest directory and skips discovery.\n *\n * @module @forinda/kickjs/core/assets\n */\n\nimport { existsSync, readFileSync, readdirSync } from 'node:fs'\nimport { isAbsolute, join, resolve, sep } from 'node:path'\nimport { createToken, type InjectionToken } from './token'\nimport { groupAssetKeys } from './asset-keys'\n\nexport const ASSET_MANIFEST_VERSION = 1 as const\n\ninterface AssetManifest {\n version: typeof ASSET_MANIFEST_VERSION\n entries: Record<string, string>\n}\n\ninterface ResolvedManifest {\n manifest: AssetManifest\n root: string\n}\n\n/**\n * Augmentable interface — `kick typegen` (assets-plan.md PR 4) emits\n * a `declare global` augmentation listing every namespace + key for\n * the project's `assetMap`. Adopters who haven't run typegen yet\n * still get a runtime-correct Proxy; only typed autocomplete waits\n * on the augmentation.\n */\nexport interface KickAssets {}\n\n/**\n * DI token for the `assets` Proxy. Adopter code that wants to\n * receive assets via constructor injection registers via\n * `container.registerInstance(ASSETS, useAssets())` in `bootstrap()`\n * and injects with `@Inject(ASSETS)`.\n */\nexport const ASSETS: InjectionToken<KickAssets> = createToken<KickAssets>('kick/assets/Map')\n\nexport class UnknownAssetError extends Error {\n readonly namespace: string\n readonly key: string\n constructor(namespace: string, key: string) {\n super(\n `Unknown asset '${namespace}/${key}'. ` +\n `Make sure the namespace exists in kick.config.ts assetMap and that the file is present in the source directory.`,\n )\n this.name = 'UnknownAssetError'\n this.namespace = namespace\n this.key = key\n }\n}\n\nlet manifestCache: ResolvedManifest | null | undefined = undefined\n\nexport function resolveAsset(namespace: string, key: string): string {\n const resolved = loadManifest()\n if (!resolved) {\n throw new UnknownAssetError(namespace, key)\n }\n const entry = resolved.manifest.entries[`${namespace}/${key}`]\n if (!entry) {\n throw new UnknownAssetError(namespace, key)\n }\n return isAbsolute(entry) ? entry : resolve(resolved.root, entry)\n}\n\n/** Reset the manifest cache. Tests use this; production code shouldn't need it. */\nexport function clearAssetCache(): void {\n manifestCache = undefined\n}\n\nfunction loadManifest(): ResolvedManifest | null {\n // In development we deliberately skip the cache so adding or removing\n // a template file (e.g. dropping `welcome-back.ejs` into\n // `src/templates/mails/`) takes effect without a server restart. The\n // synthesise-dev-manifest path walks ~10 files; the cost is negligible\n // compared to the user-visible surprise of \"I added a file but\n // assets.x.y() throws\". Production keeps the cache because the built\n // manifest under `dist/.kickjs-assets.json` doesn't change at runtime.\n if (process.env.NODE_ENV !== 'production') {\n return discoverManifest()\n }\n if (manifestCache !== undefined) return manifestCache\n manifestCache = discoverManifest()\n return manifestCache\n}\n\nfunction discoverManifest(): ResolvedManifest | null {\n const envRoot = process.env.KICK_ASSETS_ROOT\n if (envRoot) {\n const fromEnv = readBuiltManifest(envRoot)\n if (fromEnv) return fromEnv\n }\n\n const cwd = process.cwd()\n\n // Honour `kick.config.build.outDir` BEFORE the standard probe list\n // so adopters with non-default Vite outputs (e.g. `output/`,\n // `target/`, anything else) don't have to set KICK_ASSETS_ROOT\n // every time. Cached config load — same loader the dev fallback\n // uses. Skip when outDir matches one of the standard candidates;\n // it'd be probed redundantly otherwise.\n const config = loadConfigSync(cwd)\n const configOutDir = config?.build?.outDir\n if (configOutDir && !(STANDARD_OUT_DIRS as readonly string[]).includes(configOutDir)) {\n const found = readBuiltManifest(resolve(cwd, configOutDir))\n if (found) return found\n }\n\n for (const candidate of STANDARD_OUT_DIRS) {\n const found = readBuiltManifest(join(cwd, candidate))\n if (found) return found\n }\n\n return synthesiseDevManifest(cwd, config)\n}\n\nconst STANDARD_OUT_DIRS = ['dist', 'build', 'out'] as const\n\nfunction readBuiltManifest(dir: string): ResolvedManifest | null {\n const path = join(dir, '.kickjs-assets.json')\n if (!existsSync(path)) return null\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw) as Partial<AssetManifest>\n if (parsed.version !== ASSET_MANIFEST_VERSION) return null\n if (!parsed.entries || typeof parsed.entries !== 'object') return null\n return { manifest: parsed as AssetManifest, root: resolve(dir) }\n } catch {\n return null\n }\n}\n\ninterface AssetMapConfigShape {\n assetMap?: Record<\n string,\n {\n src: string\n dest?: string\n glob?: string\n keys?: 'auto' | 'strip' | 'with-extension'\n }\n >\n build?: { outDir?: string }\n}\n\nfunction synthesiseDevManifest(\n cwd: string,\n preloadedConfig?: AssetMapConfigShape | null,\n): ResolvedManifest | null {\n const config = preloadedConfig === undefined ? loadConfigSync(cwd) : preloadedConfig\n if (!config?.assetMap) return null\n\n const entries: Record<string, string> = {}\n // Walk order is filesystem-dependent on Linux/macOS, so we collect\n // every match per-namespace first, then sort + feed `groupAssetKeys`\n // for stable manifest output. Build pipeline does the same — both\n // layers agree on the resulting key set.\n for (const [namespace, entry] of Object.entries(config.assetMap)) {\n if (!entry || typeof entry.src !== 'string') continue\n const srcAbs = resolve(cwd, entry.src)\n if (!existsSync(srcAbs)) continue\n\n const collected: Array<{ rel: string; abs: string }> = []\n walkSync(srcAbs, srcAbs, (relPath, absPath) => {\n if (entry.glob && !matchesGlobLite(relPath, entry.glob, namespace)) return\n collected.push({ rel: relPath, abs: absPath })\n })\n collected.sort((a, b) => a.rel.localeCompare(b.rel))\n\n const { pairs, collisionGroupsResolved } = groupAssetKeys(\n namespace,\n collected.map((c) => c.rel),\n { strategy: entry.keys ?? 'auto' },\n )\n const absByRel = new Map(collected.map((c) => [c.rel, c.abs]))\n for (const { rel, key } of pairs) {\n const abs = absByRel.get(rel)\n if (abs) entries[key] = abs\n }\n if (collisionGroupsResolved > 0 && process.env.NODE_ENV !== 'production') {\n // Dev-only nudge — production runs read the build manifest where\n // the collision was already resolved at build time.\n console.log(\n `[kickjs/assets] '${namespace}': ${collisionGroupsResolved} basename collision(s) auto-resolved by keeping extensions ` +\n `(set assetMap.${namespace}.keys to 'strip' or 'with-extension' to override).`,\n )\n }\n }\n\n return { manifest: { version: ASSET_MANIFEST_VERSION, entries }, root: cwd }\n}\n\nconst warnedGlobs = new Set<string>()\n\n/**\n * Tiny glob matcher used only by dev manifest synthesis. Covers the\n * common assetMap glob patterns (`**\\/*`, `**\\/*.ext`,\n * `**\\/*.{a,b}`). Anything exotic prints a one-time warning + falls\n * through to `**\\/*` semantics rather than silently dropping files.\n *\n * The build pipeline (`packages/cli/src/asset-manager/build.ts`) uses\n * the full `glob` package; adopters who hit the dev-mode warning\n * should run `kick build:assets` to produce a real manifest.\n */\nfunction matchesGlobLite(relPath: string, pattern: string, namespace: string): boolean {\n if (pattern === '**/*' || pattern === '**') return true\n const singleExt = /^\\*\\*\\/\\*\\.(\\w+)$/.exec(pattern)\n if (singleExt) return relPath.endsWith(`.${singleExt[1]}`)\n const braceExt = /^\\*\\*\\/\\*\\.\\{([^}]+)\\}$/.exec(pattern)\n if (braceExt) {\n const exts = braceExt[1].split(',').map((e) => e.trim())\n return exts.some((ext) => relPath.endsWith(`.${ext}`))\n }\n const tag = `${namespace}::${pattern}`\n if (!warnedGlobs.has(tag)) {\n warnedGlobs.add(tag)\n console.warn(\n `[kickjs/assets] Dev-mode glob '${pattern}' on '${namespace}' isn't recognised by the lite matcher. ` +\n `Falling back to '**/*'. Run 'kick build:assets' to use the full glob engine.`,\n )\n }\n return true\n}\n\nfunction walkSync(root: string, dir: string, onFile: (rel: string, abs: string) => void): void {\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n const full = join(dir, entry.name)\n if (entry.isDirectory()) {\n walkSync(root, full, onFile)\n } else if (entry.isFile()) {\n const rel = full\n .slice(root.length + 1)\n .split(sep)\n .join('/')\n onFile(rel, full)\n }\n }\n}\n\n/**\n * Sync `kick.config.{json,cjs,js}` loader for dev manifest\n * synthesis. Avoids dynamic `import()` (would force every assets\n * lookup to be async). TypeScript configs (`kick.config.ts`) are\n * skipped here — adopters who use them should run `kick build:assets`\n * to produce a real manifest the resolver can read.\n */\nfunction loadConfigSync(cwd: string): AssetMapConfigShape | null {\n const jsonPath = join(cwd, 'kick.config.json')\n if (existsSync(jsonPath)) {\n try {\n return JSON.parse(readFileSync(jsonPath, 'utf-8')) as AssetMapConfigShape\n } catch {\n return null\n }\n }\n for (const filename of ['kick.config.cjs', 'kick.config.js']) {\n const path = join(cwd, filename)\n if (!existsSync(path)) continue\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { createRequire } = require('node:module') as typeof import('node:module')\n const req = createRequire(join(cwd, 'package.json'))\n const mod = req(path) as { default?: AssetMapConfigShape } & AssetMapConfigShape\n return mod.default ?? mod ?? null\n } catch {\n continue\n }\n }\n return null\n}\n\n/**\n * Keys we MUST return `undefined` for: returning anything truthy\n * here causes Promise resolution to treat the Proxy as a thenable\n * and `await` it (resolving to whatever `then.call(...)` produces).\n * Two-element list deliberately — every other coercion-related key\n * goes through {@link PASSTHROUGH_KEYS} below so `String(assets)`,\n * template literals, and `util.inspect` keep working.\n */\nconst PROMISE_DETECTION_KEYS: ReadonlySet<string | symbol> = new Set<string | symbol>(['then'])\n\n/**\n * Keys we let fall through to the proxy target (a function-shaped\n * object). The default `Object.prototype` implementations of\n * `toString` / `valueOf` etc. are correct for these — returning\n * `undefined` here would break `String(assets)` and template\n * literals (`${assets}`) because `Symbol.toPrimitive` falling through\n * to a missing `toString` throws. Adopters that hit `assets.toString`\n * get `'function () {}'` back, which is fine — the contract is\n * \"asset access returns paths\"; everything else is incidental.\n */\nconst PASSTHROUGH_KEYS: ReadonlySet<string | symbol> = new Set<string | symbol>([\n 'toString',\n 'valueOf',\n 'constructor',\n 'prototype',\n 'name',\n 'length',\n 'asymmetricMatch',\n 'nodeType',\n Symbol.toPrimitive,\n Symbol.toStringTag,\n Symbol.iterator,\n Symbol.asyncIterator,\n])\n\nfunction createNamespaceProxy(parts: readonly string[]): unknown {\n const target = function () {} as unknown as Record<string, unknown>\n return new Proxy(target, {\n apply() {\n if (parts.length < 2) {\n throw new UnknownAssetError(parts[0] ?? '', '')\n }\n const [namespace, ...rest] = parts\n return resolveAsset(namespace, rest.join('/'))\n },\n get(t, prop) {\n if (PROMISE_DETECTION_KEYS.has(prop)) return undefined\n if (PASSTHROUGH_KEYS.has(prop)) return Reflect.get(t, prop)\n if (typeof prop === 'symbol') return undefined\n return createNamespaceProxy([...parts, prop])\n },\n })\n}\n\nfunction createAssetProxy(): KickAssets {\n return new Proxy({} as Record<string, unknown>, {\n get(t, prop) {\n if (PROMISE_DETECTION_KEYS.has(prop)) return undefined\n if (PASSTHROUGH_KEYS.has(prop)) return Reflect.get(t, prop)\n if (typeof prop === 'symbol') return undefined\n return createNamespaceProxy([prop])\n },\n }) as unknown as KickAssets\n}\n\n/**\n * Variant A — the ambient Proxy. Default accessor for static call\n * sites where the namespace + key are literals known at compile time.\n *\n * @example\n * ```ts\n * import { assets } from '@forinda/kickjs'\n * const path = assets.mails.welcome()\n * ```\n */\nexport const assets: KickAssets = createAssetProxy()\n\n/**\n * Variant B — the hook accessor. Returns the same Proxy as `assets`;\n * factory shape exists for testability + DI. Mock via\n * `vi.mock('@forinda/kickjs', () => ({ useAssets: () => fakeAssets }))`.\n *\n * @example\n * ```ts\n * import { useAssets } from '@forinda/kickjs'\n * class MailService {\n * private assets = useAssets()\n * send(name: string) {\n * return ejs.renderFile(this.assets.mails[name](), {})\n * }\n * }\n * ```\n */\nexport function useAssets(): KickAssets {\n return assets\n}\n","import 'reflect-metadata'\nimport {\n Constructor,\n Scope,\n METADATA,\n type ClassKind,\n type PostConstructStatus,\n} from './interfaces'\nimport { resolveAsset } from './assets'\nimport { createLogger } from './logger'\nimport {\n getClassMeta,\n getClassMetaOrUndefined,\n getMetaMap,\n getMetaRecord,\n getMethodMetaOrUndefined,\n} from './metadata'\nimport { isInjectionToken, type InjectionToken } from './token'\n\nconst log = createLogger('Container')\n\n/** Internal registration entry that tracks how a dependency should be resolved */\ninterface Registration {\n target: Constructor\n scope: Scope\n instance?: any\n factory?: () => any\n // Observability fields\n kind: ClassKind\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n}\n\n/** Create a Registration with observability defaults */\nfunction createReg(\n fields: Partial<Registration> & Pick<Registration, 'target' | 'scope'>,\n): Registration {\n return {\n kind: 'unknown',\n resolveCount: 0,\n postConstructStatus: 'skipped',\n dependencies: [],\n ...fields,\n }\n}\n\n/** Format a token for display in error messages */\nexport function tokenName(token: any): string {\n if (typeof token === 'symbol') return token.toString()\n if (isInjectionToken(token)) return token.name\n return token?.name || String(token)\n}\n\n// ── Persistent Store ────────────────────────────────────────────────────\n// globalThis-based storage that survives BOTH Container.reset() AND Vite's\n// ssrLoadModule() re-evaluation. When Vite re-evaluates a module, the\n// Container class gets a new identity (new statics). But globalThis persists.\n//\n// This is how DB connections, Redis clients, WsAdapter instances, and any\n// other stateful bindings survive HMR without being dropped.\n//\n// Pattern from: React Router (globalThis for state), Vinxi (globalThis.app)\n\n// ── Change Event System ─────────────────────────────────────────────────\n// Batched (50ms debounce) so `kick g module` creating 10+ files emits ONE\n// notification, not 10. Any adapter can subscribe: Swagger, DevTools, WS, etc.\n\n/** A single container change event */\nexport interface ContainerChangeEvent {\n token: string\n event: 'registered' | 'resolved' | 'invalidated'\n kind?: ClassKind\n timestamp: number\n}\n\n/** Listener that receives batched change events */\nexport type ContainerChangeListener = (changes: ContainerChangeEvent[]) => void\n\n/**\n * Type-safe registry of string DI tokens. Augmented by `kick typegen` —\n * the generated `.kickjs/types/registry.d.ts` adds entries like:\n *\n * ```ts\n * declare module '@forinda/kickjs' {\n * interface KickJsRegistry {\n * UserService: import('../../src/modules/users/user.service').UserService\n * }\n * }\n * ```\n *\n * Once typegen has run, `container.resolve('UserService')` returns the\n * correct type instead of `any`. Class-token resolution\n * (`container.resolve(UserService)`) is already type-safe via the\n * `Constructor<T>` overload and does not need typegen.\n */\nexport interface KickJsRegistry {}\n\ninterface PersistentStore {\n factories: Array<{ token: any; factory: () => any; scope: Scope }>\n instances: Array<{ token: any; instance: any }>\n /** Resolved instances keyed by string name — survives class identity changes */\n resolvedInstances: Map<string, any>\n}\n\nfunction getPersistentStore(): PersistentStore {\n if (!(globalThis as any).__kickjs_persistent) {\n ;(globalThis as any).__kickjs_persistent = {\n factories: [],\n instances: [],\n resolvedInstances: new Map(),\n } satisfies PersistentStore\n }\n return (globalThis as any).__kickjs_persistent\n}\n\n/**\n * Inversion-of-Control (IoC) container that manages dependency registration,\n * resolution, and lifecycle. Implements the Singleton pattern so all parts of\n * the application share a single container instance.\n *\n * Supports constructor injection, property injection (@Autowired),\n * factory registrations, and lifecycle hooks (@PostConstruct).\n *\n * Persistent state (DB connections, Redis, etc.) is stored on globalThis so\n * it survives both Container.reset() and Vite module re-evaluation.\n */\nexport class Container {\n private static instance: Container\n private registrations = new Map<any, Registration>()\n private resolving = new Set<any>()\n\n // ── Reactive change tracking ──────────────────────────────────────\n private changeListeners = new Set<ContainerChangeListener>()\n private pendingChanges: ContainerChangeEvent[] = []\n private notifyTimer: ReturnType<typeof setTimeout> | null = null\n private static readonly DEBOUNCE_MS = 50\n\n /** Callback set by the decorators module to flush pending registrations */\n static _onReady: ((container: Container) => void) | null = null\n /** Callback invoked on reset so decorators can update their container reference */\n static _onReset: ((container: Container) => void) | null = null\n /**\n * Environment resolver for @Value decorator. Set by the unified\n * `@forinda/kickjs` config layer to return Zod-validated, type-coerced\n * env values instead of raw process.env strings.\n */\n static _envResolver: ((key: string) => any) | null = null\n /**\n * Request store provider for REQUEST-scoped DI. Set by the HTTP layer\n * inside `@forinda/kickjs` to return the current request's\n * AsyncLocalStorage store. Returns { instances: Map, values: Map } or\n * null if outside a request.\n */\n static _requestStoreProvider:\n | (() => { instances: Map<any, any>; values: Map<any, any> } | null)\n | null = null\n\n static getInstance(): Container {\n if (!Container.instance) {\n Container.instance = new Container()\n }\n // Flush any decorator registrations that queued before the container existed\n if (Container._onReady) {\n Container._onReady(Container.instance)\n Container._onReady = null\n }\n return Container.instance\n }\n\n /**\n * Resets the container by replacing the singleton with a fresh instance.\n * Persistent registrations (DB, Redis, etc.) are replayed from globalThis\n * so they survive both reset() and Vite module re-evaluation.\n */\n static reset(): void {\n Container.instance = new Container()\n // Notify decorators so they update their container reference\n Container._onReset?.(Container.instance)\n // Replay persistent registrations from globalThis store\n const store = getPersistentStore()\n for (const { token, factory, scope } of store.factories) {\n if (!Container.instance.has(token)) {\n // Check if we have a previously resolved instance for this token\n const name = tokenName(token)\n const existing = store.resolvedInstances.get(name)\n Container.instance.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existing, // Reuse resolved instance if available\n resolveCount: existing ? 1 : 0,\n postConstructStatus: existing ? 'completed' : 'skipped',\n }),\n )\n }\n }\n for (const { token, instance } of store.instances) {\n if (!Container.instance.has(token)) {\n Container.instance.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n }),\n )\n }\n }\n }\n\n /**\n * Create an isolated container instance (not the global singleton).\n * Useful for concurrent tests that must not share DI state.\n *\n * @example\n * ```ts\n * const container = Container.create()\n * container.register(UserService, UserService)\n * const svc = container.resolve(UserService)\n * ```\n */\n static create(): Container {\n return new Container()\n }\n\n /** Register a class constructor under the given token */\n register(token: any, target: Constructor, scope: Scope = Scope.SINGLETON): void {\n const kind = getClassMeta<ClassKind>(METADATA.CLASS_KIND, target, 'unknown')\n const dependencies = this.extractDependencies(target)\n const reg = createReg({ target, scope, kind, dependencies })\n this.registrations.set(token, reg)\n // Store a name-based fallback so HMR class re-creation (new identity)\n // can still resolve by the original class name.\n if (typeof token === 'function' && token.name) {\n this.registrations.set(`__hmr__${token.name}`, reg)\n }\n this.emit(tokenName(token), 'registered', kind)\n }\n\n /** Register a factory function under the given token */\n registerFactory(token: any, factory: () => any, scope: Scope = Scope.SINGLETON): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n // Check if we already have a resolved instance from a previous HMR cycle.\n // Only reuse if the factory hasn't changed (same reference). When a new\n // factory is provided (e.g. swapping implementations), clear the stale\n // instance so the new factory runs on next resolve.\n const existingEntry = store.factories.find((r) => tokenName(r.token) === name)\n const factoryUnchanged = existingEntry?.factory === factory\n const existingInstance = factoryUnchanged ? store.resolvedInstances.get(name) : undefined\n\n // Clear stale resolved instance when factory changes\n if (!factoryUnchanged) {\n store.resolvedInstances.delete(name)\n }\n\n this.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existingInstance,\n resolveCount: existingInstance ? 1 : 0,\n postConstructStatus: existingInstance ? 'completed' : 'skipped',\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation\n const idx = store.factories.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.factories[idx] = { token, factory, scope }\n } else {\n store.factories.push({ token, factory, scope })\n }\n this.emit(name, 'registered', 'factory')\n }\n\n /** Register a pre-constructed singleton instance */\n registerInstance(token: any, instance: any): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n\n this.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n firstResolvedAt: Date.now(),\n lastResolvedAt: Date.now(),\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation\n store.resolvedInstances.set(name, instance)\n const idx = store.instances.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.instances[idx] = { token, instance }\n } else {\n store.instances.push({ token, instance })\n }\n this.emit(name, 'registered', 'instance')\n }\n\n /** Check if a binding exists for the given token */\n has(token: any): boolean {\n return this.registrations.has(token)\n }\n\n /** Return a snapshot of all registered tokens with their scope and instantiation status */\n getRegistrations(): Array<{\n token: string\n scope: string\n kind: ClassKind\n instantiated: boolean\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n }> {\n const entries: Array<ReturnType<Container['getRegistrations']>[number]> = []\n for (const [token, reg] of this.registrations) {\n entries.push({\n token: tokenName(token),\n scope: reg.scope,\n kind: reg.kind,\n // Singletons check cached instance; transients check resolveCount\n instantiated:\n reg.scope === Scope.SINGLETON ? reg.instance !== undefined : reg.resolveCount > 0,\n resolveCount: reg.resolveCount,\n lastResolvedAt: reg.lastResolvedAt,\n firstResolvedAt: reg.firstResolvedAt,\n resolveDurationMs: reg.resolveDurationMs,\n postConstructStatus: reg.postConstructStatus,\n dependencies: reg.dependencies,\n })\n }\n return entries\n }\n\n /**\n * Resolve a dependency by its token.\n *\n * Four overloads, in order of preference:\n * - **`InjectionToken<T>`** (from `createToken<T>(name)`) → returns `T`.\n * Collision-safe by construction; no codegen required.\n * - **Class constructor** → returns an instance of that class. Always\n * type-safe; no codegen required.\n * - **String token in `KickJsRegistry`** (populated by `kick typegen`) →\n * returns the augmented type.\n * - **Anything else** → returns `any`. Use this only for legacy code or\n * string tokens that haven't been registered with typegen yet.\n */\n resolve<T>(token: InjectionToken<T>): T\n resolve<T>(token: Constructor<T>): T\n resolve<K extends keyof KickJsRegistry & string>(token: K): KickJsRegistry[K]\n resolve<T = any>(token: any): T\n resolve<T = any>(token: any): T {\n let reg = this.registrations.get(token)\n // HMR fallback: when Vite re-evaluates a module, decorated classes get new\n // identity. Try resolving by class name if the primary token lookup fails.\n if (!reg && typeof token === 'function' && token.name) {\n reg = this.registrations.get(`__hmr__${token.name}`)\n }\n if (!reg) {\n throw new Error(`No binding found for: ${tokenName(token)}`)\n }\n\n if (reg.scope === Scope.SINGLETON && reg.instance !== undefined) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(tokenName(token), 'resolved', reg.kind)\n return reg.instance\n }\n\n // REQUEST scope: one instance per HTTP request, cached in AsyncLocalStorage\n if (reg.scope === Scope.REQUEST) {\n if (!Container._requestStoreProvider) {\n throw new Error(\n `Cannot resolve REQUEST-scoped \"${tokenName(token)}\": no request store provider configured. ` +\n `Ensure requestScopeMiddleware() is in your middleware pipeline.`,\n )\n }\n const store = Container._requestStoreProvider()\n if (!store) {\n throw new Error(\n `Cannot resolve REQUEST-scoped \"${tokenName(token)}\" outside an HTTP request context.`,\n )\n }\n // Check for pre-registered request values (user, tenant, etc.)\n if (store.values.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(tokenName(token), 'resolved', reg.kind)\n return store.values.get(token) as T\n }\n // Check per-request instance cache\n if (store.instances.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(tokenName(token), 'resolved', reg.kind)\n return store.instances.get(token) as T\n }\n // Factory-registered binding: invoke the factory once per request\n // and cache the result alongside class-instantiated REQUEST tokens.\n // Without this branch, registerFactory(token, fn, Scope.REQUEST)\n // silently falls through to createInstance(reg) which `new`s the\n // factory's placeholder `target: Object` and returns an empty {}\n // — the factory function never runs.\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(tokenName(token), 'resolved', reg.kind)\n return instance as T\n }\n // Create instance and cache for this request only\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(tokenName(token), 'resolved', reg.kind)\n return instance as T\n }\n\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n // Persist resolved factory instances so they survive module re-evaluation\n getPersistentStore().resolvedInstances.set(tokenName(token), instance)\n }\n this.emit(tokenName(token), 'resolved', reg.kind)\n return instance\n }\n\n if (this.resolving.has(token)) {\n const chain = [...this.resolving].map(tokenName)\n chain.push(tokenName(token))\n throw new Error(`Circular dependency detected: ${chain.join(' -> ')}`)\n }\n this.resolving.add(token)\n\n try {\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n }\n this.emit(tokenName(token), 'resolved', reg.kind)\n return instance\n } finally {\n this.resolving.delete(token)\n }\n }\n\n /** Lifecycle hook called during Application.setup() after module registration */\n bootstrap(): void {\n // Reserved for future use — adapters and modules register via\n // container.register(), registerFactory(), and registerInstance().\n }\n\n // ── Reactive Change API ─────────────────────────────────────────────\n\n /**\n * Subscribe to container changes. Returns an unsubscribe function.\n *\n * Changes are batched (50ms debounce) so bulk operations like\n * `kick g module` creating 10+ files emit ONE batch, not 10 events.\n *\n * Any adapter can subscribe:\n * - SwaggerAdapter: re-build spec when controllers change\n * - DevToolsAdapter: push SSE events to dashboard\n * - WsAdapter: re-discover namespaces when controllers change\n * - OtelAdapter: emit tracing spans for DI resolution\n * - Custom user adapters\n *\n * @example\n * ```ts\n * const unsub = container.onChange((changes) => {\n * for (const c of changes) {\n * console.log(`${c.event}: ${c.token} (${c.kind})`)\n * }\n * })\n * // later:\n * unsub()\n * ```\n */\n onChange(callback: ContainerChangeListener): () => void {\n this.changeListeners.add(callback)\n return () => this.changeListeners.delete(callback)\n }\n\n /**\n * Invalidate a specific registration. Clears the cached instance so the\n * next resolve() creates a fresh one. Also invalidates dependents\n * (anything that injected this token) by walking the dependency graph.\n *\n * Called by the Vite HMR plugin when a module file changes.\n * Skips persistent registrations (DB connections, etc.).\n */\n invalidate(token: any): void {\n const name = tokenName(token)\n // Try both the token itself and the HMR fallback name\n const reg = this.registrations.get(token) ?? this.registrations.get(`__hmr__${name}`)\n if (!reg) return\n\n // Don't invalidate persistent registrations (DB, Redis, etc.)\n const store = getPersistentStore()\n if (store.resolvedInstances.has(name)) return\n\n // Clear cached instance — next resolve() will re-create\n reg.instance = undefined\n reg.resolveCount = 0\n reg.postConstructStatus = 'skipped'\n this.emit(name, 'invalidated', reg.kind)\n\n // Walk dependency graph — invalidate anything that injected this token\n for (const [depToken, depReg] of this.registrations) {\n if (depReg.dependencies.includes(name)) {\n this.invalidate(depToken)\n }\n }\n }\n\n /** Batch-emit a change event (debounced, flushed after 50ms of quiet) */\n private emit(token: string, event: ContainerChangeEvent['event'], kind?: ClassKind): void {\n // Dedupe within the pending batch: a high-throughput TRANSIENT\n // token that resolves 1000× per request would otherwise queue\n // 1000 identical 'resolved' events. The downstream listener only\n // cares that the token's state changed, so collapse to one entry\n // (the earliest timestamp wins; the latest update is reflected\n // in resolveCount/lastResolvedAt on the registration itself,\n // which the listener reads at notify time).\n const existing = this.pendingChanges.find((p) => p.token === token && p.event === event)\n if (existing) {\n existing.timestamp = Date.now()\n } else {\n this.pendingChanges.push({ token, event, kind, timestamp: Date.now() })\n }\n\n if (this.notifyTimer) clearTimeout(this.notifyTimer)\n this.notifyTimer = setTimeout(() => {\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }, Container.DEBOUNCE_MS)\n }\n\n /** Flush pending change events immediately (useful in tests) */\n flushChanges(): void {\n if (this.notifyTimer) {\n clearTimeout(this.notifyTimer)\n this.notifyTimer = null\n }\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }\n\n private createInstance(reg: Registration): any {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, reg.target, [])\n\n const args = paramTypes.map((paramType, index) => {\n // Check for @Inject token override on constructor parameter\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, reg.target)\n const token = injectTokens[index] || paramType\n // Scope validation: SINGLETON cannot inject REQUEST-scoped dependencies\n if (reg.scope === Scope.SINGLETON) {\n const depReg = this.registrations.get(token)\n if (depReg && depReg.scope === Scope.REQUEST) {\n throw new Error(\n `Cannot inject REQUEST-scoped \"${tokenName(token)}\" into SINGLETON \"${tokenName(reg.target)}\". ` +\n `Singletons outlive requests. Use TRANSIENT or REQUEST scope for the parent.`,\n )\n }\n }\n return this.resolve(token)\n })\n\n const instance = new reg.target(...args)\n\n // Property injection via @Autowired\n this.injectProperties(instance, reg.target)\n\n // @PostConstruct lifecycle hook\n const postConstruct = getClassMetaOrUndefined<string | symbol>(\n METADATA.POST_CONSTRUCT,\n reg.target.prototype,\n )\n if (postConstruct && typeof instance[postConstruct] === 'function') {\n try {\n const result = instance[postConstruct]()\n if (result && typeof result.then === 'function') {\n reg.postConstructStatus = 'pending'\n ;(result as Promise<void>)\n .then(() => {\n reg.postConstructStatus = 'completed'\n })\n .catch(() => {\n reg.postConstructStatus = 'failed'\n })\n log.warn(\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() returned a Promise ` +\n `but the container is synchronous. The async operation will not be awaited.`,\n )\n } else {\n reg.postConstructStatus = 'completed'\n }\n } catch (err) {\n reg.postConstructStatus = 'failed'\n log.error(\n err,\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`,\n )\n }\n }\n\n return instance\n }\n\n /**\n * Extract every dependency token name a class declares — both\n * constructor params (`@Inject` + emitted `design:paramtypes`) and\n * `@Autowired` properties (read lazily at injection time, but\n * captured here so devtools surfaces a complete dependency graph).\n *\n * Without the property branch, classes that rely entirely on\n * `@Autowired` show `dependencies: []` in the introspection panel\n * even though they have real DI deps — see /_debug topology.\n */\n private extractDependencies(target: Constructor): string[] {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, target, [])\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, target)\n const ctorDeps = paramTypes.map((type, index) => {\n const token = injectTokens[index] || type\n return tokenName(token)\n })\n\n // @Autowired properties live on `target.prototype`. Token may be\n // explicit (`@Autowired('SOME_TOKEN')`) or fall back to the\n // emitted `design:type` metadata of the property.\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, target.prototype)\n const propDeps: string[] = []\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, target.prototype, prop)\n if (resolvedToken) propDeps.push(tokenName(resolvedToken))\n }\n\n // De-dupe — a class can declare the same token in both surfaces.\n return [...new Set([...ctorDeps, ...propDeps])]\n }\n\n private injectProperties(instance: any, target: Constructor): void {\n // @Autowired — lazy DI property injection\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, target.prototype)\n\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, target.prototype, prop)\n if (resolvedToken) {\n Object.defineProperty(instance, prop, {\n get: () => this.resolve(resolvedToken),\n enumerable: true,\n configurable: true,\n })\n }\n }\n\n // @Value — lazy environment variable injection\n const valueProps = getMetaMap<string, { envKey: string; defaultValue?: any }>(\n METADATA.VALUE,\n target.prototype,\n )\n\n for (const [prop, config] of valueProps) {\n Object.defineProperty(instance, prop, {\n get() {\n // Use the registered env resolver if available (wired by the\n // config layer in @forinda/kickjs). Returns Zod-validated,\n // type-coerced values (e.g. PORT as number).\n if (Container._envResolver) {\n const val = Container._envResolver(config.envKey)\n if (val !== undefined) return val\n }\n\n // Fallback to raw process.env for apps that haven't called loadEnv()\n const val = process.env[config.envKey]\n if (val !== undefined) return val\n if (config.defaultValue !== undefined) return config.defaultValue\n throw new Error(\n `@Value('${config.envKey}'): Environment variable \"${config.envKey}\" is not set and no default was provided.`,\n )\n },\n enumerable: true,\n configurable: true,\n })\n }\n\n // @Asset — lazy asset path injection (assets-plan.md). Same lazy-\n // getter pattern as @Value: resolve on every property access via\n // the asset manager's cached resolver, not at class instantiation.\n // Splits the key on the FIRST '/' so `mails/orders/confirmation`\n // becomes namespace=`mails` + key=`orders/confirmation`.\n const assetProps = getMetaMap<string, { assetKey: string }>(METADATA.ASSET, target.prototype)\n\n if (assetProps.size > 0) {\n for (const [prop, config] of assetProps) {\n const slashIdx = config.assetKey.indexOf('/')\n if (slashIdx === -1) {\n throw new Error(\n `@Asset('${config.assetKey}'): asset key must include a '/' separator (e.g. 'mails/welcome').`,\n )\n }\n const namespace = config.assetKey.slice(0, slashIdx)\n const key = config.assetKey.slice(slashIdx + 1)\n Object.defineProperty(instance, prop, {\n get() {\n return resolveAsset(namespace, key)\n },\n enumerable: true,\n configurable: true,\n })\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.
|
|
2
|
+
* @forinda/kickjs v5.7.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{i as parseQuery}from"./query-
|
|
12
|
-
//# sourceMappingURL=context-
|
|
11
|
+
import{i as parseQuery}from"./query-Di7R8qNX.mjs";import{AsyncLocalStorage}from"node:async_hooks";const requestStore=new AsyncLocalStorage;function getRequestStore(){let store=requestStore.getStore();if(!store)throw Error(`No active request context. REQUEST-scoped services can only be resolved during an HTTP request.`);return store}function getRequestValue(key){return requestStore.getStore()?.values.get(key)}const signalControllerKey=Symbol.for(`kickjs.requestContext.signalController`);var RequestContext=class{constructor(req,res,next){this.req=req,this.res=res,this.next=next}metadataReadOnly(){return requestStore.getStore()?.values??null}metadataForWrite(){let store=requestStore.getStore();if(store)return store.values;throw Error(`RequestContext.set called outside an AsyncLocalStorage frame. Mount requestScopeMiddleware() before constructing RequestContext, or wrap test setup in requestStore.run({ requestId, instances: new Map(), values: new Map() }, () => ...).`)}get body(){return this.req.body}get params(){return this.req.params}get query(){return this.req.query}get headers(){return this.req.headers}get requestId(){return this.req.requestId??this.req.headers[`x-request-id`]}get signal(){let req=this.req,cached=req[signalControllerKey];if(cached)return cached.signal;let ctrl=new AbortController;req[signalControllerKey]=ctrl;let abort=()=>ctrl.abort(`request closed`);return this.req.once(`close`,abort),this.res.once(`close`,abort),ctrl.signal}get session(){return this.req.session}get user(){return this.metadataReadOnly()?.get(`user`)??this.req.user}get tenantId(){return this.user?.tenantId}get roles(){let u=this.user;return u?.tenantRoles??u?.roles??[]}qs(fieldConfig){return parseQuery(this.req.query,fieldConfig)}get file(){return this.req.file}get files(){let f=this.req.files;return Array.isArray(f)?f:void 0}get(key){return this.metadataReadOnly()?.get(key)}set(key,value){this.metadataForWrite().set(key,value)}json(data,status=200){return this.res.status(status).json(data)}created(data){return this.res.status(201).json(data)}noContent(){return this.res.status(204).end()}notFound(message=`Not Found`){return this.res.status(404).json({message})}badRequest(message){return this.res.status(400).json({message})}html(content,status=200){return this.res.status(status).type(`html`).send(content)}download(buffer,filename,contentType=`application/octet-stream`){return this.res.setHeader(`Content-Disposition`,`attachment; filename="${filename}"`),this.res.setHeader(`Content-Type`,contentType),this.res.send(buffer)}render(template,data={}){return this.res.render(template,data)}async paginate(fetcher,fieldConfig){let parsed=this.qs(fieldConfig),{data,total}=await fetcher(parsed),{page,limit}=parsed.pagination,totalPages=Math.ceil(total/limit)||1,response={data,meta:{page,limit,total,totalPages,hasNext:page<totalPages,hasPrev:page>1}};return this.json(response)}sse(){this.res.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`,"X-Accel-Buffering":`no`}),this.res.flushHeaders();let closeCallbacks=[];return this.req.on(`close`,()=>{for(let cb of closeCallbacks)cb()}),{send:(data,event,id)=>{id&&this.res.write(`id: ${id}\n`),event&&this.res.write(`event: ${event}\n`),this.res.write(`data: ${JSON.stringify(data)}\n\n`)},comment:text=>{this.res.write(`: ${text}\n\n`)},onClose:fn=>{closeCallbacks.push(fn)},close:()=>{this.res.end()}}}};export{requestStore as i,getRequestStore as n,getRequestValue as r,RequestContext as t};
|
|
12
|
+
//# sourceMappingURL=context-CvJQzbBF.mjs.map
|