@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 +42 -11
- package/dist/index.d.mts +19 -3
- package/dist/index.mjs +98 -14
- package/package.json +1 -1
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
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
22
|
-
const
|
|
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 = (
|
|
25
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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