@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
- - **Angular-backed async DI**: when `createApplication({ injector })` receives
129
- an `@supacloud/app` root injector, each request gets an isolated child
130
- injector with `REQUEST_CONTEXT`, including across `await` boundaries.
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
- The root injector is normally created and owned by `bootstrapBun`. The Elysia
209
- adapter does not take ownership of an injected root injector; stop it from the
210
- same Bun bootstrap that created it. Existing applications may omit `injector`
211
- and continue using compiler-generated request scopes unchanged.
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" | "injector">, imported?: Record<string, Record<string, unknown>>): Elysia;
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(observedAspects(options.module.aspects ?? [], `module:${options.module.name}`, options.observer), observedAspects(command.aspects ?? [], "command", options.observer));
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 commandAspects = route.command ? commandsByClassName.get(route.command)?.aspects ?? [] : [];
1751
- const routePipeline = observedAspects(route.aspects ?? [], "route", options.onExecution);
1752
- const commandPipeline = observedAspects(commandAspects, "command", options.onExecution);
1753
- const modulePipeline = observedAspects(compiled.aspects ?? [], `module:${compiled.name}`, options.onExecution);
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 invoke();
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, invoke));
1788
+ }, () => commandExecutor(invocation, once(invokeRoute)));
1783
1789
  };
1784
- return modulePipeline(route.command ? commandContext : routeContext, invokeRoute);
1790
+ return invokeCommand();
1785
1791
  };
1786
- const result = options.injector ? await runInRequestContext(options.injector, [provideToken(REQUEST_CONTEXT, requestContext)], execute) : await execute();
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 = observedAspects(compiled.aspects ?? [], `module:${compiled.name}`, observer);
1850
- const jobPipeline = observedAspects(job.aspects ?? [], "job", observer);
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.16.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.4.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.21.0",
62
- "@supacloud/commands": "0.4.0",
63
- "@supacloud/db": "0.7.1",
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"