@supacloud/elysia 0.14.1 → 0.16.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
@@ -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;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
2
  import { Elysia as Elysia2 } from "elysia";
3
- import { CommandError as CommandError2, decodeCommandPreview } from "@supacloud/contracts";
3
+ import { decodeCommandPreview } from "@supacloud/contracts";
4
4
  import {
5
5
  provideToken,
6
6
  runInRequestContext
@@ -8,6 +8,22 @@ import {
8
8
  import { REQUEST_CONTEXT } from "@supacloud/app";
9
9
 
10
10
  // src/command-errors.ts
11
+ function commandErrorCode(error) {
12
+ if (!(error instanceof Error) || error.name !== "CommandError" || !("code" in error))
13
+ return;
14
+ switch (error.code) {
15
+ case "COMMAND_INPUT_INVALID":
16
+ case "COMMAND_REJECTED":
17
+ case "COMMAND_IDEMPOTENCY_CONFLICT":
18
+ case "COMMAND_INPUT_EXPIRED":
19
+ case "COMMAND_UNAVAILABLE":
20
+ case "COMMAND_RECEIPT_INVALID":
21
+ case "COMMAND_OUTCOME_UNKNOWN":
22
+ return error.code;
23
+ default:
24
+ return;
25
+ }
26
+ }
11
27
  function commandErrorStatus(code) {
12
28
  switch (code) {
13
29
  case "COMMAND_INPUT_INVALID":
@@ -1591,6 +1607,42 @@ function assertDeclaredResponseStatus(route, value, configuredStatus) {
1591
1607
  throw new ApplicationError("Response validation failed", { status: 500, code: "RESPONSE_CONTRACT_UNDECLARED" });
1592
1608
  }
1593
1609
  function createModulePlugin(compiled, services, ctxFactory = defaultRequestContext, options = {}, imported = {}) {
1610
+ const supportedMethods = new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
1611
+ const supportedFields = new Set([
1612
+ "method",
1613
+ "path",
1614
+ "handler",
1615
+ "body",
1616
+ "params",
1617
+ "query",
1618
+ "headers",
1619
+ "cookie",
1620
+ "response",
1621
+ "responses",
1622
+ "contract",
1623
+ "schemaKinds",
1624
+ "nativeResponse",
1625
+ "invoker",
1626
+ "command",
1627
+ "aspects",
1628
+ "paramTransforms",
1629
+ "paramDefaults",
1630
+ "queryTransforms",
1631
+ "queryDefaults",
1632
+ "title",
1633
+ "data",
1634
+ "input",
1635
+ "result",
1636
+ "request"
1637
+ ]);
1638
+ for (const controller of compiled.controllers) {
1639
+ for (const route of controller.routes) {
1640
+ const unsupported = Object.keys(route).filter((field) => !supportedFields.has(field));
1641
+ if (!supportedMethods.has(route.method) || unsupported.length > 0) {
1642
+ throw new ApplicationError(`Unsupported compiled route ${route.method} ${controller.path}${route.path}` + (unsupported.length > 0 ? `: ${unsupported.join(", ")}` : ""), { code: "ROUTE_DESCRIPTOR_UNSUPPORTED" });
1643
+ }
1644
+ }
1645
+ }
1594
1646
  const hasCommandRoutes = compiled.controllers.some((controller) => controller.routes.some((route) => route.command !== undefined));
1595
1647
  if (hasCommandRoutes && !options.commandGovernance && !options.commandExecutor) {
1596
1648
  throw new ApplicationError(`Module "${compiled.name}" has command routes but no commandGovernance`, { code: "COMMAND_GOVERNANCE_UNCONFIGURED" });
@@ -1810,9 +1862,10 @@ async function executeJob(compiled, services, job, input, requestContext, import
1810
1862
  }
1811
1863
  }
1812
1864
  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)
1865
+ const protocolCode = commandErrorCode(error);
1866
+ if (protocolCode) {
1867
+ return Response.json({ ok: false, code: protocolCode, message: protocolCode }, {
1868
+ status: commandErrorStatus(protocolCode)
1816
1869
  });
1817
1870
  }
1818
1871
  if (isPublicApplicationError(error)) {
@@ -1823,6 +1876,13 @@ function defaultErrorResponse(error, frameworkCode) {
1823
1876
  ...error.details === undefined ? {} : { details: error.details }
1824
1877
  }, { status: error.status });
1825
1878
  }
1879
+ if (frameworkCode === "PARSE") {
1880
+ return Response.json({
1881
+ ok: false,
1882
+ code: "PARSE_ERROR",
1883
+ message: "Request body could not be parsed"
1884
+ }, { status: 400 });
1885
+ }
1826
1886
  if (frameworkCode === "VALIDATION") {
1827
1887
  if (isRecord(error) && error.type === "response") {
1828
1888
  return Response.json({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/elysia",
3
- "version": "0.14.1",
3
+ "version": "0.16.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
  },
@@ -54,7 +58,7 @@
54
58
  "elysia": "^1.4.30"
55
59
  },
56
60
  "devDependencies": {
57
- "@supacloud/compiler": "0.19.1",
61
+ "@supacloud/compiler": "0.21.0",
58
62
  "@supacloud/commands": "0.4.0",
59
63
  "@supacloud/db": "0.7.1",
60
64
  "@types/bun": "^1.4.2",