@noxfly/noxus 3.0.0-dev.4 → 3.0.0-dev.6
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/child.js.map +1 -0
- package/dist/child.mjs.map +1 -0
- package/dist/main.d.mts +4 -4
- package/dist/main.d.ts +4 -4
- package/dist/main.js.map +1 -0
- package/dist/main.mjs.map +1 -0
- package/dist/preload.js.map +1 -0
- package/dist/preload.mjs.map +1 -0
- package/dist/renderer.d.mts +4 -4
- package/dist/renderer.d.ts +4 -4
- package/dist/renderer.js.map +1 -0
- package/dist/renderer.mjs.map +1 -0
- package/package.json +10 -9
- package/.editorconfig +0 -16
- package/.github/copilot-instructions.md +0 -128
- package/.vscode/settings.json +0 -3
- package/AGENTS.md +0 -5
- package/eslint.config.js +0 -109
- package/scripts/postbuild.js +0 -31
- package/src/DI/app-injector.ts +0 -173
- package/src/DI/injector-explorer.ts +0 -201
- package/src/DI/token.ts +0 -53
- package/src/decorators/controller.decorator.ts +0 -58
- package/src/decorators/guards.decorator.ts +0 -15
- package/src/decorators/injectable.decorator.ts +0 -81
- package/src/decorators/method.decorator.ts +0 -66
- package/src/decorators/middleware.decorator.ts +0 -15
- package/src/index.ts +0 -10
- package/src/internal/app.ts +0 -219
- package/src/internal/bootstrap.ts +0 -141
- package/src/internal/exceptions.ts +0 -57
- package/src/internal/preload-bridge.ts +0 -75
- package/src/internal/renderer-client.ts +0 -374
- package/src/internal/renderer-events.ts +0 -110
- package/src/internal/request.ts +0 -102
- package/src/internal/router.ts +0 -365
- package/src/internal/routes.ts +0 -142
- package/src/internal/socket.ts +0 -75
- package/src/main.ts +0 -26
- package/src/non-electron-process.ts +0 -22
- package/src/preload.ts +0 -10
- package/src/renderer.ts +0 -13
- package/src/utils/forward-ref.ts +0 -31
- package/src/utils/logger.ts +0 -430
- package/src/utils/radix-tree.ts +0 -243
- package/src/utils/types.ts +0 -21
- package/src/window/window-manager.ts +0 -302
- package/tsconfig.json +0 -29
- package/tsup.config.ts +0 -50
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/forward-ref.ts","../src/DI/token.ts","../src/DI/app-injector.ts","../src/internal/request.ts","../src/internal/renderer-events.ts","../src/internal/renderer-client.ts"],"sourcesContent":["/**\r\n * @copyright 2025 NoxFly\r\n * @license MIT\r\n * @author NoxFly\r\n */\r\n\r\nimport { Type } from \"./types\";\r\n\r\n/**\r\n * A function that returns a type.\r\n * Used for forward references to types that are not yet defined.\r\n */\r\nexport interface ForwardRefFn<T = any> {\r\n (): Type<T>;\r\n}\r\n\r\n/**\r\n * A wrapper class for forward referenced types.\r\n */\r\nexport class ForwardReference<T = any> {\r\n constructor(public readonly forwardRefFn: ForwardRefFn<T>) {}\r\n}\r\n\r\n/**\r\n * Creates a forward reference to a type.\r\n * @param fn A function that returns the type.\r\n * @returns A ForwardReference instance.\r\n */\r\nexport function forwardRef<T = any>(fn: ForwardRefFn<T>): ForwardReference<T> {\r\n return new ForwardReference(fn);\r\n}\r\n","/**\r\n * @copyright 2025 NoxFly\r\n * @license MIT\r\n * @author NoxFly\r\n */\r\n\r\nimport { Type } from '../utils/types';\r\n\r\n/**\r\n * A DI token uniquely identifies a dependency.\r\n * It can wrap a class (Type<T>) or be a named symbol token.\r\n *\r\n * Using tokens instead of reflect-metadata means dependencies are\r\n * declared explicitly — no magic type inference, no emitDecoratorMetadata.\r\n *\r\n * @example\r\n * // Class token (most common)\r\n * const MY_SERVICE = token(MyService);\r\n *\r\n * // Named symbol token (for interfaces or non-class values)\r\n * const DB_URL = token<string>('DB_URL');\r\n */\r\nexport class Token<T> {\r\n public readonly description: string;\r\n\r\n constructor(\r\n public readonly target: Type<T> | string,\r\n ) {\r\n this.description = typeof target === 'string' ? target : target.name;\r\n }\r\n\r\n public toString(): string {\r\n return `Token(${this.description})`;\r\n }\r\n}\r\n\r\n/**\r\n * Creates a DI token for a class type or a named value.\r\n *\r\n * @example\r\n * export const MY_SERVICE = token(MyService);\r\n * export const DB_URL = token<string>('DB_URL');\r\n */\r\nexport function token<T>(target: Type<T> | string): Token<T> {\r\n return new Token<T>(target);\r\n}\r\n\r\n/**\r\n * The key used to look up a class token in the registry.\r\n * For class tokens, the key is the class constructor itself.\r\n * For named tokens, the key is the Token instance.\r\n */\r\nexport type TokenKey<T = unknown> = Type<T> | Token<T>;\r\n","/**\r\n * @copyright 2025 NoxFly\r\n * @license MIT\r\n * @author NoxFly\r\n */\r\n\r\nimport { ForwardReference } from '../utils/forward-ref';\r\nimport { Type } from '../utils/types';\r\nimport { Token, TokenKey } from './token';\r\n\r\n/**\r\n * Lifetime of a binding in the DI container.\r\n * - singleton: created once, shared for the lifetime of the app.\r\n * - scope: created once per request scope.\r\n * - transient: new instance every time it is resolved.\r\n */\r\nexport type Lifetime = 'singleton' | 'scope' | 'transient';\r\n\r\n/**\r\n * Internal representation of a registered binding.\r\n */\r\nexport interface IBinding<T = unknown> {\r\n lifetime: Lifetime;\r\n implementation: Type<T>;\r\n /** Explicit constructor dependencies, declared by the class itself. */\r\n deps: ReadonlyArray<TokenKey>;\r\n instance?: T;\r\n}\r\n\r\nfunction keyOf<T>(k: TokenKey<T>): Type<T> | Token<T> {\r\n return k;\r\n}\r\n\r\n/**\r\n * AppInjector is the core DI container.\r\n * It no longer uses reflect-metadata — all dependency information\r\n * comes from explicitly declared `deps` arrays on each binding.\r\n */\r\nexport class AppInjector {\r\n public readonly bindings = new Map<Type<unknown> | Token<unknown>, IBinding<unknown>>();\r\n public readonly singletons = new Map<Type<unknown> | Token<unknown>, unknown>();\r\n public readonly scoped = new Map<Type<unknown> | Token<unknown>, unknown>();\r\n\r\n constructor(public readonly name: string | null = null) {}\r\n\r\n /**\r\n * Creates a child scope for per-request lifetime resolution.\r\n */\r\n public createScope(): AppInjector {\r\n const scope = new AppInjector();\r\n (scope as any).bindings = this.bindings;\r\n (scope as any).singletons = this.singletons;\r\n return scope;\r\n }\r\n\r\n /**\r\n * Registers a binding explicitly.\r\n */\r\n public register<T>(\r\n key: TokenKey<T>,\r\n implementation: Type<T>,\r\n lifetime: Lifetime,\r\n deps: ReadonlyArray<TokenKey> = [],\r\n ): void {\r\n const k = keyOf(key) as TokenKey<unknown>;\r\n if (!this.bindings.has(k)) {\r\n this.bindings.set(k, { lifetime, implementation: implementation as Type<unknown>, deps });\r\n }\r\n }\r\n\r\n /**\r\n * Resolves a dependency by token or class reference.\r\n */\r\n public resolve<T>(target: TokenKey<T> | ForwardReference<T>): T {\r\n if (target instanceof ForwardReference) {\r\n return this._resolveForwardRef(target);\r\n }\r\n\r\n const k = keyOf(target) as TokenKey<unknown>;\r\n\r\n if (this.singletons.has(k)) {\r\n return this.singletons.get(k) as T;\r\n }\r\n\r\n const binding = this.bindings.get(k);\r\n\r\n if (!binding) {\r\n const name = target instanceof Token\r\n ? target.description\r\n : (target as Type<unknown>).name\r\n ?? 'unknown';\r\n\r\n throw new Error(\r\n `[Noxus DI] No binding found for \"${name}\".\\n`\r\n + `Did you forget to declare it in @Injectable({ deps }) or in bootstrapApplication({ singletons })?`,\r\n );\r\n }\r\n\r\n switch (binding.lifetime) {\r\n case 'transient':\r\n return this._instantiate(binding) as T;\r\n\r\n case 'scope': {\r\n if (this.scoped.has(k)) return this.scoped.get(k) as T;\r\n const inst = this._instantiate(binding);\r\n this.scoped.set(k, inst);\r\n return inst as T;\r\n }\r\n\r\n case 'singleton': {\r\n if (this.singletons.has(k)) return this.singletons.get(k) as T;\r\n const inst = this._instantiate(binding);\r\n this.singletons.set(k, inst);\r\n if (binding.instance === undefined) {\r\n (binding as IBinding<unknown>).instance = inst as unknown;\r\n }\r\n return inst as T;\r\n }\r\n }\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n\r\n private _resolveForwardRef<T>(ref: ForwardReference<T>): T {\r\n let resolved: T | undefined;\r\n return new Proxy({} as object, {\r\n get: (_obj, prop, receiver) => {\r\n resolved ??= this.resolve(ref.forwardRefFn()) as T;\r\n const value = Reflect.get(resolved as object, prop, receiver);\r\n return typeof value === 'function' ? (value as Function).bind(resolved) : value;\r\n },\r\n set: (_obj, prop, value, receiver) => {\r\n resolved ??= this.resolve(ref.forwardRefFn()) as T;\r\n return Reflect.set(resolved as object, prop, value, receiver);\r\n },\r\n getPrototypeOf: () => {\r\n resolved ??= this.resolve(ref.forwardRefFn()) as T;\r\n return Object.getPrototypeOf(resolved);\r\n },\r\n }) as T;\r\n }\r\n\r\n private _instantiate<T>(binding: IBinding<T>): T {\r\n const resolvedDeps = binding.deps.map((dep) => this.resolve(dep));\r\n return new binding.implementation(...resolvedDeps) as T;\r\n }\r\n}\r\n\r\n/**\r\n * The global root injector. All singletons live here.\r\n */\r\nexport const RootInjector = new AppInjector('root');\r\n\r\n/**\r\n * Resets the root injector to a clean state.\r\n * **Intended for testing only** — clears all bindings, singletons, and scoped instances\r\n * so that each test can start from a fresh DI container without restarting the process.\r\n */\r\nexport function resetRootInjector(): void {\r\n RootInjector.bindings.clear();\r\n RootInjector.singletons.clear();\r\n RootInjector.scoped.clear();\r\n // Lazy import to avoid circular dependency (InjectorExplorer → app-injector → InjectorExplorer)\r\n const { InjectorExplorer } = require('./injector-explorer') as typeof import('./injector-explorer');\r\n InjectorExplorer.reset();\r\n}\r\n\r\n/**\r\n * Convenience function: resolve a token from the root injector.\r\n */\r\nexport function inject<T>(t: TokenKey<T> | ForwardReference<T>): T {\r\n return RootInjector.resolve(t);\r\n}\r\n","/**\r\n * @copyright 2025 NoxFly\r\n * @license MIT\r\n * @author NoxFly\r\n */\r\n\r\n\r\nimport { AtomicHttpMethod, HttpMethod } from '../decorators/method.decorator';\r\nimport { AppInjector, RootInjector } from '../DI/app-injector';\r\n\r\n/**\r\n * The Request class represents an HTTP request in the Noxus framework.\r\n * It encapsulates the request data, including the event, ID, method, path, and body.\r\n * It also provides a context for dependency injection through the AppInjector.\r\n */\r\nexport class Request {\r\n public readonly context: AppInjector = RootInjector.createScope();\r\n\r\n public readonly params: Record<string, string> = {};\r\n public readonly query: Record<string, unknown>;\r\n\r\n constructor(\r\n public readonly event: Electron.MessageEvent,\r\n public readonly senderId: number,\r\n public readonly id: string,\r\n public readonly method: HttpMethod,\r\n public readonly path: string,\r\n public readonly body: unknown,\r\n query?: Record<string, unknown>,\r\n ) {\r\n this.path = path.replace(/^\\/|\\/$/g, '');\r\n this.query = query ?? {};\r\n }\r\n}\r\n\r\n/**\r\n * The IRequest interface defines the structure of a request object.\r\n * It includes properties for the sender ID, request ID, path, method, and an optional body.\r\n * This interface is used to standardize the request data across the application.\r\n */\r\nexport interface IRequest<TBody = unknown> {\r\n senderId: number;\r\n requestId: string;\r\n path: string;\r\n method: HttpMethod;\r\n body?: TBody;\r\n query?: Record<string, unknown>;\r\n}\r\n\r\nexport interface IBatchRequestItem<TBody = unknown> {\r\n requestId?: string;\r\n path: string;\r\n method: AtomicHttpMethod;\r\n body?: TBody;\r\n query?: Record<string, unknown>;\r\n}\r\n\r\nexport interface IBatchRequestPayload {\r\n requests: IBatchRequestItem[];\r\n}\r\n\r\n/**\r\n * Creates a Request object from the IPC event data.\r\n * This function extracts the necessary information from the IPC event and constructs a Request instance.\r\n */\r\nexport interface IResponse<TBody = unknown> {\r\n requestId: string;\r\n status: number;\r\n body?: TBody;\r\n error?: string;\r\n stack?: string;\r\n}\r\n\r\nexport interface IBatchResponsePayload {\r\n responses: IResponse[];\r\n}\r\n\r\nexport const RENDERER_EVENT_TYPE = 'noxus:event';\r\n\r\nexport interface IRendererEventMessage<TPayload = unknown> {\r\n type: typeof RENDERER_EVENT_TYPE;\r\n event: string;\r\n payload?: TPayload;\r\n}\r\n\r\nexport function createRendererEventMessage<TPayload = unknown>(event: string, payload?: TPayload): IRendererEventMessage<TPayload> {\r\n return {\r\n type: RENDERER_EVENT_TYPE,\r\n event,\r\n payload,\r\n };\r\n}\r\n\r\nexport function isRendererEventMessage(value: unknown): value is IRendererEventMessage {\r\n if(value === null || typeof value !== 'object') {\r\n return false;\r\n }\r\n\r\n const possibleMessage = value as Partial<IRendererEventMessage>;\r\n\r\n return possibleMessage.type === RENDERER_EVENT_TYPE && typeof possibleMessage.event === 'string';\r\n}\r\n","/**\r\n * @copyright 2025 NoxFly\r\n * @license MIT\r\n * @author NoxFly\r\n */\r\n\r\n/**\r\n * Lightweight event registry to help renderer processes subscribe to\r\n * push messages sent by the main process through Noxus.\r\n */\r\nimport { IRendererEventMessage, isRendererEventMessage } from './request';\r\n\r\nexport type RendererEventHandler<TPayload = unknown> = (payload: TPayload) => void;\r\n\r\nexport interface RendererEventSubscription {\r\n unsubscribe(): void;\r\n}\r\n\r\nexport class RendererEventRegistry {\r\n private readonly listeners = new Map<string, Set<RendererEventHandler>>();\r\n\r\n /**\r\n *\r\n */\r\n public subscribe<TPayload>(eventName: string, handler: RendererEventHandler<TPayload>): RendererEventSubscription {\r\n const normalizedEventName = eventName.trim();\r\n\r\n if(normalizedEventName.length === 0) {\r\n throw new Error('Renderer event name must be a non-empty string.');\r\n }\r\n\r\n const handlers = this.listeners.get(normalizedEventName) ?? new Set<RendererEventHandler>();\r\n\r\n handlers.add(handler as RendererEventHandler);\r\n this.listeners.set(normalizedEventName, handlers);\r\n\r\n return {\r\n unsubscribe: () => this.unsubscribe(normalizedEventName, handler as RendererEventHandler),\r\n };\r\n }\r\n\r\n /**\r\n *\r\n */\r\n public unsubscribe<TPayload>(eventName: string, handler: RendererEventHandler<TPayload>): void {\r\n const handlers = this.listeners.get(eventName);\r\n\r\n if(!handlers) {\r\n return;\r\n }\r\n\r\n handlers.delete(handler as RendererEventHandler);\r\n\r\n if(handlers.size === 0) {\r\n this.listeners.delete(eventName);\r\n }\r\n }\r\n\r\n /**\r\n *\r\n */\r\n public clear(eventName?: string): void {\r\n if(eventName) {\r\n this.listeners.delete(eventName);\r\n return;\r\n }\r\n\r\n this.listeners.clear();\r\n }\r\n\r\n /**\r\n *\r\n */\r\n public dispatch<TPayload>(message: IRendererEventMessage<TPayload>): void {\r\n const handlers = this.listeners.get(message.event);\r\n\r\n if(!handlers || handlers.size === 0) {\r\n return;\r\n }\r\n\r\n handlers.forEach((handler) => {\r\n try {\r\n handler(message.payload as TPayload);\r\n }\r\n catch(error) {\r\n console.error(`[Noxus] Renderer event handler for \"${message.event}\" threw an error.`, error);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n *\r\n */\r\n public tryDispatchFromMessageEvent(event: MessageEvent): boolean {\r\n if(!isRendererEventMessage(event.data)) {\r\n return false;\r\n }\r\n\r\n this.dispatch(event.data);\r\n return true;\r\n }\r\n\r\n /**\r\n *\r\n */\r\n public hasHandlers(eventName: string): boolean {\r\n const handlers = this.listeners.get(eventName);\r\n return !!handlers && handlers.size > 0;\r\n }\r\n}\r\n","/**\r\n * @copyright 2025 NoxFly\r\n * @license MIT\r\n * @author NoxFly\r\n */\r\n\r\nimport { IBatchRequestItem, IBatchResponsePayload, IRequest, IResponse } from './request';\r\nimport { RendererEventRegistry } from './renderer-events';\r\n\r\nexport interface IPortRequester {\r\n requestPort(): void;\r\n}\r\n\r\nexport interface RendererClientOptions {\r\n bridge?: IPortRequester | null;\r\n bridgeName?: string | string[];\r\n initMessageType?: string;\r\n windowRef?: Window;\r\n generateRequestId?: () => string;\r\n /**\r\n * Timeout in milliseconds for IPC requests.\r\n * If the main process does not respond within this duration,\r\n * the request Promise is rejected and the pending entry cleaned up.\r\n * Defaults to 10 000 ms. Set to 0 to disable.\r\n */\r\n requestTimeout?: number;\r\n /** @default true */\r\n enableLogging?: boolean;\r\n}\r\n\r\ninterface PendingRequest<T = unknown> {\r\n resolve: (value: T) => void;\r\n reject: (reason: IResponse<T>) => void;\r\n request: IRequest;\r\n submittedAt: number;\r\n timer?: ReturnType<typeof setTimeout>;\r\n}\r\n\r\nconst DEFAULT_INIT_EVENT = 'init-port';\r\nconst DEFAULT_BRIDGE_NAMES = ['noxus', 'ipcRenderer'];\r\n\r\nfunction defaultRequestId(): string {\r\n if(typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\r\n return crypto.randomUUID();\r\n }\r\n\r\n return `${Date.now().toString(16)}-${Math.floor(Math.random() * 1e8).toString(16)}`;\r\n}\r\n\r\nfunction normalizeBridgeNames(preferred?: string | string[]): string[] {\r\n const names: string[] = [];\r\n\r\n const add = (name: string | undefined): void => {\r\n if(!name)\r\n return;\r\n\r\n if(!names.includes(name)) {\r\n names.push(name);\r\n }\r\n };\r\n\r\n if(Array.isArray(preferred)) {\r\n for(const name of preferred) {\r\n add(name);\r\n }\r\n }\r\n else {\r\n add(preferred);\r\n }\r\n\r\n for(const fallback of DEFAULT_BRIDGE_NAMES) {\r\n add(fallback);\r\n }\r\n\r\n return names;\r\n}\r\n\r\nfunction resolveBridgeFromWindow(windowRef: Window, preferred?: string | string[]): IPortRequester | null {\r\n const names = normalizeBridgeNames(preferred);\r\n const globalRef = windowRef as unknown as Record<string, unknown> | null | undefined;\r\n\r\n if(!globalRef) {\r\n return null;\r\n }\r\n\r\n for(const name of names) {\r\n const candidate = globalRef[name];\r\n\r\n if(candidate && typeof (candidate as IPortRequester).requestPort === 'function') {\r\n return candidate as IPortRequester;\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\nexport class NoxRendererClient {\r\n public readonly events = new RendererEventRegistry();\r\n\r\n protected readonly pendingRequests = new Map<string, PendingRequest>();\r\n\r\n protected requestPort: MessagePort | undefined;\r\n protected socketPort: MessagePort | undefined;\r\n protected senderId: number | undefined;\r\n\r\n private readonly bridge: IPortRequester | null;\r\n private readonly initMessageType: string;\r\n private readonly windowRef: Window;\r\n private readonly generateRequestId: () => string;\r\n private readonly requestTimeout: number;\r\n\r\n private isReady = false;\r\n private setupPromise: Promise<void> | undefined;\r\n private setupResolve: (() => void) | undefined;\r\n private setupReject: ((reason: Error) => void) | undefined;\r\n\r\n private enableLogging: boolean;\r\n\r\n constructor(options: RendererClientOptions = {}) {\r\n this.windowRef = options.windowRef ?? window;\r\n const resolvedBridge = options.bridge ?? resolveBridgeFromWindow(this.windowRef, options.bridgeName);\r\n this.bridge = resolvedBridge ?? null;\r\n this.initMessageType = options.initMessageType ?? DEFAULT_INIT_EVENT;\r\n this.generateRequestId = options.generateRequestId ?? defaultRequestId;\r\n this.requestTimeout = options.requestTimeout ?? 10_000;\r\n this.enableLogging = options.enableLogging ?? true;\r\n }\r\n\r\n public async setup(): Promise<void> {\r\n if(this.isReady) {\r\n return Promise.resolve();\r\n }\r\n\r\n if(this.setupPromise) {\r\n return this.setupPromise;\r\n }\r\n\r\n if(!this.bridge || typeof this.bridge.requestPort !== 'function') {\r\n throw new Error('[Noxus] Renderer bridge is missing requestPort().');\r\n }\r\n\r\n this.setupPromise = new Promise<void>((resolve, reject) => {\r\n this.setupResolve = resolve;\r\n this.setupReject = reject;\r\n });\r\n\r\n this.windowRef.addEventListener('message', this.onWindowMessage);\r\n this.bridge.requestPort();\r\n\r\n return this.setupPromise;\r\n }\r\n\r\n public dispose(): void {\r\n this.windowRef.removeEventListener('message', this.onWindowMessage);\r\n\r\n this.requestPort?.close();\r\n this.socketPort?.close();\r\n\r\n this.requestPort = undefined;\r\n this.socketPort = undefined;\r\n this.senderId = undefined;\r\n this.isReady = false;\r\n\r\n for(const pending of this.pendingRequests.values()) {\r\n if(pending.timer !== undefined) {\r\n clearTimeout(pending.timer);\r\n }\r\n }\r\n\r\n this.pendingRequests.clear();\r\n }\r\n\r\n public async request<TResponse, TBody = unknown>(request: Omit<IRequest<TBody>, 'requestId' | 'senderId'>): Promise<TResponse> {\r\n const senderId = this.senderId;\r\n const requestId = this.generateRequestId();\r\n\r\n if(senderId === undefined) {\r\n return Promise.reject(this.createErrorResponse(requestId, 'MessagePort is not available'));\r\n }\r\n\r\n const readinessError = this.validateReady(requestId);\r\n\r\n if(readinessError) {\r\n return Promise.reject(readinessError as IResponse<TResponse>);\r\n }\r\n\r\n const message: IRequest<TBody> = {\r\n requestId,\r\n senderId,\r\n ...request,\r\n };\r\n\r\n return new Promise<TResponse>((resolve, reject) => {\r\n const pending: PendingRequest<TResponse> = {\r\n resolve,\r\n reject: (response: IResponse<TResponse>) => reject(response),\r\n request: message,\r\n submittedAt: Date.now(),\r\n };\r\n\r\n if(this.requestTimeout > 0) {\r\n pending.timer = setTimeout(() => {\r\n this.pendingRequests.delete(message.requestId);\r\n reject(this.createErrorResponse<TResponse>(message.requestId, `Request timed out after ${this.requestTimeout}ms`) as IResponse<TResponse>);\r\n }, this.requestTimeout);\r\n }\r\n\r\n this.pendingRequests.set(message.requestId, pending as PendingRequest);\r\n\r\n this.requestPort!.postMessage(message);\r\n });\r\n }\r\n\r\n public async batch(requests: Omit<IBatchRequestItem<unknown>, 'requestId'>[]): Promise<IBatchResponsePayload> {\r\n return this.request<IBatchResponsePayload>({\r\n method: 'BATCH',\r\n path: '',\r\n body: {\r\n requests,\r\n },\r\n });\r\n }\r\n\r\n public getSenderId(): number | undefined {\r\n return this.senderId;\r\n }\r\n\r\n private readonly onWindowMessage = (event: MessageEvent): void => {\r\n if(event.data?.type !== this.initMessageType) {\r\n return;\r\n }\r\n\r\n if(!Array.isArray(event.ports) || event.ports.length < 2) {\r\n const error = new Error('[Noxus] Renderer expected two MessagePorts (request + socket).');\r\n\r\n console.error(error);\r\n this.setupReject?.(error);\r\n this.resetSetupState();\r\n return;\r\n }\r\n\r\n this.windowRef.removeEventListener('message', this.onWindowMessage);\r\n\r\n this.requestPort = event.ports[0];\r\n this.socketPort = event.ports[1];\r\n this.senderId = event.data.senderId;\r\n\r\n if(this.requestPort === undefined || this.socketPort === undefined) {\r\n const error = new Error('[Noxus] Renderer failed to receive valid MessagePorts.');\r\n console.error(error);\r\n this.setupReject?.(error);\r\n this.resetSetupState();\r\n return;\r\n }\r\n\r\n this.attachRequestPort(this.requestPort);\r\n this.attachSocketPort(this.socketPort);\r\n\r\n this.isReady = true;\r\n this.setupResolve?.();\r\n this.resetSetupState(true);\r\n };\r\n\r\n private readonly onSocketMessage = (event: MessageEvent): void => {\r\n if(this.events.tryDispatchFromMessageEvent(event)) {\r\n return;\r\n }\r\n\r\n console.warn('[Noxus] Received a socket message that is not a renderer event payload.', event.data);\r\n };\r\n\r\n private readonly onRequestMessage = (event: MessageEvent): void => {\r\n if(this.events.tryDispatchFromMessageEvent(event)) {\r\n return;\r\n }\r\n\r\n const response: IResponse = event.data;\r\n\r\n if(!response || typeof response.requestId !== 'string') {\r\n console.error('[Noxus] Renderer received an invalid response payload.', response);\r\n return;\r\n }\r\n\r\n const pending = this.pendingRequests.get(response.requestId);\r\n\r\n if(!pending) {\r\n console.error(`[Noxus] No pending handler found for request ${response.requestId}.`);\r\n return;\r\n }\r\n\r\n if(pending.timer !== undefined) {\r\n clearTimeout(pending.timer);\r\n }\r\n\r\n this.pendingRequests.delete(response.requestId);\r\n\r\n this.onRequestCompleted(pending, response);\r\n\r\n if(response.status >= 400) {\r\n pending.reject(response as IResponse<any>);\r\n return;\r\n }\r\n\r\n pending.resolve(response.body as unknown);\r\n };\r\n\r\n protected onRequestCompleted(pending: PendingRequest, response: IResponse): void {\r\n if(!this.enableLogging) {\r\n return;\r\n }\r\n\r\n if(typeof console.groupCollapsed === 'function') {\r\n console.groupCollapsed(`${response.status} ${pending.request.method} /${pending.request.path}`);\r\n }\r\n\r\n if(response.error) {\r\n console.error('error message:', response.error);\r\n }\r\n\r\n if(response.body !== undefined) {\r\n console.info('response:', response.body);\r\n }\r\n\r\n console.info('request:', pending.request);\r\n console.info(`Request duration: ${Date.now() - pending.submittedAt} ms`);\r\n\r\n if(typeof console.groupCollapsed === 'function') {\r\n console.groupEnd();\r\n }\r\n }\r\n\r\n private attachRequestPort(port: MessagePort): void {\r\n port.onmessage = this.onRequestMessage;\r\n port.start();\r\n }\r\n\r\n private attachSocketPort(port: MessagePort): void {\r\n port.onmessage = this.onSocketMessage;\r\n port.start();\r\n }\r\n\r\n private validateReady(requestId: string): IResponse | undefined {\r\n if(!this.isElectronEnvironment()) {\r\n return this.createErrorResponse(requestId, 'Not running in Electron environment');\r\n }\r\n\r\n if(!this.requestPort) {\r\n return this.createErrorResponse(requestId, 'MessagePort is not available');\r\n }\r\n\r\n return undefined;\r\n }\r\n\r\n private createErrorResponse<T>(requestId: string, message: string): IResponse<T> {\r\n return {\r\n status: 500,\r\n requestId,\r\n error: message,\r\n };\r\n }\r\n\r\n private resetSetupState(success = false): void {\r\n if(!success) {\r\n this.setupPromise = undefined;\r\n }\r\n\r\n this.setupResolve = undefined;\r\n this.setupReject = undefined;\r\n }\r\n\r\n public isElectronEnvironment(): boolean {\r\n return typeof window !== 'undefined' && /Electron/.test(window.navigator.userAgent);\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noxfly/noxus",
|
|
3
|
-
"version": "3.0.0-dev.
|
|
3
|
+
"version": "3.0.0-dev.6",
|
|
4
|
+
"author": "NoxFly",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Lightweight HTTP-like IPC framework for Electron with standalone controllers, explicit DI, and lazy route loading. No reflect-metadata required.",
|
|
7
|
+
"homepage": "https://github.com/NoxFly/noxus",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/NoxFly/noxus.git"
|
|
11
|
+
},
|
|
12
|
+
"files": ["dist"],
|
|
4
13
|
"main": "dist/main.js",
|
|
5
14
|
"module": "dist/main.mjs",
|
|
6
15
|
"types": "dist/main.d.ts",
|
|
@@ -64,14 +73,6 @@
|
|
|
64
73
|
"standalone",
|
|
65
74
|
"lazy-loading"
|
|
66
75
|
],
|
|
67
|
-
"author": "NoxFly",
|
|
68
|
-
"license": "MIT",
|
|
69
|
-
"description": "Lightweight HTTP-like IPC framework for Electron with standalone controllers, explicit DI, and lazy route loading. No reflect-metadata required.",
|
|
70
|
-
"homepage": "https://github.com/NoxFly/noxus",
|
|
71
|
-
"repository": {
|
|
72
|
-
"type": "git",
|
|
73
|
-
"url": "git+https://github.com/NoxFly/noxus.git"
|
|
74
|
-
},
|
|
75
76
|
"engines": {
|
|
76
77
|
"node": ">= 20"
|
|
77
78
|
},
|
package/.editorconfig
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Editor configuration, see https://editorconfig.org
|
|
2
|
-
root = true
|
|
3
|
-
|
|
4
|
-
[*]
|
|
5
|
-
charset = utf-8
|
|
6
|
-
indent_style = space
|
|
7
|
-
indent_size = 4
|
|
8
|
-
insert_final_newline = true
|
|
9
|
-
trim_trailing_whitespace = true
|
|
10
|
-
|
|
11
|
-
[*.ts]
|
|
12
|
-
quote_type = single
|
|
13
|
-
|
|
14
|
-
[*.md]
|
|
15
|
-
max_line_length = off
|
|
16
|
-
trim_trailing_whitespace = false
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
# Copilot Instructions
|
|
2
|
-
|
|
3
|
-
## Core Architecture
|
|
4
|
-
- bootstrapApplication in [src/internal/bootstrap.ts](src/internal/bootstrap.ts) waits for Electron app readiness, applies log level config, wires the controller registrar, flushes DI, resolves NoxApp, registers routes and eager-loads, then returns a configured instance.
|
|
5
|
-
- [src/internal/app.ts](src/internal/app.ts) wires ipcMain events, spawns paired request/socket MessageChannels per renderer, and delegates handling to Router and NoxSocket.
|
|
6
|
-
- Package exports are split between renderer and main targets in [package.json](package.json); import Electron main APIs from @noxfly/noxus/main, renderer helpers from @noxfly/noxus or @noxfly/noxus/renderer, and preload helpers from @noxfly/noxus/preload.
|
|
7
|
-
- Dependency injection centers on RootInjector in [src/DI/app-injector.ts](src/DI/app-injector.ts); @Injectable triggers auto-registration through [src/DI/injector-explorer.ts](src/DI/injector-explorer.ts) and supports singleton, scope, and transient lifetimes.
|
|
8
|
-
- resetRootInjector() in [src/DI/app-injector.ts](src/DI/app-injector.ts) clears all bindings, singletons, and scoped instances — use it in tests to get a clean DI state between test suites.
|
|
9
|
-
- There is no Module decorator — controllers and services are standalone, registered via @Injectable and @Controller decorators with explicit deps arrays.
|
|
10
|
-
|
|
11
|
-
## Request Lifecycle
|
|
12
|
-
- Request objects from [src/internal/request.ts](src/internal/request.ts) wrap Electron MessageEvents and spawn per-request DI scopes on Request.context. They expose params, body, and query (Record<string, string>).
|
|
13
|
-
- Router in [src/internal/router.ts](src/internal/router.ts) indexes routes in a radix tree, merges controller-level and method-level decorators, and enforces root middlewares, route middlewares, then guards before invoking controller actions.
|
|
14
|
-
- The radix tree in [src/utils/radix-tree.ts](src/utils/radix-tree.ts) prioritizes static segments over parameter segments during search, so `addNote/:id` won't be shadowed by `:id`.
|
|
15
|
-
- ResponseException subclasses in [src/internal/exceptions.ts](src/internal/exceptions.ts) propagate status codes; throwing one short-circuits the pipeline so Router returns a structured error payload.
|
|
16
|
-
- Batch requests use HTTP method BATCH and normalization logic in Router.handleBatch; payloads must satisfy IBatchRequestPayload to fan out atomic subrequests.
|
|
17
|
-
|
|
18
|
-
## Communication Channels
|
|
19
|
-
- ipcMain listens for gimme-my-port and posts two transferable ports back to the renderer: index 0 carries request/response traffic, index 1 is reserved for socket-style push messages.
|
|
20
|
-
- NoxSocket in [src/internal/socket.ts](src/internal/socket.ts) maps sender IDs to {request, socket} channels and emits renderer events exclusively through channels.socket.port1. emit() returns void.
|
|
21
|
-
- Renderer helpers in [src/internal/renderer-events.ts](src/internal/renderer-events.ts) expose RendererEventRegistry.tryDispatchFromMessageEvent to route push events; the preload script must start both ports and hand the second to this registry.
|
|
22
|
-
- Renderer-facing bootstrap lives in [src/internal/renderer-client.ts](src/internal/renderer-client.ts); NoxRendererClient requests ports, wires request/socket handlers, tracks pending promises with configurable timeout (default 10s), and surfaces RendererEventRegistry for push consumption.
|
|
23
|
-
- Preload scripts should call exposeNoxusBridge from [src/internal/preload-bridge.ts](src/internal/preload-bridge.ts) to publish window.noxus.requestPort; the helper sends 'gimme-my-port' and forwards both transferable ports with the configured init message.
|
|
24
|
-
- When adjusting preload or renderer glue, ensure window.postMessage('init-port', ...) forwards both ports so the socket channel stays alive alongside the request channel.
|
|
25
|
-
|
|
26
|
-
## Route Definitions
|
|
27
|
-
- defineRoutes in [src/internal/routes.ts](src/internal/routes.ts) is the single source of truth for routing. It validates for duplicate and overlapping prefixes and supports nested children with inherited guards/middlewares.
|
|
28
|
-
- Parent routes can omit the load function and serve only as shared prefix containers for children.
|
|
29
|
-
- flattenRoutes recursively merges parent guards and middlewares into each child before validation.
|
|
30
|
-
|
|
31
|
-
## Decorator Conventions
|
|
32
|
-
- Controller paths omit leading/trailing slashes; method decorators (Get, Post, etc.) auto-trim segments via [src/decorators/method.decorator.ts](src/decorators/method.decorator.ts).
|
|
33
|
-
- Guards registered through Authorize in [src/decorators/guards.decorator.ts](src/decorators/guards.decorator.ts) aggregate at controller and action scope; they must resolve truthy or Router throws UnauthorizedException.
|
|
34
|
-
- Injectable lifetimes default to scope; use singleton for app-wide utilities (window managers, sockets) and transient for short-lived resources.
|
|
35
|
-
|
|
36
|
-
## Dependency Injection Internals
|
|
37
|
-
- InjectorExplorer in [src/DI/injector-explorer.ts](src/DI/injector-explorer.ts) accumulates registrations at import time and flushes in two phases (bind then resolve).
|
|
38
|
-
- To avoid a circular dependency between InjectorExplorer and Router, controller registration is decoupled via a ControllerRegistrar callback set by bootstrapApplication through setControllerRegistrar(). There is no require() call.
|
|
39
|
-
- flushAccumulated is serialized through a Promise-based lock (loadingLock) to prevent concurrent lazy module loads from corrupting the queue. Use waitForFlush() to await completion.
|
|
40
|
-
- Phase 2 validates that declared deps have a corresponding binding or singleton, and emits warnings for missing ones at startup.
|
|
41
|
-
|
|
42
|
-
## Logging and Utilities
|
|
43
|
-
- Logger in [src/utils/logger.ts](src/utils/logger.ts) standardizes color-coded log, warn, error, and comment output; it supports configurable log levels via Logger.setLogLevel() and file output via Logger.enableFileLogging().
|
|
44
|
-
- bootstrapApplication accepts a logLevel option ('debug' | 'info' | 'none' | LogLevel[]) to control framework verbosity.
|
|
45
|
-
- Path resolution relies on RadixTree from [src/utils/radix-tree.ts](src/utils/radix-tree.ts); normalize controller and route paths to avoid duplicate slashes.
|
|
46
|
-
- Request.params are filled by Router.extractParams; Request.query carries query parameters passed from the renderer; controllers read both directly from the Request object.
|
|
47
|
-
|
|
48
|
-
## Build and Tooling
|
|
49
|
-
- Run npm run build to invoke tsup with dual ESM/CJS outputs configured in [tsup.config.ts](tsup.config.ts); the postbuild script at [scripts/postbuild.js](scripts/postbuild.js) deduplicates license banners.
|
|
50
|
-
- Node 20 or newer is required; no reflect-metadata is needed — all dependency information comes from explicit deps arrays.
|
|
51
|
-
- Source uses baseUrl ./ with tsc-alias, so prefer absolute imports like src/module/file when editing framework code to match existing style.
|
|
52
|
-
- Dist artifacts live under dist/ and are published outputs; regenerate them via the build script rather than editing directly.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
## Validating TypeScript changes
|
|
56
|
-
|
|
57
|
-
MANDATORY: Always check for compilation errors before running any tests or validation scripts, or declaring work complete, then fix all compilation errors before moving forward.
|
|
58
|
-
|
|
59
|
-
## Coding Guidelines
|
|
60
|
-
|
|
61
|
-
### Indentation
|
|
62
|
-
|
|
63
|
-
Use spaces, 4 for any file type, except yml that are 2 spaces.
|
|
64
|
-
|
|
65
|
-
### Naming Conventions
|
|
66
|
-
|
|
67
|
-
Use PascalCase for type names
|
|
68
|
-
Use PascaleCase for class names and interfaces
|
|
69
|
-
Prefix interface names by an 'I'
|
|
70
|
-
Use PascalCase for enum values
|
|
71
|
-
Choose wisely enum over union types or consts
|
|
72
|
-
Prefer interfaces over types when possible
|
|
73
|
-
Use camelCase for function and method names
|
|
74
|
-
Use camelCase for property names and local variables
|
|
75
|
-
Use whole words in names when possible
|
|
76
|
-
Use kebab-case for file names, and avoid using the same name for multiple files in different directories to prevent confusion
|
|
77
|
-
suffix file names with their type (e.g., .service.ts, .controller.ts, .component.ts) to make it clear what the file contains and its role in the architecture
|
|
78
|
-
|
|
79
|
-
### Types
|
|
80
|
-
|
|
81
|
-
Do not export types or functions unless you need to share it across multiple components
|
|
82
|
-
Do not introduce new types or values to the global namespace unless they are truly global concepts
|
|
83
|
-
|
|
84
|
-
### Comments
|
|
85
|
-
|
|
86
|
-
Use JSDoc style comments for functions, interfaces, enums, and classes
|
|
87
|
-
Always include a description of what the function/class/interface/enum does, its parameters, its return value and an example (if applicable)
|
|
88
|
-
Use inline comments to explain why a particular implementation was chosen, especially if it's not obvious. Avoid stating what the code is doing, and instead focus on the reasoning behind it.
|
|
89
|
-
|
|
90
|
-
### Strings
|
|
91
|
-
|
|
92
|
-
Always use "double quotes" for strings.
|
|
93
|
-
Use template literals for string interpolation and multi-line strings instead of concatenation.
|
|
94
|
-
|
|
95
|
-
### Style
|
|
96
|
-
|
|
97
|
-
Use arrow functions => over anonymous function expressions.
|
|
98
|
-
Only surround arrow function parameters when necessary.
|
|
99
|
-
Only surround arrow function bodies with curly braces when they are not a direct return of an expression.
|
|
100
|
-
Always surround loop and conditional bodies with curly braces.
|
|
101
|
-
Open curly braces always go on the same line as whatever necessitates them
|
|
102
|
-
Use StrouStrup style for control statements (the else is on a new line after the closing brace of the if).
|
|
103
|
-
Whenever possible, use in top-level scopes export function x(…) {…} instead of export const x = (…) => {…}. One advantage of using the function keyword is that the stack-trace shows a good name when debugging.
|
|
104
|
-
|
|
105
|
-
### Code Quality
|
|
106
|
-
|
|
107
|
-
All files must include the NoxFly copyright header
|
|
108
|
-
Prefer async and await over Promise and then calls
|
|
109
|
-
Always await a promise to make the function appearable in the stack trace, unless you have a good reason not to (e.g., you want it to run in the background and don't care about errors).
|
|
110
|
-
Look for existing test patterns before creating new structures
|
|
111
|
-
Prefer regex capture groups with names over numbered capture groups.
|
|
112
|
-
If you create any temporary new files, scripts, or helper files for iteration, clean up these files by removing them at the end of the task
|
|
113
|
-
Never duplicate imports. Always reuse existing imports if they are present.
|
|
114
|
-
When removing an import, do not leave behind blank lines where the import was. Ensure the surrounding code remains compact.
|
|
115
|
-
Do not use any or unknown as the type for variables, parameters, or return values unless absolutely necessary. If they need type annotations, they should have proper types or interfaces defined.
|
|
116
|
-
Do not duplicate code. Always look for existing utility functions, helpers, or patterns in the codebase before implementing new functionality. Reuse and extend existing code whenever possible.
|
|
117
|
-
Avoid using bind(), call() and apply() solely to control this or partially apply arguments; prefer arrow functions or closures to capture the necessary context, and use these methods only when required by an API or interoperability.
|
|
118
|
-
Always think for the most performant code for future scalability, even if it requires more upfront work. Consider time and space complexity when designing algorithms and data structures, and prefer efficient patterns that will scale well as the codebase grows.
|
|
119
|
-
Always specify the `public`, `private`, or `protected` access modifier for class members, even if the default is public. This improves readability and makes the intended encapsulation clear to other developers.
|
|
120
|
-
Always use explicit return types for functions and methods, even when TypeScript can infer them. This improves readability and helps catch unintended return values or changes in the function's behavior over time.
|
|
121
|
-
Avoid using non-null assertions (!). Instead, handle potential null or undefined values explicitly through type guards, default values, or proper error handling to ensure safer and more robust code.
|
|
122
|
-
Always prefer composition over inheritance. Favor creating small, reusable functions and classes that can be combined to achieve complex behavior, rather than relying on deep inheritance hierarchies which can lead to tight coupling and reduced flexibility.
|
|
123
|
-
Always use strict equality (=== and !==) instead of loose equality (== and !=) to avoid unexpected type coercion and ensure more predictable comparisons.
|
|
124
|
-
Always ensure readme and copilot-instructions files are updated to reflect any architectural, structural, or convention changes made to the codebase. These documents serve as the primary reference for other developers and must accurately represent the current state of the project.
|
|
125
|
-
Do not hesitate to refactor existing code to improve readability, maintainability, or performance, even if it is not directly related to the task at hand. Continuous improvement of the codebase is essential for long-term success and developer satisfaction.
|
|
126
|
-
Do not hesitate to ask for help or clarification if a request is unclear.
|
|
127
|
-
Do not hesitate to suggest improvements or optimizations if you see an opportunity, even if it's outside the scope of your current task.
|
|
128
|
-
Always write unit tests for new functionality and bug fixes, and ensure existing tests are updated as necessary to reflect changes in the codebase. Aim for high test coverage and meaningful test cases that validate the expected behavior of the code.
|
package/.vscode/settings.json
DELETED
package/AGENTS.md
DELETED
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
|
-
);
|
package/scripts/postbuild.js
DELETED
|
@@ -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
|
-
}
|
package/src/DI/app-injector.ts
DELETED
|
@@ -1,173 +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
|
-
|
|
81
|
-
if (this.singletons.has(k)) {
|
|
82
|
-
return this.singletons.get(k) as T;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const binding = this.bindings.get(k);
|
|
86
|
-
|
|
87
|
-
if (!binding) {
|
|
88
|
-
const name = target instanceof Token
|
|
89
|
-
? target.description
|
|
90
|
-
: (target as Type<unknown>).name
|
|
91
|
-
?? 'unknown';
|
|
92
|
-
|
|
93
|
-
throw new Error(
|
|
94
|
-
`[Noxus DI] No binding found for "${name}".\n`
|
|
95
|
-
+ `Did you forget to declare it in @Injectable({ deps }) or in bootstrapApplication({ singletons })?`,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
switch (binding.lifetime) {
|
|
100
|
-
case 'transient':
|
|
101
|
-
return this._instantiate(binding) as T;
|
|
102
|
-
|
|
103
|
-
case 'scope': {
|
|
104
|
-
if (this.scoped.has(k)) return this.scoped.get(k) as T;
|
|
105
|
-
const inst = this._instantiate(binding);
|
|
106
|
-
this.scoped.set(k, inst);
|
|
107
|
-
return inst as T;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
case 'singleton': {
|
|
111
|
-
if (this.singletons.has(k)) return this.singletons.get(k) as T;
|
|
112
|
-
const inst = this._instantiate(binding);
|
|
113
|
-
this.singletons.set(k, inst);
|
|
114
|
-
if (binding.instance === undefined) {
|
|
115
|
-
(binding as IBinding<unknown>).instance = inst as unknown;
|
|
116
|
-
}
|
|
117
|
-
return inst as T;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// -------------------------------------------------------------------------
|
|
123
|
-
|
|
124
|
-
private _resolveForwardRef<T>(ref: ForwardReference<T>): T {
|
|
125
|
-
let resolved: T | undefined;
|
|
126
|
-
return new Proxy({} as object, {
|
|
127
|
-
get: (_obj, prop, receiver) => {
|
|
128
|
-
resolved ??= this.resolve(ref.forwardRefFn()) as T;
|
|
129
|
-
const value = Reflect.get(resolved as object, prop, receiver);
|
|
130
|
-
return typeof value === 'function' ? (value as Function).bind(resolved) : value;
|
|
131
|
-
},
|
|
132
|
-
set: (_obj, prop, value, receiver) => {
|
|
133
|
-
resolved ??= this.resolve(ref.forwardRefFn()) as T;
|
|
134
|
-
return Reflect.set(resolved as object, prop, value, receiver);
|
|
135
|
-
},
|
|
136
|
-
getPrototypeOf: () => {
|
|
137
|
-
resolved ??= this.resolve(ref.forwardRefFn()) as T;
|
|
138
|
-
return Object.getPrototypeOf(resolved);
|
|
139
|
-
},
|
|
140
|
-
}) as T;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private _instantiate<T>(binding: IBinding<T>): T {
|
|
144
|
-
const resolvedDeps = binding.deps.map((dep) => this.resolve(dep));
|
|
145
|
-
return new binding.implementation(...resolvedDeps) as T;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* The global root injector. All singletons live here.
|
|
151
|
-
*/
|
|
152
|
-
export const RootInjector = new AppInjector('root');
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Resets the root injector to a clean state.
|
|
156
|
-
* **Intended for testing only** — clears all bindings, singletons, and scoped instances
|
|
157
|
-
* so that each test can start from a fresh DI container without restarting the process.
|
|
158
|
-
*/
|
|
159
|
-
export function resetRootInjector(): void {
|
|
160
|
-
RootInjector.bindings.clear();
|
|
161
|
-
RootInjector.singletons.clear();
|
|
162
|
-
RootInjector.scoped.clear();
|
|
163
|
-
// Lazy import to avoid circular dependency (InjectorExplorer → app-injector → InjectorExplorer)
|
|
164
|
-
const { InjectorExplorer } = require('./injector-explorer') as typeof import('./injector-explorer');
|
|
165
|
-
InjectorExplorer.reset();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Convenience function: resolve a token from the root injector.
|
|
170
|
-
*/
|
|
171
|
-
export function inject<T>(t: TokenKey<T> | ForwardReference<T>): T {
|
|
172
|
-
return RootInjector.resolve(t);
|
|
173
|
-
}
|