@noxfly/noxus 3.0.0-dev.1 → 3.0.0-dev.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +121 -8
  2. package/dist/child.d.mts +8 -2
  3. package/dist/child.d.ts +8 -2
  4. package/dist/child.js +405 -862
  5. package/dist/child.js.map +1 -0
  6. package/dist/child.mjs +396 -854
  7. package/dist/child.mjs.map +1 -0
  8. package/dist/main.d.mts +180 -132
  9. package/dist/main.d.ts +180 -132
  10. package/dist/main.js +1087 -926
  11. package/dist/main.js.map +1 -0
  12. package/dist/main.mjs +1038 -877
  13. package/dist/main.mjs.map +1 -0
  14. package/dist/preload.js.map +1 -0
  15. package/dist/preload.mjs.map +1 -0
  16. package/dist/renderer.d.mts +30 -4
  17. package/dist/renderer.d.ts +30 -4
  18. package/dist/renderer.js +191 -128
  19. package/dist/renderer.js.map +1 -0
  20. package/dist/renderer.mjs +180 -116
  21. package/dist/renderer.mjs.map +1 -0
  22. package/package.json +18 -9
  23. package/.editorconfig +0 -16
  24. package/.github/copilot-instructions.md +0 -32
  25. package/.vscode/settings.json +0 -3
  26. package/eslint.config.js +0 -109
  27. package/scripts/postbuild.js +0 -31
  28. package/src/DI/app-injector.ts +0 -151
  29. package/src/DI/injector-explorer.ts +0 -143
  30. package/src/DI/token.ts +0 -53
  31. package/src/decorators/controller.decorator.ts +0 -58
  32. package/src/decorators/guards.decorator.ts +0 -15
  33. package/src/decorators/injectable.decorator.ts +0 -81
  34. package/src/decorators/method.decorator.ts +0 -66
  35. package/src/decorators/middleware.decorator.ts +0 -15
  36. package/src/index.ts +0 -10
  37. package/src/internal/app.ts +0 -217
  38. package/src/internal/bootstrap.ts +0 -108
  39. package/src/internal/exceptions.ts +0 -57
  40. package/src/internal/preload-bridge.ts +0 -75
  41. package/src/internal/renderer-client.ts +0 -338
  42. package/src/internal/renderer-events.ts +0 -110
  43. package/src/internal/request.ts +0 -97
  44. package/src/internal/router.ts +0 -353
  45. package/src/internal/routes.ts +0 -78
  46. package/src/internal/socket.ts +0 -73
  47. package/src/main.ts +0 -26
  48. package/src/non-electron-process.ts +0 -22
  49. package/src/preload.ts +0 -10
  50. package/src/renderer.ts +0 -13
  51. package/src/utils/forward-ref.ts +0 -31
  52. package/src/utils/logger.ts +0 -430
  53. package/src/utils/radix-tree.ts +0 -210
  54. package/src/utils/types.ts +0 -21
  55. package/src/window/window-manager.ts +0 -255
  56. package/tsconfig.json +0 -29
  57. package/tsup.config.ts +0 -50
package/eslint.config.js DELETED
@@ -1,109 +0,0 @@
1
- // @ts-nocheck
2
- const eslint = require("@eslint/js");
3
- const tseslint = require("typescript-eslint");
4
- const stylistic = require("@stylistic/eslint-plugin");
5
-
6
- module.exports = tseslint.config(
7
- {
8
- files: ["src/**/*.ts"],
9
- extends: [
10
- eslint.configs.recommended,
11
- ...tseslint.configs.recommended,
12
- ...tseslint.configs.stylistic,
13
- ],
14
- languageOptions: {
15
- parserOptions: {
16
- ecmaVersion: 2020,
17
- sourceType: "module",
18
- project: ["./tsconfig.json"],
19
- tsconfigRootDir: __dirname,
20
- }
21
- },
22
- plugins: {
23
- "@stylistic": stylistic,
24
- },
25
- rules: {
26
- "@typescript-eslint/no-empty-object-type": "off",
27
-
28
- "@typescript-eslint/no-require-imports": "off",
29
-
30
- "@typescript-eslint/explicit-member-accessibility": [
31
- "error",
32
- {
33
- "overrides": {
34
- "constructors": "no-public",
35
- }
36
- }
37
- ],
38
- "@typescript-eslint/explicit-function-return-type": [
39
- "error",
40
- {
41
- "allowExpressions": true,
42
- "allowHigherOrderFunctions": true,
43
- "allowIIFEs": true,
44
- }
45
- ],
46
- "@typescript-eslint/consistent-type-definitions": "off",
47
- "@typescript-eslint/no-explicit-any": "off",
48
- "@typescript-eslint/no-inferrable-types": "off",
49
- "@typescript-eslint/no-empty-function": "off",
50
- "@typescript-eslint/no-unused-vars": "off",
51
- "@typescript-eslint/no-namespace": "off",
52
- "@typescript-eslint/no-unsafe-function-type": "off",
53
- "@typescript-eslint/prefer-readonly": "warn",
54
-
55
- "@stylistic/object-curly-spacing": ["warn", "always"],
56
- "@stylistic/no-whitespace-before-property": "error",
57
- "@stylistic/space-before-blocks": ["warn", "always"],
58
- "@stylistic/comma-spacing": [
59
- "warn",
60
- {
61
- "before": false,
62
- "after": true
63
- }
64
- ],
65
- "@stylistic/block-spacing": ["warn", "always"],
66
- "@stylistic/brace-style": [
67
- "error",
68
- "stroustrup",
69
- {
70
- "allowSingleLine": true
71
- }
72
- ],
73
- "@stylistic/function-call-spacing": ["error", "never"],
74
- "@stylistic/arrow-spacing": "error",
75
- "@stylistic/computed-property-spacing": "warn",
76
- "@stylistic/generator-star-spacing": "error",
77
- "@stylistic/indent": ["error", 4, { "SwitchCase": 1 }],
78
- "@stylistic/semi": [2, "always"],
79
- "@stylistic/no-extra-semi": "warn",
80
- "@stylistic/semi-spacing": "warn",
81
- "@stylistic/quotes": "off",
82
- "@stylistic/keyword-spacing": [
83
- "warn",
84
- {
85
- "overrides": {
86
- "if": { "after": false },
87
- "for": { "after": false },
88
- "catch": { "after": false },
89
- "while": { "after": false },
90
- "as": { "after": false },
91
- "switch": { "after": false }
92
- }
93
- }
94
- ],
95
-
96
- "eqeqeq": "error",
97
- "no-duplicate-imports": "error",
98
- "no-empty": "off",
99
- "no-empty-function": "off",
100
- "no-extra-boolean-cast": "off",
101
- "no-inner-declarations": "off",
102
- "no-unsafe-finally": "off",
103
- "no-unsafe-optional-chaining": "error",
104
- "no-unused-vars": "off",
105
- "no-var": "error",
106
- "no-useless-catch": "off",
107
- },
108
- },
109
- );
@@ -1,31 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- function uniqueDocBlocks(filepath) {
5
- if(!fs.existsSync(filepath)) {
6
- return;
7
- }
8
-
9
- const content = fs.readFileSync(filepath, 'utf8');
10
-
11
- const reg = /\/\*\*[\t ]*\n(?: \*.*\n)*? \* *@copyright.*\n(?: \*.*\n)*? \*\/\n?/gm;
12
-
13
- let first = true;
14
- const deduped = content.replace(reg, (match) => {
15
- if (first) {
16
- first = false;
17
- return match; // keep the first
18
- }
19
- return ''; // remove others
20
- });
21
-
22
- fs.writeFileSync(filepath, deduped);
23
- }
24
-
25
- const distDir = path.join(__dirname, '../dist');
26
-
27
- for(const filename of fs.readdirSync(distDir)) {
28
- if(filename.endsWith('.d.ts') || filename.endsWith('.d.mts')) {
29
- uniqueDocBlocks(path.join(distDir, filename));
30
- }
31
- }
@@ -1,151 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { ForwardReference } from '../utils/forward-ref';
8
- import { Type } from '../utils/types';
9
- import { Token, TokenKey } from './token';
10
-
11
- /**
12
- * Lifetime of a binding in the DI container.
13
- * - singleton: created once, shared for the lifetime of the app.
14
- * - scope: created once per request scope.
15
- * - transient: new instance every time it is resolved.
16
- */
17
- export type Lifetime = 'singleton' | 'scope' | 'transient';
18
-
19
- /**
20
- * Internal representation of a registered binding.
21
- */
22
- export interface IBinding<T = unknown> {
23
- lifetime: Lifetime;
24
- implementation: Type<T>;
25
- /** Explicit constructor dependencies, declared by the class itself. */
26
- deps: ReadonlyArray<TokenKey>;
27
- instance?: T;
28
- }
29
-
30
- function keyOf<T>(k: TokenKey<T>): Type<T> | Token<T> {
31
- return k;
32
- }
33
-
34
- /**
35
- * AppInjector is the core DI container.
36
- * It no longer uses reflect-metadata — all dependency information
37
- * comes from explicitly declared `deps` arrays on each binding.
38
- */
39
- export class AppInjector {
40
- public readonly bindings = new Map<Type<unknown> | Token<unknown>, IBinding<unknown>>();
41
- public readonly singletons = new Map<Type<unknown> | Token<unknown>, unknown>();
42
- public readonly scoped = new Map<Type<unknown> | Token<unknown>, unknown>();
43
-
44
- constructor(public readonly name: string | null = null) {}
45
-
46
- /**
47
- * Creates a child scope for per-request lifetime resolution.
48
- */
49
- public createScope(): AppInjector {
50
- const scope = new AppInjector();
51
- (scope as any).bindings = this.bindings;
52
- (scope as any).singletons = this.singletons;
53
- return scope;
54
- }
55
-
56
- /**
57
- * Registers a binding explicitly.
58
- */
59
- public register<T>(
60
- key: TokenKey<T>,
61
- implementation: Type<T>,
62
- lifetime: Lifetime,
63
- deps: ReadonlyArray<TokenKey> = [],
64
- ): void {
65
- const k = keyOf(key) as TokenKey<unknown>;
66
- if (!this.bindings.has(k)) {
67
- this.bindings.set(k, { lifetime, implementation: implementation as Type<unknown>, deps });
68
- }
69
- }
70
-
71
- /**
72
- * Resolves a dependency by token or class reference.
73
- */
74
- public resolve<T>(target: TokenKey<T> | ForwardReference<T>): T {
75
- if (target instanceof ForwardReference) {
76
- return this._resolveForwardRef(target);
77
- }
78
-
79
- const k = keyOf(target) as TokenKey<unknown>;
80
- const binding = this.bindings.get(k);
81
-
82
- if (!binding) {
83
- const name = target instanceof Token ? target.description : (target as Type<unknown>).name ?? 'unknown';
84
- throw new Error(
85
- `[Noxus DI] No binding found for "${name}".\n`
86
- + `Did you forget to declare it in @Injectable({ deps }) or in bootstrapApplication({ singletons })?`,
87
- );
88
- }
89
-
90
- switch (binding.lifetime) {
91
- case 'transient':
92
- return this._instantiate(binding) as T;
93
-
94
- case 'scope': {
95
- if (this.scoped.has(k)) return this.scoped.get(k) as T;
96
- const inst = this._instantiate(binding);
97
- this.scoped.set(k, inst);
98
- return inst as T;
99
- }
100
-
101
- case 'singleton': {
102
- if (this.singletons.has(k)) return this.singletons.get(k) as T;
103
- const inst = this._instantiate(binding);
104
- this.singletons.set(k, inst);
105
- if (binding.instance === undefined) {
106
- (binding as IBinding<unknown>).instance = inst as unknown;
107
- }
108
- return inst as T;
109
- }
110
- }
111
- }
112
-
113
- // -------------------------------------------------------------------------
114
-
115
- private _resolveForwardRef<T>(ref: ForwardReference<T>): T {
116
- return new Proxy({} as object, {
117
- get: (_obj, prop, receiver) => {
118
- const realType = ref.forwardRefFn();
119
- const instance = this.resolve(realType) as Record<string | symbol, unknown>;
120
- const value = Reflect.get(instance, prop, receiver);
121
- return typeof value === 'function' ? (value as Function).bind(instance) : value;
122
- },
123
- set: (_obj, prop, value, receiver) => {
124
- const realType = ref.forwardRefFn();
125
- const instance = this.resolve(realType) as object;
126
- return Reflect.set(instance, prop, value, receiver);
127
- },
128
- getPrototypeOf: () => {
129
- const realType = ref.forwardRefFn();
130
- return (realType as unknown as { prototype: object }).prototype;
131
- },
132
- }) as T;
133
- }
134
-
135
- private _instantiate<T>(binding: IBinding<T>): T {
136
- const resolvedDeps = binding.deps.map((dep) => this.resolve(dep));
137
- return new binding.implementation(...resolvedDeps) as T;
138
- }
139
- }
140
-
141
- /**
142
- * The global root injector. All singletons live here.
143
- */
144
- export const RootInjector = new AppInjector('root');
145
-
146
- /**
147
- * Convenience function: resolve a token from the root injector.
148
- */
149
- export function inject<T>(t: TokenKey<T> | ForwardReference<T>): T {
150
- return RootInjector.resolve(t);
151
- }
@@ -1,143 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { Lifetime, RootInjector } from './app-injector';
8
- import { TokenKey } from './token';
9
- import { Type } from '../utils/types';
10
- import { Logger } from '../utils/logger';
11
- import { Guard, Middleware } from "src/main";
12
-
13
- export interface PendingRegistration {
14
- key: TokenKey;
15
- implementation: Type<unknown>;
16
- lifetime: Lifetime;
17
- deps: ReadonlyArray<TokenKey>;
18
- isController: boolean;
19
- pathPrefix?: string;
20
- }
21
-
22
- /**
23
- * InjectorExplorer accumulates registrations emitted by decorators
24
- * at import time, then flushes them in two phases (bind → resolve)
25
- * once bootstrapApplication triggers processing.
26
- *
27
- * Because deps are now explicit arrays (no reflect-metadata), this class
28
- * no longer needs to introspect constructor parameter types.
29
- */
30
- export class InjectorExplorer {
31
- private static readonly pending: PendingRegistration[] = [];
32
- private static processed = false;
33
- private static accumulating = false;
34
-
35
- // -------------------------------------------------------------------------
36
- // Public API
37
- // -------------------------------------------------------------------------
38
-
39
- public static enqueue(reg: PendingRegistration): void {
40
- if (InjectorExplorer.processed && !InjectorExplorer.accumulating) {
41
- InjectorExplorer._registerImmediate(reg);
42
- return;
43
- }
44
- InjectorExplorer.pending.push(reg);
45
- }
46
-
47
- /**
48
- * Two-phase flush of all pending registrations collected at startup.
49
- * Called by bootstrapApplication after app.whenReady().
50
- */
51
- public static processPending(singletonOverrides?: Map<TokenKey, unknown>): void {
52
- const queue = [...InjectorExplorer.pending];
53
- InjectorExplorer.pending.length = 0;
54
-
55
- InjectorExplorer._phaseOne(queue);
56
- InjectorExplorer._phaseTwo(queue, singletonOverrides);
57
-
58
- InjectorExplorer.processed = true;
59
- }
60
-
61
- /** Enters accumulation mode for lazy-loaded batches. */
62
- public static beginAccumulate(): void {
63
- InjectorExplorer.accumulating = true;
64
- }
65
-
66
- /**
67
- * Exits accumulation mode and flushes queued registrations
68
- * with the same two-phase guarantee as processPending.
69
- */
70
- public static flushAccumulated(
71
- routeGuards: Guard[] = [],
72
- routeMiddlewares: Middleware[] = [],
73
- pathPrefix = '',
74
- ): void {
75
- InjectorExplorer.accumulating = false;
76
- const queue = [...InjectorExplorer.pending];
77
- InjectorExplorer.pending.length = 0;
78
- InjectorExplorer._phaseOne(queue);
79
-
80
- // Stamp the path prefix on controller registrations
81
- for (const reg of queue) {
82
- if (reg.isController) reg.pathPrefix = pathPrefix;
83
- }
84
-
85
- InjectorExplorer._phaseTwo(queue, undefined, routeGuards, routeMiddlewares);
86
- }
87
-
88
- // -------------------------------------------------------------------------
89
- // Private helpers
90
- // -------------------------------------------------------------------------
91
-
92
- /** Phase 1: register all bindings without instantiating anything. */
93
- private static _phaseOne(queue: PendingRegistration[]): void {
94
- for (const reg of queue) {
95
- RootInjector.register(reg.key, reg.implementation, reg.lifetime, reg.deps);
96
- }
97
- }
98
-
99
- /** Phase 2: resolve singletons and register controllers in the router. */
100
- private static _phaseTwo(
101
- queue: PendingRegistration[],
102
- overrides?: Map<TokenKey, unknown>,
103
- routeGuards: Guard[] = [],
104
- routeMiddlewares: Middleware[] = [],
105
- ): void {
106
- for (const reg of queue) {
107
- // Apply value overrides (e.g. singleton instances provided via bootstrapApplication config)
108
- if (overrides?.has(reg.key)) {
109
- const override = overrides.get(reg.key);
110
- RootInjector.singletons.set(reg.key as any, override);
111
- Logger.log(`Registered ${reg.implementation.name} as singleton (overridden)`);
112
- continue;
113
- }
114
-
115
- if (reg.lifetime === 'singleton') {
116
- RootInjector.resolve(reg.key);
117
- }
118
-
119
- if (reg.isController) {
120
- // Lazily import Router to avoid circular dependency at module load time
121
- const { Router } = require('../internal/router') as { Router: { prototype: { registerController(t: Type<unknown>): void } } };
122
- const router = RootInjector.resolve(Router as any) as { registerController(t: Type<unknown>, pathPrefix: string, routeGuards: Guard[], routeMiddlewares: Middleware[]): void };
123
- router.registerController(reg.implementation, reg.pathPrefix ?? '', routeGuards, routeMiddlewares);
124
- } else if (reg.lifetime !== 'singleton') {
125
- Logger.log(`Registered ${reg.implementation.name} as ${reg.lifetime}`);
126
- }
127
- }
128
- }
129
-
130
- private static _registerImmediate(reg: PendingRegistration): void {
131
- RootInjector.register(reg.key, reg.implementation, reg.lifetime, reg.deps);
132
-
133
- if (reg.lifetime === 'singleton') {
134
- RootInjector.resolve(reg.key);
135
- }
136
-
137
- if (reg.isController) {
138
- const { Router } = require('../internal/router') as { Router: { prototype: { registerController(t: Type<unknown>): void } } };
139
- const router = RootInjector.resolve(Router as any) as { registerController(t: Type<unknown>): void };
140
- router.registerController(reg.implementation);
141
- }
142
- }
143
- }
package/src/DI/token.ts DELETED
@@ -1,53 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { Type } from '../utils/types';
8
-
9
- /**
10
- * A DI token uniquely identifies a dependency.
11
- * It can wrap a class (Type<T>) or be a named symbol token.
12
- *
13
- * Using tokens instead of reflect-metadata means dependencies are
14
- * declared explicitly — no magic type inference, no emitDecoratorMetadata.
15
- *
16
- * @example
17
- * // Class token (most common)
18
- * const MY_SERVICE = token(MyService);
19
- *
20
- * // Named symbol token (for interfaces or non-class values)
21
- * const DB_URL = token<string>('DB_URL');
22
- */
23
- export class Token<T> {
24
- public readonly description: string;
25
-
26
- constructor(
27
- public readonly target: Type<T> | string,
28
- ) {
29
- this.description = typeof target === 'string' ? target : target.name;
30
- }
31
-
32
- public toString(): string {
33
- return `Token(${this.description})`;
34
- }
35
- }
36
-
37
- /**
38
- * Creates a DI token for a class type or a named value.
39
- *
40
- * @example
41
- * export const MY_SERVICE = token(MyService);
42
- * export const DB_URL = token<string>('DB_URL');
43
- */
44
- export function token<T>(target: Type<T> | string): Token<T> {
45
- return new Token<T>(target);
46
- }
47
-
48
- /**
49
- * The key used to look up a class token in the registry.
50
- * For class tokens, the key is the class constructor itself.
51
- * For named tokens, the key is the Token instance.
52
- */
53
- export type TokenKey<T = unknown> = Type<T> | Token<T>;
@@ -1,58 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { InjectorExplorer } from '../DI/injector-explorer';
8
- import { TokenKey } from '../DI/token';
9
- import { Type } from '../utils/types';
10
-
11
- export interface ControllerOptions {
12
- /**
13
- * Explicit constructor dependencies.
14
- */
15
- deps?: ReadonlyArray<TokenKey>;
16
- }
17
-
18
- export interface IControllerMetadata {
19
- deps: ReadonlyArray<TokenKey>;
20
- }
21
-
22
- const controllerMetaMap = new WeakMap<object, IControllerMetadata>();
23
-
24
- /**
25
- * Marks a class as a Noxus controller.
26
- * Controllers are always scope-scoped injectables.
27
- * The route prefix and guards/middlewares are declared in defineRoutes(), not here.
28
- *
29
- * @example
30
- * @Controller({ deps: [UserService] })
31
- * export class UserController {
32
- * constructor(private svc: UserService) {}
33
- *
34
- * @Get('byId/:userId')
35
- * getUserById(req: Request) { ... }
36
- * }
37
- */
38
- export function Controller(options: ControllerOptions = {}): ClassDecorator {
39
- return (target) => {
40
- const meta: IControllerMetadata = {
41
- deps: options.deps ?? [],
42
- };
43
-
44
- controllerMetaMap.set(target, meta);
45
-
46
- InjectorExplorer.enqueue({
47
- key: target as unknown as Type<unknown>,
48
- implementation: target as unknown as Type<unknown>,
49
- lifetime: 'scope',
50
- deps: options.deps ?? [],
51
- isController: true,
52
- });
53
- };
54
- }
55
-
56
- export function getControllerMetadata(target: object): IControllerMetadata | undefined {
57
- return controllerMetaMap.get(target);
58
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { Request } from '../internal/request';
8
- import { MaybeAsync } from '../utils/types';
9
-
10
- /**
11
- * A guard decides whether an incoming request should reach the handler.
12
- * Implement this interface and pass the class to @Controller({ guards }) or @Get('path', { guards }).
13
- */
14
-
15
- export type Guard = (request: Request) => MaybeAsync<boolean>;
@@ -1,81 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { Lifetime } from '../DI/app-injector';
8
- import { InjectorExplorer } from '../DI/injector-explorer';
9
- import { Token, TokenKey } from '../DI/token';
10
- import { Type } from '../utils/types';
11
-
12
- export interface InjectableOptions {
13
- /**
14
- * Lifetime of this injectable.
15
- * @default 'scope'
16
- */
17
- lifetime?: Lifetime;
18
-
19
- /**
20
- * Explicit list of constructor dependencies, in the same order as the constructor parameters.
21
- * Each entry is either a class constructor or a Token created with token().
22
- *
23
- * This replaces reflect-metadata / emitDecoratorMetadata entirely.
24
- *
25
- * @example
26
- * @Injectable({ lifetime: 'singleton', deps: [MyRepo, DB_URL] })
27
- * class MyService {
28
- * constructor(private repo: MyRepo, private dbUrl: string) {}
29
- * }
30
- */
31
- deps?: ReadonlyArray<TokenKey>;
32
- }
33
-
34
- /**
35
- * Marks a class as injectable into the Noxus DI container.
36
- *
37
- * Unlike the v2 @Injectable, this decorator:
38
- * - Does NOT require reflect-metadata or emitDecoratorMetadata.
39
- * - Requires you to declare deps explicitly when the class has constructor parameters.
40
- * - Supports standalone usage — no module declaration needed.
41
- *
42
- * @example
43
- * // No dependencies
44
- * @Injectable()
45
- * class Logger {}
46
- *
47
- * // With dependencies
48
- * @Injectable({ lifetime: 'singleton', deps: [Logger, MyRepo] })
49
- * class MyService {
50
- * constructor(private logger: Logger, private repo: MyRepo) {}
51
- * }
52
- *
53
- * // With a named token
54
- * const DB_URL = token<string>('DB_URL');
55
- *
56
- * @Injectable({ deps: [DB_URL] })
57
- * class DbService {
58
- * constructor(private url: string) {}
59
- * }
60
- */
61
- export function Injectable(options: InjectableOptions = {}): ClassDecorator {
62
- const { lifetime = 'scope', deps = [] } = options;
63
-
64
- return (target) => {
65
- if (typeof target !== 'function' || !target.prototype) {
66
- throw new Error(`@Injectable can only be applied to classes, not ${typeof target}`);
67
- }
68
-
69
- const key = target as unknown as Type<unknown>;
70
-
71
- InjectorExplorer.enqueue({
72
- key,
73
- implementation: key,
74
- lifetime,
75
- deps,
76
- isController: false,
77
- });
78
- };
79
- }
80
-
81
- export { Token, TokenKey };