alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
155 lines (134 loc) • 4.86 kB
text/typescript
/// <reference types="vite/client" />
import { AsyncLocalStorage } from "node:async_hooks";
import { Alepha } from "./Alepha.ts";
import type { RunOptions } from "./interfaces/Run.ts";
import type { Service } from "./interfaces/Service.ts";
import { $module } from "./primitives/$module.ts";
import { AlsProvider } from "./providers/AlsProvider.ts";
import { CodecManager } from "./providers/CodecManager.ts";
import { EventManager } from "./providers/EventManager.ts";
import { Json } from "./providers/Json.ts";
import { JsonSchemaCodec } from "./providers/JsonSchemaCodec.ts";
import { KeylessJsonSchemaCodec } from "./providers/KeylessJsonSchemaCodec.ts";
import { SchemaCodec } from "./providers/SchemaCodec.ts";
import { SchemaValidator } from "./providers/SchemaValidator.ts";
import { StateManager } from "./providers/StateManager.ts";
// ---------------------------------------------------------------------------------------------------------------------
export * from "./index.shared.ts";
// ---------------------------------------------------------------------------------------------------------------------
/**
* Foundation of the entire framework with dependency injection and lifecycle management.
*
* **Features:**
* - Dependency injection for services
* - Service substitution/mocking
* - Type-safe environment variable loading with TypeBox schemas
* - Lifecycle hooks (start, stop, log, etc.)
* - Module definitions and composition
* - Request-scoped context access via Async Local Storage (ALS)
* - Reactive state management with atoms
* - Full TypeScript generics and type inference
*
* @module alepha.core
*/
export const AlephaCore = $module({
name: "alepha.core",
// These services are instantiated directly by Alepha's constructor in a strict order.
// Declared as `variants` (semantically: not really alternatives, but the framework
// owns their wiring) so the module tag is attached without auto-injection.
variants: [
StateManager,
CodecManager,
EventManager,
AlsProvider,
Json,
JsonSchemaCodec,
KeylessJsonSchemaCodec,
SchemaCodec,
SchemaValidator,
],
});
// ---------------------------------------------------------------------------------------------------------------------
/**
* Run Alepha application, trigger start lifecycle.
*
* ```ts
* import { Alepha, run } from "alepha";
* import { MyService } from "./services/MyService.ts";
*
* const alepha = new Alepha({ env: { APP_NAME: "MyAlephaApp" } });
*
* alepha.with(MyService);
*
* export default run(alepha);
* ```
*/
export const run = (
entry: Alepha | Service | Array<Service>,
opts?: RunOptions,
): Alepha => {
const env: Record<string, string | undefined> =
typeof process === "object" ? process.env : {};
const alepha =
entry instanceof Alepha
? entry
: Alepha.create({ env: { ...env, ...opts?.env } });
if (!(entry instanceof Alepha)) {
const entries = Array.isArray(entry) ? entry : [entry];
for (const e of entries) {
alepha.with(e);
}
}
// make alepha globally accessible (for serverless functions, etc...)
// it's not recommended, we should force 'export default run(alepha)'
(globalThis as any).__alepha = alepha;
// when alepha instance is imported via CLI, use a different global variable
if (env.ALEPHA_CLI_IMPORT) {
(globalThis as any).__cli_alepha = alepha;
}
if (alepha.isServerless() || alepha.isViteDev() || env.ALEPHA_CLI_IMPORT) {
return alepha;
}
setTimeout(async () => {
try {
await opts?.configure?.(alepha);
await alepha.start();
if (opts?.ready) {
await opts.ready(alepha);
}
if (opts?.once) {
await alepha.stop();
return alepha;
}
if (typeof process === "object") {
const traps = ["SIGTERM", "SIGINT", "SIGUSR2", "uncaughtException"];
for (const trap of traps) {
process.once(trap, async (err) => {
if (trap === "uncaughtException") {
alepha.log?.error("Uncaught Exception", err);
} else {
alepha.log?.info("Received signal", { trap });
}
try {
await alepha.stop();
console.log(" ");
process.exit(0);
} catch (error) {
alepha.log?.error("Alepha failed to stop", error);
process.exit(1);
}
});
}
}
} catch (error) {
alepha.log?.error("Alepha failed to start", error);
if (typeof process === "object") {
process.exit(1);
}
}
});
return alepha;
};
// ---------------------------------------------------------------------------------------------------------------------
// only for node.js environment
AlsProvider.create = () => new AsyncLocalStorage();