@aurora-mp/core
Version:
Core package of the aurora-mp TypeScript framework, providing dependency injection, event handling, and module registration.
1,132 lines (1,101 loc) • 38.8 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// src/index.ts
import "reflect-metadata";
// src/constants/injection-tokens.ts
var CONFIG_LOADER = Symbol.for("aurora:config:loader");
var CONFIG_SERVICE = Symbol.for("aurora:config:service");
var EVENT_SERVICE = Symbol.for("aurora:event:service");
var RPC_SERVICE = Symbol.for("aurora:rpc:service");
var LOGGER_SERVICE = Symbol.for("aurora:logger:service");
var PLATFORM_DRIVER = Symbol.for("aurora:platform:driver");
var WEBVIEW_SERVICE = Symbol.for("aurora:webview:service");
// src/constants/metadata-key.ts
var MODULE_METADATA_KEY = Symbol.for("aurora:module");
var GLOBAL_MODULE_KEY = Symbol.for("aurora:global:module");
var CONTROLLER_METADATA_KEY = Symbol.for("aurora:controller");
var CONTROLLER_EVENTS_KEY = Symbol.for("aurora:controller:events");
var CONTROLLER_PARAMS_KEY = Symbol.for("aurora:controller:params");
var CONTROLLER_RPCS_KEY = Symbol.for("aurora:controller:rpcs");
var GUARDS_METADATA_KEY = Symbol.for("aurora:guards");
var INJECT_TOKEN_KEY = Symbol.for("aurora:inject");
var INJECT_PROPERTY_KEY = Symbol.for("aurora:inject:property");
var INJECTABLE_METADATA_KEY = Symbol.for("aurora:injectable");
var INJECTABLE_SCOPE_OPTIONS = Symbol.for("aurora:injectable:scope:options");
var EVENT_HANDLER_KEY = Symbol.for("aurora:event:handler");
// src/di/container.ts
var Container = class {
static {
__name(this, "Container");
}
providers = /* @__PURE__ */ new Map();
/**
* Registers a provider instance under the given token.
* If a provider already exists for this token, it will be overridden.
*
* @param token The injection token (class constructor, string, or symbol).
* @param value The instance or value to associate with the token.
*/
register(token, value) {
if (this.providers.has(token)) {
console.warn(`A provider with the token "${String(token)}" is already registered. It will be overridden.`);
}
this.providers.set(token, value);
}
/**
* Retrieves the provider instance associated with the given token.
*
* @param token The injection token to resolve.
* @returns The instance or value stored under the token.
* @throws {Error} If no provider is found for the token.
*/
resolve(token) {
const instance = this.providers.get(token);
if (instance === void 0) {
throw new Error(`No provider found for token "${String(token)}". Make sure it is provided in a module and exported if necessary.`);
}
return instance;
}
/**
* Checks whether a provider is registered for the given token.
*
* @param token The injection token to check.
* @returns `true` if a provider exists, `false` otherwise.
*/
has(token) {
return this.providers.has(token);
}
/**
* Returns an iterator over all registered provider instances.
* Can be used for debugging or lifecycle management.
*
* @returns Iterable iterator of all stored provider values.
*/
getInstances() {
return this.providers.values();
}
};
// src/di/module-wrapper.ts
var ModuleWrapper = class {
static {
__name(this, "ModuleWrapper");
}
type;
metadata;
/**
* Modules imported by this module.
*/
_imports = /* @__PURE__ */ new Set();
/**
* Controller classes declared in this module.
*/
_controllers = /* @__PURE__ */ new Set();
/**
* Tokens exported by this module for other modules to consume.
*/
_exports = /* @__PURE__ */ new Set();
/**
* The DI container instance scoped to this module.
* Initially holds provider definitions, later replaced with their instantiated values.
*/
container = new Container();
/**
* Creates a new ModuleWrapper.
*
* @param type The module’s class constructor.
* @param metadata The metadata extracted from the @Module decorator, including
* imports, controllers, providers, and exports.
*/
constructor(type, metadata) {
this.type = type;
this.metadata = metadata;
metadata.controllers?.forEach((c) => this._controllers.add(c));
metadata.exports?.forEach((e) => this._exports.add(e));
metadata.providers?.forEach((p) => {
const token = this.getTokenFromProvider(p);
this.container.register(token, p);
});
}
/**
* Gets the set of modules imported by this module.
*/
get imports() {
return this._imports;
}
/**
* Gets the set of controller classes declared in this module.
*/
get controllers() {
return this._controllers;
}
/**
* Gets the set of tokens this module exports.
*/
get exports() {
return this._exports;
}
/**
* Adds an imported module to this module’s import graph.
*
* @param importedModule The ModuleWrapper instance to import.
*/
addImport(importedModule) {
this._imports.add(importedModule);
}
/**
* Determines the DI token for a provider definition.
* If the provider is a class constructor, the constructor itself is the token;
* otherwise uses the `provide` property of the Provider object.
*
* @param provider The provider definition (class or object).
* @returns The token under which this provider is registered.
*/
getTokenFromProvider(provider) {
return typeof provider === "function" ? provider : provider.provide;
}
};
// src/enums/event-type.enum.ts
var EventType = /* @__PURE__ */ function(EventType2) {
EventType2["ON"] = "on";
EventType2["ON_CLIENT"] = "onClient";
EventType2["ON_SERVER"] = "onServer";
return EventType2;
}({});
// src/enums/method-param-type.enum.ts
var MethodParamType = /* @__PURE__ */ function(MethodParamType2) {
MethodParamType2["PAYLOAD"] = "payload";
MethodParamType2["PARAM"] = "param";
MethodParamType2["PLAYER"] = "player";
return MethodParamType2;
}({});
// src/enums/rpc-type.enum.ts
var RpcType = /* @__PURE__ */ function(RpcType2) {
RpcType2["ON_CLIENT"] = "onClientRpc";
RpcType2["ON_SERVER"] = "onServerRpc";
return RpcType2;
}({});
// src/enums/scope.enum.ts
var Scope = /* @__PURE__ */ function(Scope2) {
Scope2[Scope2["SINGLETON"] = 0] = "SINGLETON";
Scope2[Scope2["TRANSIENT"] = 1] = "TRANSIENT";
return Scope2;
}({});
// src/enums/webview-events.enum.ts
var WebViewEvents = /* @__PURE__ */ function(WebViewEvents2) {
WebViewEvents2["DISPATCH"] = "aurora:webview:dispatch";
WebViewEvents2["EMIT_SERVER"] = "aurora:webview:emitServer";
WebViewEvents2["INVOKE_SERVER_RPC"] = "aurora:webview:invokeServerRpc";
return WebViewEvents2;
}({});
// src/utils/provider-utils.ts
function normalizeProvider(provider) {
if (typeof provider === "function") {
return {
provide: provider,
useClass: provider
};
}
if ("useFactory" in provider && provider.useFactory) {
const fp = {
provide: provider.provide,
useFactory: provider.useFactory,
inject: provider.inject ?? []
};
if (provider.scope !== void 0) {
fp.scope = provider.scope;
}
return fp;
}
if ("useValue" in provider && provider.useValue !== void 0) {
const vp = {
provide: provider.provide,
useValue: provider.useValue
};
if (provider.scope !== void 0) {
vp.scope = provider.scope;
}
return vp;
}
if ("useClass" in provider && provider.useClass) {
const cp = {
provide: provider.provide,
useClass: provider.useClass
};
if (provider.scope !== void 0) {
cp.scope = provider.scope;
}
return cp;
}
throw new Error(`[Aurora] Invalid provider configuration for token "${String(provider.provide)}"`);
}
__name(normalizeProvider, "normalizeProvider");
function getProviderScope(provider) {
const norm = normalizeProvider(provider);
if ("useFactory" in norm) {
return norm.scope ?? Scope.SINGLETON;
}
let ctor;
if ("useClass" in norm) {
ctor = norm.useClass;
} else if ("useValue" in norm) {
ctor = norm.useValue.constructor;
}
if (ctor) {
const meta = Reflect.getMetadata(INJECTABLE_SCOPE_OPTIONS, ctor);
return meta ?? Scope.SINGLETON;
}
return Scope.SINGLETON;
}
__name(getProviderScope, "getProviderScope");
// src/utils/token-utils.ts
function getTokenName(token) {
return typeof token === "function" ? token.name : String(token);
}
__name(getTokenName, "getTokenName");
function tokenToString(token) {
if (typeof token === "function") {
return token.name;
}
if (typeof token === "symbol") {
return token.toString();
}
if (token && typeof token === "object") {
if ("provide" in token) {
return tokenToString(token.provide);
}
if ("useClass" in token) {
return `[useClass: ${tokenToString(token.useClass)}]`;
}
if ("useValue" in token) {
return `[useValue: ${typeof token.useValue}]`;
}
if ("useFactory" in token) {
const name = token.useFactory.name || "<anonymous>";
return `[useFactory: ${name}]`;
}
}
return String(token);
}
__name(tokenToString, "tokenToString");
// src/bootstrap/controller-flow.handler.ts
var ControllerFlowHandler = class {
static {
__name(this, "ControllerFlowHandler");
}
container;
logger = console;
constructor(container) {
this.container = container;
}
setLogger(logger) {
this.logger = logger;
}
/**
* Maps the ExecutionContext to an array of arguments for the controller method.
* @param context The current execution context containing raw arguments.
* @param event Metadata for the event handler including parameter definitions.
* @returns An array of arguments to apply to the controller method.
*/
createArgs(context, handler) {
if (!handler.params?.length) {
return context.args;
}
const sorted = [
...handler.params
].sort((a, b) => a.index - b.index);
const rawArgs = context.args;
const args = [];
for (const param of sorted) {
let value;
switch (param.type) {
case MethodParamType.PLAYER:
value = param.data ? rawArgs[0]?.[param.data] : rawArgs[0];
break;
case MethodParamType.PAYLOAD:
let rawPayload = rawArgs[1];
if (typeof rawPayload === "string") {
try {
rawPayload = JSON.parse(rawPayload);
} catch {
}
}
if (rawPayload != null && typeof rawPayload === "object") {
value = param.data ? rawPayload[param.data] : rawPayload;
} else {
value = rawArgs.slice(1);
}
break;
case MethodParamType.PARAM:
const obj = rawArgs[1];
if (obj != null && typeof obj === "object") {
value = obj?.[param.data];
} else {
value = rawArgs[param.index];
}
break;
default:
throw new Error(`Unknown parameter type ${param.type}`);
}
args[param.index] = value;
}
return args;
}
async canActivate(context) {
const targetClass = context.getClass();
const handler = context.getHandler();
const classGuards = Reflect.getMetadata(GUARDS_METADATA_KEY, targetClass) || [];
const methodGuards = Reflect.getMetadata(GUARDS_METADATA_KEY, targetClass.prototype, handler.name) || [];
const allGuards = [
...classGuards,
...methodGuards
];
for (const guard of allGuards) {
const instance = this.container.resolve(guard);
const allowed = await instance.canActivate(context);
if (!allowed) {
this.logger.debug(`[Aurora] Access denied by ${guard.name} on ${targetClass.name}.${handler.name}`);
return false;
}
this.logger.debug(`[Aurora] Guard ${guard.name} granted access.`);
}
return true;
}
wrapWithGuardsProxy(instance, targetClass) {
const methodNames = Object.getOwnPropertyNames(targetClass.prototype).filter((name) => name !== "constructor" && typeof instance[name] === "function");
return new Proxy(instance, {
get: /* @__PURE__ */ __name((obj, prop, receiver) => {
if (typeof prop !== "string" || !methodNames.includes(prop)) {
return Reflect.get(obj, prop, receiver);
}
const original = Reflect.get(obj, prop, receiver);
return async (...args) => {
const ctx = {
name: `${targetClass.name}.${prop}`,
args,
payload: args[1] ?? args[0],
player: args[0],
getClass: /* @__PURE__ */ __name(() => targetClass, "getClass"),
getHandler: /* @__PURE__ */ __name(() => targetClass.prototype[prop], "getHandler"),
getPlayer: /* @__PURE__ */ __name(() => args[0], "getPlayer")
};
if (!await this.canActivate(ctx)) {
this.logger.warn(`[Aurora] Access denied to ${ctx.name}`);
return;
}
return original.apply(obj, args);
};
}, "get")
});
}
hasGuards(targetClass) {
const proto = targetClass.prototype;
const classGuards = Reflect.getMetadata(GUARDS_METADATA_KEY, targetClass) || [];
if (classGuards.length) return true;
return Object.getOwnPropertyNames(proto).some((m) => m !== "constructor" && (Reflect.getMetadata(GUARDS_METADATA_KEY, proto, m) || []).length > 0);
}
};
// src/bootstrap/event-binder.ts
var EventBinder = class {
static {
__name(this, "EventBinder");
}
platformDriver;
flowHandler;
constructor(platformDriver, flowHandler) {
this.platformDriver = platformDriver;
this.flowHandler = flowHandler;
}
/**
* Binds all controller event handlers for a given set of modules/controllers.
* @param controllersWithInstances Array of [ControllerClass, controllerInstance]
*/
bindControllerEvents(controllersWithInstances) {
for (const [controllerType, controllerInstance] of controllersWithInstances) {
const eventHandlers = Reflect.getMetadata(CONTROLLER_EVENTS_KEY, controllerType) || [];
for (const handler of eventHandlers) {
const params = Reflect.getOwnMetadata(CONTROLLER_PARAMS_KEY, controllerType.prototype, handler.methodName) ?? [];
handler.params = params;
const guards = Reflect.getOwnMetadata(GUARDS_METADATA_KEY, controllerType.prototype, handler.methodName) ?? [];
handler.guards = guards;
const dispatcher = this.createDispatcher(controllerInstance, handler);
switch (handler.type) {
case EventType.ON:
this.platformDriver.on(handler.name, dispatcher);
break;
case EventType.ON_CLIENT:
if (this.platformDriver.onClient) this.platformDriver.onClient(handler.name, dispatcher);
break;
case EventType.ON_SERVER:
if (this.platformDriver.onServer) this.platformDriver.onServer(handler.name, dispatcher);
break;
}
}
}
}
/**
* Creates a dispatcher for the event handler method (param injection).
*/
createDispatcher(instance, handler) {
return async (...args) => {
try {
const context = {
name: handler.name,
args,
payload: args,
player: handler.type === EventType.ON_CLIENT ? args[0] : void 0,
getClass: /* @__PURE__ */ __name(() => instance.constructor, "getClass"),
getHandler: /* @__PURE__ */ __name(() => instance[handler.methodName], "getHandler"),
getPlayer: /* @__PURE__ */ __name(() => args[0], "getPlayer")
};
const allowed = await this.flowHandler.canActivate(context);
if (!allowed) {
console.warn(`[Aurora] Access denied for event "${handler.name}"`);
return;
}
const methodArgs = this.flowHandler.createArgs(context, handler);
await instance[handler.methodName](...methodArgs);
} catch (error) {
console.error(`[Aurora] Error handling event "${handler.name}" on "${instance.constructor.name}"`, error);
}
};
}
};
// src/bootstrap/rpc-binder.ts
var RpcBinder = class {
static {
__name(this, "RpcBinder");
}
platformDriver;
flowHandler;
constructor(platformDriver, flowHandler) {
this.platformDriver = platformDriver;
this.flowHandler = flowHandler;
}
bindControllerRpcs(controllers) {
for (const [controllerType, controllerInstance] of controllers) {
const rpcs = Reflect.getMetadata(CONTROLLER_RPCS_KEY, controllerType) || [];
for (const rpc of rpcs) {
const params = Reflect.getOwnMetadata(CONTROLLER_PARAMS_KEY, controllerType.prototype, rpc.methodName) || [];
rpc.params = params;
const guards = Reflect.getOwnMetadata(GUARDS_METADATA_KEY, controllerType.prototype, rpc.methodName) ?? [];
rpc.guards = guards;
const dispatcher = this.createDispatcher(controllerInstance, rpc);
switch (rpc.type) {
case RpcType.ON_CLIENT:
if (this.platformDriver.onRpcServer) this.platformDriver.onRpcServer(rpc.name, dispatcher);
break;
case RpcType.ON_SERVER:
if (this.platformDriver.onRpcClient) this.platformDriver.onRpcClient(rpc.name, dispatcher);
break;
}
}
}
}
createDispatcher(instance, rpc) {
return async (...args) => {
try {
const context = {
name: rpc.name,
args,
payload: args,
// TODO: player: handler.type === EventType.ON_CLIENT ? args[0] : undefined,
getClass: /* @__PURE__ */ __name(() => instance.constructor, "getClass"),
getHandler: /* @__PURE__ */ __name(() => instance[rpc.methodName], "getHandler"),
getPlayer: /* @__PURE__ */ __name(() => args[0], "getPlayer")
};
const allowed = await this.flowHandler.canActivate(context);
if (!allowed) {
console.warn(`[Aurora] Access denied for RPC "${rpc.name}"`);
return;
}
const methodArgs = this.flowHandler.createArgs(context, rpc);
return await instance[rpc.methodName](...methodArgs);
} catch (error) {
console.error(`[AuroraDI] Error handling RPC "${rpc.name}" on "${instance.constructor.name}"`, error);
}
};
}
};
// src/bootstrap/application.factory.ts
var ApplicationFactory = class _ApplicationFactory {
static {
__name(this, "ApplicationFactory");
}
platformDriver;
applicationRef;
moduleWrappers = /* @__PURE__ */ new Map();
globalModules = /* @__PURE__ */ new Set();
instanceContainer = new Container();
flowHandler;
eventBinder;
rpcBinder;
plugins = [];
logger = console;
config;
started = false;
closed = false;
debug = false;
/**
* @param platformDriver The platform driver for event binding/runtime APIs.
* @internal
*/
constructor(platformDriver) {
this.platformDriver = platformDriver;
this.instanceContainer.register(PLATFORM_DRIVER, this.platformDriver);
this.flowHandler = new ControllerFlowHandler(this.instanceContainer);
this.eventBinder = new EventBinder(platformDriver, this.flowHandler);
this.rpcBinder = new RpcBinder(platformDriver, this.flowHandler);
this.applicationRef = {
start: this.start.bind(this),
get: this.get.bind(this),
close: this.close.bind(this),
usePlugins: this.usePlugins.bind(this)
};
}
/**
* Bootstraps an Aurora application and returns its public interface.
* @param rootModule The application's root module (entry point)
* @param platformDriver The platform driver for this runtime
* @returns A Promise resolving to the IApplication instance
*/
static async create(rootModule, platformDriver, plugins = []) {
const factory = new _ApplicationFactory(platformDriver);
factory.usePlugins(...plugins);
await factory.initialize(rootModule);
return factory.applicationRef;
}
usePlugins(...plugins) {
this.plugins.push(...plugins);
return this;
}
/**
* Starts the application and binds all controller event handlers.
*/
async start() {
if (this.started) {
this.logger.warn("[Aurora] Application already started.");
return;
}
this.started = true;
this.logger.info("[Aurora] Starting application, binding events and rpcs.");
this.bindControllerEvents();
this.bindControllerRpcs();
for (const plugin of this.plugins) {
if (plugin.onBootstrap) {
await plugin.onBootstrap(this.applicationRef);
}
}
await this.callLifecycle("onAppStarted");
this.registerShutdownListeners();
this.logger.info("[Aurora] Application started, listening for events and rpcs.");
}
/**
* Shuts down the application and calls shutdown hooks.
*/
async close(signal) {
if (this.closed) {
this.logger.warn("[Aurora] Application already closed.");
return;
}
this.closed = true;
this.logger.info(`[Aurora] Closing application (signal: ${signal})`);
await this.callLifecycle("onAppShutdown", signal);
this.logger.info("[Aurora] Application closed");
}
/**
* Resolves an instance from the DI container.
* @param token The provider token or class
*/
async get(token) {
if (!this.instanceContainer.has(token)) {
throw new Error(`[Aurora] Provider for token "${getTokenName(token)}" could not be found in the application context.`);
}
return this.instanceContainer.resolve(token);
}
/**
* Binds all controller events (decorated handlers) to the platform driver via EventBinder.
*/
bindControllerEvents() {
const controllersWithInstances = [];
for (const module of this.moduleWrappers.values()) {
for (const controllerType of module.controllers) {
controllersWithInstances.push([
controllerType,
this.instanceContainer.resolve(controllerType)
]);
}
}
this.eventBinder.bindControllerEvents(controllersWithInstances);
}
bindControllerRpcs() {
const controllersWithInstances = [];
for (const module of this.moduleWrappers.values()) {
for (const controllerType of module.controllers) {
controllersWithInstances.push([
controllerType,
this.instanceContainer.resolve(controllerType)
]);
}
}
this.rpcBinder.bindControllerRpcs(controllersWithInstances);
}
/**
* Internal bootstrap: scans modules, builds the graph, and triggers instantiation.
* @param rootModuleType The root module class
*/
async initialize(rootModuleType) {
await this.scanModules(rootModuleType);
const rootModule = this.moduleWrappers.get(rootModuleType);
await this.initializeCoreServices(rootModule);
console.dir(this.plugins);
for (const plugin of this.plugins) {
if (plugin.onInit) {
await plugin.onInit(this.applicationRef);
}
}
if (this.debug) {
this.logModulesTree();
}
await this.instantiateModules();
await this.callLifecycle("onAppInit");
this.logger.info("[Aurora] Application initialized successfully.");
}
// TODO
registerShutdownListeners() {
}
/**
* Recursively scans all modules, handles imports/exports and registers global modules.
*/
async scanModules(moduleType, seen = /* @__PURE__ */ new Set()) {
if (seen.has(moduleType)) {
throw new Error(`[Aurora] Circular dependency detected in module imports: ${moduleType.name} is part of a cycle.`);
}
seen.add(moduleType);
if (this.moduleWrappers.has(moduleType)) {
return this.moduleWrappers.get(moduleType);
}
const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, moduleType);
if (!metadata) throw new Error(`[Aurora] Class ${moduleType.name} is not a valid module. Did you forget ?`);
const moduleWrapper = new ModuleWrapper(moduleType, metadata);
this.moduleWrappers.set(moduleType, moduleWrapper);
if (Reflect.getMetadata(GLOBAL_MODULE_KEY, moduleType)) {
this.globalModules.add(moduleWrapper);
}
for (const importedType of metadata.imports ?? []) {
const importedModuleWrapper = await this.scanModules(importedType, new Set(seen));
moduleWrapper.addImport(importedModuleWrapper);
}
return moduleWrapper;
}
/**
* Eagerly instantiates and assigns core services like Logger and Config.
*/
async initializeCoreServices(rootModule) {
if (this.findModuleByProvider(CONFIG_SERVICE, rootModule)) {
try {
this.config = await this.resolveDependency(CONFIG_SERVICE, rootModule);
this.debug = this.config.get("DEBUG", false);
} catch {
this.logger.warn(`[Aurora] CONFIG_SERVICE not found or failed to load. Debug logging will be disabled.`);
}
}
try {
this.logger = await this.resolveDependency(LOGGER_SERVICE, rootModule);
} catch {
this.logger.warn(`[Aurora] LOGGER_SERVICE not found. Falling back to console logging.`);
}
this.flowHandler.setLogger(this.logger);
}
/**
* Prints the module graph in a tree format (debug/dev only).
*/
logModulesTree() {
this.logger.debug("[Aurora] Modules tree");
for (const [type, wrapper] of this.moduleWrappers.entries()) {
const meta = wrapper.metadata;
this.logger.debug(`- ${type.name}${this.globalModules.has(wrapper) ? " [GLOBAL]" : ""}`);
if (meta.imports?.length) this.logger.debug(` imports: ${meta.imports.map((x) => x.name).join(", ")}`);
if (meta.exports?.length) this.logger.debug(` exports: ${meta.exports.map((x) => tokenToString(x)).join(", ")}`);
if (meta.providers?.length) this.logger.debug(` providers: ${meta.providers.map((x) => tokenToString(x)).join(", ")}`);
if (meta.controllers?.length) this.logger.debug(` controllers: ${meta.controllers.map((x) => x.name).join(", ")}`);
}
this.logger.debug("[Aurora] End tree\n");
}
async callLifecycle(hook, ...args) {
for (const wrapper of this.moduleWrappers.values()) {
for (const ctrlType of wrapper.controllers) {
const instance = this.instanceContainer.resolve(ctrlType);
const fn = instance[hook];
if (typeof fn === "function") {
this.logger.debug(`[Aurora] Calling ${hook} on ${ctrlType.name}.`);
await fn.apply(instance, args);
}
}
}
}
/**
* Instantiate all providers first, then all controllers.
*/
async instantiateModules() {
for (const moduleWrapper of this.moduleWrappers.values()) {
for (const providerDef of moduleWrapper.metadata.providers ?? []) {
const { provide } = normalizeProvider(providerDef);
await this.resolveDependency(provide, moduleWrapper);
}
for (const controller of moduleWrapper.controllers) {
await this.resolveDependency(controller, moduleWrapper);
}
}
}
/**
* Create an instance of the given class, resolving and injecting both
* constructor-parameter tokens and decorated property tokens.
*
* @param targetClass The class to instantiate.
* @param contextModule The module wrapper providing the DI context.
* @returns A Promise resolving to a new instance with all dependencies injected.
*/
async instantiateClass(targetClass, contextModule) {
const paramTypes = Reflect.getMetadata("design:paramtypes", targetClass) || [];
const customTokens = Reflect.getOwnMetadata(INJECT_TOKEN_KEY, targetClass) || [];
const dependencies = await Promise.all(paramTypes.map(async (paramType, index) => {
const token = customTokens[index] || paramType;
if (!token) {
throw new Error(`[Aurora] Could not resolve dependency for ${targetClass.name} at constructor index ${index}.`);
}
return this.resolveDependency(token, contextModule);
}));
const instance = new targetClass(...dependencies);
const propsToInject = [];
let ctor = targetClass;
while (ctor && ctor !== Function.prototype) {
const ownProps = Reflect.getOwnMetadata(INJECT_PROPERTY_KEY, ctor);
if (ownProps) {
propsToInject.push(...ownProps);
}
ctor = Object.getPrototypeOf(ctor);
}
for (const { key, token } of propsToInject) {
instance[key] = await this.resolveDependency(token, contextModule);
}
if (this.flowHandler.hasGuards(targetClass)) {
return this.flowHandler.wrapWithGuardsProxy(instance, targetClass);
}
return instance;
}
/**
* Resolves a provider (controller/service) in the DI graph, including global modules.
* @param token The token/class to resolve
* @param contextModule The module to start searching from
* @param seen (Cycle detection)
*/
async resolveDependency(token, contextModule, seen = /* @__PURE__ */ new Set()) {
if (this.instanceContainer.has(token)) {
return this.instanceContainer.resolve(token);
}
if (seen.has(token)) {
throw new Error(`[Aurora] Circular dependency detected for token "${getTokenName(token)}".`);
}
seen.add(token);
const destinationModule = this.findModuleByProvider(token, contextModule);
if (!destinationModule) {
throw new Error(`[AuroraDI] Cannot resolve dependency for token "${getTokenName(token)}"`);
}
const providerDef = this.findProviderDefinition(token, destinationModule);
if (!providerDef) {
throw new Error(`[AuroraDI] Cannot resolve dependency for token "${getTokenName(token)}"`);
}
const normalized = normalizeProvider(providerDef);
const scope = getProviderScope(providerDef);
if ("useValue" in normalized && normalized.useValue !== void 0) {
this.instanceContainer.register(token, normalized.useValue);
return normalized.useValue;
}
if ("useFactory" in normalized && normalized.useFactory) {
const args = await Promise.all((normalized.inject ?? []).map(async ({ token: inj, optional }) => {
try {
return await this.resolveDependency(inj, destinationModule, seen);
} catch (err) {
if (optional) {
return void 0;
}
throw err;
}
}));
const result = await normalized.useFactory(...args);
if (scope === Scope.SINGLETON) {
this.instanceContainer.register(token, result);
}
return result;
}
if ("useClass" in normalized && normalized.useClass) {
const instance = await this.instantiateClass(normalized.useClass, destinationModule);
if (scope === Scope.SINGLETON) {
this.instanceContainer.register(token, instance);
}
return instance;
}
throw new Error(`[AuroraDI] Cannot resolve dependency for token "${getTokenName(token)}"`);
}
/**
* Finds the module able to provide the given token.
* 1. Current module (providers/controllers)
* 2. Imported modules (recursive, if they export the token)
* 3. All global modules (@Global)
*/
findModuleByProvider(token, contextModule) {
if (this.findProviderDefinition(token, contextModule)) {
return contextModule;
}
for (const imported of contextModule.imports) {
if (imported.exports.has(token)) {
const found = this.findModuleByProvider(token, imported);
if (found) {
return found;
}
}
}
for (const globalMod of this.globalModules) {
if (globalMod.exports.has(token) && this.findProviderDefinition(token, globalMod)) {
return globalMod;
}
}
return void 0;
}
/**
* Finds a provider definition for a token in a given module, including controllers.
*/
findProviderDefinition(token, moduleWrapper) {
if (moduleWrapper.controllers.has(token)) {
return token;
}
return moduleWrapper.metadata.providers?.find((provider) => {
const normalized = normalizeProvider(provider);
return normalized.provide === token;
});
}
};
// src/decorators/events/create-event-decorator.ts
function createEventDecorator(type, name, webViewId) {
return (target, methodKey) => {
const eventName = name ?? methodKey;
const existingEvents = Reflect.getOwnMetadata(CONTROLLER_EVENTS_KEY, target.constructor) ?? [];
if (existingEvents.some((e) => e.methodName === methodKey)) {
throw new Error(`Cannot apply multiple event decorators to the same method "${String(methodKey)}".`);
}
const updatedEvents = [
...existingEvents,
{
type,
name: eventName,
methodName: methodKey,
params: [],
...webViewId !== void 0 ? {
webViewId
} : {}
}
];
Reflect.defineMetadata(CONTROLLER_EVENTS_KEY, updatedEvents, target.constructor);
};
}
__name(createEventDecorator, "createEventDecorator");
// src/decorators/events/on.decorator.ts
function On(eventName) {
return createEventDecorator(EventType.ON, eventName);
}
__name(On, "On");
// src/decorators/params/create-param-decorator.ts
function createParamDecorator(type) {
return (data) => {
return (target, propertyKey, parameterIndex) => {
if (!propertyKey) {
return;
}
const paramTypes = Reflect.getOwnMetadata("design:paramtypes", target, propertyKey) || [];
const existingParams = Reflect.getOwnMetadata(CONTROLLER_PARAMS_KEY, target, propertyKey) ?? [];
existingParams.push({
index: parameterIndex,
type,
data,
metatype: paramTypes[parameterIndex],
method: propertyKey.toString()
});
Reflect.defineMetadata(CONTROLLER_PARAMS_KEY, existingParams, target, propertyKey);
};
};
}
__name(createParamDecorator, "createParamDecorator");
// src/decorators/params/param.decorator.ts
var Param = createParamDecorator(MethodParamType.PARAM);
// src/decorators/params/payload.decorator.ts
var Payload = createParamDecorator(MethodParamType.PAYLOAD);
// src/decorators/params/player.decorator.ts
var Player = createParamDecorator(MethodParamType.PLAYER);
// src/decorators/rpc/create-rpc-decorator.ts
function createRpcDecorator(type, name, webViewId) {
return (target, methodKey) => {
const rpcName = name ?? methodKey;
const existingRpcs = Reflect.getOwnMetadata(CONTROLLER_RPCS_KEY, target.constructor) ?? [];
const updatedRpcs = [
...existingRpcs,
{
type,
name: rpcName,
methodName: methodKey,
params: [],
...webViewId !== void 0 ? {
webViewId
} : {}
}
];
Reflect.defineMetadata(CONTROLLER_RPCS_KEY, updatedRpcs, target.constructor);
};
}
__name(createRpcDecorator, "createRpcDecorator");
// src/decorators/controller.decorator.ts
function Controller() {
return (target) => {
if (Reflect.hasOwnMetadata(CONTROLLER_METADATA_KEY, target)) {
throw new Error(`Cannot apply decorator multiple times on the same target (${target.name}).`);
}
Reflect.defineMetadata(CONTROLLER_METADATA_KEY, true, target);
};
}
__name(Controller, "Controller");
// src/decorators/global.decorator.ts
function Global() {
return (target) => {
if (Reflect.hasOwnMetadata(GLOBAL_MODULE_KEY, target)) {
throw new Error(`Cannot apply decorator multiple times on the same target (${target.name}).`);
}
Reflect.defineMetadata(GLOBAL_MODULE_KEY, true, target);
};
}
__name(Global, "Global");
// src/decorators/inject.decorator.ts
function Inject(token) {
const decoratorFn = /* @__PURE__ */ __name((target, propertyKey, parameterIndex) => {
if (typeof parameterIndex === "number") {
const existingParams = Reflect.getOwnMetadata(INJECT_TOKEN_KEY, target) || [];
existingParams[parameterIndex] = token;
Reflect.defineMetadata(INJECT_TOKEN_KEY, existingParams, target);
} else {
const ctor = target.constructor;
const existingProps = Reflect.getOwnMetadata(INJECT_PROPERTY_KEY, ctor) || [];
existingProps.push({
key: propertyKey,
token
});
Reflect.defineMetadata(INJECT_PROPERTY_KEY, existingProps, ctor);
}
}, "decoratorFn");
return decoratorFn;
}
__name(Inject, "Inject");
// src/decorators/injectable.decorator.ts
function Injectable(options) {
return (target) => {
if (Reflect.hasOwnMetadata(INJECTABLE_METADATA_KEY, target)) {
throw new Error(`Cannot apply decorator multiple times on the same target (${target.name}).`);
}
Reflect.defineMetadata(INJECTABLE_METADATA_KEY, true, target);
Reflect.defineMetadata(INJECTABLE_SCOPE_OPTIONS, options?.scope ?? Scope.SINGLETON, target);
};
}
__name(Injectable, "Injectable");
// src/decorators/module.decorator.ts
var VALID_MODULE_KEYS = [
"imports",
"controllers",
"providers",
"exports"
];
function Module(metadata) {
return (target) => {
if (Reflect.hasOwnMetadata(MODULE_METADATA_KEY, target)) {
throw new Error(`Cannot apply decorator multiple times on the same target (${target.name}).`);
}
for (const key in metadata) {
if (!VALID_MODULE_KEYS.includes(key)) {
throw new Error(`Invalid property '${key}' passed into the metadata in ${target.name}.`);
}
}
Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, target);
};
}
__name(Module, "Module");
// src/decorators/set-metatadata.decorator.ts
function SetMetadata(key, value) {
return (target, propertyKey, _descriptor) => {
Reflect.defineMetadata(key, value, target, propertyKey);
};
}
__name(SetMetadata, "SetMetadata");
// src/decorators/use-guards.decorator.ts
function UseGuards(...guards) {
return (target, propertyKey, descriptor) => {
if (propertyKey && descriptor) {
const existing = Reflect.getMetadata(GUARDS_METADATA_KEY, target, propertyKey) || [];
Reflect.defineMetadata(GUARDS_METADATA_KEY, [
...existing,
...guards
], target, propertyKey);
} else {
const existing = Reflect.getMetadata(GUARDS_METADATA_KEY, target) || [];
Reflect.defineMetadata(GUARDS_METADATA_KEY, [
...existing,
...guards
], target);
}
};
}
__name(UseGuards, "UseGuards");
export {
ApplicationFactory,
CONFIG_LOADER,
CONFIG_SERVICE,
CONTROLLER_EVENTS_KEY,
CONTROLLER_METADATA_KEY,
CONTROLLER_PARAMS_KEY,
CONTROLLER_RPCS_KEY,
Container,
Controller,
ControllerFlowHandler,
EVENT_HANDLER_KEY,
EVENT_SERVICE,
EventBinder,
EventType,
GLOBAL_MODULE_KEY,
GUARDS_METADATA_KEY,
Global,
INJECTABLE_METADATA_KEY,
INJECTABLE_SCOPE_OPTIONS,
INJECT_PROPERTY_KEY,
INJECT_TOKEN_KEY,
Inject,
Injectable,
LOGGER_SERVICE,
MODULE_METADATA_KEY,
MethodParamType,
Module,
ModuleWrapper,
On,
PLATFORM_DRIVER,
Param,
Payload,
Player,
RPC_SERVICE,
RpcType,
Scope,
SetMetadata,
UseGuards,
WEBVIEW_SERVICE,
WebViewEvents,
createEventDecorator,
createParamDecorator,
createRpcDecorator,
getProviderScope,
getTokenName,
normalizeProvider,
tokenToString
};