@aponiajs/platform-elysia 0.3.22 → 0.5.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 CHANGED
@@ -15,6 +15,10 @@ The first Elysia platform slice for Aponia:
15
15
  - constructor-injected controllers;
16
16
  - Nest-style `@Module()`, `@Controller()`, route, and `@Injectable()` metadata;
17
17
  - automatic translation of decorated controllers into native Elysia routes;
18
+ - Standard Schema route validation for `body`, `query`, `params`, `headers`,
19
+ and `response`;
20
+ - Nest-style request parameter decorators — `@Body()`, `@Query()`, `@Param()`,
21
+ `@Headers()`, `@Cookie()`, `@Req()`, `@Res()`, and `@Ctx()`;
18
22
  - Nest-style startup logging for module initialization and route mapping;
19
23
  - controller factories that return native Elysia plugins;
20
24
  - `handle`, `listen`, and `close` application methods.
@@ -41,6 +45,41 @@ const application = await AponiaFactory.create(AppModule);
41
45
  await application.listen(3000);
42
46
  ```
43
47
 
48
+ ## Routes with the native Elysia context
49
+
50
+ Controllers are Nest-shaped: a route decorator declares the method, path, and
51
+ schema, and parameter decorators inject the request. Types come from the
52
+ handler's own annotations.
53
+
54
+ ```ts
55
+ import { Body, Controller, Ctx, Param, Post } from "@aponiajs/common";
56
+ import { type ElysiaRouteContext } from "@aponiajs/platform-elysia";
57
+ import { z } from "zod";
58
+
59
+ const createUser = { body: z.object({ name: z.string().min(2) }) };
60
+ type CreateUser = z.infer<(typeof createUser)["body"]>;
61
+
62
+ @Controller("users")
63
+ class UserController {
64
+ @Post("/", createUser)
65
+ createUser(@Body() body: CreateUser, @Param("tenant") tenant: string) {
66
+ return { tenant, name: body.name };
67
+ }
68
+
69
+ @Post("native", createUser)
70
+ createNatively(@Ctx() context: ElysiaRouteContext<typeof createUser>) {
71
+ context.set.headers["x-created"] = "1";
72
+ return context.body.name === "root"
73
+ ? context.status(403, "forbidden")
74
+ : { name: context.body.name };
75
+ }
76
+ }
77
+ ```
78
+
79
+ `ElysiaRouteContext<typeof schema>` is Elysia's own context type narrowed by the
80
+ declared schema, so `status`, `set`, `cookie`, `store`, `redirect`, and plugin
81
+ decorators behave exactly as they do in a plain Elysia handler.
82
+
44
83
  ## Native Elysia plugins
45
84
 
46
85
  Use existing Elysia plugins through Nest-style module imports:
@@ -50,8 +89,9 @@ bun add @elysiajs/cors @elysiajs/jwt
50
89
  ```
51
90
 
52
91
  ```ts
92
+ import { Module } from "@aponiajs/common";
93
+ import { ElysiaPluginModule } from "@aponiajs/platform-elysia";
53
94
  import { cors } from "@elysiajs/cors";
54
- import { jwt } from "@elysiajs/jwt";
55
95
 
56
96
  @Module({
57
97
  imports: [
@@ -67,6 +107,11 @@ The plugin is passed unchanged to Elysia's native `.use()` implementation. For
67
107
  plugins that depend on an injectable service, use an async registration:
68
108
 
69
109
  ```ts
110
+ import { Module } from "@aponiajs/common";
111
+ import { ElysiaPluginModule } from "@aponiajs/platform-elysia";
112
+ import { jwt } from "@elysiajs/jwt";
113
+ import { ConfigModule, ConfigService } from "./config/config.module.ts";
114
+
70
115
  @Module({
71
116
  imports: [
72
117
  ElysiaPluginModule.registerAsync({
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, Elysia } from "elysia";
3
+ import { AnyElysia, Context, Elysia, InputSchema, UnwrapRoute } from "elysia";
4
4
  //#region src/decorated-module.d.ts
5
5
  type AponiaRootModule = ModuleImport;
6
6
  declare function compileRootModule(rootModule: AponiaRootModule): ModuleDefinition;
@@ -27,6 +27,15 @@ declare class AponiaFactory {
27
27
  static create(rootModule: AponiaRootModule, options?: AponiaApplicationOptions): Promise<AponiaElysiaApplication>;
28
28
  }
29
29
  //#endregion
30
+ //#region src/route-context.d.ts
31
+ /**
32
+ * The native Elysia request context for a declared route schema. Handlers that
33
+ * take the whole context — through `@Ctx()` or a single unannotated parameter —
34
+ * keep `status`, `set`, `cookie`, `store`, `redirect`, and plugin decorators
35
+ * fully typed.
36
+ */
37
+ type ElysiaRouteContext<TSchema extends InputSchema = {}> = Context<UnwrapRoute<TSchema, {}, string>>;
38
+ //#endregion
30
39
  //#region src/controller.d.ts
31
40
  declare const ELYSIA_CONTROLLER = "aponia.elysia.controller";
32
41
  interface ElysiaControllerDefinition<TController, TDependencies extends readonly Token<unknown>[], TPlugin extends AnyElysia> extends ControllerDefinition {
@@ -55,4 +64,4 @@ declare class ElysiaPluginModule {
55
64
  static registerAsync<const TDependencies extends readonly Token<unknown>[], const TPlugin extends NativeElysiaPlugin>(options: AsyncElysiaPluginModuleOptions<TDependencies, TPlugin>): DynamicModule;
56
65
  }
57
66
  //#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 };
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 };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { AponiaError, Logger, Module, createToken, getConstructorDependencies, getControllerMetadata, getModuleMetadata, getRouteMetadata, provideFactory, provideValue, tokenName } from "@aponiajs/common";
1
+ import { AponiaError, Logger, Module, createToken, getConstructorDependencies, getControllerMetadata, getModuleMetadata, getRouteMetadata, getRouteParameterMetadata, isStandardSchema, 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
@@ -125,12 +125,60 @@ function compileDecoratedController(controller) {
125
125
  controller: controller.name,
126
126
  handler: String(route.propertyKey)
127
127
  });
128
- plugin.route(route.method, joinPaths(metadata.path, route.path), () => Reflect.apply(handler, instance, []));
128
+ const parameters = getRouteParameterMetadata(controller, route.propertyKey);
129
+ plugin.route(route.method, joinPaths(metadata.path, route.path), (context) => handler.call(instance, ...bindParameters(parameters, context)), toRouteHook(route.schema));
129
130
  }
130
131
  return plugin;
131
132
  }
132
133
  });
133
134
  }
135
+ /**
136
+ * Builds the handler arguments described by parameter decorators. A handler
137
+ * without them receives the whole context, which keeps `@Ctx()` optional for
138
+ * single-argument handlers.
139
+ */
140
+ function bindParameters(parameters, context) {
141
+ if (parameters.length === 0) return [context];
142
+ const size = Math.max(...parameters.map((parameter) => parameter.index)) + 1;
143
+ const bound = Array.from({ length: size }, () => void 0);
144
+ for (const parameter of parameters) bound[parameter.index] = resolveParameter(parameter, context);
145
+ return bound;
146
+ }
147
+ function resolveParameter(parameter, context) {
148
+ const source = parameterSource(parameter, context);
149
+ if (parameter.property === void 0) return source;
150
+ if (typeof source !== "object" || source === null) return;
151
+ const value = source[parameter.property];
152
+ return parameter.kind === "cookie" ? value?.value : value;
153
+ }
154
+ function parameterSource(parameter, context) {
155
+ const contextRecord = context;
156
+ switch (parameter.kind) {
157
+ case "context": return context;
158
+ case "set": return context.set;
159
+ case "request": return context.request;
160
+ default: return contextRecord[parameter.kind];
161
+ }
162
+ }
163
+ function toRouteHook(schema) {
164
+ if (!schema) return;
165
+ const hook = {
166
+ ...schema.body ? { body: toElysiaSchema(schema.body) } : {},
167
+ ...schema.query ? { query: toElysiaSchema(schema.query) } : {},
168
+ ...schema.params ? { params: toElysiaSchema(schema.params) } : {},
169
+ ...schema.headers ? { headers: toElysiaSchema(schema.headers) } : {},
170
+ ...schema.response ? { response: toElysiaSchema(schema.response) } : {}
171
+ };
172
+ return Object.keys(hook).length === 0 ? void 0 : hook;
173
+ }
174
+ /**
175
+ * Standard Schema validators pass through unchanged. Platform-native TypeBox
176
+ * validators reach the platform through the neutral `NativeSchema` contract,
177
+ * which cannot describe TypeBox's `Kind` symbol, so they are restored here.
178
+ */
179
+ function toElysiaSchema(validator) {
180
+ return isStandardSchema(validator) ? validator : validator;
181
+ }
134
182
  function joinPaths(controllerPath, routePath) {
135
183
  const segments = [controllerPath, routePath].map((path) => path.trim().replace(/^\/+|\/+$/g, "")).filter(Boolean);
136
184
  return segments.length === 0 ? "/" : `/${segments.join("/")}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aponiajs/platform-elysia",
3
- "version": "0.3.22",
3
+ "version": "0.5.0",
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,11 +33,13 @@
33
33
  "prepublishOnly": "bun run build"
34
34
  },
35
35
  "dependencies": {
36
- "@aponiajs/common": "0.3.22",
37
- "@aponiajs/core": "0.3.22"
36
+ "@aponiajs/common": "0.5.0",
37
+ "@aponiajs/core": "0.5.0"
38
38
  },
39
39
  "devDependencies": {
40
- "elysia": "^1.4.29"
40
+ "arktype": "^2.2.3",
41
+ "elysia": "^1.4.29",
42
+ "zod": "^4.4.3"
41
43
  },
42
44
  "peerDependencies": {
43
45
  "elysia": "^1.4.29"