@aponiajs/platform-elysia 0.6.0-alpha.3 → 0.6.0-alpha.6
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 +120 -4
- package/dist/index.d.mts +82 -3
- package/dist/index.mjs +16 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -41,8 +41,12 @@ import { AponiaFactory, ElysiaPluginModule } from "@aponiajs/platform-elysia";
|
|
|
41
41
|
@Module({})
|
|
42
42
|
class AppModule {}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
await
|
|
44
|
+
async function bootstrap(): Promise<void> {
|
|
45
|
+
const application = await AponiaFactory.create(AppModule);
|
|
46
|
+
await application.listen(3000);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await bootstrap();
|
|
46
50
|
```
|
|
47
51
|
|
|
48
52
|
## Routes with the native Elysia context
|
|
@@ -134,10 +138,122 @@ shared configured module is installed once across diamond imports. A stable
|
|
|
134
138
|
`key` keeps module diagnostics deterministic and prevents duplicate
|
|
135
139
|
registrations with the same key.
|
|
136
140
|
|
|
141
|
+
### Typing what a plugin adds
|
|
142
|
+
|
|
143
|
+
Compiling a decorated controller erases the plugin instances its module imports,
|
|
144
|
+
so no plugin type reaches a handler on its own. Name the plugins in the second
|
|
145
|
+
type argument of `ElysiaRouteContext` and the context types what they add:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { Controller, Ctx, Get } from "@aponiajs/common";
|
|
149
|
+
import { type ElysiaRouteContext } from "@aponiajs/platform-elysia";
|
|
150
|
+
import { Elysia } from "elysia";
|
|
151
|
+
|
|
152
|
+
export const clock = new Elysia({ name: "clock" })
|
|
153
|
+
.decorate("now", () => new Date().toISOString())
|
|
154
|
+
.state("requests", 0)
|
|
155
|
+
.derive({ as: "global" }, () => ({ traceId: crypto.randomUUID() }));
|
|
156
|
+
|
|
157
|
+
@Controller("health")
|
|
158
|
+
class HealthController {
|
|
159
|
+
@Get()
|
|
160
|
+
read(@Ctx() context: ElysiaRouteContext<typeof clock>) {
|
|
161
|
+
context.store.requests += 1;
|
|
162
|
+
return { now: context.now(), traceId: context.traceId };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The first argument takes either the plugins or a route schema, so a handler
|
|
168
|
+
without a schema never writes an empty one. A tuple covers several plugins, and
|
|
169
|
+
the second argument is only needed when both are typed:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
ElysiaRouteContext<[typeof clock, typeof cache]>;
|
|
173
|
+
ElysiaRouteContext<typeof createUser, typeof clock>;
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
An application that always mounts the same plugins declares the pairing once and
|
|
177
|
+
keeps every handler short:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
// src/app.context.ts
|
|
181
|
+
import { type ElysiaInputSchema, type ElysiaRouteContext } from "@aponiajs/platform-elysia";
|
|
182
|
+
import { cache } from "./cache.plugin.ts";
|
|
183
|
+
import { clock } from "./clock.plugin.ts";
|
|
184
|
+
|
|
185
|
+
export type AppContext<TSchema extends ElysiaInputSchema = {}> = ElysiaRouteContext<
|
|
186
|
+
TSchema,
|
|
187
|
+
[typeof clock, typeof cache]
|
|
188
|
+
>;
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
@Get()
|
|
193
|
+
read(@Ctx() context: AppContext) {}
|
|
194
|
+
|
|
195
|
+
@Post("/", createUser)
|
|
196
|
+
create(@Ctx() context: AppContext<typeof createUser>) {}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Dropping `typeof`
|
|
200
|
+
|
|
201
|
+
`defineElysiaPlugin` converts a native plugin into a module import that also
|
|
202
|
+
carries the plugin type. Export it beside a same-named type and the plugin is
|
|
203
|
+
usable in both a value and a type position:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
// src/clock.plugin.ts
|
|
207
|
+
import { defineElysiaPlugin } from "@aponiajs/platform-elysia";
|
|
208
|
+
import { Elysia } from "elysia";
|
|
209
|
+
|
|
210
|
+
export const clock = defineElysiaPlugin(
|
|
211
|
+
new Elysia({ name: "clock" }).decorate("now", () => new Date().toISOString()),
|
|
212
|
+
{ key: "clock" },
|
|
213
|
+
);
|
|
214
|
+
export type clock = typeof clock;
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The import goes straight into `imports`, with no `ElysiaPluginModule.register`
|
|
218
|
+
around it, and the annotation needs no `typeof`. Rename the context type on
|
|
219
|
+
import for the shortest form:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { Controller, Ctx, Get, Module } from "@aponiajs/common";
|
|
223
|
+
import { type ElysiaRouteContext as e } from "@aponiajs/platform-elysia";
|
|
224
|
+
import { cache } from "./cache.plugin.ts";
|
|
225
|
+
import { clock } from "./clock.plugin.ts";
|
|
226
|
+
|
|
227
|
+
@Controller("health")
|
|
228
|
+
class HealthController {
|
|
229
|
+
@Get()
|
|
230
|
+
read(@Ctx() context: e<clock>) {
|
|
231
|
+
return { now: context.now() };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@Get("cached")
|
|
235
|
+
readCached(@Ctx() context: e<[clock, cache]>) {
|
|
236
|
+
return { cached: context.cache.read("health") };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@Module({ imports: [clock, cache], controllers: [HealthController] })
|
|
241
|
+
class HealthModule {}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`ElysiaPluginModule.register` and `registerAsync` stay available and unchanged;
|
|
245
|
+
`defineElysiaPlugin` is `register` plus the plugin it installs, and the context
|
|
246
|
+
type accepts either form. The plugin instance itself remains reachable as
|
|
247
|
+
`clock.plugin`.
|
|
248
|
+
|
|
249
|
+
The mapping follows Elysia's own `.use()` rule, so what is typed is exactly what
|
|
250
|
+
arrives at runtime: `decorate`, `state`, `resolve`, and `derive` declared
|
|
251
|
+
`global`, plus `scoped` derives and resolves. A plugin-local derive stays inside
|
|
252
|
+
the plugin and is absent from both the type and the context. Naming no plugin
|
|
253
|
+
costs nothing at runtime — the values are still there, only untyped.
|
|
254
|
+
|
|
137
255
|
`configureNative` remains available as an application-level escape hatch. It
|
|
138
256
|
preserves Elysia's accumulated plugin types on `getNativeApplication()`.
|
|
139
|
-
Module-imported plugin state and decorators are available at runtime, but do
|
|
140
|
-
not yet flow into decorated controller parameter types.
|
|
141
257
|
|
|
142
258
|
[npm package](https://www.npmjs.com/package/@aponiajs/platform-elysia) ·
|
|
143
259
|
[complete package catalog](../../docs/packages.md)
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Constructor, ControllerDefinition, DynamicModule, LogLevel, LoggerService, ModuleDefinition, ModuleImport, Token, TokenValues } from "@aponiajs/common";
|
|
2
2
|
import "@aponiajs/core";
|
|
3
|
-
import { AnyElysia, Context, Elysia, InputSchema, UnwrapRoute } from "elysia";
|
|
3
|
+
import { AnyElysia, Context, Elysia, InputSchema, SingletonBase, UnwrapRoute } from "elysia";
|
|
4
4
|
//#region src/decorated-module.d.ts
|
|
5
5
|
type AponiaRootModule = ModuleImport;
|
|
6
6
|
declare function compileRootModule(rootModule: AponiaRootModule): ModuleDefinition;
|
|
@@ -28,13 +28,75 @@ declare class AponiaFactory {
|
|
|
28
28
|
}
|
|
29
29
|
//#endregion
|
|
30
30
|
//#region src/route-context.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* One plugin a handler reads from: a native Elysia instance, or the module
|
|
33
|
+
* import `defineElysiaPlugin` produces for it.
|
|
34
|
+
*/
|
|
35
|
+
type ElysiaPluginSource = AnyElysia | {
|
|
36
|
+
readonly plugin: AnyElysia;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* One plugin, or every plugin whose types a handler depends on.
|
|
40
|
+
*/
|
|
41
|
+
type ElysiaPluginTypes = ElysiaPluginSource | readonly ElysiaPluginSource[];
|
|
42
|
+
type ResolvePlugin<TSource> = TSource extends {
|
|
43
|
+
readonly plugin: infer TPlugin extends AnyElysia;
|
|
44
|
+
} ? TPlugin : TSource;
|
|
45
|
+
type PluginUnion<TPlugins extends ElysiaPluginTypes> = ResolvePlugin<TPlugins extends readonly (infer TSource)[] ? TSource : TPlugins>;
|
|
46
|
+
type UnionToIntersection<TUnion> = (TUnion extends unknown ? (value: TUnion) => void : never) extends ((value: infer TIntersection) => void) ? TIntersection : never;
|
|
47
|
+
type MergeRecords<TUnion> = UnionToIntersection<TUnion> extends (infer TMerged extends Record<string, unknown>) ? TMerged : {};
|
|
48
|
+
/**
|
|
49
|
+
* The singleton a controller sees once the plugins are mounted. `use()` merges
|
|
50
|
+
* a plugin's global `~Singleton` into its parent, and its `scoped` `~Ephemeral`
|
|
51
|
+
* derives and resolves reach the routes mounted alongside it, so both are part
|
|
52
|
+
* of the context. Plugin-local derives stay inside the plugin and are excluded.
|
|
53
|
+
*/
|
|
54
|
+
type MountedSingleton<TPlugins extends ElysiaPluginTypes> = {
|
|
55
|
+
decorator: MergeRecords<PluginUnion<TPlugins>["~Singleton"]["decorator"]>;
|
|
56
|
+
store: MergeRecords<PluginUnion<TPlugins>["~Singleton"]["store"]>;
|
|
57
|
+
derive: MergeRecords<PluginUnion<TPlugins>["~Singleton"]["derive"] | PluginUnion<TPlugins>["~Ephemeral"]["derive"]>;
|
|
58
|
+
resolve: MergeRecords<PluginUnion<TPlugins>["~Singleton"]["resolve"] | PluginUnion<TPlugins>["~Ephemeral"]["resolve"]>;
|
|
59
|
+
} extends (infer TSingleton extends SingletonBase) ? TSingleton : never;
|
|
31
60
|
/**
|
|
32
61
|
* The native Elysia request context for a declared route schema. Handlers that
|
|
33
62
|
* take the whole context — through `@Ctx()` or a single unannotated parameter —
|
|
34
63
|
* keep `status`, `set`, `cookie`, `store`, `redirect`, and plugin decorators
|
|
35
64
|
* fully typed.
|
|
65
|
+
*
|
|
66
|
+
* Compiling a decorated controller erases the plugin instances a module
|
|
67
|
+
* imports, so name the plugins a handler reads from. The first argument takes
|
|
68
|
+
* either a route schema or the plugins, so a handler without a schema never
|
|
69
|
+
* writes an empty one:
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* read(@Ctx() context: ElysiaRouteContext<typeof clock>) {}
|
|
73
|
+
* read(@Ctx() context: ElysiaRouteContext<[typeof clock, typeof cache]>) {}
|
|
74
|
+
* create(@Ctx() context: ElysiaRouteContext<typeof createUser, typeof clock>) {}
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* A plugin exported through `defineElysiaPlugin` alongside a same-named type
|
|
78
|
+
* drops the `typeof`, which reads best under a short import alias:
|
|
79
|
+
*
|
|
80
|
+
* ```ts
|
|
81
|
+
* import { type ElysiaRouteContext as e } from "@aponiajs/platform-elysia";
|
|
82
|
+
*
|
|
83
|
+
* read(@Ctx() context: e<clock>) {}
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* An application that always mounts the same plugins declares the pairing once
|
|
87
|
+
* and keeps its handlers short:
|
|
88
|
+
*
|
|
89
|
+
* ```ts
|
|
90
|
+
* export type AppContext<TSchema extends ElysiaInputSchema = {}> =
|
|
91
|
+
* ElysiaRouteContext<TSchema, [typeof clock, typeof cache]>;
|
|
92
|
+
* ```
|
|
36
93
|
*/
|
|
37
|
-
type ElysiaRouteContext<
|
|
94
|
+
type ElysiaRouteContext<TSchemaOrPlugins extends InputSchema | ElysiaPluginTypes = {}, TPlugins extends ElysiaPluginTypes = never> = TSchemaOrPlugins extends ElysiaPluginTypes ? Context<UnwrapRoute<{}, {}, string>, MountedSingleton<TSchemaOrPlugins>> : Context<UnwrapRoute<TSchemaOrPlugins extends InputSchema ? TSchemaOrPlugins : {}, {}, string>, MountedSingleton<TPlugins>>;
|
|
95
|
+
/**
|
|
96
|
+
* Elysia's own route schema shape, re-exported so an application can write its
|
|
97
|
+
* own context alias without importing from `elysia` directly.
|
|
98
|
+
*/
|
|
99
|
+
type ElysiaInputSchema = InputSchema;
|
|
38
100
|
//#endregion
|
|
39
101
|
//#region src/controller.d.ts
|
|
40
102
|
declare const ELYSIA_CONTROLLER = "aponia.elysia.controller";
|
|
@@ -63,5 +125,22 @@ declare class ElysiaPluginModule {
|
|
|
63
125
|
static register<const TPlugin extends NativeElysiaPlugin>(plugin: TPlugin, options?: ElysiaPluginModuleOptions): DynamicModule;
|
|
64
126
|
static registerAsync<const TDependencies extends readonly Token<unknown>[], const TPlugin extends NativeElysiaPlugin>(options: AsyncElysiaPluginModuleOptions<TDependencies, TPlugin>): DynamicModule;
|
|
65
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* A module import that carries the native plugin it installs, so the plugin is
|
|
130
|
+
* both mountable and usable as a context type without a separate reference.
|
|
131
|
+
*/
|
|
132
|
+
interface ElysiaPluginImport<TPlugin extends AnyElysia> extends DynamicModule {
|
|
133
|
+
readonly plugin: TPlugin;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Convert a native Elysia plugin into a module import. The result goes straight
|
|
137
|
+
* into `imports` and doubles as the plugin type an `ElysiaRouteContext` reads:
|
|
138
|
+
*
|
|
139
|
+
* ```ts
|
|
140
|
+
* export const clock = defineElysiaPlugin(new Elysia({ name: "clock" }), { key: "clock" });
|
|
141
|
+
* export type clock = typeof clock;
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
declare function defineElysiaPlugin<const TPlugin extends AnyElysia>(plugin: TPlugin, options?: ElysiaPluginModuleOptions): ElysiaPluginImport<TPlugin>;
|
|
66
145
|
//#endregion
|
|
67
|
-
export { type AponiaApplicationOptions, AponiaElysiaApplication, AponiaFactory, type AponiaRootModule, type AsyncElysiaPluginModuleOptions, type ConfiguredAponiaApplicationOptions, ELYSIA_CONTROLLER, type ElysiaControllerDefinition, ElysiaPluginModule, type ElysiaPluginModuleOptions, type ElysiaRouteContext, type NativeElysiaConfigurator, type NativeElysiaPlugin, compileRootModule, defineElysiaController };
|
|
146
|
+
export { type AponiaApplicationOptions, AponiaElysiaApplication, AponiaFactory, type AponiaRootModule, type AsyncElysiaPluginModuleOptions, type ConfiguredAponiaApplicationOptions, ELYSIA_CONTROLLER, type ElysiaControllerDefinition, type ElysiaInputSchema, type ElysiaPluginImport, ElysiaPluginModule, type ElysiaPluginModuleOptions, type ElysiaPluginSource, type ElysiaPluginTypes, type ElysiaRouteContext, type NativeElysiaConfigurator, type NativeElysiaPlugin, compileRootModule, defineElysiaController, defineElysiaPlugin };
|
package/dist/index.mjs
CHANGED
|
@@ -198,6 +198,21 @@ var ElysiaPluginModule = @Module({}) class {
|
|
|
198
198
|
}, options.key);
|
|
199
199
|
}
|
|
200
200
|
};
|
|
201
|
+
/**
|
|
202
|
+
* Convert a native Elysia plugin into a module import. The result goes straight
|
|
203
|
+
* into `imports` and doubles as the plugin type an `ElysiaRouteContext` reads:
|
|
204
|
+
*
|
|
205
|
+
* ```ts
|
|
206
|
+
* export const clock = defineElysiaPlugin(new Elysia({ name: "clock" }), { key: "clock" });
|
|
207
|
+
* export type clock = typeof clock;
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
function defineElysiaPlugin(plugin, options = {}) {
|
|
211
|
+
return Object.freeze({
|
|
212
|
+
...ElysiaPluginModule.register(plugin, options),
|
|
213
|
+
plugin
|
|
214
|
+
});
|
|
215
|
+
}
|
|
201
216
|
function isElysiaPluginModule(module) {
|
|
202
217
|
return module.providers.some((provider) => provider.provide === ELYSIA_PLUGIN);
|
|
203
218
|
}
|
|
@@ -306,4 +321,4 @@ function inferControllerPath(plugin) {
|
|
|
306
321
|
return plugin.routes[0]?.path ?? "/";
|
|
307
322
|
}
|
|
308
323
|
//#endregion
|
|
309
|
-
export { AponiaElysiaApplication, AponiaFactory, ELYSIA_CONTROLLER, ElysiaPluginModule, compileRootModule, defineElysiaController };
|
|
324
|
+
export { AponiaElysiaApplication, AponiaFactory, ELYSIA_CONTROLLER, ElysiaPluginModule, compileRootModule, defineElysiaController, defineElysiaPlugin };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aponiajs/platform-elysia",
|
|
3
|
-
"version": "0.6.0-alpha.
|
|
3
|
+
"version": "0.6.0-alpha.6",
|
|
4
4
|
"description": "Minimal Elysia application platform for Aponia modules and controllers.",
|
|
5
5
|
"homepage": "https://github.com/aponiajs/aponiajs#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"prepublishOnly": "bun run build"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@aponiajs/common": "0.6.0-alpha.
|
|
37
|
-
"@aponiajs/core": "0.6.0-alpha.
|
|
36
|
+
"@aponiajs/common": "0.6.0-alpha.6",
|
|
37
|
+
"@aponiajs/core": "0.6.0-alpha.6"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"arktype": "^2.2.3",
|