@supacloud/elysia 0.7.0 → 0.8.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
@@ -116,6 +116,15 @@ once is rejected.
116
116
  Executes a compiler-emitted Job descriptor with its static aspect list and
117
117
  compiler-generated job scope.
118
118
 
119
+ ### `assertFeatureTransition(spec, state, event)`
120
+
121
+ Checks a declared feature transition against an authoritative state and returns
122
+ the destination state. Unknown events, stale/illegal states and inherited object
123
+ members fail with HTTP 409 / `FEATURE_TRANSITION_CONFLICT`; malformed destinations
124
+ fail with `FEATURE_SPEC_INVALID`. The helper is a matrix assertion, not a workflow
125
+ engine, persistence or authorization layer. Call it inside the application's
126
+ transaction and persist with a row lock or expected-version check.
127
+
119
128
  ### `ApplicationError`
120
129
 
121
130
  Lightweight error class carrying HTTP `status`, machine-readable `code`, and
@@ -134,3 +143,10 @@ adapters. The memory harness is limited to deterministic HTTP, key-value
134
143
  transaction and object-storage contract tests.
135
144
  `policy` supplies explicit permission grants/revocations and idempotency claims;
136
145
  `storage.failNext()` makes storage failure paths deterministic.
146
+
147
+ Set `memoryGovernance: true` to enable test-only authorization, transaction,
148
+ idempotency and audit adapters; permissions still require explicit grants.
149
+ HTTP receipt fingerprints include route, body, params, query and business
150
+ headers, not mutable request scopes/services or identity/tracing transport.
151
+ Authorization runs again on a replay. Use durable application-owned adapters
152
+ for production receipts, transactions and audits.
@@ -0,0 +1,15 @@
1
+ /** Structural contract: the runtime does not import the compiler or discover metadata. */
2
+ export interface FeatureTransitionSpec {
3
+ name: string;
4
+ states: readonly string[];
5
+ transitions: Readonly<Record<string, {
6
+ from: string;
7
+ to: string;
8
+ }>>;
9
+ }
10
+ /**
11
+ * Check a declared transition against an authoritative state read by the caller.
12
+ * This is a matrix assertion, not persistence, authorization, or an FSM engine.
13
+ * Call inside the application's transaction and persist with a version check.
14
+ */
15
+ export declare function assertFeatureTransition<Spec extends FeatureTransitionSpec>(spec: Spec, state: string, event: string): Spec["states"][number];
package/dist/index.d.ts CHANGED
@@ -90,6 +90,8 @@ export interface CommandInvocation {
90
90
  services: Record<string, unknown>;
91
91
  }
92
92
  export type CommandExecutor = (invocation: CommandInvocation, next: () => unknown | Promise<unknown>) => unknown | Promise<unknown>;
93
+ export { assertFeatureTransition } from "./feature";
94
+ export type { FeatureTransitionSpec } from "./feature";
93
95
  export interface ApplicationAspectContext {
94
96
  kind: "route" | "command" | "job";
95
97
  name: string;
package/dist/index.js CHANGED
@@ -1,6 +1,22 @@
1
1
  // src/index.ts
2
2
  import { Elysia } from "elysia";
3
3
 
4
+ // src/feature.ts
5
+ function assertFeatureTransition(spec, state, event) {
6
+ const transition = Object.hasOwn(spec.transitions, event) ? spec.transitions[event] : undefined;
7
+ if (!transition || !spec.states.includes(state) || transition.from !== state) {
8
+ throw new ApplicationError("Feature transition is not allowed", {
9
+ status: 409,
10
+ code: "FEATURE_TRANSITION_CONFLICT"
11
+ });
12
+ }
13
+ if (!spec.states.includes(transition.to)) {
14
+ throw new ApplicationError("Feature transition references an undeclared state", {
15
+ code: "FEATURE_SPEC_INVALID"
16
+ });
17
+ }
18
+ return transition.to;
19
+ }
4
20
  // src/memory.ts
5
21
  import { AsyncLocalStorage } from "node:async_hooks";
6
22
 
@@ -240,7 +256,7 @@ function createMemorySandbox(options = {}) {
240
256
  }
241
257
  },
242
258
  idempotency(invocation, next) {
243
- return policy.runOnce(policy.subject(invocation.requestContext), invocation.command.name, requireIdempotencyKey(invocation), invocation.input, next);
259
+ return policy.runOnce(policy.subject(invocation.requestContext), invocation.command.name, requireIdempotencyKey(invocation), memoryCommandInput(invocation), next);
244
260
  },
245
261
  transaction(_invocation, next) {
246
262
  const count = audit.length;
@@ -297,6 +313,21 @@ function createMemorySandbox(options = {}) {
297
313
  }
298
314
  };
299
315
  }
316
+ function memoryCommandInput(invocation) {
317
+ const input = invocation.input;
318
+ if (typeof input !== "object" || input === null)
319
+ return input;
320
+ const ignoredHeaders = new Set(["authorization", "idempotency-key", "x-request-id", "traceparent", "tracestate"]);
321
+ const headers = Object.fromEntries([...invocation.request.headers.entries()].filter(([name]) => !ignoredHeaders.has(name)));
322
+ return {
323
+ method: invocation.request.method,
324
+ path: new URL(invocation.request.url).pathname,
325
+ body: "body" in input ? input.body : undefined,
326
+ params: "params" in input ? input.params : undefined,
327
+ query: "query" in input ? input.query : undefined,
328
+ headers
329
+ };
330
+ }
300
331
  function storageKey(bucket, key) {
301
332
  return JSON.stringify([bucket, key]);
302
333
  }
@@ -756,6 +787,7 @@ export {
756
787
  EXECUTION_ID_HEADER,
757
788
  IDEMPOTENCY_KEY_HEADER,
758
789
  VERIFIED_JWT_SUBJECT_HEADER,
790
+ assertFeatureTransition,
759
791
  composeAspects,
760
792
  composeCommandExecutors,
761
793
  createApplication,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/elysia",
3
- "version": "0.7.0",
3
+ "version": "0.8.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",