@forinda/kickjs 9.2.0 → 10.0.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-DjfRMob8.d.mts → adapter-CvEm8Gpp.d.mts} +23 -1
- package/dist/adapter-CvEm8Gpp.d.mts.map +1 -0
- package/dist/{application-CGxZCYl1.d.mts → application-1Hln746x.d.mts} +24 -8
- package/dist/application-1Hln746x.d.mts.map +1 -0
- package/dist/application-D21ZVAWG.mjs +12 -0
- package/dist/application-D21ZVAWG.mjs.map +1 -0
- package/dist/bootstrap-2hi7Zhs8.mjs +12 -0
- package/dist/bootstrap-2hi7Zhs8.mjs.map +1 -0
- package/dist/{client-ip-pcINkkPr.mjs → client-ip-BkLX8wXf.mjs} +2 -2
- package/dist/{client-ip-pcINkkPr.mjs.map → client-ip-BkLX8wXf.mjs.map} +1 -1
- package/dist/config/index.mjs +2 -2
- package/dist/{config-DmGiAIuH.mjs → config-rXsEm1rn.mjs} +3 -3
- package/dist/{config-DmGiAIuH.mjs.map → config-rXsEm1rn.mjs.map} +1 -1
- package/dist/{container-CAPN7pBG.mjs → container-CsCdem73.mjs} +4 -4
- package/dist/{container-CAPN7pBG.mjs.map → container-CsCdem73.mjs.map} +1 -1
- package/dist/context-Bd02q5Sm.mjs +12 -0
- package/dist/context-Bd02q5Sm.mjs.map +1 -0
- package/dist/{context-errors-DYJjhalQ.mjs → context-errors-BUdMBVWO.mjs} +2 -2
- package/dist/{context-errors-DYJjhalQ.mjs.map → context-errors-BUdMBVWO.mjs.map} +1 -1
- package/dist/contributor-runner-CZTAFvla.mjs +12 -0
- package/dist/{contributor-runner-vk9XlOED.mjs.map → contributor-runner-CZTAFvla.mjs.map} +1 -1
- package/dist/core/adapter.d.mts +1 -1
- package/dist/core/adapter.mjs +1 -1
- package/dist/core/app-module.mjs +1 -1
- package/dist/core/cache.mjs +2 -2
- package/dist/core/cache.mjs.map +1 -1
- package/dist/core/container.mjs +2 -2
- package/dist/core/cron.mjs +2 -2
- package/dist/core/cron.mjs.map +1 -1
- 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 +2 -2
- package/dist/core/interfaces.mjs.map +1 -1
- package/dist/core/logger.mjs +2 -2
- package/dist/core/logger.mjs.map +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 +2 -2
- package/dist/core/reactivity.mjs.map +1 -1
- package/dist/core-B0mErNE1.mjs +12 -0
- package/dist/{core-DKrKVLfj.mjs.map → core-B0mErNE1.mjs.map} +1 -1
- package/dist/{csrf-CxJItZVa.mjs → csrf-BnetlmWY.mjs} +3 -3
- package/dist/{csrf-CxJItZVa.mjs.map → csrf-BnetlmWY.mjs.map} +1 -1
- package/dist/{decorate-DfH1NIqV.mjs → decorate-B7-z930n.mjs} +1 -1
- package/dist/{define-adapter-C6lFhCL-.mjs → define-adapter-B4D_Rci1.mjs} +2 -2
- package/dist/{define-adapter-C6lFhCL-.mjs.map → define-adapter-B4D_Rci1.mjs.map} +1 -1
- package/dist/disposables-CgQqGmPL.mjs +12 -0
- package/dist/{disposables-NlW6Mibk.mjs.map → disposables-CgQqGmPL.mjs.map} +1 -1
- package/dist/env-ngWmBB9d.mjs +13 -0
- package/dist/env-ngWmBB9d.mjs.map +1 -0
- package/dist/{error-handler-itG65lj0.d.mts → error-handler-Chw07m-M.d.mts} +2 -2
- package/dist/{error-handler-itG65lj0.d.mts.map → error-handler-Chw07m-M.d.mts.map} +1 -1
- package/dist/handler-CyRoAqNt.mjs +13 -0
- package/dist/handler-CyRoAqNt.mjs.map +1 -0
- 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.d.mts +1 -1
- package/dist/http/context.mjs +2 -2
- package/dist/http/middleware/cors.mjs +2 -2
- package/dist/http/middleware/cors.mjs.map +1 -1
- package/dist/http/middleware/csrf.mjs +2 -2
- package/dist/http/middleware/error-handler.d.mts +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 +2 -2
- package/dist/http/middleware/rate-limit.mjs.map +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 +2 -2
- package/dist/http/middleware/session.mjs.map +1 -1
- package/dist/http/middleware/spa.d.mts +1 -1
- package/dist/http/middleware/spa.mjs +2 -2
- package/dist/http/middleware/spa.mjs.map +1 -1
- package/dist/http/middleware/upload.d.mts +1 -1
- package/dist/http/middleware/upload.mjs +2 -2
- package/dist/http/middleware/validate.mjs +1 -1
- package/dist/http/middleware/views.d.mts +1 -1
- package/dist/http/middleware/views.mjs +2 -2
- package/dist/http/middleware/views.mjs.map +1 -1
- 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/http/runtimes/fastify.d.mts +2 -1
- package/dist/http/runtimes/fastify.d.mts.map +1 -1
- package/dist/http/runtimes/fastify.mjs +3 -3
- package/dist/http/runtimes/fastify.mjs.map +1 -1
- package/dist/http/runtimes/h3-web.d.mts +2 -49
- package/dist/http/runtimes/h3-web.mjs +2 -3
- package/dist/http/runtimes/h3.d.mts +48 -18
- package/dist/http/runtimes/h3.d.mts.map +1 -1
- package/dist/http/runtimes/h3.mjs +2 -2
- package/dist/http/runtimes/h3.mjs.map +1 -1
- package/dist/index.d.mts +7 -7
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/jobs-Ccs6PkmC.mjs +12 -0
- package/dist/{jobs-BmpeFvi0.mjs.map → jobs-Ccs6PkmC.mjs.map} +1 -1
- package/dist/kv-stores-9IkW7Rjj.mjs +12 -0
- package/dist/{kv-stores-DJwxntyw.mjs.map → kv-stores-9IkW7Rjj.mjs.map} +1 -1
- package/dist/{kv-stores-BZTe23Tw.d.mts → kv-stores-BYeO77zK.d.mts} +20 -5
- package/dist/kv-stores-BYeO77zK.d.mts.map +1 -0
- package/dist/{metadata-CzzRUnAx.mjs → metadata-DCWKG472.mjs} +3 -3
- package/dist/{metadata-CzzRUnAx.mjs.map → metadata-DCWKG472.mjs.map} +1 -1
- package/dist/{module-registry-BnmRNy-3.mjs → module-registry-Cl12vSJd.mjs} +2 -2
- package/dist/{module-registry-BnmRNy-3.mjs.map → module-registry-Cl12vSJd.mjs.map} +1 -1
- package/dist/{observers-COlQfinD.mjs → observers-9FRE8VCB.mjs} +2 -2
- package/dist/{observers-COlQfinD.mjs.map → observers-9FRE8VCB.mjs.map} +1 -1
- package/dist/query-DUkboS0j.mjs +12 -0
- package/dist/{query-B5DTq_4W.mjs.map → query-DUkboS0j.mjs.map} +1 -1
- package/dist/reply-DElkObMy.mjs +12 -0
- package/dist/{reply-CbTYIj_e.mjs.map → reply-DElkObMy.mjs.map} +1 -1
- package/dist/{respond-wbMSqHYo.mjs → respond-Dcj1NF_X.mjs} +2 -2
- package/dist/{respond-wbMSqHYo.mjs.map → respond-Dcj1NF_X.mjs.map} +1 -1
- package/dist/route-flag-jd8PDm20.mjs +12 -0
- package/dist/{route-flag-Bb7n9Mnp.mjs.map → route-flag-jd8PDm20.mjs.map} +1 -1
- package/dist/route-policy-DlunIjGc.mjs +12 -0
- package/dist/{route-policy-C_Wsc-b1.mjs.map → route-policy-DlunIjGc.mjs.map} +1 -1
- package/dist/router-builder-DZsCjpeV.mjs +12 -0
- package/dist/{router-builder-DawnRzlp.mjs.map → router-builder-DZsCjpeV.mjs.map} +1 -1
- package/dist/{router-builder-CDkW88KQ.d.mts → router-builder-D_SkWTfE.d.mts} +2 -2
- package/dist/{router-builder-CDkW88KQ.d.mts.map → router-builder-D_SkWTfE.d.mts.map} +1 -1
- package/dist/{upload-Cy7qQy7M.d.mts → upload-DL_jrBGk.d.mts} +2 -2
- package/dist/upload-DL_jrBGk.d.mts.map +1 -0
- package/dist/{upload-config-D01Ty40f.mjs → upload-config-BiL1QXkT.mjs} +2 -2
- package/dist/{upload-config-D01Ty40f.mjs.map → upload-config-BiL1QXkT.mjs.map} +1 -1
- package/dist/web.d.mts +9 -7
- package/dist/web.d.mts.map +1 -1
- package/dist/web.mjs +2 -2
- package/dist/web.mjs.map +1 -1
- package/package.json +7 -8
- package/dist/adapter-DjfRMob8.d.mts.map +0 -1
- package/dist/application-BSJpaiSY.mjs +0 -12
- package/dist/application-BSJpaiSY.mjs.map +0 -1
- package/dist/application-CGxZCYl1.d.mts.map +0 -1
- package/dist/bootstrap-CbTF6xvv.mjs +0 -12
- package/dist/bootstrap-CbTF6xvv.mjs.map +0 -1
- package/dist/context-gpvcMlG5.mjs +0 -12
- package/dist/context-gpvcMlG5.mjs.map +0 -1
- package/dist/contributor-runner-vk9XlOED.mjs +0 -12
- package/dist/core-DKrKVLfj.mjs +0 -12
- package/dist/disposables-NlW6Mibk.mjs +0 -12
- package/dist/env-__fzPsHD.mjs +0 -13
- package/dist/env-__fzPsHD.mjs.map +0 -1
- package/dist/handler-DnZbcleS.mjs +0 -13
- package/dist/handler-DnZbcleS.mjs.map +0 -1
- package/dist/http/runtimes/h3-web.d.mts.map +0 -1
- package/dist/http/runtimes/h3-web.mjs.map +0 -1
- package/dist/jobs-BmpeFvi0.mjs +0 -12
- package/dist/kv-stores-BZTe23Tw.d.mts.map +0 -1
- package/dist/kv-stores-DJwxntyw.mjs +0 -12
- package/dist/query-B5DTq_4W.mjs +0 -12
- package/dist/reply-CbTYIj_e.mjs +0 -12
- package/dist/route-flag-Bb7n9Mnp.mjs +0 -12
- package/dist/route-policy-C_Wsc-b1.mjs +0 -12
- package/dist/router-builder-DawnRzlp.mjs +0 -12
- package/dist/upload-Cy7qQy7M.d.mts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.mjs","names":[],"sources":["../../src/core/decorators.ts"],"sourcesContent":["import 'reflect-metadata'\nimport { METADATA, Scope, type BuilderOf, type ServiceOptions } from './interfaces'\nimport { Container, type KickJsRegistry } from './container'\nimport {\n setClassMeta,\n setMethodMeta,\n pushClassMeta,\n pushMethodMeta,\n setInMetaMap,\n setInMetaRecord,\n} from './metadata'\n\n// ── Decorator Registration System ───────────────────────────────────────\n// Decorators execute at class-definition time (module load). The Container\n// may not exist yet when the first decorator fires. We queue registrations\n// and flush them on first Container.getInstance() call.\n//\n// `allRegistrations` is a persistent registry that survives Container.reset().\n// On reset (HMR), all previously decorated classes are re-registered on the\n// fresh container. This ensures @Service, @Controller, @Repository, etc.\n// all survive HMR without manual re-registration.\n\ntype PendingRegistration = { target: any; scope: Scope }\nconst pendingRegistrations: PendingRegistration[] = []\n// Keyed by class name to prevent memory leaks during HMR — new class with\n// the same name replaces the old entry instead of accumulating references.\nconst allRegistrations = new Map<string, { target: any; scope: Scope }>()\nlet containerRef: any = null\n\nfunction flushPending(container: any): void {\n containerRef = container\n for (const { target, scope } of pendingRegistrations) {\n if (!container.has(target)) {\n container.register(target, target, scope)\n }\n }\n pendingRegistrations.length = 0\n}\n\n// Wire up synchronously — Container._onReady is called on first getInstance()\nContainer._onReady = flushPending\n\n/** Replay every decorator registration onto a container. */\nfunction seed(container: any): void {\n for (const [, { target, scope }] of allRegistrations) {\n if (!container.has(target)) {\n container.register(target, target, scope)\n }\n }\n}\n\n// On Container.reset(), update containerRef and replay ALL decorator\n// registrations on the fresh container. This handles HMR where the container\n// is wiped but not all decorated modules are re-evaluated.\nContainer._onReset = (container: any) => {\n containerRef = container\n seed(container)\n}\n\n// On Container.create(), seed the same way but leave `containerRef` alone: an\n// isolated container should receive what has been registered so far, not\n// become the destination for everything registered next.\nContainer._onCreate = seed\n\n// ── Class Decorators ────────────────────────────────────────────────────\n\n/**\n * Classes already warned about.\n *\n * Keyed by the constructor, not its `name`: distinct classes can share a name —\n * two `DatabaseService` classes in separate modules is ordinary — and deduping\n * by name silently suppressed the second one's warning, which is precisely the\n * failure this warning exists to prevent.\n *\n * A WeakSet means an HMR reload, which builds a NEW class object, warns again.\n * That is correct rather than noisy: the reloaded code still has the problem,\n * and the notice stops as soon as it is fixed.\n */\nconst preDestroyWarned = new WeakSet<object>()\n\n/**\n * `@PreDestroy` only runs for REQUEST-scoped services — the request scope is\n * what closes and triggers it. On a SINGLETON it is silently inert, which\n * reads as a working teardown and is not: the reported case was a Postgres\n * pool never closed on shutdown, invisible in development and surfacing as\n * connection exhaustion under repeated restarts and HMR reloads.\n *\n * The asymmetry is the trap. `@PostConstruct` DOES run for singletons, so the\n * pair looks like init/teardown while one half quietly opts out on a scope the\n * author may never have considered.\n *\n * Warn rather than run it: making it fire on shutdown is a behaviour change\n * whose ordering, timeout and concurrency semantics have to match the adapter\n * shutdown path, and an adapter's `shutdown()` is already the seam for\n * application-lifetime resources.\n */\nfunction warnIfInertPreDestroy(target: any, scope: Scope): void {\n if (scope === Scope.REQUEST) return\n const proto = target?.prototype\n if (!proto) return\n const hook = Reflect.getMetadata?.(METADATA.PRE_DESTROY, proto)\n if (!hook) return\n if (preDestroyWarned.has(target)) return\n preDestroyWarned.add(target)\n const name = target.name || String(target)\n console.warn(\n ` kickjs: @PreDestroy on ${name} (${scope}) will never run — that hook fires ` +\n `only when a REQUEST scope closes.\\n` +\n ` For application-lifetime resources, release them from an adapter's ` +\n `shutdown() hook instead.`,\n )\n}\n\nfunction registerInContainer(target: any, scope: Scope): void {\n setClassMeta(METADATA.INJECTABLE, true, target)\n setClassMeta(METADATA.SCOPE, scope, target)\n // Method decorators run before the class decorator, so PRE_DESTROY metadata\n // is already present by the time we get here.\n warnIfInertPreDestroy(target, scope)\n\n // Track in persistent registry — survives Container.reset() for HMR replay.\n // Keyed by name so HMR class re-creation replaces the old entry.\n const name = target.name || String(target)\n allRegistrations.set(name, { target, scope })\n\n if (containerRef) {\n // Container already initialized — register immediately\n if (!containerRef.has(target)) {\n containerRef.register(target, target, scope)\n }\n } else {\n // Container not ready yet — queue for later\n pendingRegistrations.push({ target, scope })\n }\n}\n\n/** Mark a class as injectable with lifecycle scope */\nexport function Injectable(options?: ServiceOptions): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'injectable', target)\n registerInContainer(target, options?.scope ?? Scope.SINGLETON)\n }\n}\n\n/** Mark a class as a service (semantic alias for Injectable) */\nexport function Service(options?: ServiceOptions): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'service', target)\n registerInContainer(target, options?.scope ?? Scope.SINGLETON)\n }\n}\n\n/** Mark a class as a generic managed component */\nexport function Component(options?: ServiceOptions): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'component', target)\n registerInContainer(target, options?.scope ?? Scope.SINGLETON)\n }\n}\n\n/** Mark a class as a repository */\nexport function Repository(options?: ServiceOptions): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'repository', target)\n registerInContainer(target, options?.scope ?? Scope.SINGLETON)\n }\n}\n\n/**\n * Mark a class as an HTTP controller and register it in the DI container.\n *\n * Route prefixes are defined by the module's `routes().path` — the single\n * source of truth for where routes mount. The legacy\n * `@Controller('/path')` parameter was removed in v4; if your code\n * still passes a string, see `docs/guide/migration-v3-to-v4.md` for\n * the rename map.\n */\nexport function Controller(): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'controller', target)\n registerInContainer(target, Scope.SINGLETON)\n }\n}\n\n// ── Method Decorators ───────────────────────────────────────────────────\n\n/** Mark a method as a lifecycle hook called after instantiation */\nexport function PostConstruct(): MethodDecorator {\n return (target, propertyKey) => {\n setClassMeta(METADATA.POST_CONSTRUCT, propertyKey, target)\n }\n}\n\n/**\n * Mark a method as a teardown hook — the counterpart to {@link PostConstruct}.\n *\n * For REQUEST-scoped services it runs when the request's scope closes\n * (response finished or aborted): release per-request resources there\n * (transactions, handles, subscriptions). May be async; errors are logged\n * and swallowed so one failing hook can't break request completion.\n *\n * It does NOT run on a SINGLETON — the default scope — because nothing closes\n * a singleton's scope. Unlike {@link PostConstruct}, which does run for\n * singletons, so the pair is not symmetric. Applying it to a singleton logs a\n * warning naming the class rather than failing silently.\n *\n * For application-lifetime resources (a connection pool, a timer, a socket\n * server) use an adapter's `shutdown()` hook, which the framework runs on a\n * real shutdown AND on every HMR reload, time-boxed and concurrent.\n */\nexport function PreDestroy(): MethodDecorator {\n return (target, propertyKey) => {\n setClassMeta(METADATA.PRE_DESTROY, propertyKey, target)\n }\n}\n\n// ── Injection Decorators (property + constructor parameter) ─────────────\n\n/**\n * A decorator that's valid as either a property decorator OR a\n * constructor-parameter decorator. forinda/kick-js#235 §2 — `@Autowired`\n * and `@Inject` previously diverged at the type level (property-only\n * vs parameter-only) and adopters who picked the wrong name for the\n * position got a cryptic TS1240 error. Both names now accept either\n * position and route to the correct metadata bucket at runtime.\n */\nexport interface PropertyOrParameterDecorator {\n /** Property decorator form — `@Autowired(TOKEN) private foo!: Foo`. */\n (target: object, propertyKey: string | symbol): void\n /** Constructor-parameter form — `constructor(@Inject(TOKEN) foo: Foo) {}`. */\n (target: object, propertyKey: string | symbol | undefined, parameterIndex: number): void\n}\n\nfunction applyInjection(\n token: unknown,\n target: object,\n propertyKey: string | symbol | undefined,\n parameterIndex: number | undefined,\n): void {\n // Legacy parameter decorators receive 3 args (target, propertyKey,\n // parameterIndex); property decorators receive 2 (target, propertyKey).\n // The 3rd-arg-number-vs-undefined check is the canonical position\n // detection in TypeScript's legacy decorator runtime.\n if (typeof parameterIndex === 'number') {\n setInMetaRecord(METADATA.INJECT, target, parameterIndex, token)\n return\n }\n if (propertyKey != null) {\n // `target` for a property decorator on a non-static field is the\n // class prototype; the AUTOWIRED bucket is keyed off the prototype\n // identity at resolution time, so pass it through unchanged.\n setInMetaMap(METADATA.AUTOWIRED, target, propertyKey as string, token)\n }\n}\n\n/**\n * Inject a dependency. Works in two positions:\n *\n * - **Property decorator** — `@Autowired(TOKEN) private repo!: UserRepo`.\n * Resolved lazily from the container the first time the property is\n * read.\n * - **Constructor parameter decorator** — `constructor(@Autowired(TOKEN) repo: UserRepo) {}`.\n * Resolved at instantiation; injected into the ctor call.\n *\n * `@Inject` is the same function under another name — they share\n * runtime + types so the two names are interchangeable. Pick whichever\n * reads better at the call site.\n *\n * Like `@Inject`, the typed string-literal overload narrows the token\n * to a key of the augmented `KickJsRegistry`. After `kick typegen`\n * populates the registry, `@Autowired('kick/prisma/Client')` auto-\n * completes the key and typo'd literals become TS2345 errors.\n */\nexport function Autowired<K extends keyof KickJsRegistry & string>(\n token: K,\n): PropertyOrParameterDecorator\nexport function Autowired(token?: unknown): PropertyOrParameterDecorator\nexport function Autowired(token?: unknown): PropertyOrParameterDecorator {\n return ((\n target: object,\n propertyKey: string | symbol | undefined,\n parameterIndex?: number,\n ): void => {\n applyInjection(token, target, propertyKey, parameterIndex)\n }) as PropertyOrParameterDecorator\n}\n\n/**\n * Inject a dependency by token. Same shape as {@link Autowired} —\n * works as either a property decorator or a constructor-parameter\n * decorator. Pick whichever name reads better at the call site.\n *\n * ## Typed string-literal overload\n *\n * When called with a string literal that matches a key of the\n * augmented `KickJsRegistry`, TypeScript narrows the parameter type\n * to the registered type. Typo'd literals become compile errors\n * (the string isn't `keyof KickJsRegistry`). Class identities and\n * `InjectionToken<T>` keep working unchanged via the second overload.\n *\n * @example\n * ```ts\n * import { Service, Inject } from '@forinda/kickjs'\n * import type { PrismaClient } from '@prisma/client'\n *\n * `@Service()`\n * class UserRepo {\n * constructor(@Inject('kick/prisma/Client') private db: PrismaClient) {}\n * // OR property form, same decorator:\n * @Inject('kick/prisma/Client') private db2!: PrismaClient\n * }\n * ```\n *\n * After `kick typegen` runs, the literal autocompletes from the\n * registry and a typo (`@Inject('kick/prisma/Cleint')`) becomes a\n * TS2345 error.\n */\nexport function Inject<K extends keyof KickJsRegistry & string>(\n token: K,\n): PropertyOrParameterDecorator\nexport function Inject(token?: unknown): PropertyOrParameterDecorator\nexport function Inject(token?: unknown): PropertyOrParameterDecorator {\n return ((\n target: object,\n propertyKey: string | symbol | undefined,\n parameterIndex?: number,\n ): void => {\n applyInjection(token, target, propertyKey, parameterIndex)\n }) as PropertyOrParameterDecorator\n}\n\n/**\n * Global ambient registry of typed environment variables, populated by\n * `kick typegen` from the project's env schema (typically\n * `src/env.ts`'s `export default defineEnv(...)`).\n *\n * Empty by default — declarations come from `.kickjs/types/env.ts`\n * generated alongside the rest of the typegen output. Once populated,\n * `@Value('PORT')` autocompletes the key, type-checks the default\n * value, and `process.env.PORT` is also typed via the parallel\n * `NodeJS.ProcessEnv` augmentation in the same generated file.\n */\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n interface KickEnv {}\n}\n\n/** A string key known to the typed env registry (`never` until typegen runs) */\nexport type EnvKey = keyof KickEnv & string\n\n/**\n * Look up the type of a typed env entry.\n *\n * Decorators can't propagate the property type from the key, so users\n * still need to write the property's type — but `Env<K>` lets them\n * derive it from `KickEnv` instead of duplicating the schema:\n *\n * ```ts\n * @Service()\n * class MyService {\n * @Value('PORT')\n * private port!: Env<'PORT'> // → number, narrowed from KickEnv['PORT']\n * }\n * ```\n */\nexport type Env<K extends EnvKey = EnvKey> = KickEnv[K]\n\n/**\n * Constraint that allows any string when the typed env registry is\n * empty (no `kick typegen` has run yet, so existing code with raw\n * string keys keeps working) but locks the argument down to known\n * `EnvKey` literals once the registry is populated.\n *\n * Implementation: when `EnvKey` is `never`, the conditional resolves\n * to the user's `K`, accepting anything. Once typegen has populated\n * `KickEnv`, `EnvKey` becomes a real union and the conditional forces\n * `K` to be assignable to it.\n */\ntype ValueKey<K extends string> = [EnvKey] extends [never] ? K : K & EnvKey\n\n/**\n * Inject an environment variable value. Evaluated lazily so the env\n * is available at access time, not at decoration time.\n *\n * If no default is provided and the env var is missing, throws at access time\n * to catch misconfiguration early instead of returning undefined.\n *\n * Uses metadata + instance getter to work correctly with `useDefineForClassFields`.\n *\n * Type-safety:\n * - Without `kick typegen` run, any string key is accepted (back-compat).\n * - After typegen, `KickEnv` is populated and unknown keys become a tsc\n * error. The optional `defaultValue` is type-checked against the\n * schema's inferred type for that key.\n */\nexport function Value<K extends string>(\n envKey: ValueKey<K>,\n defaultValue?: K extends EnvKey ? KickEnv[K] : unknown,\n): PropertyDecorator {\n return (target, propertyKey) => {\n setInMetaMap(METADATA.VALUE, target, propertyKey as string, { envKey, defaultValue })\n }\n}\n\n// ── @Asset — typed asset path injection ─────────────────────────────────\n\nimport type { KickAssets } from './assets'\n\n/**\n * Flatten the nested `KickAssets` augmentation (typegen-emitted in\n * PR 4) into a string-literal union of dot/slash-joined paths.\n *\n * `interface KickAssets { mails: { welcome: () => string; orders: {\n * confirmation: () => string } } }` becomes\n * `'mails/welcome' | 'mails/orders/confirmation'`.\n *\n * Returns `never` when `KickAssets` is empty (no typegen has run yet)\n * so `AssetKeyArg` falls through to accept any string.\n */\ntype FlattenAssets<T, Prefix extends string = ''> = {\n [K in keyof T & string]: T[K] extends () => string\n ? `${Prefix}${K}`\n : T[K] extends Record<string, unknown>\n ? FlattenAssets<T[K], `${Prefix}${K}/`>\n : never\n}[keyof T & string]\n\n/** Flat union of every known `<namespace>/<key>` path. */\nexport type AssetKey = FlattenAssets<KickAssets>\n\n/**\n * Constraint mirroring `ValueKey<K>` — accepts any string when\n * `AssetKey` is `never` (back-compat for adopters who haven't run\n * `kick typegen` yet) but locks the argument to known literals once\n * the typegen-augmented `KickAssets` interface is populated.\n */\ntype AssetKeyArg<K extends string> = [AssetKey] extends [never] ? K : K & AssetKey\n\n/**\n * Inject the resolved file-system path for a typed asset. Mirrors\n * `@Value`'s lazy-getter pattern — the asset is resolved on every\n * property access, NOT at class instantiation, so:\n *\n * - Tests can swap fixtures + clear the cache without re-instantiating\n * the consuming class.\n * - The first access cost is paid once per process (the manifest cache\n * in `core/assets.ts` makes subsequent calls cheap).\n *\n * Type-safety:\n *\n * - Without typegen, any string key is accepted (back-compat — same\n * trick as `@Value`).\n * - After typegen, `KickAssets` is populated and `AssetKey` is a\n * union of every flattened `<namespace>/<key>` path. Unknown keys\n * become a TS error.\n *\n * @example\n * ```ts\n * import { Service, Asset } from '@forinda/kickjs'\n *\n * `@Service()`\n * class MailService {\n * `@Asset('mails/welcome')`\n * private welcomeTemplate!: string\n *\n * send(user: User) {\n * return ejs.renderFile(this.welcomeTemplate, { user })\n * }\n * }\n * ```\n */\nexport function Asset<K extends string>(assetKey: AssetKeyArg<K>): PropertyDecorator {\n return (target, propertyKey) => {\n setInMetaMap(METADATA.ASSET, target, propertyKey as string, { assetKey })\n }\n}\n\n// ── HTTP Route Decorators ───────────────────────────────────────────────\n\nexport interface RouteDefinition {\n method: string\n path: string\n handlerName: string\n validation?: {\n /** Schema validating the request body — Zod, Valibot, Yup, any Standard Schema or KickSchema. */\n body?: any\n /** Schema validating query parameters — any library `detectSchema` accepts. */\n query?: any\n /** Schema validating URL params — any library `detectSchema` accepts. */\n params?: any\n /**\n * DECLARED response schema — never validated at runtime. Feeds two\n * consumers from one source of truth: the Swagger adapter's success\n * response schema, and `kick typegen`'s `KickRoutes[...].response`\n * (winning over return-type inference when present).\n */\n response?: any\n /** Schema name in OpenAPI components/schemas for the request body. Auto-generated from handler name if omitted. */\n name?: string\n }\n}\n\nfunction createRouteDecorator(method: string) {\n return (path?: string, validation?: RouteDefinition['validation']): MethodDecorator => {\n return (target, propertyKey) => {\n pushClassMeta<RouteDefinition>(METADATA.ROUTES, target.constructor, {\n method,\n path: path || '/',\n handlerName: propertyKey as string,\n validation,\n })\n }\n }\n}\n\nexport const Get = createRouteDecorator('GET')\nexport const Post = createRouteDecorator('POST')\nexport const Put = createRouteDecorator('PUT')\nexport const Delete = createRouteDecorator('DELETE')\nexport const Patch = createRouteDecorator('PATCH')\n\n// ── Query Params Decorator ─────────────────────────────────────────────\n\nexport interface ApiQueryParamsConfig {\n /** Fields that can be used in filter queries (e.g., `?filter=status:eq:active`) */\n filterable?: string[]\n /** Fields that can be used in sort queries (e.g., `?sort=createdAt:desc`) */\n sortable?: string[]\n /** Fields that are searched with the `?q=` parameter */\n searchable?: string[]\n}\n\n/**\n * Column-object-based query params config (e.g., from DrizzleQueryParamsConfig).\n * `Object.keys()` is used to derive field names for OpenAPI docs.\n */\nexport interface ColumnApiQueryParamsConfig {\n columns: Record<string, any>\n sortable?: Record<string, any>\n searchColumns?: any[]\n [key: string]: any\n}\n\n/**\n * Normalize a query params config to the string-based ApiQueryParamsConfig.\n * Handles both string-based and column-object-based configs.\n */\nexport function normalizeApiQueryParamsConfig(\n config: ApiQueryParamsConfig | ColumnApiQueryParamsConfig,\n): ApiQueryParamsConfig {\n if ('columns' in config && config.columns && typeof config.columns === 'object') {\n return {\n filterable: Object.keys(config.columns),\n sortable: config.sortable ? Object.keys(config.sortable) : undefined,\n searchable: config.searchColumns\n ? config.searchColumns.map((col: any) => col?.name ?? '').filter(Boolean)\n : undefined,\n }\n }\n return config as ApiQueryParamsConfig\n}\n\n/**\n * Document the query parameters accepted by a GET endpoint.\n * Used by SwaggerAdapter to generate `filter`, `sort`, `page`, `limit`, and `q` params\n * in the OpenAPI spec, with descriptions listing the allowed fields.\n *\n * Accepts both string-based configs and column-object configs (e.g., DrizzleQueryParamsConfig).\n *\n * @example\n * ```ts\n * // String-based\n * @ApiQueryParams({\n * filterable: ['status', 'category', 'price'],\n * sortable: ['name', 'createdAt', 'price'],\n * searchable: ['name', 'description'],\n * })\n *\n * // Column-object-based (Drizzle)\n * @ApiQueryParams(TASK_QUERY_CONFIG)\n * ```\n */\nexport function ApiQueryParams(\n config: ApiQueryParamsConfig | ColumnApiQueryParamsConfig,\n): MethodDecorator {\n return (target, propertyKey) => {\n const normalized = normalizeApiQueryParamsConfig(config)\n setMethodMeta(METADATA.QUERY_PARAMS, normalized, target.constructor, propertyKey as string)\n }\n}\n\n// ── Middleware Decorators ───────────────────────────────────────────────\n\n/**\n * Middleware handler function.\n * Generic `TCtx` defaults to `any` — import `RequestContext` from\n * `@forinda/kickjs` for full type safety:\n *\n * ```ts\n * import type { MiddlewareHandler } from '@forinda/kickjs'\n * import type { RequestContext } from '@forinda/kickjs'\n *\n * const auth: MiddlewareHandler<RequestContext> = (ctx, next) => {\n * ctx.body // fully typed\n * next()\n * }\n * ```\n */\nexport type MiddlewareHandler<TCtx = any> = (ctx: TCtx, next: () => void) => void | Promise<void>\n\n/** Attach middleware handlers to a class or method */\nexport function Middleware(...handlers: MiddlewareHandler[]): ClassDecorator & MethodDecorator {\n return (target: any, propertyKey?: string | symbol) => {\n if (propertyKey) {\n pushMethodMeta(\n METADATA.METHOD_MIDDLEWARES,\n target.constructor,\n propertyKey as string,\n ...handlers,\n )\n } else {\n pushClassMeta(METADATA.CLASS_MIDDLEWARES, target, ...handlers)\n }\n }\n}\n\n// ── File Upload Decorator ───────────────────────────────────────────────\n\n/**\n * File filter — accepts short extensions ('jpg'), full MIME types ('image/jpeg'),\n * wildcards ('image/*'), or a function `(mimetype, filename) => boolean` for full control.\n */\nexport type FileTypeFilter = string[] | ((mimetype: string, filename: string) => boolean)\n\n/**\n * Shared upload options used by both the `@FileUpload` decorator and the\n * `upload.single()` / `upload.array()` / `upload.none()` middleware.\n */\nexport interface BaseUploadOptions {\n /** Max file size in bytes (default: 5MB) */\n maxSize?: number\n /**\n * Allowed file types:\n * - **string[]** — short extensions ('jpg'), full MIME types ('image/jpeg'), or wildcards ('image/*')\n * - **function** — `(mimetype, filename) => boolean` for full control\n */\n allowedTypes?: FileTypeFilter\n /** Extend the built-in extension-to-MIME map */\n customMimeMap?: Record<string, string>\n}\n\nexport interface FileUploadConfig extends BaseUploadOptions {\n mode: 'single' | 'array' | 'none'\n /** Form field name (default: 'file') */\n fieldName?: string\n /** Max files for array mode (default: 10) */\n maxCount?: number\n}\n\n/** Configure file upload handling for a controller method */\nexport function FileUpload(config: FileUploadConfig): MethodDecorator {\n return (target, propertyKey) => {\n setMethodMeta(METADATA.FILE_UPLOAD, config, target.constructor, propertyKey as string)\n }\n}\n\n// ── Builder Decorator ───────────────────────────────────────────────────\n\nfunction attachBuilder(target: any): void {\n setClassMeta(METADATA.BUILDER, true, target)\n target.builder = function () {\n const props: Record<string, any> = {}\n const proxy: any = new Proxy(\n {},\n {\n get(_, key) {\n if (key === 'build') {\n return () => Object.assign(new target(), props)\n }\n return (value: any) => {\n props[key as string] = value\n return proxy\n }\n },\n },\n )\n return proxy\n }\n}\n\n/**\n * Add a static `builder()` method for fluent construction.\n *\n * Legacy decorators cannot widen the class type, so by default the\n * caller sees the unmodified shape. Opt into typing by adding a single\n * `declare static` line:\n *\n * ```ts\n * @Builder\n * class UserDto {\n * name!: string\n * email!: string\n * declare static readonly builder: () => BuilderOf<UserDto>\n * }\n * ```\n *\n * `readonly` keeps SonarQube's `typescript:S1444` quiet — the runtime\n * assigns `target.builder` once at decoration time and never again.\n *\n * If you'd rather not write the `declare`, use `withBuilder()` instead —\n * same runtime, types inferred automatically.\n */\nexport function Builder(target: any): void {\n attachBuilder(target)\n}\n\n/**\n * Factory variant of `@Builder`. Wraps the class with the same runtime\n * and returns it intersected with `{ builder(): BuilderOf<T> }` so the\n * static is fully typed without any `declare` boilerplate.\n *\n * ```ts\n * class UserDtoBase {\n * name!: string\n * email!: string\n * }\n * export const UserDto = withBuilder(UserDtoBase)\n * export type UserDto = InstanceType<typeof UserDto>\n *\n * UserDto.builder().name('Alice').email('a@b.com').build() // fully typed\n * ```\n */\nexport function withBuilder<C extends new (...args: any[]) => any>(\n ctor: C,\n): C & { builder(): BuilderOf<InstanceType<C>> } {\n attachBuilder(ctor)\n return ctor as C & { builder(): BuilderOf<InstanceType<C>> }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"decorators.mjs","names":[],"sources":["../../src/core/decorators.ts"],"sourcesContent":["import 'reflect-metadata'\nimport { METADATA, Scope, type BuilderOf, type ServiceOptions } from './interfaces'\nimport { Container, type KickJsRegistry } from './container'\nimport {\n setClassMeta,\n setMethodMeta,\n pushClassMeta,\n pushMethodMeta,\n setInMetaMap,\n setInMetaRecord,\n} from './metadata'\n\n// ── Decorator Registration System ───────────────────────────────────────\n// Decorators execute at class-definition time (module load). The Container\n// may not exist yet when the first decorator fires. We queue registrations\n// and flush them on first Container.getInstance() call.\n//\n// `allRegistrations` is a persistent registry that survives Container.reset().\n// On reset (HMR), all previously decorated classes are re-registered on the\n// fresh container. This ensures @Service, @Controller, @Repository, etc.\n// all survive HMR without manual re-registration.\n\ntype PendingRegistration = { target: any; scope: Scope }\nconst pendingRegistrations: PendingRegistration[] = []\n// Keyed by class name to prevent memory leaks during HMR — new class with\n// the same name replaces the old entry instead of accumulating references.\nconst allRegistrations = new Map<string, { target: any; scope: Scope }>()\nlet containerRef: any = null\n\nfunction flushPending(container: any): void {\n containerRef = container\n for (const { target, scope } of pendingRegistrations) {\n if (!container.has(target)) {\n container.register(target, target, scope)\n }\n }\n pendingRegistrations.length = 0\n}\n\n// Wire up synchronously — Container._onReady is called on first getInstance()\nContainer._onReady = flushPending\n\n/** Replay every decorator registration onto a container. */\nfunction seed(container: any): void {\n for (const [, { target, scope }] of allRegistrations) {\n if (!container.has(target)) {\n container.register(target, target, scope)\n }\n }\n}\n\n// On Container.reset(), update containerRef and replay ALL decorator\n// registrations on the fresh container. This handles HMR where the container\n// is wiped but not all decorated modules are re-evaluated.\nContainer._onReset = (container: any) => {\n containerRef = container\n seed(container)\n}\n\n// On Container.create(), seed the same way but leave `containerRef` alone: an\n// isolated container should receive what has been registered so far, not\n// become the destination for everything registered next.\nContainer._onCreate = seed\n\n// ── Class Decorators ────────────────────────────────────────────────────\n\n/**\n * Classes already warned about.\n *\n * Keyed by the constructor, not its `name`: distinct classes can share a name —\n * two `DatabaseService` classes in separate modules is ordinary — and deduping\n * by name silently suppressed the second one's warning, which is precisely the\n * failure this warning exists to prevent.\n *\n * A WeakSet means an HMR reload, which builds a NEW class object, warns again.\n * That is correct rather than noisy: the reloaded code still has the problem,\n * and the notice stops as soon as it is fixed.\n */\nconst preDestroyWarned = new WeakSet<object>()\n\n/**\n * `@PreDestroy` only runs for REQUEST-scoped services — the request scope is\n * what closes and triggers it. On a SINGLETON it is silently inert, which\n * reads as a working teardown and is not: the reported case was a Postgres\n * pool never closed on shutdown, invisible in development and surfacing as\n * connection exhaustion under repeated restarts and HMR reloads.\n *\n * The asymmetry is the trap. `@PostConstruct` DOES run for singletons, so the\n * pair looks like init/teardown while one half quietly opts out on a scope the\n * author may never have considered.\n *\n * Warn rather than run it: making it fire on shutdown is a behaviour change\n * whose ordering, timeout and concurrency semantics have to match the adapter\n * shutdown path, and an adapter's `shutdown()` is already the seam for\n * application-lifetime resources.\n */\nfunction warnIfInertPreDestroy(target: any, scope: Scope): void {\n if (scope === Scope.REQUEST) return\n const proto = target?.prototype\n if (!proto) return\n const hook = Reflect.getMetadata?.(METADATA.PRE_DESTROY, proto)\n if (!hook) return\n if (preDestroyWarned.has(target)) return\n preDestroyWarned.add(target)\n const name = target.name || String(target)\n console.warn(\n ` kickjs: @PreDestroy on ${name} (${scope}) will never run — that hook fires ` +\n `only when a REQUEST scope closes.\\n` +\n ` For application-lifetime resources, release them from an adapter's ` +\n `shutdown() hook instead.`,\n )\n}\n\nfunction registerInContainer(target: any, scope: Scope): void {\n setClassMeta(METADATA.INJECTABLE, true, target)\n setClassMeta(METADATA.SCOPE, scope, target)\n // Method decorators run before the class decorator, so PRE_DESTROY metadata\n // is already present by the time we get here.\n warnIfInertPreDestroy(target, scope)\n\n // Track in persistent registry — survives Container.reset() for HMR replay.\n // Keyed by name so HMR class re-creation replaces the old entry.\n const name = target.name || String(target)\n allRegistrations.set(name, { target, scope })\n\n if (containerRef) {\n // Container already initialized — register immediately\n if (!containerRef.has(target)) {\n containerRef.register(target, target, scope)\n }\n } else {\n // Container not ready yet — queue for later\n pendingRegistrations.push({ target, scope })\n }\n}\n\n/** Mark a class as injectable with lifecycle scope */\nexport function Injectable(options?: ServiceOptions): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'injectable', target)\n registerInContainer(target, options?.scope ?? Scope.SINGLETON)\n }\n}\n\n/** Mark a class as a service (semantic alias for Injectable) */\nexport function Service(options?: ServiceOptions): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'service', target)\n registerInContainer(target, options?.scope ?? Scope.SINGLETON)\n }\n}\n\n/** Mark a class as a generic managed component */\nexport function Component(options?: ServiceOptions): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'component', target)\n registerInContainer(target, options?.scope ?? Scope.SINGLETON)\n }\n}\n\n/** Mark a class as a repository */\nexport function Repository(options?: ServiceOptions): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'repository', target)\n registerInContainer(target, options?.scope ?? Scope.SINGLETON)\n }\n}\n\n/**\n * Mark a class as an HTTP controller and register it in the DI container.\n *\n * Route prefixes are defined by the module's `routes().path` — the single\n * source of truth for where routes mount. The legacy\n * `@Controller('/path')` parameter was removed in v4; if your code\n * still passes a string, see `docs/guide/migration-v3-to-v4.md` for\n * the rename map.\n */\nexport function Controller(): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'controller', target)\n registerInContainer(target, Scope.SINGLETON)\n }\n}\n\n// ── Method Decorators ───────────────────────────────────────────────────\n\n/** Mark a method as a lifecycle hook called after instantiation */\nexport function PostConstruct(): MethodDecorator {\n return (target, propertyKey) => {\n setClassMeta(METADATA.POST_CONSTRUCT, propertyKey, target)\n }\n}\n\n/**\n * Mark a method as a teardown hook — the counterpart to {@link PostConstruct}.\n *\n * For REQUEST-scoped services it runs when the request's scope closes\n * (response finished or aborted): release per-request resources there\n * (transactions, handles, subscriptions). May be async; errors are logged\n * and swallowed so one failing hook can't break request completion.\n *\n * It does NOT run on a SINGLETON — the default scope — because nothing closes\n * a singleton's scope. Unlike {@link PostConstruct}, which does run for\n * singletons, so the pair is not symmetric. Applying it to a singleton logs a\n * warning naming the class rather than failing silently.\n *\n * For application-lifetime resources (a connection pool, a timer, a socket\n * server) use an adapter's `shutdown()` hook, which the framework runs on a\n * real shutdown AND on every HMR reload, time-boxed and concurrent.\n */\nexport function PreDestroy(): MethodDecorator {\n return (target, propertyKey) => {\n setClassMeta(METADATA.PRE_DESTROY, propertyKey, target)\n }\n}\n\n// ── Injection Decorators (property + constructor parameter) ─────────────\n\n/**\n * A decorator that's valid as either a property decorator OR a\n * constructor-parameter decorator. forinda/kick-js#235 §2 — `@Autowired`\n * and `@Inject` previously diverged at the type level (property-only\n * vs parameter-only) and adopters who picked the wrong name for the\n * position got a cryptic TS1240 error. Both names now accept either\n * position and route to the correct metadata bucket at runtime.\n */\nexport interface PropertyOrParameterDecorator {\n /** Property decorator form — `@Autowired(TOKEN) private foo!: Foo`. */\n (target: object, propertyKey: string | symbol): void\n /** Constructor-parameter form — `constructor(@Inject(TOKEN) foo: Foo) {}`. */\n (target: object, propertyKey: string | symbol | undefined, parameterIndex: number): void\n}\n\nfunction applyInjection(\n token: unknown,\n target: object,\n propertyKey: string | symbol | undefined,\n parameterIndex: number | undefined,\n): void {\n // Legacy parameter decorators receive 3 args (target, propertyKey,\n // parameterIndex); property decorators receive 2 (target, propertyKey).\n // The 3rd-arg-number-vs-undefined check is the canonical position\n // detection in TypeScript's legacy decorator runtime.\n if (typeof parameterIndex === 'number') {\n setInMetaRecord(METADATA.INJECT, target, parameterIndex, token)\n return\n }\n if (propertyKey != null) {\n // `target` for a property decorator on a non-static field is the\n // class prototype; the AUTOWIRED bucket is keyed off the prototype\n // identity at resolution time, so pass it through unchanged.\n setInMetaMap(METADATA.AUTOWIRED, target, propertyKey as string, token)\n }\n}\n\n/**\n * Inject a dependency. Works in two positions:\n *\n * - **Property decorator** — `@Autowired(TOKEN) private repo!: UserRepo`.\n * Resolved lazily from the container the first time the property is\n * read.\n * - **Constructor parameter decorator** — `constructor(@Autowired(TOKEN) repo: UserRepo) {}`.\n * Resolved at instantiation; injected into the ctor call.\n *\n * `@Inject` is the same function under another name — they share\n * runtime + types so the two names are interchangeable. Pick whichever\n * reads better at the call site.\n *\n * Like `@Inject`, the typed string-literal overload narrows the token\n * to a key of the augmented `KickJsRegistry`. After `kick typegen`\n * populates the registry, `@Autowired('kick/prisma/Client')` auto-\n * completes the key and typo'd literals become TS2345 errors.\n */\nexport function Autowired<K extends keyof KickJsRegistry & string>(\n token: K,\n): PropertyOrParameterDecorator\nexport function Autowired(token?: unknown): PropertyOrParameterDecorator\nexport function Autowired(token?: unknown): PropertyOrParameterDecorator {\n return ((\n target: object,\n propertyKey: string | symbol | undefined,\n parameterIndex?: number,\n ): void => {\n applyInjection(token, target, propertyKey, parameterIndex)\n }) as PropertyOrParameterDecorator\n}\n\n/**\n * Inject a dependency by token. Same shape as {@link Autowired} —\n * works as either a property decorator or a constructor-parameter\n * decorator. Pick whichever name reads better at the call site.\n *\n * ## Typed string-literal overload\n *\n * When called with a string literal that matches a key of the\n * augmented `KickJsRegistry`, TypeScript narrows the parameter type\n * to the registered type. Typo'd literals become compile errors\n * (the string isn't `keyof KickJsRegistry`). Class identities and\n * `InjectionToken<T>` keep working unchanged via the second overload.\n *\n * @example\n * ```ts\n * import { Service, Inject } from '@forinda/kickjs'\n * import type { PrismaClient } from '@prisma/client'\n *\n * `@Service()`\n * class UserRepo {\n * constructor(@Inject('kick/prisma/Client') private db: PrismaClient) {}\n * // OR property form, same decorator:\n * @Inject('kick/prisma/Client') private db2!: PrismaClient\n * }\n * ```\n *\n * After `kick typegen` runs, the literal autocompletes from the\n * registry and a typo (`@Inject('kick/prisma/Cleint')`) becomes a\n * TS2345 error.\n */\nexport function Inject<K extends keyof KickJsRegistry & string>(\n token: K,\n): PropertyOrParameterDecorator\nexport function Inject(token?: unknown): PropertyOrParameterDecorator\nexport function Inject(token?: unknown): PropertyOrParameterDecorator {\n return ((\n target: object,\n propertyKey: string | symbol | undefined,\n parameterIndex?: number,\n ): void => {\n applyInjection(token, target, propertyKey, parameterIndex)\n }) as PropertyOrParameterDecorator\n}\n\n/**\n * Global ambient registry of typed environment variables, populated by\n * `kick typegen` from the project's env schema (typically\n * `src/env.ts`'s `export default defineEnv(...)`).\n *\n * Empty by default — declarations come from `.kickjs/types/env.ts`\n * generated alongside the rest of the typegen output. Once populated,\n * `@Value('PORT')` autocompletes the key, type-checks the default\n * value, and `process.env.PORT` is also typed via the parallel\n * `NodeJS.ProcessEnv` augmentation in the same generated file.\n */\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n interface KickEnv {}\n}\n\n/** A string key known to the typed env registry (`never` until typegen runs) */\nexport type EnvKey = keyof KickEnv & string\n\n/**\n * Look up the type of a typed env entry.\n *\n * Decorators can't propagate the property type from the key, so users\n * still need to write the property's type — but `Env<K>` lets them\n * derive it from `KickEnv` instead of duplicating the schema:\n *\n * ```ts\n * @Service()\n * class MyService {\n * @Value('PORT')\n * private port!: Env<'PORT'> // → number, narrowed from KickEnv['PORT']\n * }\n * ```\n */\nexport type Env<K extends EnvKey = EnvKey> = KickEnv[K]\n\n/**\n * Constraint that allows any string when the typed env registry is\n * empty (no `kick typegen` has run yet, so existing code with raw\n * string keys keeps working) but locks the argument down to known\n * `EnvKey` literals once the registry is populated.\n *\n * Implementation: when `EnvKey` is `never`, the conditional resolves\n * to the user's `K`, accepting anything. Once typegen has populated\n * `KickEnv`, `EnvKey` becomes a real union and the conditional forces\n * `K` to be assignable to it.\n */\ntype ValueKey<K extends string> = [EnvKey] extends [never] ? K : K & EnvKey\n\n/**\n * Inject an environment variable value. Evaluated lazily so the env\n * is available at access time, not at decoration time.\n *\n * If no default is provided and the env var is missing, throws at access time\n * to catch misconfiguration early instead of returning undefined.\n *\n * Uses metadata + instance getter to work correctly with `useDefineForClassFields`.\n *\n * Type-safety:\n * - Without `kick typegen` run, any string key is accepted (back-compat).\n * - After typegen, `KickEnv` is populated and unknown keys become a tsc\n * error. The optional `defaultValue` is type-checked against the\n * schema's inferred type for that key.\n */\nexport function Value<K extends string>(\n envKey: ValueKey<K>,\n defaultValue?: K extends EnvKey ? KickEnv[K] : unknown,\n): PropertyDecorator {\n return (target, propertyKey) => {\n setInMetaMap(METADATA.VALUE, target, propertyKey as string, { envKey, defaultValue })\n }\n}\n\n// ── @Asset — typed asset path injection ─────────────────────────────────\n\nimport type { KickAssets } from './assets'\n\n/**\n * Flatten the nested `KickAssets` augmentation (typegen-emitted in\n * PR 4) into a string-literal union of dot/slash-joined paths.\n *\n * `interface KickAssets { mails: { welcome: () => string; orders: {\n * confirmation: () => string } } }` becomes\n * `'mails/welcome' | 'mails/orders/confirmation'`.\n *\n * Returns `never` when `KickAssets` is empty (no typegen has run yet)\n * so `AssetKeyArg` falls through to accept any string.\n */\ntype FlattenAssets<T, Prefix extends string = ''> = {\n [K in keyof T & string]: T[K] extends () => string\n ? `${Prefix}${K}`\n : T[K] extends Record<string, unknown>\n ? FlattenAssets<T[K], `${Prefix}${K}/`>\n : never\n}[keyof T & string]\n\n/** Flat union of every known `<namespace>/<key>` path. */\nexport type AssetKey = FlattenAssets<KickAssets>\n\n/**\n * Constraint mirroring `ValueKey<K>` — accepts any string when\n * `AssetKey` is `never` (back-compat for adopters who haven't run\n * `kick typegen` yet) but locks the argument to known literals once\n * the typegen-augmented `KickAssets` interface is populated.\n */\ntype AssetKeyArg<K extends string> = [AssetKey] extends [never] ? K : K & AssetKey\n\n/**\n * Inject the resolved file-system path for a typed asset. Mirrors\n * `@Value`'s lazy-getter pattern — the asset is resolved on every\n * property access, NOT at class instantiation, so:\n *\n * - Tests can swap fixtures + clear the cache without re-instantiating\n * the consuming class.\n * - The first access cost is paid once per process (the manifest cache\n * in `core/assets.ts` makes subsequent calls cheap).\n *\n * Type-safety:\n *\n * - Without typegen, any string key is accepted (back-compat — same\n * trick as `@Value`).\n * - After typegen, `KickAssets` is populated and `AssetKey` is a\n * union of every flattened `<namespace>/<key>` path. Unknown keys\n * become a TS error.\n *\n * @example\n * ```ts\n * import { Service, Asset } from '@forinda/kickjs'\n *\n * `@Service()`\n * class MailService {\n * `@Asset('mails/welcome')`\n * private welcomeTemplate!: string\n *\n * send(user: User) {\n * return ejs.renderFile(this.welcomeTemplate, { user })\n * }\n * }\n * ```\n */\nexport function Asset<K extends string>(assetKey: AssetKeyArg<K>): PropertyDecorator {\n return (target, propertyKey) => {\n setInMetaMap(METADATA.ASSET, target, propertyKey as string, { assetKey })\n }\n}\n\n// ── HTTP Route Decorators ───────────────────────────────────────────────\n\nexport interface RouteDefinition {\n method: string\n path: string\n handlerName: string\n validation?: {\n /** Schema validating the request body — Zod, Valibot, Yup, any Standard Schema or KickSchema. */\n body?: any\n /** Schema validating query parameters — any library `detectSchema` accepts. */\n query?: any\n /** Schema validating URL params — any library `detectSchema` accepts. */\n params?: any\n /**\n * DECLARED response schema — never validated at runtime. Feeds two\n * consumers from one source of truth: the Swagger adapter's success\n * response schema, and `kick typegen`'s `KickRoutes[...].response`\n * (winning over return-type inference when present).\n */\n response?: any\n /** Schema name in OpenAPI components/schemas for the request body. Auto-generated from handler name if omitted. */\n name?: string\n }\n}\n\nfunction createRouteDecorator(method: string) {\n return (path?: string, validation?: RouteDefinition['validation']): MethodDecorator => {\n return (target, propertyKey) => {\n pushClassMeta<RouteDefinition>(METADATA.ROUTES, target.constructor, {\n method,\n path: path || '/',\n handlerName: propertyKey as string,\n validation,\n })\n }\n }\n}\n\nexport const Get = createRouteDecorator('GET')\nexport const Post = createRouteDecorator('POST')\nexport const Put = createRouteDecorator('PUT')\nexport const Delete = createRouteDecorator('DELETE')\nexport const Patch = createRouteDecorator('PATCH')\n\n// ── Query Params Decorator ─────────────────────────────────────────────\n\nexport interface ApiQueryParamsConfig {\n /** Fields that can be used in filter queries (e.g., `?filter=status:eq:active`) */\n filterable?: string[]\n /** Fields that can be used in sort queries (e.g., `?sort=createdAt:desc`) */\n sortable?: string[]\n /** Fields that are searched with the `?q=` parameter */\n searchable?: string[]\n}\n\n/**\n * Column-object-based query params config (e.g., from DrizzleQueryParamsConfig).\n * `Object.keys()` is used to derive field names for OpenAPI docs.\n */\nexport interface ColumnApiQueryParamsConfig {\n columns: Record<string, any>\n sortable?: Record<string, any>\n searchColumns?: any[]\n [key: string]: any\n}\n\n/**\n * Normalize a query params config to the string-based ApiQueryParamsConfig.\n * Handles both string-based and column-object-based configs.\n */\nexport function normalizeApiQueryParamsConfig(\n config: ApiQueryParamsConfig | ColumnApiQueryParamsConfig,\n): ApiQueryParamsConfig {\n if ('columns' in config && config.columns && typeof config.columns === 'object') {\n return {\n filterable: Object.keys(config.columns),\n sortable: config.sortable ? Object.keys(config.sortable) : undefined,\n searchable: config.searchColumns\n ? config.searchColumns.map((col: any) => col?.name ?? '').filter(Boolean)\n : undefined,\n }\n }\n return config as ApiQueryParamsConfig\n}\n\n/**\n * Document the query parameters accepted by a GET endpoint.\n * Used by SwaggerAdapter to generate `filter`, `sort`, `page`, `limit`, and `q` params\n * in the OpenAPI spec, with descriptions listing the allowed fields.\n *\n * Accepts both string-based configs and column-object configs (e.g., DrizzleQueryParamsConfig).\n *\n * @example\n * ```ts\n * // String-based\n * @ApiQueryParams({\n * filterable: ['status', 'category', 'price'],\n * sortable: ['name', 'createdAt', 'price'],\n * searchable: ['name', 'description'],\n * })\n *\n * // Column-object-based (Drizzle)\n * @ApiQueryParams(TASK_QUERY_CONFIG)\n * ```\n */\nexport function ApiQueryParams(\n config: ApiQueryParamsConfig | ColumnApiQueryParamsConfig,\n): MethodDecorator {\n return (target, propertyKey) => {\n const normalized = normalizeApiQueryParamsConfig(config)\n setMethodMeta(METADATA.QUERY_PARAMS, normalized, target.constructor, propertyKey as string)\n }\n}\n\n// ── Middleware Decorators ───────────────────────────────────────────────\n\n/**\n * Middleware handler function.\n * Generic `TCtx` defaults to `any` — import `RequestContext` from\n * `@forinda/kickjs` for full type safety:\n *\n * ```ts\n * import type { MiddlewareHandler } from '@forinda/kickjs'\n * import type { RequestContext } from '@forinda/kickjs'\n *\n * const auth: MiddlewareHandler<RequestContext> = (ctx, next) => {\n * ctx.body // fully typed\n * next()\n * }\n * ```\n */\nexport type MiddlewareHandler<TCtx = any> = (ctx: TCtx, next: () => void) => void | Promise<void>\n\n/** Attach middleware handlers to a class or method */\nexport function Middleware(...handlers: MiddlewareHandler[]): ClassDecorator & MethodDecorator {\n return (target: any, propertyKey?: string | symbol) => {\n if (propertyKey) {\n pushMethodMeta(\n METADATA.METHOD_MIDDLEWARES,\n target.constructor,\n propertyKey as string,\n ...handlers,\n )\n } else {\n pushClassMeta(METADATA.CLASS_MIDDLEWARES, target, ...handlers)\n }\n }\n}\n\n// ── File Upload Decorator ───────────────────────────────────────────────\n\n/**\n * File filter — accepts short extensions ('jpg'), full MIME types ('image/jpeg'),\n * wildcards ('image/*'), or a function `(mimetype, filename) => boolean` for full control.\n */\nexport type FileTypeFilter = string[] | ((mimetype: string, filename: string) => boolean)\n\n/**\n * Shared upload options used by both the `@FileUpload` decorator and the\n * `upload.single()` / `upload.array()` / `upload.none()` middleware.\n */\nexport interface BaseUploadOptions {\n /** Max file size in bytes (default: 5MB) */\n maxSize?: number\n /**\n * Allowed file types:\n * - **string[]** — short extensions ('jpg'), full MIME types ('image/jpeg'), or wildcards ('image/*')\n * - **function** — `(mimetype, filename) => boolean` for full control\n */\n allowedTypes?: FileTypeFilter\n /** Extend the built-in extension-to-MIME map */\n customMimeMap?: Record<string, string>\n}\n\nexport interface FileUploadConfig extends BaseUploadOptions {\n mode: 'single' | 'array' | 'none'\n /** Form field name (default: 'file') */\n fieldName?: string\n /** Max files for array mode (default: 10) */\n maxCount?: number\n}\n\n/** Configure file upload handling for a controller method */\nexport function FileUpload(config: FileUploadConfig): MethodDecorator {\n return (target, propertyKey) => {\n setMethodMeta(METADATA.FILE_UPLOAD, config, target.constructor, propertyKey as string)\n }\n}\n\n// ── Builder Decorator ───────────────────────────────────────────────────\n\nfunction attachBuilder(target: any): void {\n setClassMeta(METADATA.BUILDER, true, target)\n target.builder = function () {\n const props: Record<string, any> = {}\n const proxy: any = new Proxy(\n {},\n {\n get(_, key) {\n if (key === 'build') {\n return () => Object.assign(new target(), props)\n }\n return (value: any) => {\n props[key as string] = value\n return proxy\n }\n },\n },\n )\n return proxy\n }\n}\n\n/**\n * Add a static `builder()` method for fluent construction.\n *\n * Legacy decorators cannot widen the class type, so by default the\n * caller sees the unmodified shape. Opt into typing by adding a single\n * `declare static` line:\n *\n * ```ts\n * @Builder\n * class UserDto {\n * name!: string\n * email!: string\n * declare static readonly builder: () => BuilderOf<UserDto>\n * }\n * ```\n *\n * `readonly` keeps SonarQube's `typescript:S1444` quiet — the runtime\n * assigns `target.builder` once at decoration time and never again.\n *\n * If you'd rather not write the `declare`, use `withBuilder()` instead —\n * same runtime, types inferred automatically.\n */\nexport function Builder(target: any): void {\n attachBuilder(target)\n}\n\n/**\n * Factory variant of `@Builder`. Wraps the class with the same runtime\n * and returns it intersected with `{ builder(): BuilderOf<T> }` so the\n * static is fully typed without any `declare` boilerplate.\n *\n * ```ts\n * class UserDtoBase {\n * name!: string\n * email!: string\n * }\n * export const UserDto = withBuilder(UserDtoBase)\n * export type UserDto = InstanceType<typeof UserDto>\n *\n * UserDto.builder().name('Alice').email('a@b.com').build() // fully typed\n * ```\n */\nexport function withBuilder<C extends new (...args: any[]) => any>(\n ctor: C,\n): C & { builder(): BuilderOf<InstanceType<C>> } {\n attachBuilder(ctor)\n return ctor as C & { builder(): BuilderOf<InstanceType<C>> }\n}\n"],"mappings":"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"}
|
package/dist/core/errors.mjs
CHANGED
package/dist/core/interfaces.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs
|
|
2
|
+
* @forinda/kickjs v10.0.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"reflect-metadata";let Scope
|
|
11
|
+
import"reflect-metadata";let Scope=/* @__PURE__ */ function(Scope){return Scope.SINGLETON=`singleton`,Scope.TRANSIENT=`transient`,Scope.REQUEST=`request`,Scope}({});const METADATA={INJECTABLE:`kick:injectable`,SCOPE:`kick:scope`,CLASS_KIND:`kick:class-kind`,AUTOWIRED:`kick:autowired`,INJECT:`kick:inject`,POST_CONSTRUCT:`kick:post_construct`,PRE_DESTROY:`kick:pre_destroy`,BUILDER:`kick:builder`,QUERY_PARAMS:`kick:query:params`,ROUTES:`kick:routes`,CLASS_MIDDLEWARES:`kick:class:middlewares`,METHOD_MIDDLEWARES:`kick:method:middlewares`,CLASS_CONTRIBUTORS:`kick:class:contributors`,METHOD_CONTRIBUTORS:`kick:method:contributors`,CLASS_FLAGS:`kick:class:flags`,METHOD_FLAGS:`kick:method:flags`,FILE_UPLOAD:`kick:file:upload`,VALUE:`kick:value`,ASSET:`kick:asset`,PARAM_TYPES:`design:paramtypes`,PROPERTY_TYPE:`design:type`,RETURN_TYPE:`design:returntype`};export{METADATA,Scope};
|
|
12
12
|
//# sourceMappingURL=interfaces.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interfaces.mjs","names":[],"sources":["../../src/core/interfaces.ts"],"sourcesContent":["import 'reflect-metadata'\n\n/** Lifecycle scope for DI registrations */\nexport enum Scope {\n SINGLETON = 'singleton',\n TRANSIENT = 'transient',\n /** One instance per HTTP request, cached in AsyncLocalStorage. Throws outside request context. */\n REQUEST = 'request',\n}\n\n/** Generic constructor type */\nexport type Constructor<T = any> = new (...args: any[]) => T\n\n/** A value that may or may not be wrapped in a Promise */\nexport type MaybePromise<T = void> = T | Promise<T>\n\n/** Options for @Service, @Repository, @Component decorators */\nexport interface ServiceOptions {\n scope?: Scope\n}\n\n/** Fluent builder type */\nexport type BuilderOf<T> = {\n [K in keyof T as T[K] extends Function ? never : K]-?: (value: T[K]) => BuilderOf<T>\n} & { build(): T }\n\n/** Class with static builder() method */\nexport interface Buildable<T> {\n builder(): BuilderOf<T>\n}\n\n/** Decorator class kind for devtools introspection */\nexport type ClassKind =\n | 'service'\n | 'controller'\n | 'repository'\n | 'component'\n | 'injectable'\n | 'factory'\n | 'instance'\n | 'unknown'\n\n/** PostConstruct lifecycle status */\nexport type PostConstructStatus = 'pending' | 'completed' | 'failed' | 'skipped'\n\n/**\n * String metadata keys for the DI system + decorator subsystem. Every\n * key starts with the `kick:` prefix; the suffix is a short identifier\n * that may itself contain `:`, `-`, or `_` separators (historical —\n * keys grew alongside the framework). The §22 v4 convention is \"no\n * Symbols\", not \"use a strict naming scheme\" — Symbols don't survive\n * JSON serialisation, can't be addressed by string from worker\n * threads, and don't show up in DevTools snapshots.\n *\n * `reflect-metadata` accepts both Symbol and string keys identically,\n * so the consumer-side change is internal — references go through the\n * `METADATA` enum's named members (e.g. `METADATA.INJECT`), not the\n * underlying string.\n *\n * The `design:*` entries match what TypeScript's\n * `emitDecoratorMetadata` writes; those names are fixed by the\n * compiler + must stay as-is.\n */\nexport const METADATA = {\n INJECTABLE: 'kick:injectable',\n SCOPE: 'kick:scope',\n CLASS_KIND: 'kick:class-kind',\n AUTOWIRED: 'kick:autowired',\n INJECT: 'kick:inject',\n POST_CONSTRUCT: 'kick:post_construct',\n PRE_DESTROY: 'kick:pre_destroy',\n BUILDER: 'kick:builder',\n QUERY_PARAMS: 'kick:query:params',\n ROUTES: 'kick:routes',\n CLASS_MIDDLEWARES: 'kick:class:middlewares',\n METHOD_MIDDLEWARES: 'kick:method:middlewares',\n CLASS_CONTRIBUTORS: 'kick:class:contributors',\n METHOD_CONTRIBUTORS: 'kick:method:contributors',\n CLASS_FLAGS: 'kick:class:flags',\n METHOD_FLAGS: 'kick:method:flags',\n FILE_UPLOAD: 'kick:file:upload',\n VALUE: 'kick:value',\n ASSET: 'kick:asset',\n // TypeScript emit metadata keys — fixed by the compiler.\n PARAM_TYPES: 'design:paramtypes',\n PROPERTY_TYPE: 'design:type',\n RETURN_TYPE: 'design:returntype',\n} as const\n"],"mappings":";;;;;;;;;;yBAGA,IAAY,MAAL,
|
|
1
|
+
{"version":3,"file":"interfaces.mjs","names":["\"SINGLETON\"","\"TRANSIENT\"","\"REQUEST\""],"sources":["../../src/core/interfaces.ts"],"sourcesContent":["import 'reflect-metadata'\n\n/** Lifecycle scope for DI registrations */\nexport enum Scope {\n SINGLETON = 'singleton',\n TRANSIENT = 'transient',\n /** One instance per HTTP request, cached in AsyncLocalStorage. Throws outside request context. */\n REQUEST = 'request',\n}\n\n/** Generic constructor type */\nexport type Constructor<T = any> = new (...args: any[]) => T\n\n/** A value that may or may not be wrapped in a Promise */\nexport type MaybePromise<T = void> = T | Promise<T>\n\n/** Options for @Service, @Repository, @Component decorators */\nexport interface ServiceOptions {\n scope?: Scope\n}\n\n/** Fluent builder type */\nexport type BuilderOf<T> = {\n [K in keyof T as T[K] extends Function ? never : K]-?: (value: T[K]) => BuilderOf<T>\n} & { build(): T }\n\n/** Class with static builder() method */\nexport interface Buildable<T> {\n builder(): BuilderOf<T>\n}\n\n/** Decorator class kind for devtools introspection */\nexport type ClassKind =\n | 'service'\n | 'controller'\n | 'repository'\n | 'component'\n | 'injectable'\n | 'factory'\n | 'instance'\n | 'unknown'\n\n/** PostConstruct lifecycle status */\nexport type PostConstructStatus = 'pending' | 'completed' | 'failed' | 'skipped'\n\n/**\n * String metadata keys for the DI system + decorator subsystem. Every\n * key starts with the `kick:` prefix; the suffix is a short identifier\n * that may itself contain `:`, `-`, or `_` separators (historical —\n * keys grew alongside the framework). The §22 v4 convention is \"no\n * Symbols\", not \"use a strict naming scheme\" — Symbols don't survive\n * JSON serialisation, can't be addressed by string from worker\n * threads, and don't show up in DevTools snapshots.\n *\n * `reflect-metadata` accepts both Symbol and string keys identically,\n * so the consumer-side change is internal — references go through the\n * `METADATA` enum's named members (e.g. `METADATA.INJECT`), not the\n * underlying string.\n *\n * The `design:*` entries match what TypeScript's\n * `emitDecoratorMetadata` writes; those names are fixed by the\n * compiler + must stay as-is.\n */\nexport const METADATA = {\n INJECTABLE: 'kick:injectable',\n SCOPE: 'kick:scope',\n CLASS_KIND: 'kick:class-kind',\n AUTOWIRED: 'kick:autowired',\n INJECT: 'kick:inject',\n POST_CONSTRUCT: 'kick:post_construct',\n PRE_DESTROY: 'kick:pre_destroy',\n BUILDER: 'kick:builder',\n QUERY_PARAMS: 'kick:query:params',\n ROUTES: 'kick:routes',\n CLASS_MIDDLEWARES: 'kick:class:middlewares',\n METHOD_MIDDLEWARES: 'kick:method:middlewares',\n CLASS_CONTRIBUTORS: 'kick:class:contributors',\n METHOD_CONTRIBUTORS: 'kick:method:contributors',\n CLASS_FLAGS: 'kick:class:flags',\n METHOD_FLAGS: 'kick:method:flags',\n FILE_UPLOAD: 'kick:file:upload',\n VALUE: 'kick:value',\n ASSET: 'kick:asset',\n // TypeScript emit metadata keys — fixed by the compiler.\n PARAM_TYPES: 'design:paramtypes',\n PROPERTY_TYPE: 'design:type',\n RETURN_TYPE: 'design:returntype',\n} as const\n"],"mappings":";;;;;;;;;;yBAGA,IAAY,MAAL,yBAAA,MAAA,OACL,OAAAA,UAAA,YACA,MAAAC,UAAA,YAEA,MAAAC,QAAA,eACF,EAAA,CAAA,CAAA,EAuDA,MAAa,SAAW,CACtB,WAAY,kBACZ,MAAO,aACP,WAAY,kBACZ,UAAW,iBACX,OAAQ,cACR,eAAgB,sBAChB,YAAa,mBACb,QAAS,eACT,aAAc,oBACd,OAAQ,cACR,kBAAmB,yBACnB,mBAAoB,0BACpB,mBAAoB,0BACpB,oBAAqB,2BACrB,YAAa,mBACb,aAAc,oBACd,YAAa,mBACb,MAAO,aACP,MAAO,aAEP,YAAa,oBACb,cAAe,cACf,YAAa,mBACf"}
|
package/dist/core/logger.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs
|
|
2
|
+
* @forinda/kickjs v10.0.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
const LEVEL_RANK={trace:10,debug:20,info:30,warn:40,error:50,fatal:60,silent:100};function thresholdRank(){let level=((typeof process<`u`?process.env?.LOG_LEVEL:void 0)??`info`).toLowerCase();return LEVEL_RANK[level]??LEVEL_RANK.info}function jsonFormat(){return(typeof process<`u`?process.env?.LOG_FORMAT:void 0)?.toLowerCase()===`json`}function isPlainObject(value){if(typeof value!=`object`||!value)return!1;let proto=Object.getPrototypeOf(value);return proto===Object.prototype||proto===null}function serializeError(err,depth=4,seen
|
|
11
|
+
const LEVEL_RANK={trace:10,debug:20,info:30,warn:40,error:50,fatal:60,silent:100};function thresholdRank(){let level=((typeof process<`u`?process.env?.LOG_LEVEL:void 0)??`info`).toLowerCase();return LEVEL_RANK[level]??LEVEL_RANK.info}function jsonFormat(){return(typeof process<`u`?process.env?.LOG_FORMAT:void 0)?.toLowerCase()===`json`}function isPlainObject(value){if(typeof value!=`object`||!value)return!1;let proto=Object.getPrototypeOf(value);return proto===Object.prototype||proto===null}function serializeError(err,depth=4,seen=/* @__PURE__ */ new Set){if(!(err instanceof Error))return err;if(seen.has(err)||depth<=0)return`[circular cause]`;seen.add(err);let out={type:err.name,message:err.message,stack:err.stack};for(let[key,value]of Object.entries(err))key in out||(out[key]=value);let cause=err.cause;return cause!==void 0&&(out.cause=serializeError(cause,depth-1,seen)),out}function formatMessage(msg,args){let i=0,filled=msg.replace(/%[sdifjoO%]/g,token=>{if(token===`%%`)return`%`;if(i>=args.length)return token;let value=args[i++];return token===`%d`||token===`%i`?String(Math.trunc(Number(value))):token===`%f`?String(Number(value)):token===`%s`&&typeof value==`string`?value:safeStringify(value)}),rest=args.slice(i).map(a=>typeof a==`string`?a:safeStringify(a));return rest.length?`${filled} ${rest.join(` `)}`:filled}const RESERVED_FIELDS=/* @__PURE__ */ new Set([`level`,`time`,`component`,`msg`,`err`]);function jsonLine(level,component,bindings,msg,args){let fields=Object.create(null),rest=[],err;for(let arg of args)arg instanceof Error?err??=serializeError(arg):isPlainObject(arg)?Object.assign(fields,arg):rest.push(arg);let line={level:LEVEL_RANK[level],time:Date.now(),...component?{component}:{}};for(let source of[bindings,fields])for(let key of Object.keys(source))RESERVED_FIELDS.has(key)||(line[key]=source[key]);err!==void 0&&(line.err=err),line.msg=formatMessage(msg,rest);try{return JSON.stringify(line,bigintSafe)}catch{let safe={};for(let[key,value]of Object.entries(line))try{JSON.stringify(value,bigintSafe),safe[key]=value}catch{safe[key]=key===`err`&&value&&typeof value==`object`?pick(value,[`type`,`message`,`stack`]):`[unserializable]`}return JSON.stringify(safe,bigintSafe)}}const bigintSafe=(_key,value)=>typeof value==`bigint`?value.toString():value;function pick(obj,keys){let out={};for(let key of keys)typeof obj[key]==`string`&&(out[key]=obj[key]);return out}var ConsoleLoggerProvider=class ConsoleLoggerProvider{prefix;useColor;bindings;constructor(prefix,bindings={}){this.prefix=prefix??``,this.bindings=bindings,this.useColor=typeof process<`u`&&process.stdout?.isTTY===!0&&!process.env.NO_COLOR}json(level,msg,args){return jsonFormat()?(console.log(jsonLine(level,this.prefix,this.bindings,msg,args)),!0):!1}fmt(level,color,msg){let tag=this.prefix?`[${this.prefix}]`:``;return this.useColor?`${color}${level}\x1b[0m ${tag?`\x1b[36m${tag}\x1b[0m `:``}${msg}`:`${level} ${tag?`${tag} `:``}${msg}`}enabled(rank){return rank>=thresholdRank()}info(msg,...args){this.enabled(LEVEL_RANK.info)&&!this.json(`info`,msg,args)&&console.log(this.fmt(`INFO`,`\x1B[32m`,msg),...args)}warn(msg,...args){this.enabled(LEVEL_RANK.warn)&&!this.json(`warn`,msg,args)&&console.warn(this.fmt(`WARN`,`\x1B[33m`,msg),...args)}error(msg,...args){this.enabled(LEVEL_RANK.error)&&!this.json(`error`,msg,args)&&console.error(this.fmt(`ERROR`,`\x1B[31m`,msg),...args)}debug(msg,...args){this.enabled(LEVEL_RANK.debug)&&!this.json(`debug`,msg,args)&&console.debug(this.fmt(`DEBUG`,`\x1B[90m`,msg),...args)}trace(msg,...args){this.enabled(LEVEL_RANK.trace)&&!this.json(`trace`,msg,args)&&console.trace(this.fmt(`TRACE`,`\x1B[90m`,msg),...args)}fatal(msg,...args){this.enabled(LEVEL_RANK.fatal)&&!this.json(`fatal`,msg,args)&&console.error(this.fmt(`FATAL`,`\x1B[1m\x1B[31m`,msg),...args)}child(bindings){let{component,...fields}=bindings;return new ConsoleLoggerProvider(component,{...this.bindings,...fields})}};let activeProvider=new ConsoleLoggerProvider;const loggerCache=/* @__PURE__ */ new Map;var Logger=class Logger{_provider;_providerVersion;_name;_fields;static _providerVersion=0;constructor(name,fields){this._name=name,this._fields=fields,this._providerVersion=Logger._providerVersion,this._provider=this.derive()}derive(){return!this._name&&!this._fields?activeProvider:activeProvider.child({...this._fields,component:this._name??``})}get provider(){return this._providerVersion!==Logger._providerVersion&&(this._providerVersion=Logger._providerVersion,this._provider=this.derive()),this._provider}static setProvider(provider){activeProvider=provider,Logger._providerVersion++,loggerCache.clear()}static getProvider(){return activeProvider}static resetProvider(){activeProvider=new ConsoleLoggerProvider,Logger._providerVersion++,loggerCache.clear()}static for(name){let cached=loggerCache.get(name);return cached||(cached=new Logger(name),loggerCache.set(name,cached)),cached}child(nameOrFields){return typeof nameOrFields==`string`?new Logger(nameOrFields):new Logger(this._name,{...this._fields,...nameOrFields})}info(msg,...args){this.provider.info(msg,...args)}warn(msg,...args){this.provider.warn(msg,...args)}error(msgOrErr,...rest){if(typeof msgOrErr==`string`){this.provider.error(msgOrErr,...rest);return}let[maybeMsg,...extra]=rest;typeof maybeMsg==`string`?this.provider.error(maybeMsg,msgOrErr,...extra):this.provider.error(describeError(msgOrErr),msgOrErr,...rest)}debug(msg,...args){this.provider.debug(msg,...args)}trace(msg,...args){this.provider.trace?this.provider.trace(msg,...args):this.provider.debug(msg,...args)}fatal(msg,...args){this.provider.fatal?this.provider.fatal(msg,...args):this.provider.error(msg,...args)}};function describeError(err,maxDepth=4){let parts=[],seen=/* @__PURE__ */ new Set,current=err;for(let depth=0;current!=null&&depth<maxDepth;depth++){if(seen.has(current)){parts.push(`[circular cause]`);break}if(seen.add(current),current instanceof Error)parts.push(current.name?`${current.name}: ${current.message}`:current.message),current=current.cause;else{parts.push(typeof current==`string`?current:safeStringify(current));break}}return parts.length>0?parts.join(` ← caused by `):`Unknown error`}function safeStringify(value){try{return JSON.stringify(value)??String(value)}catch{return String(value)}}function createLogger(name){return Logger.for(name)}export{ConsoleLoggerProvider,Logger,createLogger,describeError,jsonLine,serializeError};
|
|
12
12
|
//# sourceMappingURL=logger.mjs.map
|
package/dist/core/logger.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.mjs","names":[],"sources":["../../src/core/logger.ts"],"sourcesContent":["// ── LoggerProvider interface ───────────────────────────────────────────\n\n/**\n * Pluggable logger backend.\n *\n * Implement this interface to plug in any logging library (pino,\n * winston, bunyan, console, etc.). The framework only ever calls the\n * methods declared here — it doesn't know or care which logger is\n * underneath.\n *\n * Default: `ConsoleLoggerProvider` (zero runtime deps).\n *\n * @example\n * ```ts\n * import pino from 'pino'\n * import { Logger, type LoggerProvider } from '@forinda/kickjs'\n *\n * class PinoProvider implements LoggerProvider {\n * constructor(private p = pino()) {}\n * info(msg, ...args) { this.p.info(msg, ...args) }\n * warn(msg, ...args) { this.p.warn(msg, ...args) }\n * error(msg, ...args) { this.p.error(msg, ...args) }\n * debug(msg, ...args) { this.p.debug(msg, ...args) }\n * child({ component }) { return new PinoProvider(this.p.child({ component })) }\n * }\n *\n * Logger.setProvider(new PinoProvider())\n * ```\n *\n * See `docs/guide/logging.md` for Pino, Winston, and silent-logger recipes.\n */\nexport interface LoggerProvider {\n info(msg: string, ...args: any[]): void\n warn(msg: string, ...args: any[]): void\n error(msg: string, ...args: any[]): void\n debug(msg: string, ...args: any[]): void\n trace?(msg: string, ...args: any[]): void\n fatal?(msg: string, ...args: any[]): void\n /**\n * Return a child provider scoped to the given component name. `bindings`\n * may also carry fields from `logger.child({ ... })` (a request id, a job\n * name) — a provider that ignores them keeps working as before.\n */\n child(bindings: { component: string; [field: string]: unknown }): LoggerProvider\n}\n\n// ── Log levels ─────────────────────────────────────────────────────────\n\n/** Severity ordering — a message prints only when its rank ≥ the threshold. */\nconst LEVEL_RANK: Record<string, number> = {\n trace: 10,\n debug: 20,\n info: 30,\n warn: 40,\n error: 50,\n fatal: 60,\n silent: 100,\n}\n\n/**\n * Resolve the active threshold rank from `LOG_LEVEL` (default `info`).\n * Read per-call so a late `process.env.LOG_LEVEL` assignment still\n * takes effect. An unrecognised value falls back to `info`.\n */\nfunction thresholdRank(): number {\n // `process` is guarded because this module is reachable from the\n // edge-safe web pipeline (`@forinda/kickjs/web`), where strict runtimes\n // expose no `process` global at all. The colour probe below already\n // guarded; this one did not, so merely importing the logger could throw\n // on such a runtime.\n const level = (\n (typeof process !== 'undefined' ? process.env?.LOG_LEVEL : undefined) ?? 'info'\n ).toLowerCase()\n return LEVEL_RANK[level] ?? LEVEL_RANK.info\n}\n\n/** `LOG_FORMAT=json` switches the default provider to one JSON object per line. */\nfunction jsonFormat(): boolean {\n return (\n (typeof process !== 'undefined' ? process.env?.LOG_FORMAT : undefined)?.toLowerCase() === 'json'\n )\n}\n\n/** A plain `{ ... }` object — merged into a JSON line as fields. */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') return false\n const proto = Object.getPrototypeOf(value)\n return proto === Object.prototype || proto === null\n}\n\n/**\n * An error as a JSON field — `type`, `message`, `stack`, own enumerable\n * properties (a driver's `code`), and its `cause` chain, depth-capped and\n * cycle-guarded like {@link describeError}.\n */\nexport function serializeError(err: unknown, depth = 4, seen = new Set<unknown>()): unknown {\n if (!(err instanceof Error)) return err\n if (seen.has(err) || depth <= 0) return '[circular cause]'\n seen.add(err)\n const out: Record<string, unknown> = { type: err.name, message: err.message, stack: err.stack }\n for (const [key, value] of Object.entries(err)) if (!(key in out)) out[key] = value\n const cause = (err as { cause?: unknown }).cause\n if (cause !== undefined) out.cause = serializeError(cause, depth - 1, seen)\n return out\n}\n\n/**\n * Fill printf-style placeholders the way `console.log` does (`%s %d %i %f\n * %j %o %O`, `%%`), then append what's left — so `log.info('hi %s', name)`\n * reads the same in JSON as on a terminal.\n */\nfunction formatMessage(msg: string, args: unknown[]): string {\n let i = 0\n const filled = msg.replace(/%[sdifjoO%]/g, (token) => {\n if (token === '%%') return '%'\n if (i >= args.length) return token\n const value = args[i++]\n if (token === '%d' || token === '%i') return String(Math.trunc(Number(value)))\n if (token === '%f') return String(Number(value))\n if (token === '%s') return typeof value === 'string' ? value : safeStringify(value)\n return safeStringify(value)\n })\n const rest = args.slice(i).map((a) => (typeof a === 'string' ? a : safeStringify(a)))\n return rest.length ? `${filled} ${rest.join(' ')}` : filled\n}\n\n/** Keys of a JSON line only the logger writes — see {@link jsonLine}. */\nconst RESERVED_FIELDS = new Set(['level', 'time', 'component', 'msg', 'err'])\n\n/**\n * One JSON log line, field-compatible with pino (`level` 10–60, `time` in\n * epoch ms, `msg`) so `pino-pretty` and log shippers read it as-is. Trailing\n * plain objects become fields, an `Error` becomes `err`, and anything else\n * fills the message's placeholders or is appended to it.\n */\nexport function jsonLine(\n level: keyof typeof LEVEL_RANK,\n component: string,\n bindings: Record<string, unknown>,\n msg: string,\n args: unknown[],\n): string {\n // Null prototype: a `__proto__` key in logged data stays a plain field.\n const fields: Record<string, unknown> = Object.create(null)\n const rest: unknown[] = []\n let err: unknown\n for (const arg of args) {\n if (arg instanceof Error) err ??= serializeError(arg)\n else if (isPlainObject(arg)) Object.assign(fields, arg)\n else rest.push(arg)\n }\n const line: Record<string, unknown> = {\n level: LEVEL_RANK[level],\n time: Date.now(),\n ...(component ? { component } : {}),\n }\n // Logged data is often untrusted (a request body): it must not rewrite the\n // line's level, time, source or message, or pass a plain object off as the\n // error. Those come from the logger only; a field with one of those names is\n // dropped.\n for (const source of [bindings, fields]) {\n for (const key of Object.keys(source)) {\n if (!RESERVED_FIELDS.has(key)) line[key] = source[key]\n }\n }\n if (err !== undefined) line.err = err\n line.msg = formatMessage(msg, rest)\n try {\n return JSON.stringify(line, bigintSafe)\n } catch {\n // Something can't be written (a circular field). Drop only that: every\n // other field stays, and `err` — usually why the line exists — keeps at\n // least its type, message and stack.\n const safe: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(line)) {\n try {\n JSON.stringify(value, bigintSafe)\n safe[key] = value\n } catch {\n safe[key] =\n key === 'err' && value && typeof value === 'object'\n ? pick(value as Record<string, unknown>, ['type', 'message', 'stack'])\n : '[unserializable]'\n }\n }\n return JSON.stringify(safe, bigintSafe)\n }\n}\n\nconst bigintSafe = (_key: string, value: unknown): unknown =>\n typeof value === 'bigint' ? value.toString() : value\n\nfunction pick(obj: Record<string, unknown>, keys: readonly string[]): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const key of keys) if (typeof obj[key] === 'string') out[key] = obj[key]\n return out\n}\n\n// ── ConsoleLoggerProvider (default) ────────────────────────────────────\n\n/**\n * Default provider — emits through `console.*` methods. Zero deps.\n *\n * Respects the `LOG_LEVEL` env var (default `info`): messages below the\n * threshold are dropped, so the verbose startup detail (the route table,\n * DI wiring, HMR ticks — all `debug`) stays quiet by default and only\n * appears with `LOG_LEVEL=debug`. `error`/`fatal` always print unless\n * `LOG_LEVEL=silent`.\n *\n * `LOG_FORMAT=json` writes one pino-compatible JSON object per line instead\n * (all levels through `console.log`, so one stream carries them): fields\n * from `logger.child({ ... })` and from trailing plain-object arguments, and\n * an `Error` argument as `err` with its stack and `cause` chain. The default\n * text output is unchanged.\n *\n * Swap it for pino, winston, bunyan, or anything else by implementing\n * the `LoggerProvider` interface and calling `Logger.setProvider()`\n * before `bootstrap()`.\n */\nexport class ConsoleLoggerProvider implements LoggerProvider {\n private prefix: string\n private useColor: boolean\n /** Fields from `logger.child({ ... })` — written on JSON lines. */\n private bindings: Record<string, unknown>\n\n constructor(prefix?: string, bindings: Record<string, unknown> = {}) {\n this.prefix = prefix ?? ''\n this.bindings = bindings\n this.useColor =\n typeof process !== 'undefined' && process.stdout?.isTTY === true && !process.env.NO_COLOR\n }\n\n /** Write `msg` as a JSON line when `LOG_FORMAT=json`; returns whether it did. */\n private json(level: keyof typeof LEVEL_RANK, msg: string, args: unknown[]): boolean {\n if (!jsonFormat()) return false\n console.log(jsonLine(level, this.prefix, this.bindings, msg, args))\n return true\n }\n\n private fmt(level: string, color: string, msg: string): string {\n const tag = this.prefix ? `[${this.prefix}]` : ''\n if (this.useColor) {\n return `${color}${level}\\x1b[0m ${tag ? `\\x1b[36m${tag}\\x1b[0m ` : ''}${msg}`\n }\n return `${level} ${tag ? `${tag} ` : ''}${msg}`\n }\n\n /** Should a message at `rank` print under the current `LOG_LEVEL`? */\n private enabled(rank: number): boolean {\n return rank >= thresholdRank()\n }\n\n info(msg: string, ...args: any[]) {\n if (!this.enabled(LEVEL_RANK.info) || this.json('info', msg, args)) return\n console.log(this.fmt('INFO', '\\x1b[32m', msg), ...args)\n }\n warn(msg: string, ...args: any[]) {\n if (!this.enabled(LEVEL_RANK.warn) || this.json('warn', msg, args)) return\n console.warn(this.fmt('WARN', '\\x1b[33m', msg), ...args)\n }\n error(msg: string, ...args: any[]) {\n if (!this.enabled(LEVEL_RANK.error) || this.json('error', msg, args)) return\n console.error(this.fmt('ERROR', '\\x1b[31m', msg), ...args)\n }\n debug(msg: string, ...args: any[]) {\n if (!this.enabled(LEVEL_RANK.debug) || this.json('debug', msg, args)) return\n console.debug(this.fmt('DEBUG', '\\x1b[90m', msg), ...args)\n }\n trace(msg: string, ...args: any[]) {\n if (!this.enabled(LEVEL_RANK.trace) || this.json('trace', msg, args)) return\n console.trace(this.fmt('TRACE', '\\x1b[90m', msg), ...args)\n }\n fatal(msg: string, ...args: any[]) {\n if (!this.enabled(LEVEL_RANK.fatal) || this.json('fatal', msg, args)) return\n console.error(this.fmt('FATAL', '\\x1b[1m\\x1b[31m', msg), ...args)\n }\n child(bindings: { component: string; [field: string]: unknown }): LoggerProvider {\n const { component, ...fields } = bindings\n return new ConsoleLoggerProvider(component, { ...this.bindings, ...fields })\n }\n}\n\n// ── Active provider ────────────────────────────────────────────────────\n\nlet activeProvider: LoggerProvider = new ConsoleLoggerProvider()\n\n/** Cache of named loggers to avoid creating duplicates */\nconst loggerCache = new Map<string, Logger>()\n\n/**\n * Named logger with component context.\n *\n * @example\n * ```ts\n * // Static factory — preferred\n * const log = Logger.for('UserService')\n * log.info('User created')\n * // Output: [UserService] User created\n *\n * // Shorthand function\n * const log = createLogger('OrderService')\n *\n * // Pluggable backend — see docs/guide/logging.md\n * Logger.setProvider(new MyCustomProvider())\n *\n * // Injectable — auto-named from class\n * class MyService {\n * @Autowired() private logger!: Logger\n * // or inject a named one:\n * private log = Logger.for('MyService')\n * }\n * ```\n */\nexport class Logger {\n private _provider: LoggerProvider\n private _providerVersion: number\n private _name?: string\n private _fields?: Record<string, unknown>\n\n /** Incremented on every setProvider/resetProvider so instances know to refresh */\n private static _providerVersion = 0\n\n constructor(name?: string, fields?: Record<string, unknown>) {\n this._name = name\n this._fields = fields\n this._providerVersion = Logger._providerVersion\n this._provider = this.derive()\n }\n\n /** The active provider's child for this logger's name and fields. */\n private derive(): LoggerProvider {\n if (!this._name && !this._fields) return activeProvider\n return activeProvider.child({ ...this._fields, component: this._name ?? '' })\n }\n\n /** Re-derive the provider if setProvider() was called since construction/last access */\n private get provider(): LoggerProvider {\n if (this._providerVersion !== Logger._providerVersion) {\n this._providerVersion = Logger._providerVersion\n this._provider = this.derive()\n }\n return this._provider\n }\n\n /**\n * Replace the logging backend for **all** Logger instances created after\n * this call. Clears the logger cache so subsequent `Logger.for()` calls\n * pick up the new provider.\n *\n * @example\n * ```ts\n * Logger.setProvider(new MyWinstonProvider())\n * ```\n */\n static setProvider(provider: LoggerProvider): void {\n activeProvider = provider\n Logger._providerVersion++\n loggerCache.clear()\n }\n\n /** Return the currently active provider (useful for testing) */\n static getProvider(): LoggerProvider {\n return activeProvider\n }\n\n /**\n * Reset the provider back to the default `ConsoleLoggerProvider`.\n * Primarily intended for test teardown.\n */\n static resetProvider(): void {\n activeProvider = new ConsoleLoggerProvider()\n Logger._providerVersion++\n loggerCache.clear()\n }\n\n /** Create or retrieve a cached named logger */\n static for(name: string): Logger {\n let cached = loggerCache.get(name)\n if (!cached) {\n cached = new Logger(name)\n loggerCache.set(name, cached)\n }\n return cached\n }\n\n /** Create a child logger with a sub-component name */\n child(name: string): Logger\n /**\n * Create a child logger carrying `fields` on every line it writes — a\n * request id, a job name. Keeps this logger's name and fields. Written as\n * JSON fields under `LOG_FORMAT=json`; the default text output is unchanged.\n */\n child(fields: Record<string, unknown>): Logger\n child(nameOrFields: string | Record<string, unknown>): Logger {\n if (typeof nameOrFields === 'string') return new Logger(nameOrFields)\n return new Logger(this._name, { ...this._fields, ...nameOrFields })\n }\n\n info(msg: string, ...args: any[]) {\n this.provider.info(msg, ...args)\n }\n\n warn(msg: string, ...args: any[]) {\n this.provider.warn(msg, ...args)\n }\n\n /** Pino-style error-first form — `log.error(err, 'what failed')`. */\n error(err: unknown, msg: string, ...args: any[]): void\n /** Message-first form — `log.error('what failed', detail)`. */\n error(msg: string, ...args: any[]): void\n /** Error alone — `log.error(err)`. */\n error(err: unknown): void\n error(msgOrErr: any, ...rest: any[]): void {\n if (typeof msgOrErr === 'string') {\n // Message-first. `rest` used to be dropped on the floor, so\n // `log.error('save failed', { id })` logged only the string.\n this.provider.error(msgOrErr, ...rest)\n return\n }\n\n // Error-first (the framework's own idiom at ~16 call sites). The\n // error object used to be discarded entirely — `provider.error(msg)`\n // with the error nowhere in the call — so every `log.error(err, msg)`\n // in the codebase logged a bare sentence with NO stack, no error\n // name, and no `cause` chain. That is what made an unexpected 500\n // undiagnosable from the logs: you got the message and nothing else.\n //\n // The error is now forwarded as a trailing argument, which is what\n // the msg-first `LoggerProvider` contract can carry. `console.error`\n // renders the full stack from it; a pino/winston adapter receives it\n // as structured extra.\n const [maybeMsg, ...extra] = rest\n if (typeof maybeMsg === 'string') {\n this.provider.error(maybeMsg, msgOrErr, ...extra)\n } else {\n // No message supplied — use the error's own summary as the line so\n // the log stays readable, and still pass the object for the stack.\n this.provider.error(describeError(msgOrErr), msgOrErr, ...rest)\n }\n }\n\n debug(msg: string, ...args: any[]) {\n this.provider.debug(msg, ...args)\n }\n\n trace(msg: string, ...args: any[]) {\n if (this.provider.trace) this.provider.trace(msg, ...args)\n else this.provider.debug(msg, ...args)\n }\n\n fatal(msg: string, ...args: any[]) {\n if (this.provider.fatal) this.provider.fatal(msg, ...args)\n else this.provider.error(msg, ...args)\n }\n}\n\n/**\n * One-line summary of an unknown thrown value, including the error name\n * and — when present — the `cause` chain. ORM and driver errors routinely\n * wrap the useful detail in `cause`, so dropping it is how a 500 ends up\n * saying nothing actionable.\n *\n * Depth-capped and cycle-guarded: `cause` is attacker-influenced in some\n * stacks and self-referential chains do occur.\n */\nexport function describeError(err: unknown, maxDepth = 4): string {\n const parts: string[] = []\n const seen = new Set<unknown>()\n let current: unknown = err\n for (let depth = 0; current !== undefined && current !== null && depth < maxDepth; depth++) {\n if (seen.has(current)) {\n parts.push('[circular cause]')\n break\n }\n seen.add(current)\n if (current instanceof Error) {\n parts.push(current.name ? `${current.name}: ${current.message}` : current.message)\n current = (current as { cause?: unknown }).cause\n } else {\n parts.push(typeof current === 'string' ? current : safeStringify(current))\n break\n }\n }\n return parts.length > 0 ? parts.join(' ← caused by ') : 'Unknown error'\n}\n\n/** `JSON.stringify` that never throws on circular / exotic values. */\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value)\n } catch {\n return String(value)\n }\n}\n\n/** Shorthand for Logger.for(name) */\nexport function createLogger(name: string): Logger {\n return Logger.for(name)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"logger.mjs","names":[],"sources":["../../src/core/logger.ts"],"sourcesContent":["// ── LoggerProvider interface ───────────────────────────────────────────\n\n/**\n * Pluggable logger backend.\n *\n * Implement this interface to plug in any logging library (pino,\n * winston, bunyan, console, etc.). The framework only ever calls the\n * methods declared here — it doesn't know or care which logger is\n * underneath.\n *\n * Default: `ConsoleLoggerProvider` (zero runtime deps).\n *\n * @example\n * ```ts\n * import pino from 'pino'\n * import { Logger, type LoggerProvider } from '@forinda/kickjs'\n *\n * class PinoProvider implements LoggerProvider {\n * constructor(private p = pino()) {}\n * info(msg, ...args) { this.p.info(msg, ...args) }\n * warn(msg, ...args) { this.p.warn(msg, ...args) }\n * error(msg, ...args) { this.p.error(msg, ...args) }\n * debug(msg, ...args) { this.p.debug(msg, ...args) }\n * child({ component }) { return new PinoProvider(this.p.child({ component })) }\n * }\n *\n * Logger.setProvider(new PinoProvider())\n * ```\n *\n * See `docs/guide/logging.md` for Pino, Winston, and silent-logger recipes.\n */\nexport interface LoggerProvider {\n info(msg: string, ...args: any[]): void\n warn(msg: string, ...args: any[]): void\n error(msg: string, ...args: any[]): void\n debug(msg: string, ...args: any[]): void\n trace?(msg: string, ...args: any[]): void\n fatal?(msg: string, ...args: any[]): void\n /**\n * Return a child provider scoped to the given component name. `bindings`\n * may also carry fields from `logger.child({ ... })` (a request id, a job\n * name) — a provider that ignores them keeps working as before.\n */\n child(bindings: { component: string; [field: string]: unknown }): LoggerProvider\n}\n\n// ── Log levels ─────────────────────────────────────────────────────────\n\n/** Severity ordering — a message prints only when its rank ≥ the threshold. */\nconst LEVEL_RANK: Record<string, number> = {\n trace: 10,\n debug: 20,\n info: 30,\n warn: 40,\n error: 50,\n fatal: 60,\n silent: 100,\n}\n\n/**\n * Resolve the active threshold rank from `LOG_LEVEL` (default `info`).\n * Read per-call so a late `process.env.LOG_LEVEL` assignment still\n * takes effect. An unrecognised value falls back to `info`.\n */\nfunction thresholdRank(): number {\n // `process` is guarded because this module is reachable from the\n // edge-safe web pipeline (`@forinda/kickjs/web`), where strict runtimes\n // expose no `process` global at all. The colour probe below already\n // guarded; this one did not, so merely importing the logger could throw\n // on such a runtime.\n const level = (\n (typeof process !== 'undefined' ? process.env?.LOG_LEVEL : undefined) ?? 'info'\n ).toLowerCase()\n return LEVEL_RANK[level] ?? LEVEL_RANK.info\n}\n\n/** `LOG_FORMAT=json` switches the default provider to one JSON object per line. */\nfunction jsonFormat(): boolean {\n return (\n (typeof process !== 'undefined' ? process.env?.LOG_FORMAT : undefined)?.toLowerCase() === 'json'\n )\n}\n\n/** A plain `{ ... }` object — merged into a JSON line as fields. */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') return false\n const proto = Object.getPrototypeOf(value)\n return proto === Object.prototype || proto === null\n}\n\n/**\n * An error as a JSON field — `type`, `message`, `stack`, own enumerable\n * properties (a driver's `code`), and its `cause` chain, depth-capped and\n * cycle-guarded like {@link describeError}.\n */\nexport function serializeError(err: unknown, depth = 4, seen = new Set<unknown>()): unknown {\n if (!(err instanceof Error)) return err\n if (seen.has(err) || depth <= 0) return '[circular cause]'\n seen.add(err)\n const out: Record<string, unknown> = { type: err.name, message: err.message, stack: err.stack }\n for (const [key, value] of Object.entries(err)) if (!(key in out)) out[key] = value\n const cause = (err as { cause?: unknown }).cause\n if (cause !== undefined) out.cause = serializeError(cause, depth - 1, seen)\n return out\n}\n\n/**\n * Fill printf-style placeholders the way `console.log` does (`%s %d %i %f\n * %j %o %O`, `%%`), then append what's left — so `log.info('hi %s', name)`\n * reads the same in JSON as on a terminal.\n */\nfunction formatMessage(msg: string, args: unknown[]): string {\n let i = 0\n const filled = msg.replace(/%[sdifjoO%]/g, (token) => {\n if (token === '%%') return '%'\n if (i >= args.length) return token\n const value = args[i++]\n if (token === '%d' || token === '%i') return String(Math.trunc(Number(value)))\n if (token === '%f') return String(Number(value))\n if (token === '%s') return typeof value === 'string' ? value : safeStringify(value)\n return safeStringify(value)\n })\n const rest = args.slice(i).map((a) => (typeof a === 'string' ? a : safeStringify(a)))\n return rest.length ? `${filled} ${rest.join(' ')}` : filled\n}\n\n/** Keys of a JSON line only the logger writes — see {@link jsonLine}. */\nconst RESERVED_FIELDS = new Set(['level', 'time', 'component', 'msg', 'err'])\n\n/**\n * One JSON log line, field-compatible with pino (`level` 10–60, `time` in\n * epoch ms, `msg`) so `pino-pretty` and log shippers read it as-is. Trailing\n * plain objects become fields, an `Error` becomes `err`, and anything else\n * fills the message's placeholders or is appended to it.\n */\nexport function jsonLine(\n level: keyof typeof LEVEL_RANK,\n component: string,\n bindings: Record<string, unknown>,\n msg: string,\n args: unknown[],\n): string {\n // Null prototype: a `__proto__` key in logged data stays a plain field.\n const fields: Record<string, unknown> = Object.create(null)\n const rest: unknown[] = []\n let err: unknown\n for (const arg of args) {\n if (arg instanceof Error) err ??= serializeError(arg)\n else if (isPlainObject(arg)) Object.assign(fields, arg)\n else rest.push(arg)\n }\n const line: Record<string, unknown> = {\n level: LEVEL_RANK[level],\n time: Date.now(),\n ...(component ? { component } : {}),\n }\n // Logged data is often untrusted (a request body): it must not rewrite the\n // line's level, time, source or message, or pass a plain object off as the\n // error. Those come from the logger only; a field with one of those names is\n // dropped.\n for (const source of [bindings, fields]) {\n for (const key of Object.keys(source)) {\n if (!RESERVED_FIELDS.has(key)) line[key] = source[key]\n }\n }\n if (err !== undefined) line.err = err\n line.msg = formatMessage(msg, rest)\n try {\n return JSON.stringify(line, bigintSafe)\n } catch {\n // Something can't be written (a circular field). Drop only that: every\n // other field stays, and `err` — usually why the line exists — keeps at\n // least its type, message and stack.\n const safe: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(line)) {\n try {\n JSON.stringify(value, bigintSafe)\n safe[key] = value\n } catch {\n safe[key] =\n key === 'err' && value && typeof value === 'object'\n ? pick(value as Record<string, unknown>, ['type', 'message', 'stack'])\n : '[unserializable]'\n }\n }\n return JSON.stringify(safe, bigintSafe)\n }\n}\n\nconst bigintSafe = (_key: string, value: unknown): unknown =>\n typeof value === 'bigint' ? value.toString() : value\n\nfunction pick(obj: Record<string, unknown>, keys: readonly string[]): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const key of keys) if (typeof obj[key] === 'string') out[key] = obj[key]\n return out\n}\n\n// ── ConsoleLoggerProvider (default) ────────────────────────────────────\n\n/**\n * Default provider — emits through `console.*` methods. Zero deps.\n *\n * Respects the `LOG_LEVEL` env var (default `info`): messages below the\n * threshold are dropped, so the verbose startup detail (the route table,\n * DI wiring, HMR ticks — all `debug`) stays quiet by default and only\n * appears with `LOG_LEVEL=debug`. `error`/`fatal` always print unless\n * `LOG_LEVEL=silent`.\n *\n * `LOG_FORMAT=json` writes one pino-compatible JSON object per line instead\n * (all levels through `console.log`, so one stream carries them): fields\n * from `logger.child({ ... })` and from trailing plain-object arguments, and\n * an `Error` argument as `err` with its stack and `cause` chain. The default\n * text output is unchanged.\n *\n * Swap it for pino, winston, bunyan, or anything else by implementing\n * the `LoggerProvider` interface and calling `Logger.setProvider()`\n * before `bootstrap()`.\n */\nexport class ConsoleLoggerProvider implements LoggerProvider {\n private prefix: string\n private useColor: boolean\n /** Fields from `logger.child({ ... })` — written on JSON lines. */\n private bindings: Record<string, unknown>\n\n constructor(prefix?: string, bindings: Record<string, unknown> = {}) {\n this.prefix = prefix ?? ''\n this.bindings = bindings\n this.useColor =\n typeof process !== 'undefined' && process.stdout?.isTTY === true && !process.env.NO_COLOR\n }\n\n /** Write `msg` as a JSON line when `LOG_FORMAT=json`; returns whether it did. */\n private json(level: keyof typeof LEVEL_RANK, msg: string, args: unknown[]): boolean {\n if (!jsonFormat()) return false\n console.log(jsonLine(level, this.prefix, this.bindings, msg, args))\n return true\n }\n\n private fmt(level: string, color: string, msg: string): string {\n const tag = this.prefix ? `[${this.prefix}]` : ''\n if (this.useColor) {\n return `${color}${level}\\x1b[0m ${tag ? `\\x1b[36m${tag}\\x1b[0m ` : ''}${msg}`\n }\n return `${level} ${tag ? `${tag} ` : ''}${msg}`\n }\n\n /** Should a message at `rank` print under the current `LOG_LEVEL`? */\n private enabled(rank: number): boolean {\n return rank >= thresholdRank()\n }\n\n info(msg: string, ...args: any[]) {\n if (!this.enabled(LEVEL_RANK.info) || this.json('info', msg, args)) return\n console.log(this.fmt('INFO', '\\x1b[32m', msg), ...args)\n }\n warn(msg: string, ...args: any[]) {\n if (!this.enabled(LEVEL_RANK.warn) || this.json('warn', msg, args)) return\n console.warn(this.fmt('WARN', '\\x1b[33m', msg), ...args)\n }\n error(msg: string, ...args: any[]) {\n if (!this.enabled(LEVEL_RANK.error) || this.json('error', msg, args)) return\n console.error(this.fmt('ERROR', '\\x1b[31m', msg), ...args)\n }\n debug(msg: string, ...args: any[]) {\n if (!this.enabled(LEVEL_RANK.debug) || this.json('debug', msg, args)) return\n console.debug(this.fmt('DEBUG', '\\x1b[90m', msg), ...args)\n }\n trace(msg: string, ...args: any[]) {\n if (!this.enabled(LEVEL_RANK.trace) || this.json('trace', msg, args)) return\n console.trace(this.fmt('TRACE', '\\x1b[90m', msg), ...args)\n }\n fatal(msg: string, ...args: any[]) {\n if (!this.enabled(LEVEL_RANK.fatal) || this.json('fatal', msg, args)) return\n console.error(this.fmt('FATAL', '\\x1b[1m\\x1b[31m', msg), ...args)\n }\n child(bindings: { component: string; [field: string]: unknown }): LoggerProvider {\n const { component, ...fields } = bindings\n return new ConsoleLoggerProvider(component, { ...this.bindings, ...fields })\n }\n}\n\n// ── Active provider ────────────────────────────────────────────────────\n\nlet activeProvider: LoggerProvider = new ConsoleLoggerProvider()\n\n/** Cache of named loggers to avoid creating duplicates */\nconst loggerCache = new Map<string, Logger>()\n\n/**\n * Named logger with component context.\n *\n * @example\n * ```ts\n * // Static factory — preferred\n * const log = Logger.for('UserService')\n * log.info('User created')\n * // Output: [UserService] User created\n *\n * // Shorthand function\n * const log = createLogger('OrderService')\n *\n * // Pluggable backend — see docs/guide/logging.md\n * Logger.setProvider(new MyCustomProvider())\n *\n * // Injectable — auto-named from class\n * class MyService {\n * @Autowired() private logger!: Logger\n * // or inject a named one:\n * private log = Logger.for('MyService')\n * }\n * ```\n */\nexport class Logger {\n private _provider: LoggerProvider\n private _providerVersion: number\n private _name?: string\n private _fields?: Record<string, unknown>\n\n /** Incremented on every setProvider/resetProvider so instances know to refresh */\n private static _providerVersion = 0\n\n constructor(name?: string, fields?: Record<string, unknown>) {\n this._name = name\n this._fields = fields\n this._providerVersion = Logger._providerVersion\n this._provider = this.derive()\n }\n\n /** The active provider's child for this logger's name and fields. */\n private derive(): LoggerProvider {\n if (!this._name && !this._fields) return activeProvider\n return activeProvider.child({ ...this._fields, component: this._name ?? '' })\n }\n\n /** Re-derive the provider if setProvider() was called since construction/last access */\n private get provider(): LoggerProvider {\n if (this._providerVersion !== Logger._providerVersion) {\n this._providerVersion = Logger._providerVersion\n this._provider = this.derive()\n }\n return this._provider\n }\n\n /**\n * Replace the logging backend for **all** Logger instances created after\n * this call. Clears the logger cache so subsequent `Logger.for()` calls\n * pick up the new provider.\n *\n * @example\n * ```ts\n * Logger.setProvider(new MyWinstonProvider())\n * ```\n */\n static setProvider(provider: LoggerProvider): void {\n activeProvider = provider\n Logger._providerVersion++\n loggerCache.clear()\n }\n\n /** Return the currently active provider (useful for testing) */\n static getProvider(): LoggerProvider {\n return activeProvider\n }\n\n /**\n * Reset the provider back to the default `ConsoleLoggerProvider`.\n * Primarily intended for test teardown.\n */\n static resetProvider(): void {\n activeProvider = new ConsoleLoggerProvider()\n Logger._providerVersion++\n loggerCache.clear()\n }\n\n /** Create or retrieve a cached named logger */\n static for(name: string): Logger {\n let cached = loggerCache.get(name)\n if (!cached) {\n cached = new Logger(name)\n loggerCache.set(name, cached)\n }\n return cached\n }\n\n /** Create a child logger with a sub-component name */\n child(name: string): Logger\n /**\n * Create a child logger carrying `fields` on every line it writes — a\n * request id, a job name. Keeps this logger's name and fields. Written as\n * JSON fields under `LOG_FORMAT=json`; the default text output is unchanged.\n */\n child(fields: Record<string, unknown>): Logger\n child(nameOrFields: string | Record<string, unknown>): Logger {\n if (typeof nameOrFields === 'string') return new Logger(nameOrFields)\n return new Logger(this._name, { ...this._fields, ...nameOrFields })\n }\n\n info(msg: string, ...args: any[]) {\n this.provider.info(msg, ...args)\n }\n\n warn(msg: string, ...args: any[]) {\n this.provider.warn(msg, ...args)\n }\n\n /** Pino-style error-first form — `log.error(err, 'what failed')`. */\n error(err: unknown, msg: string, ...args: any[]): void\n /** Message-first form — `log.error('what failed', detail)`. */\n error(msg: string, ...args: any[]): void\n /** Error alone — `log.error(err)`. */\n error(err: unknown): void\n error(msgOrErr: any, ...rest: any[]): void {\n if (typeof msgOrErr === 'string') {\n // Message-first. `rest` used to be dropped on the floor, so\n // `log.error('save failed', { id })` logged only the string.\n this.provider.error(msgOrErr, ...rest)\n return\n }\n\n // Error-first (the framework's own idiom at ~16 call sites). The\n // error object used to be discarded entirely — `provider.error(msg)`\n // with the error nowhere in the call — so every `log.error(err, msg)`\n // in the codebase logged a bare sentence with NO stack, no error\n // name, and no `cause` chain. That is what made an unexpected 500\n // undiagnosable from the logs: you got the message and nothing else.\n //\n // The error is now forwarded as a trailing argument, which is what\n // the msg-first `LoggerProvider` contract can carry. `console.error`\n // renders the full stack from it; a pino/winston adapter receives it\n // as structured extra.\n const [maybeMsg, ...extra] = rest\n if (typeof maybeMsg === 'string') {\n this.provider.error(maybeMsg, msgOrErr, ...extra)\n } else {\n // No message supplied — use the error's own summary as the line so\n // the log stays readable, and still pass the object for the stack.\n this.provider.error(describeError(msgOrErr), msgOrErr, ...rest)\n }\n }\n\n debug(msg: string, ...args: any[]) {\n this.provider.debug(msg, ...args)\n }\n\n trace(msg: string, ...args: any[]) {\n if (this.provider.trace) this.provider.trace(msg, ...args)\n else this.provider.debug(msg, ...args)\n }\n\n fatal(msg: string, ...args: any[]) {\n if (this.provider.fatal) this.provider.fatal(msg, ...args)\n else this.provider.error(msg, ...args)\n }\n}\n\n/**\n * One-line summary of an unknown thrown value, including the error name\n * and — when present — the `cause` chain. ORM and driver errors routinely\n * wrap the useful detail in `cause`, so dropping it is how a 500 ends up\n * saying nothing actionable.\n *\n * Depth-capped and cycle-guarded: `cause` is attacker-influenced in some\n * stacks and self-referential chains do occur.\n */\nexport function describeError(err: unknown, maxDepth = 4): string {\n const parts: string[] = []\n const seen = new Set<unknown>()\n let current: unknown = err\n for (let depth = 0; current !== undefined && current !== null && depth < maxDepth; depth++) {\n if (seen.has(current)) {\n parts.push('[circular cause]')\n break\n }\n seen.add(current)\n if (current instanceof Error) {\n parts.push(current.name ? `${current.name}: ${current.message}` : current.message)\n current = (current as { cause?: unknown }).cause\n } else {\n parts.push(typeof current === 'string' ? current : safeStringify(current))\n break\n }\n }\n return parts.length > 0 ? parts.join(' ← caused by ') : 'Unknown error'\n}\n\n/** `JSON.stringify` that never throws on circular / exotic values. */\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value)\n } catch {\n return String(value)\n }\n}\n\n/** Shorthand for Logger.for(name) */\nexport function createLogger(name: string): Logger {\n return Logger.for(name)\n}\n"],"mappings":"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"}
|
package/dist/core/path.mjs
CHANGED
package/dist/core/plugin.d.mts
CHANGED
package/dist/core/plugin.mjs
CHANGED
package/dist/core/reactivity.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs
|
|
2
|
+
* @forinda/kickjs v10.0.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
let activeEffect=null;const targetMap
|
|
11
|
+
let activeEffect=null;const targetMap=/* @__PURE__ */ new WeakMap;function createEffect(fn){let effect=fn;return effect.deps=/* @__PURE__ */ new Set,effect}function cleanupEffect(effect){for(let depSet of effect.deps)depSet.delete(effect);effect.deps.clear()}function runTracked(effect,fn){cleanupEffect(effect);let prev=activeEffect;activeEffect=effect;try{return fn()}finally{activeEffect=prev}}function track(target,key){if(!activeEffect)return;let depsMap=targetMap.get(target);depsMap||(depsMap=/* @__PURE__ */ new Map,targetMap.set(target,depsMap));let deps=depsMap.get(key);deps||(deps=/* @__PURE__ */ new Set,depsMap.set(key,deps)),deps.add(activeEffect),activeEffect.deps.add(deps)}function trigger(target,key){let depsMap=targetMap.get(target);if(!depsMap)return;let deps=depsMap.get(key);if(deps&&deps.size!==0)for(let effect of Array.from(deps))effect()}function ref(initialValue){let subscribers=/* @__PURE__ */ new Set,wrapper={_value:initialValue};return{get value(){return track(wrapper,`_value`),wrapper._value},set value(newValue){let oldValue=wrapper._value;if(!Object.is(oldValue,newValue)){wrapper._value=newValue,trigger(wrapper,`_value`);for(let fn of subscribers)fn(newValue,oldValue)}},subscribe(fn){return subscribers.add(fn),()=>subscribers.delete(fn)},toJSON(){return wrapper._value}}}function computed(getter){let cached,dirty=!0,disposed=!1,wrapper={_value:void 0},effect=createEffect(()=>{dirty=!0,trigger(wrapper,`_value`)}),recompute=()=>{if(disposed){let prev=activeEffect;activeEffect=null;try{return getter()}finally{activeEffect=prev}}return dirty&&=(cached=runTracked(effect,getter),wrapper._value=cached,!1),cached};return{get value(){return track(wrapper,`_value`),recompute()},dispose(){disposed=!0,cleanupEffect(effect)},toJSON(){return recompute()}}}function watch(source,callback,options){let oldValue,stopped=!1,getter=typeof source==`function`?source:()=>source.value,job=createEffect(()=>{if(stopped)return;let newValue=runTracked(job,getter);Object.is(newValue,oldValue)||(callback(newValue,oldValue),oldValue=newValue)});return oldValue=runTracked(job,getter),options?.immediate&&callback(oldValue,void 0),()=>{stopped=!0,cleanupEffect(job)}}const proxyCache=/* @__PURE__ */ new WeakMap;function reactive(target){let existing=proxyCache.get(target);if(existing)return existing;let proxy=new Proxy(target,{get(obj,key,receiver){let value=Reflect.get(obj,key,receiver);return typeof key==`symbol`?value:(track(obj,key),typeof value==`object`&&value&&!Array.isArray(value)?reactive(value):value)},set(obj,key,value,receiver){let oldValue=Reflect.get(obj,key,receiver),result=Reflect.set(obj,key,value,receiver);return!Object.is(oldValue,value)&&typeof key!=`symbol`&&trigger(obj,key),result}});return proxyCache.set(target,proxy),proxy}function isRef(value){return typeof value==`object`&&!!value&&`subscribe`in value&&`value`in value}function unref(value){return isRef(value)?value.value:value}function toRefs(obj){let result={};for(let key in obj)result[key]=ref(obj[key]);return result}export{computed,isRef,reactive,ref,toRefs,unref,watch};
|
|
12
12
|
//# sourceMappingURL=reactivity.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reactivity.mjs","names":[],"sources":["../../src/core/reactivity.ts"],"sourcesContent":["/**\n * Lightweight reactivity system inspired by Vue 3's composition API.\n * Provides ref(), computed(), watch(), and reactive() for observable\n * backend state — config, metrics, health, circuit breakers.\n */\n\n// ── Dependency Tracking ─────────────────────────────────────────────────\n//\n// Effects carry back-references to every dep Set they were added to, so\n// stopping a watcher / disposing a computed — or simply re-running an\n// effect whose getter reads different keys this time — removes it from the\n// old Sets instead of leaving dead subscriptions to accumulate (and be\n// invoked!) forever. Same cleanup discipline as Vue 3's ReactiveEffect.\n\ninterface Effect {\n (): void\n /** Dep Sets this effect is currently registered in (for cleanup). */\n deps: Set<Set<Effect>>\n}\n\nlet activeEffect: Effect | null = null\nconst targetMap = new WeakMap<object, Map<string | symbol, Set<Effect>>>()\n\nfunction createEffect(fn: () => void): Effect {\n const effect = fn as Effect\n effect.deps = new Set()\n return effect\n}\n\n/** Remove the effect from every dep Set it was tracked into. */\nfunction cleanupEffect(effect: Effect): void {\n for (const depSet of effect.deps) depSet.delete(effect)\n effect.deps.clear()\n}\n\n/**\n * Run `fn` with `effect` as the active tracking target, first clearing the\n * effect's previous subscriptions so conditional reads don't leave stale deps.\n */\nfunction runTracked<T>(effect: Effect, fn: () => T): T {\n cleanupEffect(effect)\n const prev = activeEffect\n activeEffect = effect\n try {\n return fn()\n } finally {\n activeEffect = prev\n }\n}\n\nfunction track(target: object, key: string | symbol): void {\n if (!activeEffect) return\n let depsMap = targetMap.get(target)\n if (!depsMap) {\n depsMap = new Map()\n targetMap.set(target, depsMap)\n }\n let deps = depsMap.get(key)\n if (!deps) {\n deps = new Set()\n depsMap.set(key, deps)\n }\n deps.add(activeEffect)\n activeEffect.deps.add(deps)\n}\n\nfunction trigger(target: object, key: string | symbol): void {\n const depsMap = targetMap.get(target)\n if (!depsMap) return\n const deps = depsMap.get(key)\n if (!deps || deps.size === 0) return\n // Iterate a snapshot — a triggered effect re-tracks (mutating `deps`)\n // during its run, which would otherwise corrupt this iteration.\n for (const effect of Array.from(deps)) {\n effect()\n }\n}\n\n// ── ref() ────────────────────────────────────────────────────────────────\n\nexport interface Ref<T = any> {\n value: T\n /** Subscribe to changes. Returns unsubscribe function. */\n subscribe(fn: (newValue: T, oldValue: T) => void): () => void\n /**\n * Auto-unwrap on `JSON.stringify` — returns the underlying value\n * so refs serialize transparently inside larger payloads (introspect\n * snapshots, devtools state, request responses). Equivalent to\n * `unref(ref)` but invoked implicitly by the JSON pipeline.\n */\n toJSON(): T\n}\n\n/**\n * Create a reactive reference. Reading `.value` tracks the dependency,\n * writing `.value` triggers watchers and computed recalculations.\n *\n * @example\n * ```ts\n * const count = ref(0)\n * count.value++ // triggers watchers\n * ```\n */\nexport function ref<T>(initialValue: T): Ref<T> {\n const subscribers = new Set<(newValue: T, oldValue: T) => void>()\n const wrapper = { _value: initialValue }\n\n return {\n get value(): T {\n track(wrapper, '_value')\n return wrapper._value\n },\n set value(newValue: T) {\n const oldValue = wrapper._value\n if (Object.is(oldValue, newValue)) return\n wrapper._value = newValue\n trigger(wrapper, '_value')\n for (const fn of subscribers) {\n fn(newValue, oldValue)\n }\n },\n subscribe(fn: (newValue: T, oldValue: T) => void): () => void {\n subscribers.add(fn)\n return () => subscribers.delete(fn)\n },\n /**\n * Auto-unwrap on `JSON.stringify`. Without this, serializing a ref\n * leaks the wrapper shape (`{\"value\": …, \"subscribe\": …}`) instead\n * of the underlying value — particularly painful for adopters who\n * keep adapter / plugin state in refs and surface it through\n * `introspect()`. Returning `value` here means\n * `JSON.stringify({ count: ref(0) })` produces `{\"count\":0}` as\n * expected, without callers having to `.value`-unwrap by hand.\n */\n toJSON(): T {\n return wrapper._value\n },\n }\n}\n\n// ── computed() ───────────────────────────────────────────────────────────\n\nexport interface ComputedRef<T = any> {\n readonly value: T\n /**\n * Auto-unwrap on `JSON.stringify` — see {@link Ref.toJSON} for the\n * motivation. Triggers a recompute when the cached value is stale,\n * same as reading `.value`.\n */\n toJSON(): T\n /**\n * Unsubscribe this computed from its reactive sources. Call when the\n * computed's lifetime is shorter than its sources' (e.g. created\n * per-request/per-tenant against app-lifetime refs) — without it the\n * internal effect stays pinned in the sources' dep Sets forever.\n * After disposal, reading `.value` still works but recomputes on every\n * access and no longer tracks.\n */\n dispose(): void\n}\n\n/**\n * Create a computed value that auto-recalculates when its reactive\n * dependencies change. Cached until a dependency triggers.\n *\n * @example\n * ```ts\n * const count = ref(0)\n * const doubled = computed(() => count.value * 2)\n * count.value = 5\n * console.log(doubled.value) // 10\n * ```\n */\nexport function computed<T>(getter: () => T): ComputedRef<T> {\n let cached: T\n let dirty = true\n let disposed = false\n const wrapper = { _value: undefined as T }\n\n const effect = createEffect(() => {\n dirty = true\n trigger(wrapper, '_value')\n })\n\n const recompute = (): T => {\n // Disposed: plain evaluation with tracking SUSPENDED — neither this\n // computed's effect nor any OUTER active effect (a watcher reading\n // `.value`) may attach to the sources through a disposed computed;\n // otherwise disposal wouldn't actually sever the subscription chain.\n if (disposed) {\n const prev = activeEffect\n activeEffect = null\n try {\n return getter()\n } finally {\n activeEffect = prev\n }\n }\n if (dirty) {\n cached = runTracked(effect, getter)\n wrapper._value = cached\n dirty = false\n }\n return cached\n }\n\n return {\n get value(): T {\n track(wrapper, '_value')\n return recompute()\n },\n dispose(): void {\n disposed = true\n cleanupEffect(effect)\n },\n /**\n * Auto-unwrap on `JSON.stringify`, matching {@link ref}'s behavior\n * so adopters can drop a computed straight into a JSON-bound\n * payload (introspect snapshot, devtools state) and get the value\n * rather than the wrapper shape.\n *\n * Computed values cache through the same dirty-flag the getter\n * uses, so calling `toJSON` is at worst one recompute when stale —\n * identical cost to reading `.value`.\n */\n toJSON(): T {\n return recompute()\n },\n }\n}\n\n// ── watch() ──────────────────────────────────────────────────────────────\n\nexport interface WatchOptions {\n /** Run the callback immediately with the current value (default: false) */\n immediate?: boolean\n}\n\ntype WatchSource<T> = Ref<T> | ComputedRef<T> | (() => T)\n\n/**\n * Watch a reactive source and run a callback when it changes.\n * Returns a stop function to unsubscribe.\n *\n * @example\n * ```ts\n * const count = ref(0)\n * const stop = watch(count, (newVal, oldVal) => {\n * console.log(`Changed from ${oldVal} to ${newVal}`)\n * })\n * count.value = 1 // logs: \"Changed from 0 to 1\"\n * stop() // no more callbacks\n * ```\n */\nexport function watch<T>(\n source: WatchSource<T>,\n callback: (newValue: T, oldValue: T) => void,\n options?: WatchOptions,\n): () => void {\n let oldValue: T\n let stopped = false\n\n const getter = typeof source === 'function' ? source : () => source.value\n\n const job = createEffect(() => {\n if (stopped) return\n // Re-run the getter UNDER tracking so deps are refreshed each run —\n // a conditional getter (`cond ? a.value : b.value`) drops the branch\n // it no longer reads instead of staying subscribed to it forever.\n const newValue = runTracked(job, getter)\n if (!Object.is(newValue, oldValue)) {\n callback(newValue, oldValue)\n oldValue = newValue\n }\n })\n\n // Initial run to establish tracking\n oldValue = runTracked(job, getter)\n\n if (options?.immediate) {\n callback(oldValue, undefined as T)\n }\n\n return () => {\n // Detach from every dep Set — a flag alone would leave the dead effect\n // referenced (and invoked) by its sources for their entire lifetime.\n stopped = true\n cleanupEffect(job)\n }\n}\n\n// ── reactive() ───────────────────────────────────────────────────────────\n\n/**\n * Create a deeply reactive proxy around a plain object. Property reads\n * are tracked, property writes trigger watchers and computed recalculations.\n *\n * @example\n * ```ts\n * const state = reactive({ users: 0, errors: 0 })\n * const errorRate = computed(() =>\n * state.users > 0 ? state.errors / state.users : 0\n * )\n * state.users = 100\n * state.errors = 5\n * console.log(errorRate.value) // 0.05\n * ```\n */\n// Memoize proxies per target: repeated reads of the same nested object must\n// return the SAME proxy — otherwise every access allocates a fresh Proxy and\n// referential identity breaks (`state.nested !== state.nested`).\nconst proxyCache = new WeakMap<object, any>()\n\nexport function reactive<T extends Record<string, any>>(target: T): T {\n const existing = proxyCache.get(target)\n if (existing) return existing as T\n const proxy = new Proxy(target, {\n get(obj, key, receiver) {\n const value = Reflect.get(obj, key, receiver)\n if (typeof key === 'symbol') return value\n track(obj, key)\n // Deep reactivity for nested objects\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n return reactive(value)\n }\n return value\n },\n set(obj, key, value, receiver) {\n const oldValue = Reflect.get(obj, key, receiver)\n const result = Reflect.set(obj, key, value, receiver)\n if (!Object.is(oldValue, value) && typeof key !== 'symbol') {\n trigger(obj, key)\n }\n return result\n },\n })\n proxyCache.set(target, proxy)\n return proxy\n}\n\n// ── Utility ──────────────────────────────────────────────────────────────\n\n/** Check if a value is a Ref */\nexport function isRef(value: any): value is Ref {\n return value !== null && typeof value === 'object' && 'subscribe' in value && 'value' in value\n}\n\n/** Unwrap a ref or return the value as-is */\nexport function unref<T>(value: Ref<T> | T): T {\n return isRef(value) ? value.value : value\n}\n\n/** Convert all properties of an object to refs */\nexport function toRefs<T extends Record<string, any>>(obj: T): { [K in keyof T]: Ref<T[K]> } {\n const result = {} as any\n for (const key in obj) {\n result[key] = ref(obj[key])\n }\n return result\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reactivity.mjs","names":[],"sources":["../../src/core/reactivity.ts"],"sourcesContent":["/**\n * Lightweight reactivity system inspired by Vue 3's composition API.\n * Provides ref(), computed(), watch(), and reactive() for observable\n * backend state — config, metrics, health, circuit breakers.\n */\n\n// ── Dependency Tracking ─────────────────────────────────────────────────\n//\n// Effects carry back-references to every dep Set they were added to, so\n// stopping a watcher / disposing a computed — or simply re-running an\n// effect whose getter reads different keys this time — removes it from the\n// old Sets instead of leaving dead subscriptions to accumulate (and be\n// invoked!) forever. Same cleanup discipline as Vue 3's ReactiveEffect.\n\ninterface Effect {\n (): void\n /** Dep Sets this effect is currently registered in (for cleanup). */\n deps: Set<Set<Effect>>\n}\n\nlet activeEffect: Effect | null = null\nconst targetMap = new WeakMap<object, Map<string | symbol, Set<Effect>>>()\n\nfunction createEffect(fn: () => void): Effect {\n const effect = fn as Effect\n effect.deps = new Set()\n return effect\n}\n\n/** Remove the effect from every dep Set it was tracked into. */\nfunction cleanupEffect(effect: Effect): void {\n for (const depSet of effect.deps) depSet.delete(effect)\n effect.deps.clear()\n}\n\n/**\n * Run `fn` with `effect` as the active tracking target, first clearing the\n * effect's previous subscriptions so conditional reads don't leave stale deps.\n */\nfunction runTracked<T>(effect: Effect, fn: () => T): T {\n cleanupEffect(effect)\n const prev = activeEffect\n activeEffect = effect\n try {\n return fn()\n } finally {\n activeEffect = prev\n }\n}\n\nfunction track(target: object, key: string | symbol): void {\n if (!activeEffect) return\n let depsMap = targetMap.get(target)\n if (!depsMap) {\n depsMap = new Map()\n targetMap.set(target, depsMap)\n }\n let deps = depsMap.get(key)\n if (!deps) {\n deps = new Set()\n depsMap.set(key, deps)\n }\n deps.add(activeEffect)\n activeEffect.deps.add(deps)\n}\n\nfunction trigger(target: object, key: string | symbol): void {\n const depsMap = targetMap.get(target)\n if (!depsMap) return\n const deps = depsMap.get(key)\n if (!deps || deps.size === 0) return\n // Iterate a snapshot — a triggered effect re-tracks (mutating `deps`)\n // during its run, which would otherwise corrupt this iteration.\n for (const effect of Array.from(deps)) {\n effect()\n }\n}\n\n// ── ref() ────────────────────────────────────────────────────────────────\n\nexport interface Ref<T = any> {\n value: T\n /** Subscribe to changes. Returns unsubscribe function. */\n subscribe(fn: (newValue: T, oldValue: T) => void): () => void\n /**\n * Auto-unwrap on `JSON.stringify` — returns the underlying value\n * so refs serialize transparently inside larger payloads (introspect\n * snapshots, devtools state, request responses). Equivalent to\n * `unref(ref)` but invoked implicitly by the JSON pipeline.\n */\n toJSON(): T\n}\n\n/**\n * Create a reactive reference. Reading `.value` tracks the dependency,\n * writing `.value` triggers watchers and computed recalculations.\n *\n * @example\n * ```ts\n * const count = ref(0)\n * count.value++ // triggers watchers\n * ```\n */\nexport function ref<T>(initialValue: T): Ref<T> {\n const subscribers = new Set<(newValue: T, oldValue: T) => void>()\n const wrapper = { _value: initialValue }\n\n return {\n get value(): T {\n track(wrapper, '_value')\n return wrapper._value\n },\n set value(newValue: T) {\n const oldValue = wrapper._value\n if (Object.is(oldValue, newValue)) return\n wrapper._value = newValue\n trigger(wrapper, '_value')\n for (const fn of subscribers) {\n fn(newValue, oldValue)\n }\n },\n subscribe(fn: (newValue: T, oldValue: T) => void): () => void {\n subscribers.add(fn)\n return () => subscribers.delete(fn)\n },\n /**\n * Auto-unwrap on `JSON.stringify`. Without this, serializing a ref\n * leaks the wrapper shape (`{\"value\": …, \"subscribe\": …}`) instead\n * of the underlying value — particularly painful for adopters who\n * keep adapter / plugin state in refs and surface it through\n * `introspect()`. Returning `value` here means\n * `JSON.stringify({ count: ref(0) })` produces `{\"count\":0}` as\n * expected, without callers having to `.value`-unwrap by hand.\n */\n toJSON(): T {\n return wrapper._value\n },\n }\n}\n\n// ── computed() ───────────────────────────────────────────────────────────\n\nexport interface ComputedRef<T = any> {\n readonly value: T\n /**\n * Auto-unwrap on `JSON.stringify` — see {@link Ref.toJSON} for the\n * motivation. Triggers a recompute when the cached value is stale,\n * same as reading `.value`.\n */\n toJSON(): T\n /**\n * Unsubscribe this computed from its reactive sources. Call when the\n * computed's lifetime is shorter than its sources' (e.g. created\n * per-request/per-tenant against app-lifetime refs) — without it the\n * internal effect stays pinned in the sources' dep Sets forever.\n * After disposal, reading `.value` still works but recomputes on every\n * access and no longer tracks.\n */\n dispose(): void\n}\n\n/**\n * Create a computed value that auto-recalculates when its reactive\n * dependencies change. Cached until a dependency triggers.\n *\n * @example\n * ```ts\n * const count = ref(0)\n * const doubled = computed(() => count.value * 2)\n * count.value = 5\n * console.log(doubled.value) // 10\n * ```\n */\nexport function computed<T>(getter: () => T): ComputedRef<T> {\n let cached: T\n let dirty = true\n let disposed = false\n const wrapper = { _value: undefined as T }\n\n const effect = createEffect(() => {\n dirty = true\n trigger(wrapper, '_value')\n })\n\n const recompute = (): T => {\n // Disposed: plain evaluation with tracking SUSPENDED — neither this\n // computed's effect nor any OUTER active effect (a watcher reading\n // `.value`) may attach to the sources through a disposed computed;\n // otherwise disposal wouldn't actually sever the subscription chain.\n if (disposed) {\n const prev = activeEffect\n activeEffect = null\n try {\n return getter()\n } finally {\n activeEffect = prev\n }\n }\n if (dirty) {\n cached = runTracked(effect, getter)\n wrapper._value = cached\n dirty = false\n }\n return cached\n }\n\n return {\n get value(): T {\n track(wrapper, '_value')\n return recompute()\n },\n dispose(): void {\n disposed = true\n cleanupEffect(effect)\n },\n /**\n * Auto-unwrap on `JSON.stringify`, matching {@link ref}'s behavior\n * so adopters can drop a computed straight into a JSON-bound\n * payload (introspect snapshot, devtools state) and get the value\n * rather than the wrapper shape.\n *\n * Computed values cache through the same dirty-flag the getter\n * uses, so calling `toJSON` is at worst one recompute when stale —\n * identical cost to reading `.value`.\n */\n toJSON(): T {\n return recompute()\n },\n }\n}\n\n// ── watch() ──────────────────────────────────────────────────────────────\n\nexport interface WatchOptions {\n /** Run the callback immediately with the current value (default: false) */\n immediate?: boolean\n}\n\ntype WatchSource<T> = Ref<T> | ComputedRef<T> | (() => T)\n\n/**\n * Watch a reactive source and run a callback when it changes.\n * Returns a stop function to unsubscribe.\n *\n * @example\n * ```ts\n * const count = ref(0)\n * const stop = watch(count, (newVal, oldVal) => {\n * console.log(`Changed from ${oldVal} to ${newVal}`)\n * })\n * count.value = 1 // logs: \"Changed from 0 to 1\"\n * stop() // no more callbacks\n * ```\n */\nexport function watch<T>(\n source: WatchSource<T>,\n callback: (newValue: T, oldValue: T) => void,\n options?: WatchOptions,\n): () => void {\n let oldValue: T\n let stopped = false\n\n const getter = typeof source === 'function' ? source : () => source.value\n\n const job = createEffect(() => {\n if (stopped) return\n // Re-run the getter UNDER tracking so deps are refreshed each run —\n // a conditional getter (`cond ? a.value : b.value`) drops the branch\n // it no longer reads instead of staying subscribed to it forever.\n const newValue = runTracked(job, getter)\n if (!Object.is(newValue, oldValue)) {\n callback(newValue, oldValue)\n oldValue = newValue\n }\n })\n\n // Initial run to establish tracking\n oldValue = runTracked(job, getter)\n\n if (options?.immediate) {\n callback(oldValue, undefined as T)\n }\n\n return () => {\n // Detach from every dep Set — a flag alone would leave the dead effect\n // referenced (and invoked) by its sources for their entire lifetime.\n stopped = true\n cleanupEffect(job)\n }\n}\n\n// ── reactive() ───────────────────────────────────────────────────────────\n\n/**\n * Create a deeply reactive proxy around a plain object. Property reads\n * are tracked, property writes trigger watchers and computed recalculations.\n *\n * @example\n * ```ts\n * const state = reactive({ users: 0, errors: 0 })\n * const errorRate = computed(() =>\n * state.users > 0 ? state.errors / state.users : 0\n * )\n * state.users = 100\n * state.errors = 5\n * console.log(errorRate.value) // 0.05\n * ```\n */\n// Memoize proxies per target: repeated reads of the same nested object must\n// return the SAME proxy — otherwise every access allocates a fresh Proxy and\n// referential identity breaks (`state.nested !== state.nested`).\nconst proxyCache = new WeakMap<object, any>()\n\nexport function reactive<T extends Record<string, any>>(target: T): T {\n const existing = proxyCache.get(target)\n if (existing) return existing as T\n const proxy = new Proxy(target, {\n get(obj, key, receiver) {\n const value = Reflect.get(obj, key, receiver)\n if (typeof key === 'symbol') return value\n track(obj, key)\n // Deep reactivity for nested objects\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n return reactive(value)\n }\n return value\n },\n set(obj, key, value, receiver) {\n const oldValue = Reflect.get(obj, key, receiver)\n const result = Reflect.set(obj, key, value, receiver)\n if (!Object.is(oldValue, value) && typeof key !== 'symbol') {\n trigger(obj, key)\n }\n return result\n },\n })\n proxyCache.set(target, proxy)\n return proxy\n}\n\n// ── Utility ──────────────────────────────────────────────────────────────\n\n/** Check if a value is a Ref */\nexport function isRef(value: any): value is Ref {\n return value !== null && typeof value === 'object' && 'subscribe' in value && 'value' in value\n}\n\n/** Unwrap a ref or return the value as-is */\nexport function unref<T>(value: Ref<T> | T): T {\n return isRef(value) ? value.value : value\n}\n\n/** Convert all properties of an object to refs */\nexport function toRefs<T extends Record<string, any>>(obj: T): { [K in keyof T]: Ref<T[K]> } {\n const result = {} as any\n for (const key in obj) {\n result[key] = ref(obj[key])\n }\n return result\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @forinda/kickjs v10.0.0
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) Felix Orinda
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*
|
|
9
|
+
* @license MIT
|
|
10
|
+
*/
|
|
11
|
+
import{l as __require}from"./env-ngWmBB9d.mjs";import{METADATA}from"./core/interfaces.mjs";import{i as createToken,p as _setAssetResolver}from"./container-CsCdem73.mjs";import"./core/logger.mjs";import{c as pushClassMeta,l as pushMethodMeta}from"./metadata-DCWKG472.mjs";import"./core/decorators.mjs";import"./core/cron.mjs";import"./jobs-Ccs6PkmC.mjs";import"./core/cache.mjs";import{n as assertFlagTest}from"./route-flag-jd8PDm20.mjs";import"./contributor-runner-CZTAFvla.mjs";import"reflect-metadata";import{existsSync,readFileSync,readdirSync}from"node:fs";import{isAbsolute,join,resolve,sep}from"node:path";function defineContextDecoratorImpl(spec){if(typeof spec!=`object`||!spec)throw TypeError(`defineContextDecorator: spec must be an object literal. See architecture.md §20.4 for the spec shape.`);if(typeof spec.key!=`string`||spec.key.length===0){let got=typeof spec.key==`string`?`""`:typeof spec.key;throw TypeError(`defineContextDecorator: spec.key must be a non-empty string (got ${got}). Pick a name that matches a key declared on \`ContextMeta\` (or one of the framework keys).`)}if(typeof spec.resolve!=`function`)throw TypeError(`defineContextDecorator(${spec.key}): spec.resolve is required and must be a function (got ${typeof spec.resolve}). Each contributor needs a resolver that produces the value to write into ctx.set(${JSON.stringify(spec.key)}, …).`);if(spec.onError!==void 0&&typeof spec.onError!=`function`)throw TypeError(`defineContextDecorator(${spec.key}): spec.onError must be a function when provided (got ${typeof spec.onError}).`);if(spec.dependsOn!==void 0&&!Array.isArray(spec.dependsOn))throw TypeError(`defineContextDecorator(${spec.key}): spec.dependsOn must be an array when provided (got ${typeof spec.dependsOn}).`);if(spec.requiredParams!==void 0&&!Array.isArray(spec.requiredParams))throw TypeError(`defineContextDecorator(${spec.key}): spec.requiredParams must be an array when provided (got ${typeof spec.requiredParams}).`);if(Array.isArray(spec.requiredParams)&&spec.requiredParams.some(p=>typeof p!=`string`||p.length===0))throw TypeError(`defineContextDecorator(${spec.key}): spec.requiredParams entries must be non-empty strings.`);if(Array.isArray(spec.dependsOn)&&spec.dependsOn.some(dep=>typeof dep!=`string`||dep.length===0))throw TypeError(`defineContextDecorator(${spec.key}): spec.dependsOn entries must be non-empty strings.`);let definedAt=(/* @__PURE__ */ Error()).stack,defaults=Object.freeze({...spec.paramDefaults??{}}),sharedDeps=Object.freeze({...spec.deps??{}}),sharedDependsOn=Object.freeze([...spec.dependsOn??[]]),sharedOptional=spec.optional??!1,buildRegistration=params=>{let frozenParams=Object.freeze({...params}),onError=spec.onError?(err,ctx)=>spec.onError(err,ctx,frozenParams):void 0;return spec.skipWhen!==void 0&&assertFlagTest(spec.skipWhen,`contributor '${spec.key}' skipWhen`),spec.onlyWhen!==void 0&&assertFlagTest(spec.onlyWhen,`contributor '${spec.key}' onlyWhen`),Object.freeze({key:spec.key,deps:sharedDeps,dependsOn:sharedDependsOn,optional:sharedOptional,skipWhen:spec.skipWhen,onlyWhen:spec.onlyWhen,beforeValidation:spec.beforeValidation===!0,onError,resolve:(ctx,deps)=>spec.resolve(ctx,deps,frozenParams),definedAt})},defaultRegistration=buildRegistration({...defaults}),isDecoratorCall=args=>args.length===0?!1:args.length>=2||typeof args[0]==`function`,applyAsDecorator=(registration,target,propertyKey)=>{if(propertyKey===void 0)pushClassMeta(METADATA.CLASS_CONTRIBUTORS,target,registration);else{let constructor=target.constructor;pushMethodMeta(METADATA.METHOD_CONTRIBUTORS,constructor,String(propertyKey),registration)}},isPlainObject=value=>{let proto=Object.getPrototypeOf(value);return proto===Object.prototype||proto===null},mergeParams=override=>{if(override===void 0)return{...defaults};if(typeof override!=`object`||!override||Array.isArray(override)||!isPlainObject(override)){let got=override===null?`null`:Array.isArray(override)?`array`:typeof override==`object`?`instance of ${override.constructor?.name??`unknown class`}`:typeof override;throw TypeError(`defineContextDecorator(${spec.key}): factory call requires a plain object literal, got ${got}. Class instances and built-ins (Map, Date, etc) silently spread to empty objects — call the decorator with a spread object literal instead, e.g. \`MyDecorator({ ...someInstance })\`.`)}return{...defaults,...override}},sharedRequiredParams=Object.freeze([...spec.requiredParams??[]]),assertRequiredParams=(params,usage)=>{if(sharedRequiredParams.length===0)return;let missing=sharedRequiredParams.filter(name=>params[name]===void 0);if(missing.length!==0)throw TypeError(`defineContextDecorator(${spec.key}): ${usage} is missing required param(s) ${missing.map(m=>`'${m}'`).join(`, `)}. Supply them at the call site — e.g. \`@${spec.key}({ ${missing[0]}: … })\` — or add a default in \`paramDefaults\` if one is genuinely correct for every route.`)};function decoratorOrFactory(...args){if(isDecoratorCall(args)){let[target,propertyKey]=args;assertRequiredParams({...defaults},"bare `@decorator` usage"),applyAsDecorator(defaultRegistration,target,propertyKey);return}let params=mergeParams(args[0]);assertRequiredParams(params,`factory call`);let registration=buildRegistration(params);return(target,propertyKey)=>{applyAsDecorator(registration,target,propertyKey)}}Object.defineProperty(decoratorOrFactory,"name",{value:`ContextDecorator(${spec.key})`,writable:!1,enumerable:!1,configurable:!0});let decorator=Object.assign(decoratorOrFactory,{with:params=>{let merged=mergeParams(params);return assertRequiredParams(merged,"`.with()`"),{registration:buildRegistration(merged)}}});return Object.defineProperty(decorator,"registration",{get(){return assertRequiredParams({...defaults},"`.registration`"),defaultRegistration},enumerable:!0,configurable:!1}),Object.freeze(decorator)}const defineContextDecorator=Object.freeze(Object.assign(defineContextDecoratorImpl,{withParams:()=>spec=>defineContextDecoratorImpl(spec)}));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=/* @__PURE__ */ 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=/* @__PURE__ */ 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 toPosixPath(p){return p.replaceAll(`\\`,`/`)}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 toPosixPath(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);if(process.env.NODE_ENV!==`production`){let fresh=synthesiseDevManifest(cwd,config);if(fresh)return fresh}let 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=/* @__PURE__ */ 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}}let snapshotPath=join(cwd,`.kickjs`,`kick.config.json`);if(existsSync(snapshotPath))try{return JSON.parse(readFileSync(snapshotPath,`utf-8`))}catch{return null}return null}const PROMISE_DETECTION_KEYS=/* @__PURE__ */ new Set([`then`]),PASSTHROUGH_KEYS=/* @__PURE__ */ 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}_setAssetResolver(resolveAsset);export{clearAssetCache as a,groupAssetKeys as c,assets as i,stripExt as l,ASSET_MANIFEST_VERSION as n,resolveAsset as o,UnknownAssetError as r,useAssets as s,ASSETS as t,defineContextDecorator as u};
|
|
12
|
+
//# sourceMappingURL=core-B0mErNE1.mjs.map
|