@aponiajs/platform-elysia 0.3.18 → 0.3.19

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 CHANGED
@@ -32,7 +32,7 @@ and custom logger integration.
32
32
 
33
33
  ```ts
34
34
  import { Module } from "@aponiajs/common";
35
- import { AponiaFactory } from "@aponiajs/platform-elysia";
35
+ import { AponiaFactory, ElysiaPluginModule } from "@aponiajs/platform-elysia";
36
36
 
37
37
  @Module({})
38
38
  class AppModule {}
@@ -43,25 +43,56 @@ await application.listen(3000);
43
43
 
44
44
  ## Native Elysia plugins
45
45
 
46
- Use existing Elysia plugins without an Aponia adapter:
46
+ Use existing Elysia plugins through Nest-style module imports:
47
47
 
48
48
  ```bash
49
- bun add @elysiajs/cors
49
+ bun add @elysiajs/cors @elysiajs/jwt
50
50
  ```
51
51
 
52
52
  ```ts
53
53
  import { cors } from "@elysiajs/cors";
54
+ import { jwt } from "@elysiajs/jwt";
55
+
56
+ @Module({
57
+ imports: [
58
+ ElysiaPluginModule.register(cors(), {
59
+ key: "cors",
60
+ }),
61
+ ],
62
+ })
63
+ class AppModule {}
64
+ ```
65
+
66
+ The plugin is passed unchanged to Elysia's native `.use()` implementation. For
67
+ plugins that depend on an injectable service, use an async registration:
54
68
 
55
- const application = await AponiaFactory.create(AppModule, {
56
- configureNative: (elysia) => elysia.use(cors()),
57
- });
69
+ ```ts
70
+ @Module({
71
+ imports: [
72
+ ElysiaPluginModule.registerAsync({
73
+ key: "jwt",
74
+ imports: [ConfigModule],
75
+ inject: [ConfigService],
76
+ useFactory: (config: ConfigService) =>
77
+ jwt({
78
+ name: "jwt",
79
+ secret: config.get("JWT_SECRET"),
80
+ }),
81
+ }),
82
+ ],
83
+ })
84
+ class AuthModule {}
58
85
  ```
59
86
 
60
- `configureNative` receives the real Elysia application before Aponia mounts its
61
- controllers. Use Elysia's own `.use()` API for instance, functional, array, and
62
- lazy plugins; no adapter or route copying is involved. Return the same
63
- application so Elysia's plugin types remain available from
64
- `application.getNativeApplication()`.
87
+ Imported plugins are installed in dependency order before controllers and a
88
+ shared configured module is installed once across diamond imports. A stable
89
+ `key` keeps module diagnostics deterministic and prevents duplicate
90
+ registrations with the same key.
91
+
92
+ `configureNative` remains available as an application-level escape hatch. It
93
+ preserves Elysia's accumulated plugin types on `getNativeApplication()`.
94
+ Module-imported plugin state and decorators are available at runtime, but do
95
+ not yet flow into decorated controller parameter types.
65
96
 
66
97
  [npm package](https://www.npmjs.com/package/@aponiajs/platform-elysia) ·
67
98
  [complete package catalog](../../docs/packages.md)
package/dist/index.d.mts CHANGED
@@ -1,7 +1,8 @@
1
- import { Constructor, ControllerDefinition, LogLevel, LoggerService, ModuleClass, ModuleDefinition, Token, TokenValues } from "@aponiajs/common";
1
+ import { Constructor, ControllerDefinition, DynamicModule, LogLevel, LoggerService, ModuleDefinition, ModuleImport, Token, TokenValues } from "@aponiajs/common";
2
+ import "@aponiajs/core";
2
3
  import { AnyElysia, Elysia } from "elysia";
3
4
  //#region src/decorated-module.d.ts
4
- type AponiaRootModule = ModuleClass | ModuleDefinition;
5
+ type AponiaRootModule = ModuleImport;
5
6
  declare function compileRootModule(rootModule: AponiaRootModule): ModuleDefinition;
6
7
  //#endregion
7
8
  //#region src/application.d.ts
@@ -39,4 +40,19 @@ declare function defineElysiaController<TController, const TDependencies extends
39
40
  readonly buildPlugin: (controller: TController) => TPlugin;
40
41
  }): ElysiaControllerDefinition<TController, TDependencies, TPlugin>;
41
42
  //#endregion
42
- export { type AponiaApplicationOptions, AponiaElysiaApplication, AponiaFactory, type AponiaRootModule, type ConfiguredAponiaApplicationOptions, ELYSIA_CONTROLLER, type ElysiaControllerDefinition, type NativeElysiaConfigurator, compileRootModule, defineElysiaController };
43
+ //#region src/plugin-module.d.ts
44
+ type NativeElysiaPlugin = Parameters<Elysia["use"]>[0];
45
+ interface ElysiaPluginModuleOptions {
46
+ readonly key?: string;
47
+ }
48
+ interface AsyncElysiaPluginModuleOptions<TDependencies extends readonly Token<unknown>[], TPlugin extends NativeElysiaPlugin> extends ElysiaPluginModuleOptions {
49
+ readonly imports?: readonly ModuleImport[];
50
+ readonly inject: TDependencies;
51
+ readonly useFactory: (...dependencies: TokenValues<TDependencies>) => TPlugin;
52
+ }
53
+ declare class ElysiaPluginModule {
54
+ static register<const TPlugin extends NativeElysiaPlugin>(plugin: TPlugin, options?: ElysiaPluginModuleOptions): DynamicModule;
55
+ static registerAsync<const TDependencies extends readonly Token<unknown>[], const TPlugin extends NativeElysiaPlugin>(options: AsyncElysiaPluginModuleOptions<TDependencies, TPlugin>): DynamicModule;
56
+ }
57
+ //#endregion
58
+ export { type AponiaApplicationOptions, AponiaElysiaApplication, AponiaFactory, type AponiaRootModule, type AsyncElysiaPluginModuleOptions, type ConfiguredAponiaApplicationOptions, ELYSIA_CONTROLLER, type ElysiaControllerDefinition, ElysiaPluginModule, type ElysiaPluginModuleOptions, type NativeElysiaConfigurator, type NativeElysiaPlugin, compileRootModule, defineElysiaController };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { AponiaError, Logger, getConstructorDependencies, getControllerMetadata, getModuleMetadata, getRouteMetadata, tokenName } from "@aponiajs/common";
1
+ import { AponiaError, Logger, Module, createToken, getConstructorDependencies, getControllerMetadata, getModuleMetadata, getRouteMetadata, provideFactory, provideValue, tokenName } from "@aponiajs/common";
2
2
  import { createContainer } from "@aponiajs/core";
3
3
  import { Elysia } from "elysia";
4
4
  //#region src/controller.ts
@@ -18,29 +18,54 @@ function isElysiaController(controller) {
18
18
  //#endregion
19
19
  //#region src/decorated-module.ts
20
20
  function compileRootModule(rootModule) {
21
- if (typeof rootModule !== "function") return rootModule;
22
- const compiled = /* @__PURE__ */ new Map();
21
+ if (isModuleDefinition(rootModule)) return rootModule;
22
+ const compiledClasses = /* @__PURE__ */ new Map();
23
+ const compiledDynamicModules = /* @__PURE__ */ new Map();
23
24
  const visiting = [];
24
- const compile = (moduleClass) => {
25
- const cached = compiled.get(moduleClass);
25
+ const compile = (moduleImport) => {
26
+ if (typeof moduleImport === "function") return compileClass(moduleImport);
27
+ if (isModuleDefinition(moduleImport)) return moduleImport;
28
+ return compileDynamicModule(moduleImport);
29
+ };
30
+ const compileClass = (moduleClass) => {
31
+ const cached = compiledClasses.get(moduleClass);
26
32
  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
- }
33
+ assertNoModuleCycle(moduleClass, visiting);
32
34
  const metadata = getModuleMetadata(moduleClass);
33
- if (!metadata) throw new AponiaError("INVALID_MODULE", `Class "${moduleClass.name}" is missing the @Module() decorator.`, { module: moduleClass.name });
35
+ if (!metadata) throw missingModuleDecorator(moduleClass);
34
36
  visiting.push(moduleClass);
35
37
  try {
36
38
  const definition = Object.freeze({
37
39
  id: moduleClass.name,
38
- imports: Object.freeze((metadata.imports ?? []).map((moduleImport) => typeof moduleImport === "function" ? compile(moduleImport) : moduleImport)),
40
+ imports: Object.freeze((metadata.imports ?? []).map(compile)),
39
41
  controllers: Object.freeze((metadata.controllers ?? []).map(compileDecoratedController)),
40
42
  providers: Object.freeze((metadata.providers ?? []).map(compileProvider)),
41
43
  exports: Object.freeze([...metadata.exports ?? []])
42
44
  });
43
- compiled.set(moduleClass, definition);
45
+ compiledClasses.set(moduleClass, definition);
46
+ return definition;
47
+ } finally {
48
+ visiting.pop();
49
+ }
50
+ };
51
+ const compileDynamicModule = (dynamicModule) => {
52
+ const cached = compiledDynamicModules.get(dynamicModule);
53
+ if (cached) return cached;
54
+ assertNoModuleCycle(dynamicModule, visiting);
55
+ const metadata = getModuleMetadata(dynamicModule.module);
56
+ if (!metadata) throw missingModuleDecorator(dynamicModule.module);
57
+ const mergedMetadata = mergeModuleMetadata(metadata, dynamicModule);
58
+ visiting.push(dynamicModule);
59
+ try {
60
+ const definition = Object.freeze({
61
+ id: dynamicModule.id,
62
+ instanceId: dynamicModule.instanceId,
63
+ imports: Object.freeze((mergedMetadata.imports ?? []).map(compile)),
64
+ controllers: Object.freeze((mergedMetadata.controllers ?? []).map(compileDecoratedController)),
65
+ providers: Object.freeze((mergedMetadata.providers ?? []).map(compileProvider)),
66
+ exports: Object.freeze([...mergedMetadata.exports ?? []])
67
+ });
68
+ compiledDynamicModules.set(dynamicModule, definition);
44
69
  return definition;
45
70
  } finally {
46
71
  visiting.pop();
@@ -48,6 +73,30 @@ function compileRootModule(rootModule) {
48
73
  };
49
74
  return compile(rootModule);
50
75
  }
76
+ function isModuleDefinition(moduleImport) {
77
+ return typeof moduleImport !== "function" && "controllers" in moduleImport && !("module" in moduleImport);
78
+ }
79
+ function moduleImportName(moduleImport) {
80
+ if (typeof moduleImport === "function") return moduleImport.name;
81
+ return moduleImport.id;
82
+ }
83
+ function assertNoModuleCycle(moduleImport, visiting) {
84
+ const cycleIndex = visiting.indexOf(moduleImport);
85
+ if (cycleIndex < 0) return;
86
+ const cycle = [...visiting.slice(cycleIndex), moduleImport].map(moduleImportName);
87
+ throw new AponiaError("MODULE_CYCLE", `Module import cycle detected: ${cycle.join(" -> ")}.`, { cycle });
88
+ }
89
+ function missingModuleDecorator(moduleClass) {
90
+ return new AponiaError("INVALID_MODULE", `Class "${moduleClass.name}" is missing the @Module() decorator.`, { module: moduleClass.name });
91
+ }
92
+ function mergeModuleMetadata(metadata, dynamicModule) {
93
+ return Object.freeze({
94
+ imports: Object.freeze([...metadata.imports ?? [], ...dynamicModule.imports ?? []]),
95
+ controllers: Object.freeze([...metadata.controllers ?? [], ...dynamicModule.controllers ?? []]),
96
+ providers: Object.freeze([...metadata.providers ?? [], ...dynamicModule.providers ?? []]),
97
+ exports: Object.freeze([...metadata.exports ?? [], ...dynamicModule.exports ?? []])
98
+ });
99
+ }
51
100
  function compileProvider(provider) {
52
101
  if (typeof provider !== "function") return provider;
53
102
  const inject = getConstructorDependencies(provider);
@@ -87,6 +136,40 @@ function joinPaths(controllerPath, routePath) {
87
136
  return segments.length === 0 ? "/" : `/${segments.join("/")}`;
88
137
  }
89
138
  //#endregion
139
+ //#region src/plugin-module.ts
140
+ const ELYSIA_PLUGIN = createToken("aponia.elysia.native-plugin");
141
+ const keyedModuleIdentityPrefix = "aponia.elysia.plugin-module:";
142
+ var ElysiaPluginModule = @Module({}) class {
143
+ static register(plugin, options = {}) {
144
+ return createPluginModule({ providers: [provideValue(ELYSIA_PLUGIN, plugin)] }, options.key);
145
+ }
146
+ static registerAsync(options) {
147
+ return createPluginModule({
148
+ imports: options.imports,
149
+ providers: [provideFactory(ELYSIA_PLUGIN, options.inject, options.useFactory)]
150
+ }, options.key);
151
+ }
152
+ };
153
+ function isElysiaPluginModule(module) {
154
+ return module.providers.some((provider) => provider.provide === ELYSIA_PLUGIN);
155
+ }
156
+ function getElysiaPlugin(container, module) {
157
+ return container.resolveModuleProvider(module, ELYSIA_PLUGIN);
158
+ }
159
+ function createPluginModule(metadata, key) {
160
+ if (key !== void 0 && key.trim().length === 0) throw new TypeError("Elysia plugin module key must not be empty.");
161
+ const hasStableKey = key !== void 0;
162
+ const id = hasStableKey ? `ElysiaPluginModule[${key}]` : "ElysiaPluginModule";
163
+ const instanceId = hasStableKey ? Symbol.for(`${keyedModuleIdentityPrefix}${key}`) : Symbol(id);
164
+ return Object.freeze({
165
+ module: ElysiaPluginModule,
166
+ id,
167
+ instanceId,
168
+ imports: Object.freeze([...metadata.imports ?? []]),
169
+ providers: Object.freeze([...metadata.providers ?? []])
170
+ });
171
+ }
172
+ //#endregion
90
173
  //#region src/application.ts
91
174
  var AponiaElysiaApplication = class {
92
175
  #nativeApplication;
@@ -133,6 +216,7 @@ var AponiaFactory = class {
133
216
  if (nativeApplication !== baseApplication) throw new AponiaError("INVALID_NATIVE_APPLICATION", "configureNative must return the Elysia application it receives.");
134
217
  for (const module of container.graph.modules) {
135
218
  container.initializeModule(module);
219
+ if (isElysiaPluginModule(module)) nativeApplication.use(getElysiaPlugin(container, module));
136
220
  logger?.log(`${module.id} dependencies initialized`, "InstanceLoader");
137
221
  }
138
222
  for (const module of container.graph.modules) for (const controller of module.controllers) {
@@ -174,4 +258,4 @@ function inferControllerPath(plugin) {
174
258
  return plugin.routes[0]?.path ?? "/";
175
259
  }
176
260
  //#endregion
177
- export { AponiaElysiaApplication, AponiaFactory, ELYSIA_CONTROLLER, compileRootModule, defineElysiaController };
261
+ export { AponiaElysiaApplication, AponiaFactory, ELYSIA_CONTROLLER, ElysiaPluginModule, compileRootModule, defineElysiaController };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aponiajs/platform-elysia",
3
- "version": "0.3.18",
3
+ "version": "0.3.19",
4
4
  "description": "Minimal Elysia application platform for Aponia modules and controllers.",
5
5
  "homepage": "https://github.com/aponiajs/aponiajs#readme",
6
6
  "bugs": {