@supacloud/elysia 0.10.0 → 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,117 @@
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
+
92
+ ## Persistent Command Adapters
93
+
94
+ `createPersistentCommandAdapter(command, { identity, input })` binds a
95
+ `createTransactionalCommand` or `createExternalCommand` from `@supacloud/commands`
96
+ to `commandGovernance.rpc`.
97
+ Its capabilities distinguish `database` from `external` boundaries. It owns the
98
+ single write entry point and never invokes a second route handler or audit.
99
+ Resolve identity from a verified host context; all durable receipt reads and replays
100
+ must still pass domain authorization.
101
+
102
+ `createApplication({ normalize: false, ... })` rejects extra schema properties rather
103
+ than silently stripping them. Use shared schemas at domain and HTTP boundaries.
104
+
105
+ Alternatively, a meaningful Controller can call its injected Command directly,
106
+ with no route-level `command:` binding. `src/fixtures/webhook` follows this pattern.
107
+ `bun run generate:example` generates its factories; `src/webhook-migration-example.ts`
108
+ loads those artifacts for native HTTP/PostgreSQL acceptance. Tests reject stale
109
+ artifacts and cover writes, authorization, audit rollback and receipt recovery.
110
+ The runtime maps protocol errors without importing DB: explicit denial is 403,
111
+ authorization infrastructure failure is 503, and redacted-input lookup is 410.
112
+ See [the migration plan](../../docs/command-migration.md) for compiler policy,
113
+ authentication replay changes, deployment order and rollback limitations.
114
+
3
115
  Runtime adapter that turns `@supacloud/compiler` output into a production-ready
4
116
  [Elysia](https://elysiajs.com/) application.
5
117
 
@@ -13,8 +125,15 @@ Runtime adapter that turns `@supacloud/compiler` output into a production-ready
13
125
  controllers and services.
14
126
  - **Request-scope teardown**: invokes the compiler-generated
15
127
  `destroyRequestScope` after the response, including when the handler fails.
16
- - **TypeBox schema binding**: attaches compiled parameter, query, body, and
17
- response TypeBox schemas directly to Elysia route definitions.
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.
131
+ - **TypeBox schema binding**: attaches compiled parameter, query, body, headers,
132
+ cookie, single-response and status-map TypeBox schemas directly to Elysia
133
+ route definitions; Elysia performs request validation and normalization.
134
+ - **Schema-first client decoding**: generated clients select the declared
135
+ response schema by HTTP status and validate/normalize it before returning;
136
+ an explicit decoder receives that checked value for custom transforms.
18
137
  - **Compiler invoker execution**: uses the compiler-emitted positional invoker
19
138
  after Elysia has decoded route input, while retaining the legacy input-object
20
139
  handler path for hand-written compiled fixtures.
@@ -24,10 +143,15 @@ Runtime adapter that turns `@supacloud/compiler` output into a production-ready
24
143
  - **Static AOP pipeline**: executes compiler-emitted module, route, command, and
25
144
  job aspects with `composeAspects`; no runtime discovery or registration is
26
145
  performed.
146
+ - **Worker registration**: registers compiler-emitted Jobs, initializes their
147
+ application services once, and provides polling plus graceful shutdown around
148
+ a host-owned claim/receipt transport.
27
149
  - **Public error mapping**: transforms framework / application errors via
28
150
  `errorMapper` with standard `ApplicationError` envelope support, preserving
29
151
  HTTP 422 for request validation and HTTP 500 / `RESPONSE_VALIDATION_ERROR`
30
152
  for invalid handler output, without exposing payloads or schema internals.
153
+ - **Opt-in API documentation**: serves a generated OpenAPI JSON document and a
154
+ dependency-free viewer, plus a role-scoped GraphQL SDL snapshot viewer.
31
155
 
32
156
  ## Installation
33
157
 
@@ -41,11 +165,24 @@ bun add @supacloud/elysia elysia
41
165
  import { composeCommandExecutors, createApplication, requireIdempotencyKey } from "@supacloud/elysia";
42
166
  import AuditModule from "./.generated/audit.module";
43
167
  import CaseModule from "./.generated/case.module";
168
+ import { OPENAPI_DOCUMENT } from "./generated/openapi";
44
169
 
45
170
  const app = createApplication({
46
171
  name: "case-service",
47
172
  modules: [AuditModule, CaseModule], // topological import order
48
173
  deps: { db: createDbClient() }, // platform deps, passed to createServices
174
+ documentation: {
175
+ openApi: {
176
+ document: OPENAPI_DOCUMENT,
177
+ specPath: "/openapi.json",
178
+ uiPath: "/docs",
179
+ },
180
+ graphql: {
181
+ schema: () => Bun.file("./graphql/schema.graphql").text(),
182
+ schemaPath: "/graphql/schema.graphql",
183
+ uiPath: "/graphql/docs",
184
+ },
185
+ },
49
186
  commandGovernance: {
50
187
  authorize: (invocation) => authorize(invocation.requestContext, invocation.command.permission),
51
188
  idempotency: (invocation, next) => idempotencyStore.run(requireIdempotencyKey(invocation), next),
@@ -62,6 +199,17 @@ const app = createApplication({
62
199
  export default app;
63
200
  ```
64
201
 
202
+ Documentation is disabled unless `documentation` is provided. The OpenAPI
203
+ document can be imported from the compiler-generated `openapi.ts` module. The
204
+ GraphQL endpoint serves a local, role-scoped snapshot only; it does not enable
205
+ server introspection or create a GraphQL resolver layer. Protect or omit these
206
+ routes in production when the schema is not public.
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.
212
+
65
213
  For deterministic local verification, use the in-memory sandbox. It supplies
66
214
  stable request identity, an isolated key-value database with optimistic
67
215
  transaction rollback, and an in-memory object store without requiring
@@ -100,6 +248,72 @@ input, requestContext)`. The asynchronous compiler-generated job scope is
100
248
  destroyed after execution, including when the job throws or scope construction
101
249
  fails partway through.
102
250
 
251
+ ### Worker Registration
252
+
253
+ `createWorker` registers compiled modules and drives a host-provided claim,
254
+ acknowledge and fail transport. The worker owns Job lookup, application-service
255
+ initialization, concurrency and graceful shutdown. The platform adapter remains
256
+ the owner of leases, retries, DLQ policy and receipt semantics; its receipt type
257
+ is preserved as `TReceipt`. `createQueueWorkerTransport` adapts the structural
258
+ API of the existing `client.queue(name)` without making Elysia depend on the SDK.
259
+
260
+ ```ts
261
+ import {
262
+ createQueueWorkerTransport,
263
+ createWorker,
264
+ type WorkerClaim,
265
+ } from "@supacloud/elysia";
266
+ import type { SupaCloudQueueMutationResult } from "@supacloud/js";
267
+ import { createCompiledModules } from "./generated/application";
268
+
269
+ type QueueClaim = WorkerClaim & { queueMessageId: string };
270
+ type PlatformReceipt = SupaCloudQueueMutationResult;
271
+
272
+ // `supacloud` is a configured createSupaCloudClient(...) instance.
273
+
274
+ const transport = createQueueWorkerTransport({
275
+ queue: supacloud.queue("jobs"),
276
+ receive: { visibilityTimeoutSec: 60 },
277
+ decodeClaim: (message): QueueClaim => {
278
+ const payload = message.payload;
279
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
280
+ throw new Error("Invalid job envelope");
281
+ }
282
+ const envelope = payload as Record<string, unknown>;
283
+ if (typeof envelope.jobName !== "string" || !("input" in envelope)) {
284
+ throw new Error("Invalid job envelope");
285
+ }
286
+ return {
287
+ id: message.id,
288
+ queueMessageId: message.id,
289
+ jobName: envelope.jobName,
290
+ input: envelope.input,
291
+ attempt: message.read_ct ?? 1,
292
+ };
293
+ },
294
+ messageId: (claim) => claim.queueMessageId,
295
+ });
296
+
297
+ const worker = createWorker<QueueClaim, PlatformReceipt>({
298
+ modules: createCompiledModules(),
299
+ deps: { supacloud },
300
+ concurrency: 4,
301
+ transport,
302
+ });
303
+
304
+ await worker.start();
305
+ // The host owns process signals and calls this during shutdown.
306
+ await worker.stop();
307
+ ```
308
+
309
+ Use `mapClaim` when a platform claim has a different wire shape. Duplicate module
310
+ or Job names are rejected before registration is committed. A Job failure is
311
+ reported through `fail`; an unconfirmed `ack` is surfaced as
312
+ `WorkerReceiptUnconfirmedError` and is never followed by a blind `fail`. The
313
+ queue adapter preserves the queue client's mutation receipt type. With PGMQ,
314
+ the SDK's `fail` compatibility method archives the message; use a custom
315
+ transport when the platform needs a distinct retry or dead-letter transition.
316
+
103
317
  ## API
104
318
 
105
319
  ### External SupAuth Identity
@@ -252,3 +466,62 @@ HTTP receipt fingerprints include route, body, params, query and business
252
466
  headers, not mutable request scopes/services or identity/tracing transport.
253
467
  Authorization runs again on a replay. Use durable application-owned adapters
254
468
  for production receipts, transactions and audits.
469
+
470
+ ### Shared Schema Decoders
471
+
472
+ `createSchemaDecoder(schema)` derives a decoder's output type from a TypeBox
473
+ schema, including transforms. Invalid values throw a sanitized
474
+ `SchemaContractError`. `defineJsonContract({ body, response }, request)` creates
475
+ decoders compatible with `HttpClient.execute` while retaining the same schemas
476
+ for route registration. Keep schemas independently importable and reference
477
+ their identifiers explicitly in compiler-analyzed route decorators.
478
+
479
+ For hand-written Elysia routes, `defineRouteContract` and
480
+ `defineElysiaRoute` provide contextual handler types from the same schema value.
481
+ `registerElysiaRoute` maps the contract's `responses` status map to Elysia's
482
+ `response` option and registers the route:
483
+
484
+ ```ts
485
+ import { Elysia, t } from "elysia";
486
+ import {
487
+ defineElysiaRoute,
488
+ defineRouteContract,
489
+ registerElysiaRoute,
490
+ } from "@supacloud/elysia";
491
+
492
+ const itemRoute = defineRouteContract({
493
+ body: t.Object({ name: t.String() }),
494
+ params: t.Object({ id: t.String() }),
495
+ responses: {
496
+ 200: t.Object({ id: t.String(), name: t.String() }),
497
+ 409: t.Object({ conflict: t.Literal(true) }),
498
+ },
499
+ });
500
+
501
+ const route = defineElysiaRoute("POST", "/items/:id", itemRoute, ({ body, params, status }) =>
502
+ body.name === "existing"
503
+ ? status(409, { conflict: true })
504
+ : { id: params.id, name: body.name },
505
+ );
506
+
507
+ const app = registerElysiaRoute(new Elysia(), route);
508
+ ```
509
+
510
+ The callback is typed from the contract (including decoded transforms and
511
+ declared response statuses). Cookie values retain Elysia's native shape, so a
512
+ declared `session: t.String()` is read as `cookie.session.value`. This helper
513
+ does not add Eden-style client inference to an existing Elysia instance; the
514
+ compiler-generated client remains the source of transport types.
515
+
516
+ Response maps may use concrete statuses, `1XX`-`5XX` families, and `default`.
517
+ Because Elysia 1.4 only compiles numeric response keys, the adapter expands
518
+ family/default entries to concrete validators before registration. Exact
519
+ statuses take precedence over families, which take precedence over `default`.
520
+ An actual status absent from a structured response map fails the route contract
521
+ before Elysia can silently accept a default `200`; binary/stream routes may
522
+ intentionally leave successful transport statuses unschematized when only their
523
+ JSON error responses are declared. Unsupported selectors fail during
524
+ registration instead of silently disabling response validation.
525
+
526
+ See [type safety and migration](../../docs/type-safety.md) and [command migration](../../docs/command-migration.md) for examples and
527
+ the distinction between contract declarations and runtime verification.
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import type { CommandErrorCode } from "@supacloud/contracts";
2
+ export declare function commandErrorCode(error: unknown): CommandErrorCode | undefined;
3
+ export declare function commandErrorStatus(code: CommandErrorCode): number;
@@ -0,0 +1,29 @@
1
+ import { Elysia } from "elysia";
2
+ export type DocumentationSource<T> = T | (() => T | Promise<T>);
3
+ export interface OpenApiDocumentationOptions {
4
+ /** Generated OpenAPI document, usually imported from generated/openapi.ts. */
5
+ document: DocumentationSource<unknown>;
6
+ /** JSON specification endpoint. */
7
+ specPath?: string;
8
+ /** Human-readable documentation page. */
9
+ uiPath?: string;
10
+ title?: string;
11
+ }
12
+ export interface GraphqlDocumentationOptions {
13
+ /** Role-scoped SDL snapshot, usually loaded from graphql/schema.graphql. */
14
+ schema: DocumentationSource<string>;
15
+ /** SDL endpoint. */
16
+ schemaPath?: string;
17
+ /** Human-readable schema page. */
18
+ uiPath?: string;
19
+ title?: string;
20
+ }
21
+ export interface ApplicationDocumentationOptions {
22
+ openApi?: OpenApiDocumentationOptions;
23
+ graphql?: GraphqlDocumentationOptions;
24
+ }
25
+ /**
26
+ * Mount opt-in, read-only documentation endpoints for a compiled application.
27
+ * The plugin has no external UI dependency and does not enable GraphQL introspection.
28
+ */
29
+ export declare function createDocumentationPlugin(options?: ApplicationDocumentationOptions): Elysia;
@@ -10,3 +10,4 @@ export interface ExecutionEvent {
10
10
  export type ExecutionObserver = (event: Readonly<ExecutionEvent>) => void | Promise<void>;
11
11
  export declare function observeExecution<T>(observer: ExecutionObserver | undefined, event: Pick<ExecutionEvent, "kind" | "operation" | "stage" | "requestId">, next: () => T | Promise<T>): Promise<T>;
12
12
  export declare function executionRequestId(context: unknown): string | undefined;
13
+ export declare function executionTrace(context: unknown): Pick<ExecutionEvent, "requestId">;
@@ -0,0 +1,42 @@
1
+ export declare const WebhookInputSchema: import("@sinclair/typebox").TObject<{
2
+ id: import("@sinclair/typebox").TString;
3
+ enabled: import("@sinclair/typebox").TBoolean;
4
+ }>;
5
+ export declare const WebhookHeadersSchema: import("@sinclair/typebox").TObject<{
6
+ "idempotency-key": import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
7
+ }>;
8
+ export declare const decodeWebhookInput: (value: unknown) => {
9
+ id: string;
10
+ enabled: boolean;
11
+ };
12
+ export declare const WebhookReceiptSchema: import("@sinclair/typebox").TObject<{
13
+ tenantId: import("@sinclair/typebox").TString;
14
+ actorId: import("@sinclair/typebox").TString;
15
+ command: import("@sinclair/typebox").TLiteral<"webhook.update.v1">;
16
+ operationId: import("@sinclair/typebox").TString;
17
+ dispatchKey: import("@sinclair/typebox").TString;
18
+ status: import("@sinclair/typebox").TLiteral<"confirmed">;
19
+ audit: import("@sinclair/typebox").TLiteral<"complete">;
20
+ result: import("@sinclair/typebox").TObject<{
21
+ id: import("@sinclair/typebox").TString;
22
+ enabled: import("@sinclair/typebox").TBoolean;
23
+ }>;
24
+ }>;
25
+ export declare const WebhookKeySchema: import("@sinclair/typebox").TObject<{
26
+ key: import("@sinclair/typebox").TString;
27
+ }>;
28
+ export declare const WebhookLookupSchema: import("@sinclair/typebox").TObject<{
29
+ receipt: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
30
+ tenantId: import("@sinclair/typebox").TString;
31
+ actorId: import("@sinclair/typebox").TString;
32
+ command: import("@sinclair/typebox").TLiteral<"webhook.update.v1">;
33
+ operationId: import("@sinclair/typebox").TString;
34
+ dispatchKey: import("@sinclair/typebox").TString;
35
+ status: import("@sinclair/typebox").TLiteral<"confirmed">;
36
+ audit: import("@sinclair/typebox").TLiteral<"complete">;
37
+ result: import("@sinclair/typebox").TObject<{
38
+ id: import("@sinclair/typebox").TString;
39
+ enabled: import("@sinclair/typebox").TBoolean;
40
+ }>;
41
+ }>, import("@sinclair/typebox").TNull]>;
42
+ }>;
@@ -0,0 +1,11 @@
1
+ import { InjectionToken } from "@supacloud/app";
2
+ import type { CommandAuthorization, CommandIdentity } from "@supacloud/contracts";
3
+ import type { CommandStore } from "@supacloud/commands";
4
+ import type { CommandTransaction } from "@supacloud/db";
5
+ export declare class WebhookEnvironment {
6
+ readonly store: CommandStore<CommandTransaction>;
7
+ readonly authorize: (identity: CommandIdentity, tx: CommandTransaction) => Promise<CommandAuthorization>;
8
+ readonly writeAudit: (tx: CommandTransaction) => Promise<void>;
9
+ constructor(store: CommandStore<CommandTransaction>, authorize: (identity: CommandIdentity, tx: CommandTransaction) => Promise<CommandAuthorization>, writeAudit: (tx: CommandTransaction) => Promise<void>);
10
+ }
11
+ export declare const WEBHOOK_ENVIRONMENT: InjectionToken<WebhookEnvironment>;
@@ -0,0 +1,13 @@
1
+ import { type CommandIdentity } from "@supacloud/contracts";
2
+ export declare class UpdateWebhook {
3
+ private readonly executor;
4
+ constructor(environment: unknown);
5
+ execute(identity: CommandIdentity, key: string, input: unknown): Promise<import("@supacloud/contracts").DurableCommandReceipt<{
6
+ id: string;
7
+ enabled: boolean;
8
+ }>>;
9
+ receipt(identity: CommandIdentity, key: string): Promise<import("@supacloud/contracts").DurableCommandReceipt<{
10
+ id: string;
11
+ enabled: boolean;
12
+ }> | null>;
13
+ }
@@ -0,0 +1,15 @@
1
+ export declare class WebhookController {
2
+ private readonly context;
3
+ private readonly command;
4
+ constructor(command: unknown, context: unknown);
5
+ update(input: unknown, key: unknown): Promise<import("@supacloud/contracts").DurableCommandReceipt<{
6
+ id: string;
7
+ enabled: boolean;
8
+ }>>;
9
+ receipt(key: unknown): Promise<{
10
+ receipt: import("@supacloud/contracts").DurableCommandReceipt<{
11
+ id: string;
12
+ enabled: boolean;
13
+ }> | null;
14
+ }>;
15
+ }
@@ -0,0 +1,2 @@
1
+ export declare class WebhookModule {
2
+ }
@@ -0,0 +1,100 @@
1
+ export interface CompiledRoute {
2
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
3
+ path: string;
4
+ handler: string;
5
+ body?: unknown;
6
+ params?: unknown;
7
+ query?: unknown;
8
+ headers?: unknown;
9
+ cookie?: unknown;
10
+ response?: unknown;
11
+ responses?: Record<string | number, unknown>;
12
+ /** Compile-time ownership and transport classification for the route boundary. */
13
+ contract?: {
14
+ body?: "framework" | "domain";
15
+ response?: "framework" | "native-json" | "binary" | "stream";
16
+ evidence?: string;
17
+ };
18
+ /** Whether each declared schema is concrete or intentionally opaque. */
19
+ schemaKinds?: Partial<Record<"body" | "params" | "query" | "headers" | "cookie" | "response", "opaque" | "declared">>;
20
+ /** True when the handler returns a native Response rather than a framework value. */
21
+ nativeResponse?: boolean;
22
+ command?: string;
23
+ guards?: string[];
24
+ canMatch?: string[];
25
+ canDeactivate?: string[];
26
+ resolvers?: Record<string, string>;
27
+ redirectTo?: string;
28
+ pathMatch?: "full" | "prefix";
29
+ paramTransforms?: Record<string, "number" | "boolean" | "string">;
30
+ paramDefaults?: Record<string, unknown>;
31
+ queryTransforms?: Record<string, "number" | "boolean" | "string">;
32
+ queryDefaults?: Record<string, unknown>;
33
+ title?: string;
34
+ data?: Record<string, unknown>;
35
+ aspects?: CompiledAspect[];
36
+ invoker?: (controller: unknown, request: {
37
+ params?: Record<string, unknown>;
38
+ query?: Record<string, unknown>;
39
+ body?: unknown;
40
+ headers?: Record<string, unknown>;
41
+ cookie?: Record<string, unknown>;
42
+ context?: unknown;
43
+ }) => Promise<unknown> | unknown;
44
+ }
45
+ export interface CompiledCommand {
46
+ rpc?: string;
47
+ className: string;
48
+ name: string;
49
+ permission: string;
50
+ transaction: "required" | "none";
51
+ audit?: string;
52
+ idempotency: "required" | "none";
53
+ standalone?: boolean;
54
+ aspects?: CompiledAspect[];
55
+ }
56
+ export interface CompiledJob {
57
+ className: string;
58
+ name: string;
59
+ serviceKey: string;
60
+ scope: "application" | "request" | "job";
61
+ input?: unknown;
62
+ output?: unknown;
63
+ mode?: "task" | "workflow";
64
+ timeoutSec?: number;
65
+ maxAttempts?: number;
66
+ idempotency?: "required" | "none";
67
+ aspects?: CompiledAspect[];
68
+ }
69
+ export interface CompiledAspectContext {
70
+ kind: "route" | "command" | "job";
71
+ name: string;
72
+ input: unknown;
73
+ request?: Request;
74
+ requestContext?: unknown;
75
+ scope?: Record<string, unknown>;
76
+ services?: Record<string, unknown>;
77
+ metadata?: unknown;
78
+ }
79
+ export type CompiledAspect = (context: CompiledAspectContext, next: () => unknown | Promise<unknown>) => unknown | Promise<unknown>;
80
+ export interface CompiledController {
81
+ path: string;
82
+ serviceKey: string;
83
+ scope: "application" | "request" | "job";
84
+ routes: CompiledRoute[];
85
+ }
86
+ export interface CompiledModule {
87
+ name: string;
88
+ createServices(deps: Record<string, unknown>, imported: Record<string, Record<string, unknown>>): Record<string, unknown>;
89
+ createRequestScope?(services: Record<string, unknown>, ctx: unknown, imported?: Record<string, Record<string, unknown>>): Promise<Record<string, unknown>>;
90
+ destroyRequestScope?(scope: Record<string, unknown>): Promise<void>;
91
+ createJobScope?(services: Record<string, unknown>, ctx: unknown, imported?: Record<string, Record<string, unknown>>): Promise<Record<string, unknown>>;
92
+ destroyJobScope?(scope: Record<string, unknown>): Promise<void>;
93
+ controllers: CompiledController[];
94
+ commands: CompiledCommand[];
95
+ jobs: CompiledJob[];
96
+ aspects?: CompiledAspect[];
97
+ }
98
+ export declare function createCompiledModules(): CompiledModule[];
99
+ export declare function initializeApplication(services: Record<string, unknown>): Promise<void>;
100
+ export declare function destroyApplication(services: Record<string, unknown>): Promise<void>;
@@ -0,0 +1,19 @@
1
+ export declare const webhookCompileOptions: {
2
+ rootDir: string;
3
+ outDir: string;
4
+ strict: true;
5
+ requireRouteContracts: true;
6
+ allowRouteCommandBindings: false;
7
+ commandCapabilities: {
8
+ requirePersistentAdapters: true;
9
+ permission: true;
10
+ rpc: {
11
+ webhookUpdate: {
12
+ boundary: "database";
13
+ audit: true;
14
+ transaction: true;
15
+ idempotency: true;
16
+ };
17
+ };
18
+ };
19
+ };