@aponiajs/platform-elysia 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # @aponiajs/platform-elysia
2
+
3
+ The first Elysia platform slice for Aponia:
4
+
5
+ - `AponiaFactory.create(AppModule)` application bootstrap;
6
+ - module-owned controller discovery;
7
+ - constructor-injected controllers;
8
+ - Nest-style `@Module()`, `@Controller()`, route, and `@Injectable()` metadata;
9
+ - automatic translation of decorated controllers into native Elysia routes;
10
+ - Nest-style startup logging for module initialization and route mapping;
11
+ - controller factories that return native Elysia plugins;
12
+ - `handle`, `listen`, and `close` application methods.
13
+
14
+ This package intentionally does not yet implement request scopes, lifecycle
15
+ enhancers, schema aggregation, or the complete native-plugin compatibility
16
+ contract from the roadmap.
17
+
18
+ The decorator API is the default application authoring surface. The
19
+ `defineElysiaController` API remains available as a low-level escape hatch for
20
+ composing existing Elysia plugins without rewriting them.
21
+
22
+ See `docs/logging.md` for logger configuration, JSON output, level filtering,
23
+ and custom logger integration.
@@ -0,0 +1,37 @@
1
+ import { Constructor, ControllerDefinition, LogLevel, LoggerService, ModuleClass, ModuleDefinition, Token, TokenValues } from "@aponiajs/common";
2
+ import { AnyElysia, Elysia } from "elysia";
3
+ //#region src/decorated-module.d.ts
4
+ type AponiaRootModule = ModuleClass | ModuleDefinition;
5
+ declare function compileRootModule(rootModule: AponiaRootModule): ModuleDefinition;
6
+ //#endregion
7
+ //#region src/application.d.ts
8
+ declare class AponiaElysiaApplication {
9
+ #private;
10
+ constructor(nativeApplication: Elysia, logger: LoggerService | undefined);
11
+ getNativeApplication(): Elysia;
12
+ handle(request: Request): Response | Promise<Response>;
13
+ listen(port: number): Promise<void>;
14
+ getUrl(): string;
15
+ close(): Promise<void>;
16
+ }
17
+ interface AponiaApplicationOptions {
18
+ readonly logger?: false | LoggerService | readonly LogLevel[];
19
+ }
20
+ declare class AponiaFactory {
21
+ static create(rootModule: AponiaRootModule, options?: AponiaApplicationOptions): Promise<AponiaElysiaApplication>;
22
+ }
23
+ //#endregion
24
+ //#region src/controller.d.ts
25
+ declare const ELYSIA_CONTROLLER = "aponia.elysia.controller";
26
+ interface ElysiaControllerDefinition<TController, TDependencies extends readonly Token<unknown>[], TPlugin extends AnyElysia> extends ControllerDefinition {
27
+ readonly kind: typeof ELYSIA_CONTROLLER;
28
+ readonly inject: TDependencies;
29
+ readonly useClass: Constructor<TController, TokenValues<TDependencies>>;
30
+ readonly buildPlugin: (controller: TController) => TPlugin;
31
+ }
32
+ declare function defineElysiaController<TController, const TDependencies extends readonly Token<unknown>[], const TPlugin extends AnyElysia>(useClass: Constructor<TController, TokenValues<TDependencies>>, options: {
33
+ readonly inject: TDependencies;
34
+ readonly buildPlugin: (controller: TController) => TPlugin;
35
+ }): ElysiaControllerDefinition<TController, TDependencies, TPlugin>;
36
+ //#endregion
37
+ export { type AponiaApplicationOptions, AponiaElysiaApplication, AponiaFactory, type AponiaRootModule, ELYSIA_CONTROLLER, type ElysiaControllerDefinition, compileRootModule, defineElysiaController };
package/dist/index.mjs ADDED
@@ -0,0 +1,172 @@
1
+ import { AponiaError, Logger, getConstructorDependencies, getControllerMetadata, getModuleMetadata, getRouteMetadata, tokenName } from "@aponiajs/common";
2
+ import { createContainer } from "@aponiajs/core";
3
+ import { Elysia } from "elysia";
4
+ //#region src/controller.ts
5
+ const ELYSIA_CONTROLLER = "aponia.elysia.controller";
6
+ function defineElysiaController(useClass, options) {
7
+ return Object.freeze({
8
+ kind: ELYSIA_CONTROLLER,
9
+ token: useClass,
10
+ inject: Object.freeze([...options.inject]),
11
+ useClass,
12
+ buildPlugin: options.buildPlugin
13
+ });
14
+ }
15
+ function isElysiaController(controller) {
16
+ return controller.kind === ELYSIA_CONTROLLER;
17
+ }
18
+ //#endregion
19
+ //#region src/decorated-module.ts
20
+ function compileRootModule(rootModule) {
21
+ if (typeof rootModule !== "function") return rootModule;
22
+ const compiled = /* @__PURE__ */ new Map();
23
+ const visiting = [];
24
+ const compile = (moduleClass) => {
25
+ const cached = compiled.get(moduleClass);
26
+ if (cached) return cached;
27
+ const cycleIndex = visiting.indexOf(moduleClass);
28
+ if (cycleIndex >= 0) {
29
+ const cycle = [...visiting.slice(cycleIndex), moduleClass].map((item) => item.name);
30
+ throw new AponiaError("MODULE_CYCLE", `Module import cycle detected: ${cycle.join(" -> ")}.`, { cycle });
31
+ }
32
+ const metadata = getModuleMetadata(moduleClass);
33
+ if (!metadata) throw new AponiaError("INVALID_MODULE", `Class "${moduleClass.name}" is missing the @Module() decorator.`, { module: moduleClass.name });
34
+ visiting.push(moduleClass);
35
+ try {
36
+ const definition = Object.freeze({
37
+ id: moduleClass.name,
38
+ imports: Object.freeze((metadata.imports ?? []).map((moduleImport) => typeof moduleImport === "function" ? compile(moduleImport) : moduleImport)),
39
+ controllers: Object.freeze((metadata.controllers ?? []).map(compileDecoratedController)),
40
+ providers: Object.freeze((metadata.providers ?? []).map(compileProvider)),
41
+ exports: Object.freeze([...metadata.exports ?? []])
42
+ });
43
+ compiled.set(moduleClass, definition);
44
+ return definition;
45
+ } finally {
46
+ visiting.pop();
47
+ }
48
+ };
49
+ return compile(rootModule);
50
+ }
51
+ function compileProvider(provider) {
52
+ if (typeof provider !== "function") return provider;
53
+ const inject = getConstructorDependencies(provider);
54
+ return Object.freeze({
55
+ kind: "class",
56
+ provide: provider,
57
+ inject,
58
+ useClass: provider
59
+ });
60
+ }
61
+ function compileDecoratedController(controller) {
62
+ const metadata = getControllerMetadata(controller);
63
+ if (!metadata) throw new AponiaError("INVALID_CONTROLLER", `Class "${controller.name}" is missing the @Controller() decorator.`, { controller: controller.name });
64
+ const routes = getRouteMetadata(controller);
65
+ return Object.freeze({
66
+ kind: ELYSIA_CONTROLLER,
67
+ token: controller,
68
+ path: joinPaths(metadata.path, ""),
69
+ inject: getConstructorDependencies(controller),
70
+ useClass: controller,
71
+ buildPlugin: (instance) => {
72
+ const plugin = new Elysia();
73
+ for (const route of routes) {
74
+ const handler = instance[route.propertyKey];
75
+ if (typeof handler !== "function") throw new AponiaError("INVALID_CONTROLLER", `Route handler "${String(route.propertyKey)}" is not callable.`, {
76
+ controller: controller.name,
77
+ handler: String(route.propertyKey)
78
+ });
79
+ plugin.route(route.method, joinPaths(metadata.path, route.path), () => Reflect.apply(handler, instance, []));
80
+ }
81
+ return plugin;
82
+ }
83
+ });
84
+ }
85
+ function joinPaths(controllerPath, routePath) {
86
+ const segments = [controllerPath, routePath].map((path) => path.trim().replace(/^\/+|\/+$/g, "")).filter(Boolean);
87
+ return segments.length === 0 ? "/" : `/${segments.join("/")}`;
88
+ }
89
+ //#endregion
90
+ //#region src/application.ts
91
+ var AponiaElysiaApplication = class {
92
+ #nativeApplication;
93
+ #logger;
94
+ constructor(nativeApplication, logger) {
95
+ this.#nativeApplication = nativeApplication;
96
+ this.#logger = logger;
97
+ }
98
+ getNativeApplication() {
99
+ return this.#nativeApplication;
100
+ }
101
+ handle(request) {
102
+ return this.#nativeApplication.handle(request);
103
+ }
104
+ async listen(port) {
105
+ try {
106
+ this.#nativeApplication.listen(port);
107
+ await this.#nativeApplication.modules;
108
+ this.#logger?.log("Aponia application successfully started", "AponiaApplication");
109
+ this.#logger?.log(`Application is running on: ${this.getUrl()}`, "AponiaApplication");
110
+ } catch (error) {
111
+ this.#logger?.error(error, "AponiaApplication");
112
+ throw error;
113
+ }
114
+ }
115
+ getUrl() {
116
+ const server = this.#nativeApplication.server;
117
+ if (!server) throw new AponiaError("APPLICATION_NOT_LISTENING", "app.listen() needs to be called before calling app.getUrl().");
118
+ return server.url.origin;
119
+ }
120
+ async close() {
121
+ if (this.#nativeApplication.server) await this.#nativeApplication.stop();
122
+ }
123
+ };
124
+ var AponiaFactory = class {
125
+ static async create(rootModule, options = {}) {
126
+ const logger = createSystemLogger(options.logger);
127
+ logger?.log("Starting Aponia application...", "AponiaFactory");
128
+ const compiledRootModule = compileRootModule(rootModule);
129
+ const container = createContainer(compiledRootModule);
130
+ const nativeApplication = new Elysia({ name: compiledRootModule.id });
131
+ for (const module of container.graph.modules) {
132
+ container.initializeModule(module);
133
+ logger?.log(`${module.id} dependencies initialized`, "InstanceLoader");
134
+ }
135
+ for (const module of container.graph.modules) for (const controller of module.controllers) {
136
+ if (!isElysiaController(controller)) {
137
+ const controllerName = tokenName(controller.token);
138
+ throw new AponiaError("UNSUPPORTED_CONTROLLER", `Controller "${controllerName}" is not supported by the Elysia platform.`, {
139
+ module: module.id,
140
+ controller: controllerName
141
+ });
142
+ }
143
+ const instance = container.instantiateController(module, controller);
144
+ const plugin = Reflect.apply(controller.buildPlugin, void 0, [instance]);
145
+ if (!(plugin instanceof Elysia)) throw new AponiaError("UNSUPPORTED_CONTROLLER", `Controller "${tokenName(controller.token)}" did not build an Elysia plugin.`, {
146
+ module: module.id,
147
+ controller: tokenName(controller.token)
148
+ });
149
+ const controllerName = tokenName(controller.token);
150
+ const controllerPath = controller.path ?? inferControllerPath(plugin);
151
+ logger?.log(`${controllerName} {${controllerPath}}:`, "RoutesResolver");
152
+ for (const route of plugin.routes) logger?.log(`Mapped {${route.path}, ${String(route.method).toUpperCase()}} route`, "RouterExplorer");
153
+ nativeApplication.use(plugin);
154
+ }
155
+ await nativeApplication.modules;
156
+ return new AponiaElysiaApplication(nativeApplication, logger);
157
+ }
158
+ };
159
+ function createSystemLogger(loggerOption) {
160
+ if (loggerOption === false) return;
161
+ if (Array.isArray(loggerOption)) return new Logger("AponiaFactory", {
162
+ logLevels: loggerOption,
163
+ timestamp: true
164
+ });
165
+ if (loggerOption) return loggerOption;
166
+ return new Logger("AponiaFactory", { timestamp: true });
167
+ }
168
+ function inferControllerPath(plugin) {
169
+ return plugin.routes[0]?.path ?? "/";
170
+ }
171
+ //#endregion
172
+ export { AponiaElysiaApplication, AponiaFactory, ELYSIA_CONTROLLER, compileRootModule, defineElysiaController };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@aponiajs/platform-elysia",
3
+ "version": "0.0.0",
4
+ "description": "Minimal Elysia application platform for Aponia modules and controllers.",
5
+ "homepage": "https://github.com/aponiajs/aponiajs#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/aponiajs/aponiajs/issues"
8
+ },
9
+ "license": "MIT",
10
+ "author": "AponiaJS contributors",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/aponiajs/aponiajs.git",
14
+ "directory": "packages/platform-elysia"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "type": "module",
20
+ "exports": {
21
+ ".": "./dist/index.mjs",
22
+ "./package.json": "./package.json"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "build": "bunx --bun vp pack",
29
+ "dev": "bunx --bun vp pack --watch",
30
+ "test": "bun test",
31
+ "test:vite-plus": "vp test",
32
+ "check": "bunx --bun vp check",
33
+ "prepublishOnly": "bun run build"
34
+ },
35
+ "dependencies": {
36
+ "@aponiajs/common": "0.0.0",
37
+ "@aponiajs/core": "0.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "elysia": "^1.4.29"
41
+ },
42
+ "peerDependencies": {
43
+ "elysia": "^1.4.29"
44
+ }
45
+ }