@supacloud/elysia 0.8.1 → 0.9.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
@@ -91,8 +91,9 @@ write retry. Confirm the outcome using the application's durable receipt or
91
91
  read-back protocol. A custom `errorMapper` can override this public envelope.
92
92
 
93
93
  Native `Response` objects are passed through by Elysia, including JSON responses.
94
- Handlers returning a native `Response` must validate their JSON payload before
95
- constructing it. The adapter does not consume or parse binary/streaming responses.
94
+ Use `validatedJsonResponse` to opt into validation when constructing a native
95
+ JSON response. Otherwise handlers must validate their JSON payload themselves.
96
+ The adapter does not consume or parse binary/streaming responses.
96
97
 
97
98
  Jobs are executed explicitly with `executeJob(compiledModule, services, job,
98
99
  input, requestContext)`. The asynchronous compiler-generated job scope is
@@ -101,6 +102,39 @@ fails partway through.
101
102
 
102
103
  ## API
103
104
 
105
+ ### `validatedJsonResponse(validate, value, init?): Response`
106
+
107
+ Constructs a native JSON response after a synchronous, caller-owned type guard
108
+ validates the actual serialized JSON snapshot. The value type is inferred from
109
+ the guard; compatible extra fields are preserved. For a TypeBox contract, the
110
+ guard can delegate to `Value.Check(schema, value)` or a compiled validator.
111
+ No additional schema dependency is required by the adapter.
112
+
113
+ ```ts
114
+ import { validatedJsonResponse } from "@supacloud/elysia";
115
+ import { isReportReceipt } from "./contracts";
116
+
117
+ return validatedJsonResponse(isReportReceipt, receipt, {
118
+ status: 201,
119
+ headers: { "x-request-id": requestId },
120
+ });
121
+ ```
122
+
123
+ The helper serializes once, validates that wire snapshot, and sends those same
124
+ bytes. Validation cannot mutate the outgoing body; it is not a transform or
125
+ coercion hook. Guards must be synchronous and side-effect free. Serialization
126
+ failures and invalid receipts throw a sanitized `ApplicationError` with HTTP 500
127
+ and `RESPONSE_VALIDATION_ERROR`, without retaining payloads or validator causes.
128
+ The application's `errorMapper` can map this to its outcome-confirmation
129
+ protocol. The helper never retries a command or implies rollback.
130
+
131
+ `init` uses native `Response` options. The helper explicitly rejects null-body
132
+ statuses 204, 205 and 304, including on runtimes that otherwise accept a body
133
+ with those statuses. The default content type is
134
+ `application/json`, and explicitly supplied headers are preserved. This helper
135
+ is for bounded JSON payloads, not files or streams. Existing native `Response`
136
+ passthrough is unchanged.
137
+
104
138
  ### `createApplication(options: ApplicationOptions): Elysia`
105
139
 
106
140
  Creates the root Elysia application from compiled modules.
package/dist/index.d.ts CHANGED
@@ -144,6 +144,13 @@ export declare class ApplicationError extends Error implements PublicApplication
144
144
  readonly details?: unknown;
145
145
  constructor(message: string, options?: ApplicationErrorOptions);
146
146
  }
147
+ export type JsonResponseValidator<T> = (value: unknown) => value is T;
148
+ /**
149
+ * Validate the serialized JSON snapshot before creating a native Response.
150
+ * Validators must be synchronous and side-effect free; response failures do not
151
+ * imply that application writes were rolled back.
152
+ */
153
+ export declare function validatedJsonResponse<T>(validate: JsonResponseValidator<T>, value: NoInfer<T>, init?: ResponseInit): Response;
147
154
  export interface ErrorContext {
148
155
  request: Request;
149
156
  requestContext: unknown;
package/dist/index.js CHANGED
@@ -405,6 +405,28 @@ class ApplicationError extends Error {
405
405
  this.details = options.details;
406
406
  }
407
407
  }
408
+ function validatedJsonResponse(validate, value, init) {
409
+ if (init?.status === 204 || init?.status === 205 || init?.status === 304) {
410
+ throw new TypeError("JSON responses cannot use a null-body status");
411
+ }
412
+ let body;
413
+ try {
414
+ const serialized = JSON.stringify(value);
415
+ if (serialized === undefined || validate(JSON.parse(serialized)) !== true) {
416
+ throw new Error("Invalid JSON response");
417
+ }
418
+ body = serialized;
419
+ } catch {
420
+ throw new ApplicationError("Response validation failed", {
421
+ status: 500,
422
+ code: "RESPONSE_VALIDATION_ERROR"
423
+ });
424
+ }
425
+ const headers = new Headers(init?.headers);
426
+ if (!headers.has("content-type"))
427
+ headers.set("content-type", "application/json");
428
+ return new Response(body, { ...init, headers });
429
+ }
408
430
  function safeHeaderValue(value, maxLength) {
409
431
  if (!value || value.length > maxLength || /[\u0000-\u001f\u007f]/.test(value)) {
410
432
  return;
@@ -807,5 +829,6 @@ export {
807
829
  defaultErrorResponse,
808
830
  executeJob,
809
831
  requireIdempotencyKey,
810
- requireTrustedIdentity
832
+ requireTrustedIdentity,
833
+ validatedJsonResponse
811
834
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/elysia",
3
- "version": "0.8.1",
3
+ "version": "0.9.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",