@supacloud/elysia 0.9.0 → 0.14.1

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,28 @@
1
1
  # @supacloud/elysia
2
2
 
3
+ ## Persistent Command Adapters
4
+
5
+ `createPersistentCommandAdapter(command, { identity, input })` binds a
6
+ `createTransactionalCommand` or `createExternalCommand` from `@supacloud/commands`
7
+ to `commandGovernance.rpc`.
8
+ Its capabilities distinguish `database` from `external` boundaries. It owns the
9
+ single write entry point and never invokes a second route handler or audit.
10
+ Resolve identity from a verified host context; all durable receipt reads and replays
11
+ must still pass domain authorization.
12
+
13
+ `createApplication({ normalize: false, ... })` rejects extra schema properties rather
14
+ than silently stripping them. Use shared schemas at domain and HTTP boundaries.
15
+
16
+ Alternatively, a meaningful Controller can call its injected Command directly,
17
+ with no route-level `command:` binding. `src/fixtures/webhook` follows this pattern.
18
+ `bun run generate:example` generates its factories; `src/webhook-migration-example.ts`
19
+ loads those artifacts for native HTTP/PostgreSQL acceptance. Tests reject stale
20
+ artifacts and cover writes, authorization, audit rollback and receipt recovery.
21
+ The runtime maps protocol errors without importing DB: explicit denial is 403,
22
+ authorization infrastructure failure is 503, and redacted-input lookup is 410.
23
+ See [the migration plan](../../docs/command-migration.md) for compiler policy,
24
+ authentication replay changes, deployment order and rollback limitations.
25
+
3
26
  Runtime adapter that turns `@supacloud/compiler` output into a production-ready
4
27
  [Elysia](https://elysiajs.com/) application.
5
28
 
@@ -13,8 +36,15 @@ Runtime adapter that turns `@supacloud/compiler` output into a production-ready
13
36
  controllers and services.
14
37
  - **Request-scope teardown**: invokes the compiler-generated
15
38
  `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.
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.
42
+ - **TypeBox schema binding**: attaches compiled parameter, query, body, headers,
43
+ cookie, single-response and status-map TypeBox schemas directly to Elysia
44
+ route definitions; Elysia performs request validation and normalization.
45
+ - **Schema-first client decoding**: generated clients select the declared
46
+ response schema by HTTP status and validate/normalize it before returning;
47
+ an explicit decoder receives that checked value for custom transforms.
18
48
  - **Compiler invoker execution**: uses the compiler-emitted positional invoker
19
49
  after Elysia has decoded route input, while retaining the legacy input-object
20
50
  handler path for hand-written compiled fixtures.
@@ -24,10 +54,15 @@ Runtime adapter that turns `@supacloud/compiler` output into a production-ready
24
54
  - **Static AOP pipeline**: executes compiler-emitted module, route, command, and
25
55
  job aspects with `composeAspects`; no runtime discovery or registration is
26
56
  performed.
57
+ - **Worker registration**: registers compiler-emitted Jobs, initializes their
58
+ application services once, and provides polling plus graceful shutdown around
59
+ a host-owned claim/receipt transport.
27
60
  - **Public error mapping**: transforms framework / application errors via
28
61
  `errorMapper` with standard `ApplicationError` envelope support, preserving
29
62
  HTTP 422 for request validation and HTTP 500 / `RESPONSE_VALIDATION_ERROR`
30
63
  for invalid handler output, without exposing payloads or schema internals.
64
+ - **Opt-in API documentation**: serves a generated OpenAPI JSON document and a
65
+ dependency-free viewer, plus a role-scoped GraphQL SDL snapshot viewer.
31
66
 
32
67
  ## Installation
33
68
 
@@ -41,11 +76,24 @@ bun add @supacloud/elysia elysia
41
76
  import { composeCommandExecutors, createApplication, requireIdempotencyKey } from "@supacloud/elysia";
42
77
  import AuditModule from "./.generated/audit.module";
43
78
  import CaseModule from "./.generated/case.module";
79
+ import { OPENAPI_DOCUMENT } from "./generated/openapi";
44
80
 
45
81
  const app = createApplication({
46
82
  name: "case-service",
47
83
  modules: [AuditModule, CaseModule], // topological import order
48
84
  deps: { db: createDbClient() }, // platform deps, passed to createServices
85
+ documentation: {
86
+ openApi: {
87
+ document: OPENAPI_DOCUMENT,
88
+ specPath: "/openapi.json",
89
+ uiPath: "/docs",
90
+ },
91
+ graphql: {
92
+ schema: () => Bun.file("./graphql/schema.graphql").text(),
93
+ schemaPath: "/graphql/schema.graphql",
94
+ uiPath: "/graphql/docs",
95
+ },
96
+ },
49
97
  commandGovernance: {
50
98
  authorize: (invocation) => authorize(invocation.requestContext, invocation.command.permission),
51
99
  idempotency: (invocation, next) => idempotencyStore.run(requireIdempotencyKey(invocation), next),
@@ -62,6 +110,17 @@ const app = createApplication({
62
110
  export default app;
63
111
  ```
64
112
 
113
+ Documentation is disabled unless `documentation` is provided. The OpenAPI
114
+ document can be imported from the compiler-generated `openapi.ts` module. The
115
+ GraphQL endpoint serves a local, role-scoped snapshot only; it does not enable
116
+ server introspection or create a GraphQL resolver layer. Protect or omit these
117
+ routes in production when the schema is not public.
118
+
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.
123
+
65
124
  For deterministic local verification, use the in-memory sandbox. It supplies
66
125
  stable request identity, an isolated key-value database with optimistic
67
126
  transaction rollback, and an in-memory object store without requiring
@@ -100,8 +159,134 @@ input, requestContext)`. The asynchronous compiler-generated job scope is
100
159
  destroyed after execution, including when the job throws or scope construction
101
160
  fails partway through.
102
161
 
162
+ ### Worker Registration
163
+
164
+ `createWorker` registers compiled modules and drives a host-provided claim,
165
+ acknowledge and fail transport. The worker owns Job lookup, application-service
166
+ initialization, concurrency and graceful shutdown. The platform adapter remains
167
+ the owner of leases, retries, DLQ policy and receipt semantics; its receipt type
168
+ is preserved as `TReceipt`. `createQueueWorkerTransport` adapts the structural
169
+ API of the existing `client.queue(name)` without making Elysia depend on the SDK.
170
+
171
+ ```ts
172
+ import {
173
+ createQueueWorkerTransport,
174
+ createWorker,
175
+ type WorkerClaim,
176
+ } from "@supacloud/elysia";
177
+ import type { SupaCloudQueueMutationResult } from "@supacloud/js";
178
+ import { createCompiledModules } from "./generated/application";
179
+
180
+ type QueueClaim = WorkerClaim & { queueMessageId: string };
181
+ type PlatformReceipt = SupaCloudQueueMutationResult;
182
+
183
+ // `supacloud` is a configured createSupaCloudClient(...) instance.
184
+
185
+ const transport = createQueueWorkerTransport({
186
+ queue: supacloud.queue("jobs"),
187
+ receive: { visibilityTimeoutSec: 60 },
188
+ decodeClaim: (message): QueueClaim => {
189
+ const payload = message.payload;
190
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
191
+ throw new Error("Invalid job envelope");
192
+ }
193
+ const envelope = payload as Record<string, unknown>;
194
+ if (typeof envelope.jobName !== "string" || !("input" in envelope)) {
195
+ throw new Error("Invalid job envelope");
196
+ }
197
+ return {
198
+ id: message.id,
199
+ queueMessageId: message.id,
200
+ jobName: envelope.jobName,
201
+ input: envelope.input,
202
+ attempt: message.read_ct ?? 1,
203
+ };
204
+ },
205
+ messageId: (claim) => claim.queueMessageId,
206
+ });
207
+
208
+ const worker = createWorker<QueueClaim, PlatformReceipt>({
209
+ modules: createCompiledModules(),
210
+ deps: { supacloud },
211
+ concurrency: 4,
212
+ transport,
213
+ });
214
+
215
+ await worker.start();
216
+ // The host owns process signals and calls this during shutdown.
217
+ await worker.stop();
218
+ ```
219
+
220
+ Use `mapClaim` when a platform claim has a different wire shape. Duplicate module
221
+ or Job names are rejected before registration is committed. A Job failure is
222
+ reported through `fail`; an unconfirmed `ack` is surfaced as
223
+ `WorkerReceiptUnconfirmedError` and is never followed by a blind `fail`. The
224
+ queue adapter preserves the queue client's mutation receipt type. With PGMQ,
225
+ the SDK's `fail` compatibility method archives the message; use a custom
226
+ transport when the platform needs a distinct retry or dead-letter transition.
227
+
103
228
  ## API
104
229
 
230
+ ### External SupAuth Identity
231
+
232
+ `createSupAuthRequestContext(options)` supplies the trusted host adapter for an
233
+ external SupAuth user center. It uses `jose` signature verification, requires
234
+ configured HTTPS issuer/JWKS endpoints, audience, subject, expiry and issued-at,
235
+ and accepts only ES256/RS256. It does not implement login, sessions or token issuance.
236
+
237
+ ```ts
238
+ import { createSupAuthRequestContext } from "@supacloud/elysia";
239
+
240
+ const requestContext = createSupAuthRequestContext({
241
+ issuer: "https://identity.example/auth/v1",
242
+ audience: "authenticated",
243
+ clientId: "orders-oauth-client",
244
+ projectId: "orders",
245
+ jwksUrl: "https://identity.example/auth/v1/.well-known/jwks.json",
246
+ resolveAccess: async (identity) =>
247
+ accessRepository.findCurrentAccess(identity.issuer, identity.subject, "orders"),
248
+ });
249
+ ```
250
+
251
+ `accessRepository` is application-owned and must return `{ projectId, tenantId,
252
+ permissions }` or `null` from authoritative local data. The factory rejects
253
+ missing/wrong-project access, ignores forwarded subject/tenant headers and
254
+ returns a frozen identity/access snapshot. Commands must still authorize current
255
+ object relationships within their durable transaction; a permission snapshot is
256
+ not an RLS replacement. The bearer credential is non-enumerable on identity.
257
+ Never log the complete request/context.
258
+
259
+ The adapter protects all routes using that context factory, including health
260
+ routes; mount intentionally public routes separately. Invalid credentials and
261
+ invalid signing keys fail closed with sanitized 401 responses. Tokens must have
262
+ `role: "authenticated"` and a matching `client_id` or `azp`; when both exist they
263
+ must match each other and the configured `clientId`. Verification service failures
264
+ return sanitized 503 `AUTHENTICATION_UNAVAILABLE`, never an identity fallback. Remote
265
+ JWKS uses bounded fetch timeout and the library's key cache; no token-provided key
266
+ URL or local identity fallback is accepted. `keyResolver` is a trusted host
267
+ override for pinned key sets/testing, never request input.
268
+
269
+ ### Execution Inspection
270
+
271
+ Set `createApplication({ onExecution })` for metadata-only events: operation,
272
+ stage, kind, phase, elapsed time and a bounded request correlation ID. Module,
273
+ route and command aspects retain declared order. Standard governance exposes
274
+ authorization, idempotency, transaction, handler and successful audit stages.
275
+ Pass the final optional observer argument to `executeJob` for job traces.
276
+ No request input, token, result or error cause is sent to the observer.
277
+ The boundary and aspect index identify the static declaration; JavaScript
278
+ function names are display hints and may change when consumers minify a bundle.
279
+
280
+ Observer failures are isolated from business results; this is best-effort
281
+ telemetry, not durable audit. Use command governance for mandatory audit.
282
+ An inner successful stage does not prove the enclosing transaction committed.
283
+ The compiler's `context`/`explain` commands show the corresponding static plan.
284
+
285
+ Command transaction/idempotency continuations, custom route executors and job
286
+ handlers reject repeated invocation. This prevents accidental adapter retries
287
+ inside one invocation; cross-request/process deduplication still requires a
288
+ durable idempotency adapter.
289
+
105
290
  ### `validatedJsonResponse(validate, value, init?): Response`
106
291
 
107
292
  Constructs a native JSON response after a synchronous, caller-owned type guard
@@ -192,3 +377,62 @@ HTTP receipt fingerprints include route, body, params, query and business
192
377
  headers, not mutable request scopes/services or identity/tracing transport.
193
378
  Authorization runs again on a replay. Use durable application-owned adapters
194
379
  for production receipts, transactions and audits.
380
+
381
+ ### Shared Schema Decoders
382
+
383
+ `createSchemaDecoder(schema)` derives a decoder's output type from a TypeBox
384
+ schema, including transforms. Invalid values throw a sanitized
385
+ `SchemaContractError`. `defineJsonContract({ body, response }, request)` creates
386
+ decoders compatible with `HttpClient.execute` while retaining the same schemas
387
+ for route registration. Keep schemas independently importable and reference
388
+ their identifiers explicitly in compiler-analyzed route decorators.
389
+
390
+ For hand-written Elysia routes, `defineRouteContract` and
391
+ `defineElysiaRoute` provide contextual handler types from the same schema value.
392
+ `registerElysiaRoute` maps the contract's `responses` status map to Elysia's
393
+ `response` option and registers the route:
394
+
395
+ ```ts
396
+ import { Elysia, t } from "elysia";
397
+ import {
398
+ defineElysiaRoute,
399
+ defineRouteContract,
400
+ registerElysiaRoute,
401
+ } from "@supacloud/elysia";
402
+
403
+ const itemRoute = defineRouteContract({
404
+ body: t.Object({ name: t.String() }),
405
+ params: t.Object({ id: t.String() }),
406
+ responses: {
407
+ 200: t.Object({ id: t.String(), name: t.String() }),
408
+ 409: t.Object({ conflict: t.Literal(true) }),
409
+ },
410
+ });
411
+
412
+ const route = defineElysiaRoute("POST", "/items/:id", itemRoute, ({ body, params, status }) =>
413
+ body.name === "existing"
414
+ ? status(409, { conflict: true })
415
+ : { id: params.id, name: body.name },
416
+ );
417
+
418
+ const app = registerElysiaRoute(new Elysia(), route);
419
+ ```
420
+
421
+ The callback is typed from the contract (including decoded transforms and
422
+ declared response statuses). Cookie values retain Elysia's native shape, so a
423
+ declared `session: t.String()` is read as `cookie.session.value`. This helper
424
+ does not add Eden-style client inference to an existing Elysia instance; the
425
+ compiler-generated client remains the source of transport types.
426
+
427
+ Response maps may use concrete statuses, `1XX`-`5XX` families, and `default`.
428
+ Because Elysia 1.4 only compiles numeric response keys, the adapter expands
429
+ family/default entries to concrete validators before registration. Exact
430
+ statuses take precedence over families, which take precedence over `default`.
431
+ An actual status absent from a structured response map fails the route contract
432
+ before Elysia can silently accept a default `200`; binary/stream routes may
433
+ intentionally leave successful transport statuses unschematized when only their
434
+ JSON error responses are declared. Unsupported selectors fail during
435
+ registration instead of silently disabling response validation.
436
+
437
+ See [type safety and migration](../../docs/type-safety.md) and [command migration](../../docs/command-migration.md) for examples and
438
+ the distinction between contract declarations and runtime verification.
@@ -0,0 +1,2 @@
1
+ import type { CommandErrorCode } from "@supacloud/contracts";
2
+ 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;
@@ -0,0 +1,13 @@
1
+ export interface ExecutionEvent {
2
+ kind: "route" | "command" | "job";
3
+ operation: string;
4
+ stage: string;
5
+ phase: "started" | "succeeded" | "failed";
6
+ requestId?: string;
7
+ durationMs?: number;
8
+ }
9
+ /** Metadata only: never receives request bodies, credentials, results or errors. */
10
+ export type ExecutionObserver = (event: Readonly<ExecutionEvent>) => void | Promise<void>;
11
+ export declare function observeExecution<T>(observer: ExecutionObserver | undefined, event: Pick<ExecutionEvent, "kind" | "operation" | "stage" | "requestId">, next: () => T | Promise<T>): Promise<T>;
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
+ };
@@ -0,0 +1,37 @@
1
+ import { type JWTVerifyGetKey } from "jose";
2
+ import { type SupaCloudRequestContext, type TrustedRequestIdentity } from "./index";
3
+ export interface SupAuthIdentity extends TrustedRequestIdentity {
4
+ authenticated: true;
5
+ subject: string;
6
+ issuer: string;
7
+ clientId: string;
8
+ }
9
+ export interface SupAuthAccess {
10
+ projectId: string;
11
+ tenantId: string;
12
+ permissions: readonly string[];
13
+ }
14
+ export interface SupAuthRequestContext extends SupaCloudRequestContext {
15
+ identity: SupAuthIdentity;
16
+ access: Readonly<SupAuthAccess>;
17
+ }
18
+ export interface SupAuthContextOptions {
19
+ issuer: string;
20
+ audience: string;
21
+ /** SupAuth OAuth application binding, independent of audience/project membership. */
22
+ clientId: string;
23
+ projectId: string;
24
+ /** Explicit trusted JWKS endpoint; never read from a token's jku/x5u header. */
25
+ jwksUrl: string;
26
+ /** Defaults to ES256 and RS256; symmetric algorithms are not supported. */
27
+ algorithms?: readonly ("ES256" | "RS256")[];
28
+ /** Trusted host override for pinned/local keys and deterministic tests. */
29
+ keyResolver?: JWTVerifyGetKey;
30
+ /** Read current application-local access, not user-supplied tenant headers. */
31
+ resolveAccess(identity: Readonly<SupAuthIdentity>, request: Request): Promise<SupAuthAccess | null>;
32
+ }
33
+ /**
34
+ * External user-center verification only. SupAuth/GoTrue still owns login,
35
+ * passwords, sessions and token issuance; applications own access decisions.
36
+ */
37
+ export declare function createSupAuthRequestContext(options: SupAuthContextOptions): (request: Request) => Promise<SupAuthRequestContext>;