@h3ravel/core
Version:
Core application container, lifecycle management and service providers for H3ravel.
550 lines (539 loc) • 16.3 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/Container.ts
var Container;
var init_Container = __esm({
"src/Container.ts"() {
"use strict";
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
var import_shared, import_dotenv, import_node_path, Application;
var init_Application = __esm({
"src/Application.ts"() {
"use strict";
init_Container();
import_shared = require("@h3ravel/shared");
import_dotenv = __toESM(require("dotenv"), 1);
import_node_path = __toESM(require("path"), 1);
Application = class _Application extends Container {
static {
__name(this, "Application");
}
paths = new import_shared.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();
import_dotenv.default.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 Promise.resolve().then(() => (init_src(), src_exports))).CoreServiceProvider,
(await Promise.resolve().then(() => (init_src(), src_exports))).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 import_node_path.default.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;
var init_Controller = __esm({
"src/Controller.ts"() {
"use strict";
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;
var init_ServiceProvider = __esm({
"src/ServiceProvider.ts"() {
"use strict";
ServiceProvider = class {
static {
__name(this, "ServiceProvider");
}
static order;
static priority = 0;
app;
constructor(app) {
this.app = app;
}
};
}
});
// src/Contracts/ServiceProviderConstructor.ts
var init_ServiceProviderConstructor = __esm({
"src/Contracts/ServiceProviderConstructor.ts"() {
"use strict";
}
});
// src/Di/Inject.ts
function Inject(...dependencies) {
return function(target) {
target.__inject__ = dependencies;
};
}
function Injectable() {
return (...args) => {
if (args.length === 1) {
void args[0];
}
if (args.length === 3) {
void args[0];
void args[1];
void args[2];
}
};
}
var init_Inject = __esm({
"src/Di/Inject.ts"() {
"use strict";
__name(Inject, "Inject");
__name(Injectable, "Injectable");
}
});
// src/Exceptions/Handler.ts
var init_Handler = __esm({
"src/Exceptions/Handler.ts"() {
"use strict";
}
});
// src/Http/Kernel.ts
var Kernel;
var init_Kernel = __esm({
"src/Http/Kernel.ts"() {
"use strict";
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
var import_reflect_metadata, CoreServiceProvider;
var init_CoreServiceProvider = __esm({
"src/Providers/CoreServiceProvider.ts"() {
"use strict";
import_reflect_metadata = require("reflect-metadata");
init_ServiceProvider();
CoreServiceProvider = class extends ServiceProvider {
static {
__name(this, "CoreServiceProvider");
}
static priority = 999;
register() {
}
};
}
});
// src/Providers/ViewServiceProvider.ts
var import_edge, ViewServiceProvider;
var init_ViewServiceProvider = __esm({
"src/Providers/ViewServiceProvider.ts"() {
"use strict";
import_edge = require("edge.js");
init_ServiceProvider();
ViewServiceProvider = class extends ServiceProvider {
static {
__name(this, "ViewServiceProvider");
}
static priority = 995;
register() {
const config = this.app.make("config");
const edge = import_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);
}
};
}
});
// src/index.ts
var src_exports = {};
__export(src_exports, {
Application: () => Application,
Container: () => Container,
Controller: () => Controller,
CoreServiceProvider: () => CoreServiceProvider,
Inject: () => Inject,
Injectable: () => Injectable,
Kernel: () => Kernel,
ServiceProvider: () => ServiceProvider,
ViewServiceProvider: () => ViewServiceProvider
});
module.exports = __toCommonJS(src_exports);
var init_src = __esm({
"src/index.ts"() {
init_Application();
init_Container();
init_Controller();
init_ServiceProvider();
init_ServiceProviderConstructor();
init_Inject();
init_Handler();
init_Kernel();
init_CoreServiceProvider();
init_ViewServiceProvider();
}
});
init_src();
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Application,
Container,
Controller,
CoreServiceProvider,
Inject,
Injectable,
Kernel,
ServiceProvider,
ViewServiceProvider
});
//# sourceMappingURL=index.cjs.map