UNPKG

@h3ravel/core

Version:

Core application container, lifecycle management and service providers for H3ravel.

425 lines (417 loc) 11 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/Container.ts var Container = class { static { __name(this, "Container"); } bindings = /* @__PURE__ */ new Map(); singletons = /* @__PURE__ */ new Map(); /** * Check if the target has any decorators * * @param target * @returns */ static hasAnyDecorator(target) { if (Reflect.getMetadataKeys(target).length > 0) return true; const paramLength = target.length; for (let i = 0; i < paramLength; i++) { if (Reflect.getMetadataKeys(target, `__param_${i}`).length > 0) { return true; } } return false; } bind(key, factory) { this.bindings.set(key, factory); } /** * Bind a singleton service to the container */ singleton(key, factory) { this.bindings.set(key, () => { if (!this.singletons.has(key)) { this.singletons.set(key, factory()); } return this.singletons.get(key); }); } /** * Resolve a service from the container */ make(key) { if (this.bindings.has(key)) { return this.bindings.get(key)(); } if (typeof key === "function") { return this.build(key); } throw new Error(`No binding found for key: ${typeof key === "string" ? key : key?.name}`); } /** * Automatically build a class with constructor dependency injection */ build(ClassType) { let dependencies = []; if (Array.isArray(ClassType.__inject__)) { dependencies = ClassType.__inject__.map((alias) => { return this.make(alias); }); } else { const paramTypes = Reflect.getMetadata("design:paramtypes", ClassType) || []; dependencies = paramTypes.map((dep) => this.make(dep)); } return new ClassType(...dependencies); } /** * Check if a service is registered */ has(key) { return this.bindings.has(key); } }; // src/Application.ts import { PathLoader } from "@h3ravel/shared"; import dotenv from "dotenv"; import path from "node:path"; var Application = class _Application extends Container { static { __name(this, "Application"); } paths = new PathLoader(); booted = false; versions = { app: "0", ts: "0" }; basePath; providers = []; externalProviders = []; constructor(basePath) { super(); this.basePath = basePath; this.setPath("base", basePath); this.loadOptions(); this.registerBaseBindings(); dotenv.config({ quiet: true }); } /** * Register core bindings into the container */ registerBaseBindings() { this.bind(_Application, () => this); this.bind("path.base", () => this.basePath); this.bind("load.paths", () => this.paths); } /** * Dynamically register all configured providers */ async registerConfiguredProviders() { const providers = await this.getAllProviders(); for (const ProviderClass of providers) { if (!ProviderClass) continue; const provider = new ProviderClass(this); await this.register(provider); } } async loadOptions() { const app = await this.safeImport(this.getPath("base", "package.json")); const core = await this.safeImport("../package.json"); if (app && app.dependencies) { this.versions.app = app.dependencies["@h3ravel/core"]; } if (core && core.devDependencies) { this.versions.ts = app.devDependencies.typescript; } } /** * Load default and optional providers dynamically * * Auto-Registration Behavior * * Minimal App: Loads only core, config, http, router by default. * Full-Stack App: Installs database, mail, queue, cache → they self-register via their providers. */ async getConfiguredProviders() { return [ (await import("./index.js")).CoreServiceProvider, (await import("./index.js")).ViewServiceProvider ]; } async getAllProviders() { const coreProviders = await this.getConfiguredProviders(); const allProviders = [ ...coreProviders, ...this.externalProviders ]; const uniqueProviders = Array.from(new Set(allProviders)); return this.sortProviders(uniqueProviders); } sortProviders(providers) { const priorityMap = /* @__PURE__ */ new Map(); providers.forEach((Provider) => { priorityMap.set(Provider.name, Provider.priority ?? 0); }); providers.forEach((Provider) => { const order = Provider.order; if (!order) return; const [direction, target] = order.split(":"); const targetPriority = priorityMap.get(target) ?? 0; if (direction === "before") { priorityMap.set(Provider.name, targetPriority - 1); } else if (direction === "after") { priorityMap.set(Provider.name, targetPriority + 1); } }); const sorted = providers.sort((A, B) => (priorityMap.get(B.name) ?? 0) - (priorityMap.get(A.name) ?? 0)); if (process.env.APP_DEBUG === "true") { console.table(sorted.map((P) => ({ Provider: P.name, Priority: priorityMap.get(P.name), Order: P.order || "N/A" }))); } return sorted; } registerProviders(providers) { this.externalProviders.push(...providers); } /** * Register a provider */ async register(provider) { await provider.register(); this.providers.push(provider); } /** * Boot all providers after registration */ async boot() { if (this.booted) return; for (const provider of this.providers) { if (provider.boot) { await provider.boot(); } } this.booted = true; } /** * Attempt to dynamically import an optional module */ async safeImport(moduleName) { try { const mod = await import(moduleName); return mod.default ?? mod ?? {}; } catch { return null; } } /** * Get the base path of the app * * @returns */ getBasePath() { return this.basePath; } /** * Dynamically retrieves a path property from the class. * Any property ending with "Path" is accessible automatically. * * @param name - The base name of the path property * @returns */ getPath(name, pth) { return path.join(this.paths.getPath(name, this.basePath), pth ?? ""); } /** * Programatically set the paths. * * @param name - The base name of the path property * @param path - The new path * @returns */ setPath(name, path2) { return this.paths.setPath(name, path2, this.basePath); } /** * Returns the installed version of the system core and typescript. * * @returns */ getVersion(key) { return this.versions[key]?.replaceAll(/\^|~/g, ""); } }; // src/Controller.ts var Controller = class { static { __name(this, "Controller"); } app; constructor(app) { this.app = app; } show(..._ctx) { return; } index(..._ctx) { return; } store(..._ctx) { return; } update(..._ctx) { return; } destroy(..._ctx) { return; } }; // src/ServiceProvider.ts var ServiceProvider = class { static { __name(this, "ServiceProvider"); } static order; static priority = 0; app; constructor(app) { this.app = app; } }; // src/Di/Inject.ts function Inject(...dependencies) { return function(target) { target.__inject__ = dependencies; }; } __name(Inject, "Inject"); function Injectable() { return (...args) => { if (args.length === 1) { void args[0]; } if (args.length === 3) { void args[0]; void args[1]; void args[2]; } }; } __name(Injectable, "Injectable"); // src/Http/Kernel.ts var Kernel = class { static { __name(this, "Kernel"); } context; middleware; /** * @param context - A factory function that converts an H3Event into an HttpContext. * @param middleware - An array of middleware classes that will be executed in sequence. */ constructor(context, middleware = []) { this.context = context; this.middleware = middleware; } /** * Handles an incoming request and passes it through middleware before invoking the next handler. * * @param event - The raw H3 event object. * @param next - A callback function that represents the next layer (usually the controller or final handler). * @returns A promise resolving to the result of the request pipeline. */ async handle(event, next) { const ctx = this.context(event); const { app } = ctx.request; app.bind("view", () => async (template, params) => { const edge = app.make("edge"); return ctx.response.html(await edge.render(template, params)); }); const result = await this.runMiddleware(ctx, () => next(ctx)); if (result !== void 0 && this.isPlainObject(result)) { event.res.headers.set("Content-Type", "application/json; charset=UTF-8"); } return result; } /** * Sequentially runs middleware in the order they were registered. * * @param context - The standardized HttpContext. * @param next - Callback to execute when middleware completes. * @returns A promise resolving to the final handler's result. */ async runMiddleware(context, next) { let index = -1; const runner = /* @__PURE__ */ __name(async (i) => { if (i <= index) throw new Error("next() called multiple times"); index = i; const middleware = this.middleware[i]; if (middleware) { return middleware.handle(context, () => runner(i + 1)); } else { return next(context); } }, "runner"); return runner(0); } /** * Utility function to determine if a value is a plain object or array. * * @param value - The value to check. * @returns True if the value is a plain object or array, otherwise false. */ isPlainObject(value) { return typeof value === "object" && value !== null && (value.constructor === Object || value.constructor === Array); } }; // src/Providers/CoreServiceProvider.ts import "reflect-metadata"; var CoreServiceProvider = class extends ServiceProvider { static { __name(this, "CoreServiceProvider"); } static priority = 999; register() { } }; // src/Providers/ViewServiceProvider.ts import { Edge } from "edge.js"; var ViewServiceProvider = class extends ServiceProvider { static { __name(this, "ViewServiceProvider"); } static priority = 995; register() { const config = this.app.make("config"); const edge = Edge.create({ cache: process.env.NODE_ENV === "production" }); edge.mount(this.app.getPath("views")); edge.global("asset", this.app.make("asset")); edge.global("config", config.get); edge.global("app", this.app); this.app.bind("edge", () => edge); } }; export { Application, Container, Controller, CoreServiceProvider, Inject, Injectable, Kernel, ServiceProvider, ViewServiceProvider }; //# sourceMappingURL=index.js.map