@supacloud/elysia 0.16.0 → 0.17.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
|
@@ -125,9 +125,9 @@ Runtime adapter that turns `@supacloud/compiler` output into a production-ready
|
|
|
125
125
|
controllers and services.
|
|
126
126
|
- **Request-scope teardown**: invokes the compiler-generated
|
|
127
127
|
`destroyRequestScope` after the response, including when the handler fails.
|
|
128
|
-
- **
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
- **Compile-time DI**: constructor dependencies are directly connected by generated
|
|
129
|
+
factories. Request/job context is passed explicitly without runtime injector
|
|
130
|
+
discovery, registration or token lookup.
|
|
131
131
|
- **TypeBox schema binding**: attaches compiled parameter, query, body, headers,
|
|
132
132
|
cookie, single-response and status-map TypeBox schemas directly to Elysia
|
|
133
133
|
route definitions; Elysia performs request validation and normalization.
|
|
@@ -205,10 +205,11 @@ GraphQL endpoint serves a local, role-scoped snapshot only; it does not enable
|
|
|
205
205
|
server introspection or create a GraphQL resolver layer. Protect or omit these
|
|
206
206
|
routes in production when the schema is not public.
|
|
207
207
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
and
|
|
208
|
+
Use constructor injection and the generated `createServices`,
|
|
209
|
+
`createRequestScope` and `createJobScope` factories. Compiled applications reject
|
|
210
|
+
property `inject()` and runtime injection contexts with `SC2012`. The host provides
|
|
211
|
+
platform dependencies through `deps` and owns their lifecycle; generated factories
|
|
212
|
+
own application scope construction and teardown.
|
|
212
213
|
|
|
213
214
|
For deterministic local verification, use the in-memory sandbox. It supplies
|
|
214
215
|
stable request identity, an isolated key-value database with optimistic
|
|
@@ -33,6 +33,7 @@ export interface CompiledRoute {
|
|
|
33
33
|
title?: string;
|
|
34
34
|
data?: Record<string, unknown>;
|
|
35
35
|
aspects?: CompiledAspect[];
|
|
36
|
+
aspectPipeline?: CompiledAspectPipeline;
|
|
36
37
|
invoker?: (controller: unknown, request: {
|
|
37
38
|
params?: Record<string, unknown>;
|
|
38
39
|
query?: Record<string, unknown>;
|
|
@@ -52,6 +53,7 @@ export interface CompiledCommand {
|
|
|
52
53
|
idempotency: "required" | "none";
|
|
53
54
|
standalone?: boolean;
|
|
54
55
|
aspects?: CompiledAspect[];
|
|
56
|
+
aspectPipeline?: CompiledAspectPipeline;
|
|
55
57
|
}
|
|
56
58
|
export interface CompiledJob {
|
|
57
59
|
className: string;
|
|
@@ -65,6 +67,7 @@ export interface CompiledJob {
|
|
|
65
67
|
maxAttempts?: number;
|
|
66
68
|
idempotency?: "required" | "none";
|
|
67
69
|
aspects?: CompiledAspect[];
|
|
70
|
+
aspectPipeline?: CompiledAspectPipeline;
|
|
68
71
|
}
|
|
69
72
|
export interface CompiledAspectContext {
|
|
70
73
|
kind: "route" | "command" | "job";
|
|
@@ -94,7 +97,11 @@ export interface CompiledModule {
|
|
|
94
97
|
commands: CompiledCommand[];
|
|
95
98
|
jobs: CompiledJob[];
|
|
96
99
|
aspects?: CompiledAspect[];
|
|
100
|
+
aspectPipeline?: CompiledAspectPipeline;
|
|
97
101
|
}
|
|
102
|
+
type CompiledAspectObserver = (stage: string, run: () => unknown | Promise<unknown>) => unknown | Promise<unknown>;
|
|
103
|
+
type CompiledAspectPipeline = (context: CompiledAspectContext, next: () => unknown | Promise<unknown>, observe?: CompiledAspectObserver) => unknown | Promise<unknown>;
|
|
98
104
|
export declare function createCompiledModules(): CompiledModule[];
|
|
99
105
|
export declare function initializeApplication(services: Record<string, unknown>): Promise<void>;
|
|
100
106
|
export declare function destroyApplication(services: Record<string, unknown>): Promise<void>;
|
|
107
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Elysia } from "elysia";
|
|
2
2
|
import { type CommandPreview } from "@supacloud/contracts";
|
|
3
|
-
import { type EnvironmentInjector } from "@supacloud/app";
|
|
4
3
|
import { type ExecutionObserver } from "./execution";
|
|
5
4
|
import { type ApplicationDocumentationOptions } from "./documentation";
|
|
6
5
|
export type { ExecutionEvent, ExecutionObserver } from "./execution";
|
|
@@ -46,6 +45,7 @@ export interface CompiledRoute {
|
|
|
46
45
|
command?: string;
|
|
47
46
|
/** Statically generated route aspects. */
|
|
48
47
|
aspects?: ApplicationAspect[];
|
|
48
|
+
aspectPipeline?: ApplicationAspectPipeline;
|
|
49
49
|
}
|
|
50
50
|
export interface CompiledCommand {
|
|
51
51
|
rpc?: string;
|
|
@@ -57,6 +57,7 @@ export interface CompiledCommand {
|
|
|
57
57
|
idempotency?: "required" | "none" | string;
|
|
58
58
|
/** Statically generated command aspects. */
|
|
59
59
|
aspects?: ApplicationAspect[];
|
|
60
|
+
aspectPipeline?: ApplicationAspectPipeline;
|
|
60
61
|
}
|
|
61
62
|
export interface CompiledJob {
|
|
62
63
|
className: string;
|
|
@@ -72,6 +73,7 @@ export interface CompiledJob {
|
|
|
72
73
|
maxAttempts?: number;
|
|
73
74
|
idempotency?: "required" | "none";
|
|
74
75
|
aspects?: ApplicationAspect[];
|
|
76
|
+
aspectPipeline?: ApplicationAspectPipeline;
|
|
75
77
|
}
|
|
76
78
|
export interface CompiledController {
|
|
77
79
|
/** Controller path prefix, e.g. "/cases". */
|
|
@@ -93,6 +95,7 @@ export interface CompiledModule {
|
|
|
93
95
|
jobs?: CompiledJob[];
|
|
94
96
|
/** Statically generated module aspects. */
|
|
95
97
|
aspects?: ApplicationAspect[];
|
|
98
|
+
aspectPipeline?: ApplicationAspectPipeline;
|
|
96
99
|
}
|
|
97
100
|
export type RequestContextFactory = (request: Request) => unknown | Promise<unknown>;
|
|
98
101
|
export { createSupAuthRequestContext } from "./identity";
|
|
@@ -145,6 +148,7 @@ export type ApplicationAspect = (context: ApplicationAspectContext, next: () =>
|
|
|
145
148
|
* registers aspects.
|
|
146
149
|
*/
|
|
147
150
|
export declare function composeAspects(...aspects: (ApplicationAspect | undefined | null)[]): ApplicationAspect;
|
|
151
|
+
export type ApplicationAspectPipeline = (context: ApplicationAspectContext, next: () => unknown | Promise<unknown>, observe?: (stage: string, run: () => unknown | Promise<unknown>) => unknown | Promise<unknown>) => unknown | Promise<unknown>;
|
|
148
152
|
export type CommandAuthorizer = (invocation: CommandInvocation) => void | Promise<void>;
|
|
149
153
|
export type CommandMiddleware = (invocation: CommandInvocation, next: () => unknown | Promise<unknown>) => unknown | Promise<unknown>;
|
|
150
154
|
export interface CommandAudit {
|
|
@@ -211,8 +215,6 @@ export interface ApplicationOptions {
|
|
|
211
215
|
modules?: CompiledModule[];
|
|
212
216
|
/** Platform-level dependencies (db client etc.), passed to createServices. */
|
|
213
217
|
deps?: Record<string, unknown>;
|
|
214
|
-
/** Optional Angular-backed root injector used for async request contexts. */
|
|
215
|
-
injector?: EnvironmentInjector;
|
|
216
218
|
/** Builds the per-request context object. Defaults to { requestId, request }. */
|
|
217
219
|
requestContext?: RequestContextFactory;
|
|
218
220
|
/** Enforces permission/audit/idempotency policy for command-bound routes. */
|
|
@@ -248,7 +250,7 @@ export declare const createSupaCloudRequestContext: (request: Request) => SupaCl
|
|
|
248
250
|
export declare function createCommandExecutor(governance: CommandGovernance, observer?: ExecutionObserver): CommandExecutor;
|
|
249
251
|
/** Execute at the business-selected boundary without binding or re-entering an HTTP route. */
|
|
250
252
|
export declare function executeCompiledCommand<Input, Result>(options: {
|
|
251
|
-
module: Pick<CompiledModule, "name" | "commands" | "aspects">;
|
|
253
|
+
module: Pick<CompiledModule, "name" | "commands" | "aspects" | "aspectPipeline">;
|
|
252
254
|
command: string;
|
|
253
255
|
input: Input;
|
|
254
256
|
request: Request;
|
|
@@ -282,7 +284,7 @@ export declare function requireIdempotencyKey(invocation: CommandInvocation): st
|
|
|
282
284
|
* exposed on the `scope` context key. Request-scoped controller instances are
|
|
283
285
|
* looked up on `scope`, everything else on `services`.
|
|
284
286
|
*/
|
|
285
|
-
export declare function createModulePlugin(compiled: CompiledModule, services: Record<string, unknown>, ctxFactory?: RequestContextFactory, options?: Pick<ApplicationOptions, "commandGovernance" | "commandExecutor" | "errorMapper" | "onExecution" | "normalize"
|
|
287
|
+
export declare function createModulePlugin(compiled: CompiledModule, services: Record<string, unknown>, ctxFactory?: RequestContextFactory, options?: Pick<ApplicationOptions, "commandGovernance" | "commandExecutor" | "errorMapper" | "onExecution" | "normalize">, imported?: Record<string, Record<string, unknown>>): Elysia;
|
|
286
288
|
/**
|
|
287
289
|
* Execute one compiler-emitted Job descriptor.
|
|
288
290
|
*
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { Elysia as Elysia2 } from "elysia";
|
|
3
3
|
import { decodeCommandPreview } from "@supacloud/contracts";
|
|
4
|
-
import {
|
|
5
|
-
provideToken,
|
|
6
|
-
runInRequestContext
|
|
7
|
-
} from "@supacloud/app";
|
|
8
|
-
import { REQUEST_CONTEXT } from "@supacloud/app";
|
|
9
4
|
|
|
10
5
|
// src/command-errors.ts
|
|
11
6
|
function commandErrorCode(error) {
|
|
@@ -429,7 +424,7 @@ function createPersistentCommandAdapter(command, options) {
|
|
|
429
424
|
async execute(invocation) {
|
|
430
425
|
try {
|
|
431
426
|
const identity = decodeCommandIdentity(await options.identity(invocation));
|
|
432
|
-
return await command.execute(identity, requireIdempotencyKey(invocation), options.input(invocation));
|
|
427
|
+
return await command.execute(identity, requireIdempotencyKey(invocation), options.input(invocation), invocation.request.signal);
|
|
433
428
|
} catch (error) {
|
|
434
429
|
if (!(error instanceof CommandError))
|
|
435
430
|
throw error;
|
|
@@ -1285,6 +1280,17 @@ function observedAspects(aspects, boundary, observer) {
|
|
|
1285
1280
|
...executionTrace(context.requestContext)
|
|
1286
1281
|
}, () => aspect(context, next))));
|
|
1287
1282
|
}
|
|
1283
|
+
function descriptorPipeline(descriptor, boundary, observer) {
|
|
1284
|
+
const pipeline = descriptor.aspectPipeline;
|
|
1285
|
+
if (!pipeline)
|
|
1286
|
+
return observedAspects(descriptor.aspects ?? [], boundary, observer);
|
|
1287
|
+
return (context, next) => pipeline(context, next, observer ? (stage, run) => observeExecution(observer, {
|
|
1288
|
+
kind: context.kind,
|
|
1289
|
+
operation: context.name,
|
|
1290
|
+
stage: `${boundary}.${stage}`,
|
|
1291
|
+
...executionTrace(context.requestContext)
|
|
1292
|
+
}, run) : undefined);
|
|
1293
|
+
}
|
|
1288
1294
|
function composeCommandExecutors(...executors) {
|
|
1289
1295
|
const active = executors.filter((e) => typeof e === "function");
|
|
1290
1296
|
if (active.length === 0)
|
|
@@ -1499,7 +1505,7 @@ async function executeCompiledCommand(options) {
|
|
|
1499
1505
|
services: options.services ?? {},
|
|
1500
1506
|
...options.scope ? { scope: options.scope } : {}
|
|
1501
1507
|
};
|
|
1502
|
-
const aspects = composeAspects(
|
|
1508
|
+
const aspects = composeAspects(descriptorPipeline(options.module, `module:${options.module.name}`, options.observer), descriptorPipeline(command, "command", options.observer));
|
|
1503
1509
|
const value = await createCommandExecutor(options.governance, options.observer)(invocation, once(() => aspects({
|
|
1504
1510
|
kind: "command",
|
|
1505
1511
|
name: command.name,
|
|
@@ -1625,6 +1631,7 @@ function createModulePlugin(compiled, services, ctxFactory = defaultRequestConte
|
|
|
1625
1631
|
"invoker",
|
|
1626
1632
|
"command",
|
|
1627
1633
|
"aspects",
|
|
1634
|
+
"aspectPipeline",
|
|
1628
1635
|
"paramTransforms",
|
|
1629
1636
|
"paramDefaults",
|
|
1630
1637
|
"queryTransforms",
|
|
@@ -1747,14 +1754,13 @@ function createModulePlugin(compiled, services, ctxFactory = defaultRequestConte
|
|
|
1747
1754
|
services,
|
|
1748
1755
|
metadata: command ?? route
|
|
1749
1756
|
};
|
|
1750
|
-
const
|
|
1751
|
-
const
|
|
1752
|
-
const
|
|
1753
|
-
const
|
|
1754
|
-
const invokeRoute = () => routePipeline(routeContext, () => route.command ? commandPipeline(commandContext, () => invokeCommand()) : invoke());
|
|
1757
|
+
const routePipeline = descriptorPipeline(route, "route", options.onExecution);
|
|
1758
|
+
const commandPipeline = descriptorPipeline(command ?? {}, "command", options.onExecution);
|
|
1759
|
+
const modulePipeline = descriptorPipeline(compiled, `module:${compiled.name}`, options.onExecution);
|
|
1760
|
+
const invokeRoute = () => modulePipeline(route.command ? commandContext : routeContext, once(() => routePipeline(routeContext, once(() => route.command ? commandPipeline(commandContext, once(invoke)) : invoke()))));
|
|
1755
1761
|
const invokeCommand = () => {
|
|
1756
1762
|
if (!route.command)
|
|
1757
|
-
return
|
|
1763
|
+
return invokeRoute();
|
|
1758
1764
|
if (!command) {
|
|
1759
1765
|
throw new ApplicationError(`Command "${route.command}" is not registered`, {
|
|
1760
1766
|
code: "COMMAND_NOT_REGISTERED"
|
|
@@ -1779,11 +1785,11 @@ function createModulePlugin(compiled, services, ctxFactory = defaultRequestConte
|
|
|
1779
1785
|
operation: command.name,
|
|
1780
1786
|
stage: "commandExecutor",
|
|
1781
1787
|
...executionTrace(requestContext)
|
|
1782
|
-
}, () => commandExecutor(invocation,
|
|
1788
|
+
}, () => commandExecutor(invocation, once(invokeRoute)));
|
|
1783
1789
|
};
|
|
1784
|
-
return
|
|
1790
|
+
return invokeCommand();
|
|
1785
1791
|
};
|
|
1786
|
-
const result =
|
|
1792
|
+
const result = await execute();
|
|
1787
1793
|
assertDeclaredResponseStatus(route, result, ctx.set.status);
|
|
1788
1794
|
return result;
|
|
1789
1795
|
};
|
|
@@ -1846,8 +1852,8 @@ async function executeJob(compiled, services, job, input, requestContext, import
|
|
|
1846
1852
|
stage: "handler",
|
|
1847
1853
|
...executionTrace(requestContext)
|
|
1848
1854
|
}, () => Reflect.apply(method, instance, [decodedInput])));
|
|
1849
|
-
const pipeline =
|
|
1850
|
-
const jobPipeline =
|
|
1855
|
+
const pipeline = descriptorPipeline(compiled, `module:${compiled.name}`, observer);
|
|
1856
|
+
const jobPipeline = descriptorPipeline(job, "job", observer);
|
|
1851
1857
|
const result = await pipeline(context, () => jobPipeline(context, () => observeExecution(observer, {
|
|
1852
1858
|
kind: "job",
|
|
1853
1859
|
operation: job.name,
|
|
@@ -2,7 +2,7 @@ import { type CommandIdentity, type DurableCommandReceipt } from "@supacloud/con
|
|
|
2
2
|
import { type CommandInvocation } from "./index";
|
|
3
3
|
export interface PersistentCommandHandler<Result> {
|
|
4
4
|
readonly kind: "transactional" | "external";
|
|
5
|
-
execute(identity: CommandIdentity, key: string, value: unknown): Promise<DurableCommandReceipt<Result>>;
|
|
5
|
+
execute(identity: CommandIdentity, key: string, value: unknown, signal?: AbortSignal): Promise<DurableCommandReceipt<Result>>;
|
|
6
6
|
}
|
|
7
7
|
/** Registered persistence owns the operation; a second route handler is never invoked. */
|
|
8
8
|
export declare function createPersistentCommandAdapter<Result>(command: PersistentCommandHandler<Result>, options: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/elysia",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Elysia runtime adapter for SupaCloud compiled modules: application/request scopes, route registration and validation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,15 +52,15 @@
|
|
|
52
52
|
},
|
|
53
53
|
"overrides": {
|
|
54
54
|
"@supacloud/contracts": "0.4.0",
|
|
55
|
-
"@supacloud/commands": "0.
|
|
55
|
+
"@supacloud/commands": "0.5.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"elysia": "^1.4.30"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@supacloud/compiler": "0.
|
|
62
|
-
"@supacloud/commands": "0.
|
|
63
|
-
"@supacloud/db": "0.
|
|
61
|
+
"@supacloud/compiler": "0.22.0",
|
|
62
|
+
"@supacloud/commands": "0.5.0",
|
|
63
|
+
"@supacloud/db": "0.8.0",
|
|
64
64
|
"@types/bun": "^1.4.2",
|
|
65
65
|
"elysia": "^1.4.30",
|
|
66
66
|
"typescript": "^7.0.2"
|