@h3ravel/core
Version:
Core application container, lifecycle management and service providers for H3ravel.
425 lines (417 loc) • 11 kB
JavaScript
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