@supacloud/elysia 0.14.1 → 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
@@ -1,5 +1,94 @@
1
1
  # @supacloud/elysia
2
2
 
3
+ ## Compatibility and Acceptance Boundary
4
+
5
+ The dependency range is not a claim that every allowed version has been tested.
6
+ The focused conformance suite was verified with Bun 1.4.2 and Elysia 1.4.30.
7
+ The package declares Elysia `^1.4.30` as a peer and TypeScript `^7.0.2` as a
8
+ development dependency. `compatibility.json` records the exact exercised tuple,
9
+ including the compiler's separate TypeScript 6 semantic API. The contract-upgrade
10
+ gate checks both that semantic API and the TypeScript 7 CLI. These tests do not
11
+ establish a wider version matrix or Node.js runtime compatibility.
12
+
13
+ Run `bun run test:conformance` in this package after building the local
14
+ `@supacloud/contracts` and `@supacloud/app` dependencies and installing this
15
+ package's dependencies. The suite runs through `app.handle(Request)` without a
16
+ network listener. It is included in the normal `bun test` discovery.
17
+
18
+ | Boundary | Acceptance evidence in `src/conformance.test.ts` |
19
+ | --- | --- |
20
+ | Decoded body, params, query, headers and cookies | Native/adapter response comparison, with explicit decoded-value assertions |
21
+ | Response normalization and declared status maps | Native/adapter comparison, including a 409 response |
22
+ | Native `Response` transport | Status, body, content type, custom header and outgoing cookie preserved |
23
+ | Parent lifecycle hooks | Request, before-handler, handler and after-handler order compared |
24
+ | Parent early return | 403 response compared; controller must not run |
25
+ | Local sibling hooks | Local hook cannot intercept compiled routes |
26
+ | Request schema failure | Native 422 status retained; controller must not run |
27
+ | Malformed JSON | Native 400 status retained for multiple malformed bodies; controller must not run |
28
+ | Error mapper precedence | Custom mapper handles parse failure before request context resolution |
29
+ | Module error isolation | Internal exception redacted; sibling native error handling remains unchanged |
30
+ | Invalid handler output | Intentional 500 response with `RESPONSE_VALIDATION_ERROR` |
31
+ | Unsupported route descriptors | Unsupported methods/native hooks rejected before registration |
32
+ | Duplicate protocol package copies | Known command errors retain their status; unknown codes remain internal |
33
+
34
+ ### Intentional Adapter Semantics
35
+
36
+ - Default parse failures return HTTP 400 with `PARSE_ERROR`; request schema
37
+ failures return HTTP 422 with `VALIDATION_ERROR`. Their public messages do not
38
+ include parser details, submitted values or schema internals.
39
+ - Invalid handler output returns HTTP 500, not a client-input error. It may occur
40
+ after business work has completed and must not be interpreted as a rollback.
41
+ - Unknown handler exceptions are redacted. Known `CommandError` instances are
42
+ recognized by their Error identity, name and allowlisted code across separate
43
+ protocol package copies, never by exposing their message. A configured `errorMapper` can
44
+ override these defaults and owns the safety of its response.
45
+ - Cookie input passed to a compiled controller contains decoded values, not
46
+ Elysia's mutable cookie wrappers. Native `Response` headers can carry outgoing
47
+ cookies.
48
+ - Errors before context resolution have no request context. Error mappers must
49
+ not assume identity or request-scoped services are available.
50
+
51
+ ### Not Yet Proven by This Suite
52
+
53
+ WebSockets, streaming and disconnect behavior, multipart uploads, signed-cookie
54
+ mutation, arbitrary third-party plugins, alternate runtime/version combinations,
55
+ concurrent tenant isolation, database transaction/idempotency guarantees and
56
+ published-package installation are not covered by the conformance suite alone.
57
+ Runtime safety is covered separately below. This list records
58
+ an evidence gap, not a declaration that all these features are unsupported.
59
+ Do not claim complete Elysia compatibility from this gate.
60
+
61
+ Compiled routes accept only the HTTP methods and schema fields declared by
62
+ `CompiledRoute`, plus compiler-emitted parameter transformation/default and
63
+ descriptive metadata. This metadata does not install native Elysia hooks.
64
+ Other descriptor fields (including browser guards/resolvers and native
65
+ `beforeHandle`) or unsupported methods throw `ROUTE_DESCRIPTOR_UNSUPPORTED`
66
+ at registration. It is not an arbitrary Elysia route-options passthrough.
67
+ TypeScript/decorator inference, compiler migrations and generated client parity
68
+ require their own acceptance gates.
69
+
70
+ ### Runtime and Upgrade Gates
71
+
72
+ `bun run test:runtime-safety` requires `SUPACLOUD_COMMAND_TEST_URL` and fails
73
+ instead of skipping when it is missing. Use a dedicated loopback database named
74
+ `supacloud_commands_test`, with PostgreSQL 18 and PGMQ 1.10.0; initialize it using
75
+ `scripts/prepare-command-test-database.ts`. The gate opens real loopback HTTP
76
+ listeners and exercises compiler-generated request-scoped controllers. It proves
77
+ overlapping tenant/actor requests, duplicate-key concurrency, authorization
78
+ revocation, audit rollback, same-key retry and per-request provider teardown.
79
+ Its authentication uses a fixed test token map, not a production JWT provider.
80
+
81
+ `bun run test:contract-upgrade` copies a fixed legacy source fixture, previews and
82
+ applies its versioned migration, compiles factories/client/OpenAPI, checks positive
83
+ and negative types with both TypeScript engines, and calls the generated client
84
+ over real HTTP. It restores the old source checkpoint, regenerates artifacts and
85
+ executes the restored application. Build local contracts, app and compiler
86
+ (including declarations) before installing this package's copied file dependencies.
87
+
88
+ These gates prove the stated scenarios, not a full historical npm upgrade matrix
89
+ or all business-domain isolation. See [framework acceptance](../../docs/framework-acceptance.md)
90
+ for the evidence boundaries and upgrade policy.
91
+
3
92
  ## Persistent Command Adapters
4
93
 
5
94
  `createPersistentCommandAdapter(command, { identity, input })` binds a
@@ -36,9 +125,9 @@ Runtime adapter that turns `@supacloud/compiler` output into a production-ready
36
125
  controllers and services.
37
126
  - **Request-scope teardown**: invokes the compiler-generated
38
127
  `destroyRequestScope` after the response, including when the handler fails.
39
- - **Angular-backed async DI**: when `createApplication({ injector })` receives
40
- an `@supacloud/app` root injector, each request gets an isolated child
41
- 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.
42
131
  - **TypeBox schema binding**: attaches compiled parameter, query, body, headers,
43
132
  cookie, single-response and status-map TypeBox schemas directly to Elysia
44
133
  route definitions; Elysia performs request validation and normalization.
@@ -116,10 +205,11 @@ GraphQL endpoint serves a local, role-scoped snapshot only; it does not enable
116
205
  server introspection or create a GraphQL resolver layer. Protect or omit these
117
206
  routes in production when the schema is not public.
118
207
 
119
- The root injector is normally created and owned by `bootstrapBun`. The Elysia
120
- adapter does not take ownership of an injected root injector; stop it from the
121
- same Bun bootstrap that created it. Existing applications may omit `injector`
122
- 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.
123
213
 
124
214
  For deterministic local verification, use the in-memory sandbox. It supplies
125
215
  stable request identity, an isolated key-value database with optimistic
@@ -0,0 +1,9 @@
1
+ {
2
+ "bun": "1.4.2",
3
+ "packages": {
4
+ "elysia": "1.4.30",
5
+ "typescript": "7.0.2",
6
+ "@sinclair/typebox": "0.34.52",
7
+ "@typescript/typescript6": "6.0.2"
8
+ }
9
+ }
@@ -1,2 +1,3 @@
1
1
  import type { CommandErrorCode } from "@supacloud/contracts";
2
+ export declare function commandErrorCode(error: unknown): CommandErrorCode | undefined;
2
3
  export declare function commandErrorStatus(code: CommandErrorCode): number;
@@ -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,13 +1,24 @@
1
1
  // src/index.ts
2
2
  import { Elysia as Elysia2 } from "elysia";
3
- import { CommandError as CommandError2, decodeCommandPreview } from "@supacloud/contracts";
4
- import {
5
- provideToken,
6
- runInRequestContext
7
- } from "@supacloud/app";
8
- import { REQUEST_CONTEXT } from "@supacloud/app";
3
+ import { decodeCommandPreview } from "@supacloud/contracts";
9
4
 
10
5
  // src/command-errors.ts
6
+ function commandErrorCode(error) {
7
+ if (!(error instanceof Error) || error.name !== "CommandError" || !("code" in error))
8
+ return;
9
+ switch (error.code) {
10
+ case "COMMAND_INPUT_INVALID":
11
+ case "COMMAND_REJECTED":
12
+ case "COMMAND_IDEMPOTENCY_CONFLICT":
13
+ case "COMMAND_INPUT_EXPIRED":
14
+ case "COMMAND_UNAVAILABLE":
15
+ case "COMMAND_RECEIPT_INVALID":
16
+ case "COMMAND_OUTCOME_UNKNOWN":
17
+ return error.code;
18
+ default:
19
+ return;
20
+ }
21
+ }
11
22
  function commandErrorStatus(code) {
12
23
  switch (code) {
13
24
  case "COMMAND_INPUT_INVALID":
@@ -413,7 +424,7 @@ function createPersistentCommandAdapter(command, options) {
413
424
  async execute(invocation) {
414
425
  try {
415
426
  const identity = decodeCommandIdentity(await options.identity(invocation));
416
- return await command.execute(identity, requireIdempotencyKey(invocation), options.input(invocation));
427
+ return await command.execute(identity, requireIdempotencyKey(invocation), options.input(invocation), invocation.request.signal);
417
428
  } catch (error) {
418
429
  if (!(error instanceof CommandError))
419
430
  throw error;
@@ -1269,6 +1280,17 @@ function observedAspects(aspects, boundary, observer) {
1269
1280
  ...executionTrace(context.requestContext)
1270
1281
  }, () => aspect(context, next))));
1271
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
+ }
1272
1294
  function composeCommandExecutors(...executors) {
1273
1295
  const active = executors.filter((e) => typeof e === "function");
1274
1296
  if (active.length === 0)
@@ -1483,7 +1505,7 @@ async function executeCompiledCommand(options) {
1483
1505
  services: options.services ?? {},
1484
1506
  ...options.scope ? { scope: options.scope } : {}
1485
1507
  };
1486
- 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));
1487
1509
  const value = await createCommandExecutor(options.governance, options.observer)(invocation, once(() => aspects({
1488
1510
  kind: "command",
1489
1511
  name: command.name,
@@ -1591,6 +1613,43 @@ function assertDeclaredResponseStatus(route, value, configuredStatus) {
1591
1613
  throw new ApplicationError("Response validation failed", { status: 500, code: "RESPONSE_CONTRACT_UNDECLARED" });
1592
1614
  }
1593
1615
  function createModulePlugin(compiled, services, ctxFactory = defaultRequestContext, options = {}, imported = {}) {
1616
+ const supportedMethods = new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
1617
+ const supportedFields = new Set([
1618
+ "method",
1619
+ "path",
1620
+ "handler",
1621
+ "body",
1622
+ "params",
1623
+ "query",
1624
+ "headers",
1625
+ "cookie",
1626
+ "response",
1627
+ "responses",
1628
+ "contract",
1629
+ "schemaKinds",
1630
+ "nativeResponse",
1631
+ "invoker",
1632
+ "command",
1633
+ "aspects",
1634
+ "aspectPipeline",
1635
+ "paramTransforms",
1636
+ "paramDefaults",
1637
+ "queryTransforms",
1638
+ "queryDefaults",
1639
+ "title",
1640
+ "data",
1641
+ "input",
1642
+ "result",
1643
+ "request"
1644
+ ]);
1645
+ for (const controller of compiled.controllers) {
1646
+ for (const route of controller.routes) {
1647
+ const unsupported = Object.keys(route).filter((field) => !supportedFields.has(field));
1648
+ if (!supportedMethods.has(route.method) || unsupported.length > 0) {
1649
+ throw new ApplicationError(`Unsupported compiled route ${route.method} ${controller.path}${route.path}` + (unsupported.length > 0 ? `: ${unsupported.join(", ")}` : ""), { code: "ROUTE_DESCRIPTOR_UNSUPPORTED" });
1650
+ }
1651
+ }
1652
+ }
1594
1653
  const hasCommandRoutes = compiled.controllers.some((controller) => controller.routes.some((route) => route.command !== undefined));
1595
1654
  if (hasCommandRoutes && !options.commandGovernance && !options.commandExecutor) {
1596
1655
  throw new ApplicationError(`Module "${compiled.name}" has command routes but no commandGovernance`, { code: "COMMAND_GOVERNANCE_UNCONFIGURED" });
@@ -1695,14 +1754,13 @@ function createModulePlugin(compiled, services, ctxFactory = defaultRequestConte
1695
1754
  services,
1696
1755
  metadata: command ?? route
1697
1756
  };
1698
- const commandAspects = route.command ? commandsByClassName.get(route.command)?.aspects ?? [] : [];
1699
- const routePipeline = observedAspects(route.aspects ?? [], "route", options.onExecution);
1700
- const commandPipeline = observedAspects(commandAspects, "command", options.onExecution);
1701
- const modulePipeline = observedAspects(compiled.aspects ?? [], `module:${compiled.name}`, options.onExecution);
1702
- 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()))));
1703
1761
  const invokeCommand = () => {
1704
1762
  if (!route.command)
1705
- return invoke();
1763
+ return invokeRoute();
1706
1764
  if (!command) {
1707
1765
  throw new ApplicationError(`Command "${route.command}" is not registered`, {
1708
1766
  code: "COMMAND_NOT_REGISTERED"
@@ -1727,11 +1785,11 @@ function createModulePlugin(compiled, services, ctxFactory = defaultRequestConte
1727
1785
  operation: command.name,
1728
1786
  stage: "commandExecutor",
1729
1787
  ...executionTrace(requestContext)
1730
- }, () => commandExecutor(invocation, invoke));
1788
+ }, () => commandExecutor(invocation, once(invokeRoute)));
1731
1789
  };
1732
- return modulePipeline(route.command ? commandContext : routeContext, invokeRoute);
1790
+ return invokeCommand();
1733
1791
  };
1734
- const result = options.injector ? await runInRequestContext(options.injector, [provideToken(REQUEST_CONTEXT, requestContext)], execute) : await execute();
1792
+ const result = await execute();
1735
1793
  assertDeclaredResponseStatus(route, result, ctx.set.status);
1736
1794
  return result;
1737
1795
  };
@@ -1794,8 +1852,8 @@ async function executeJob(compiled, services, job, input, requestContext, import
1794
1852
  stage: "handler",
1795
1853
  ...executionTrace(requestContext)
1796
1854
  }, () => Reflect.apply(method, instance, [decodedInput])));
1797
- const pipeline = observedAspects(compiled.aspects ?? [], `module:${compiled.name}`, observer);
1798
- const jobPipeline = observedAspects(job.aspects ?? [], "job", observer);
1855
+ const pipeline = descriptorPipeline(compiled, `module:${compiled.name}`, observer);
1856
+ const jobPipeline = descriptorPipeline(job, "job", observer);
1799
1857
  const result = await pipeline(context, () => jobPipeline(context, () => observeExecution(observer, {
1800
1858
  kind: "job",
1801
1859
  operation: job.name,
@@ -1810,9 +1868,10 @@ async function executeJob(compiled, services, job, input, requestContext, import
1810
1868
  }
1811
1869
  }
1812
1870
  function defaultErrorResponse(error, frameworkCode) {
1813
- if (error instanceof CommandError2) {
1814
- return Response.json({ ok: false, code: error.code, message: error.code }, {
1815
- status: commandErrorStatus(error.code)
1871
+ const protocolCode = commandErrorCode(error);
1872
+ if (protocolCode) {
1873
+ return Response.json({ ok: false, code: protocolCode, message: protocolCode }, {
1874
+ status: commandErrorStatus(protocolCode)
1816
1875
  });
1817
1876
  }
1818
1877
  if (isPublicApplicationError(error)) {
@@ -1823,6 +1882,13 @@ function defaultErrorResponse(error, frameworkCode) {
1823
1882
  ...error.details === undefined ? {} : { details: error.details }
1824
1883
  }, { status: error.status });
1825
1884
  }
1885
+ if (frameworkCode === "PARSE") {
1886
+ return Response.json({
1887
+ ok: false,
1888
+ code: "PARSE_ERROR",
1889
+ message: "Request body could not be parsed"
1890
+ }, { status: 400 });
1891
+ }
1826
1892
  if (frameworkCode === "VALIDATION") {
1827
1893
  if (isRecord(error) && error.type === "response") {
1828
1894
  return Response.json({
@@ -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.14.1",
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",
@@ -15,7 +15,8 @@
15
15
  },
16
16
  "files": [
17
17
  "dist",
18
- "README.md"
18
+ "README.md",
19
+ "compatibility.json"
19
20
  ],
20
21
  "scripts": {
21
22
  "generate:example": "bun src/generate-webhook-example.ts",
@@ -25,6 +26,9 @@
25
26
  "clean": "rm -rf dist",
26
27
  "prepublishOnly": "bun run build",
27
28
  "test": "bun test",
29
+ "test:conformance": "bun test src/conformance.test.ts",
30
+ "test:runtime-safety": "SUPACLOUD_REQUIRE_RUNTIME_SAFETY=1 bun test src/runtime-safety.test.ts",
31
+ "test:contract-upgrade": "bun test src/contract-upgrade.test.ts",
28
32
  "typecheck": "tsc -p tsconfig.json --noEmit",
29
33
  "typecheck:test": "tsc -p tsconfig.test.json --noEmit"
30
34
  },
@@ -48,15 +52,15 @@
48
52
  },
49
53
  "overrides": {
50
54
  "@supacloud/contracts": "0.4.0",
51
- "@supacloud/commands": "0.4.0"
55
+ "@supacloud/commands": "0.5.0"
52
56
  },
53
57
  "peerDependencies": {
54
58
  "elysia": "^1.4.30"
55
59
  },
56
60
  "devDependencies": {
57
- "@supacloud/compiler": "0.19.1",
58
- "@supacloud/commands": "0.4.0",
59
- "@supacloud/db": "0.7.1",
61
+ "@supacloud/compiler": "0.22.0",
62
+ "@supacloud/commands": "0.5.0",
63
+ "@supacloud/db": "0.8.0",
60
64
  "@types/bun": "^1.4.2",
61
65
  "elysia": "^1.4.30",
62
66
  "typescript": "^7.0.2"