@supacloud/cli 0.75.1 → 0.77.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.
Files changed (118) hide show
  1. package/README.md +24 -4
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +18766 -73279
  4. package/dist/index.test.d.ts +1 -0
  5. package/dist/migration-inventory.integration.test.d.ts +1 -0
  6. package/dist/project-read-process.test.d.ts +1 -0
  7. package/dist/shared/cli.d.ts +18 -0
  8. package/dist/shared/context.d.ts +26 -0
  9. package/dist/shared/context.test.d.ts +1 -0
  10. package/dist/shared/execution-policy.d.ts +14 -0
  11. package/dist/shared/execution-policy.test.d.ts +1 -0
  12. package/dist/shared/global-options.d.ts +8 -0
  13. package/dist/shared/global-options.test.d.ts +1 -0
  14. package/dist/shared/mutation-protocol.d.ts +45 -0
  15. package/dist/shared/mutation-protocol.test.d.ts +1 -0
  16. package/dist/shared/outcome-guidance.d.ts +1 -0
  17. package/dist/shared/project-ref.d.ts +1 -0
  18. package/dist/shared/schema.d.ts +13 -0
  19. package/dist/shared/schema.test.d.ts +1 -0
  20. package/dist/shared/tool-server.d.ts +15 -0
  21. package/dist/shared/tool-server.test.d.ts +1 -0
  22. package/dist/shared/tools/advanced-tools.d.ts +5 -0
  23. package/dist/shared/tools/advanced-tools.test.d.ts +1 -0
  24. package/dist/shared/tools/ai-tools.d.ts +28 -0
  25. package/dist/shared/tools/ai-tools.test.d.ts +1 -0
  26. package/dist/shared/tools/app-database-check.d.ts +7 -0
  27. package/dist/shared/tools/app-database-check.test.d.ts +1 -0
  28. package/dist/shared/tools/app-delivery.test.d.ts +1 -0
  29. package/dist/shared/tools/app-dev-regressions.test.d.ts +1 -0
  30. package/dist/shared/tools/app-dev.test.d.ts +1 -0
  31. package/dist/shared/tools/app-local-dev.d.ts +15 -0
  32. package/dist/shared/tools/app-local-dev.test.d.ts +1 -0
  33. package/dist/shared/tools/app-minimal-starter.test.d.ts +1 -0
  34. package/dist/shared/tools/app-resource-generation.test.d.ts +1 -0
  35. package/dist/shared/tools/app-resource.d.ts +5 -0
  36. package/dist/shared/tools/app-scaffold-writes.d.ts +33 -0
  37. package/dist/shared/tools/app-scaffold-writes.test.d.ts +1 -0
  38. package/dist/shared/tools/app-starter-attachment-worker.d.ts +2 -0
  39. package/dist/shared/tools/app-starter-attachments.d.ts +2 -0
  40. package/dist/shared/tools/app-starter-composition.test.d.ts +1 -0
  41. package/dist/shared/tools/app-starter-database.d.ts +14 -0
  42. package/dist/shared/tools/app-starter-environment.d.ts +2 -0
  43. package/dist/shared/tools/app-starter-postgres.d.ts +4 -0
  44. package/dist/shared/tools/app-starter-reactive.d.ts +4 -0
  45. package/dist/shared/tools/app-starter-reactive.test.d.ts +1 -0
  46. package/dist/shared/tools/app-starter-review-job.d.ts +2 -0
  47. package/dist/shared/tools/app-starter-roles.d.ts +2 -0
  48. package/dist/shared/tools/app-starter-roles.test.d.ts +1 -0
  49. package/dist/shared/tools/app-starter-templates.d.ts +7 -0
  50. package/dist/shared/tools/app-starter-upload.d.ts +3 -0
  51. package/dist/shared/tools/app-starter.d.ts +12 -0
  52. package/dist/shared/tools/app-starter.test.d.ts +1 -0
  53. package/dist/shared/tools/app-tool-export.d.ts +42 -0
  54. package/dist/shared/tools/app-tools.d.ts +93 -0
  55. package/dist/shared/tools/app-tools.test.d.ts +1 -0
  56. package/dist/shared/tools/app-verification-plan.d.ts +14 -0
  57. package/dist/shared/tools/app-verification-plan.test.d.ts +1 -0
  58. package/dist/shared/tools/application-schemas.d.ts +75 -0
  59. package/dist/shared/tools/application-tools.d.ts +23 -0
  60. package/dist/shared/tools/application-tools.test.d.ts +1 -0
  61. package/dist/shared/tools/auth-tools.d.ts +3 -0
  62. package/dist/shared/tools/auth-tools.test.d.ts +1 -0
  63. package/dist/shared/tools/branch-tools.d.ts +6 -0
  64. package/dist/shared/tools/branch-tools.test.d.ts +1 -0
  65. package/dist/shared/tools/database-tools.d.ts +13 -0
  66. package/dist/shared/tools/database-tools.test.d.ts +1 -0
  67. package/dist/shared/tools/db-governance-tools.d.ts +80 -0
  68. package/dist/shared/tools/db-governance-tools.test.d.ts +1 -0
  69. package/dist/shared/tools/db-workflow.d.ts +37 -0
  70. package/dist/shared/tools/db-workflow.test.d.ts +1 -0
  71. package/dist/shared/tools/deploy-tools.d.ts +36 -0
  72. package/dist/shared/tools/deploy-tools.test.d.ts +1 -0
  73. package/dist/shared/tools/edge-function-response.d.ts +55 -0
  74. package/dist/shared/tools/edge-function-response.test.d.ts +1 -0
  75. package/dist/shared/tools/frontend-release-control.d.ts +54 -0
  76. package/dist/shared/tools/frontend-release-control.test.d.ts +1 -0
  77. package/dist/shared/tools/frontend-tools.d.ts +3 -0
  78. package/dist/shared/tools/gateway-tools.d.ts +6 -0
  79. package/dist/shared/tools/gateway-tools.test.d.ts +1 -0
  80. package/dist/shared/tools/lite-cli-command.d.ts +2 -0
  81. package/dist/shared/tools/lite-cli-tools.d.ts +46 -0
  82. package/dist/shared/tools/lite-cli-tools.test.d.ts +1 -0
  83. package/dist/shared/tools/migration-import.d.ts +22 -0
  84. package/dist/shared/tools/migration-import.test.d.ts +1 -0
  85. package/dist/shared/tools/migration-risk.d.ts +36 -0
  86. package/dist/shared/tools/migration-risk.test.d.ts +1 -0
  87. package/dist/shared/tools/mutation-tools.d.ts +3 -0
  88. package/dist/shared/tools/mutation-tools.test.d.ts +1 -0
  89. package/dist/shared/tools/oauth-client-tools.d.ts +3 -0
  90. package/dist/shared/tools/oauth-client-tools.test.d.ts +1 -0
  91. package/dist/shared/tools/project-cli-tools.d.ts +6 -0
  92. package/dist/shared/tools/project-cli-tools.test.d.ts +1 -0
  93. package/dist/shared/tools/project-endpoint-read.d.ts +10 -0
  94. package/dist/shared/tools/project-endpoint-tools.test.d.ts +1 -0
  95. package/dist/shared/tools/project-read-projection.d.ts +8 -0
  96. package/dist/shared/tools/project-read-projection.test.d.ts +1 -0
  97. package/dist/shared/tools/queue-tools.d.ts +6 -0
  98. package/dist/shared/tools/queue-tools.test.d.ts +1 -0
  99. package/dist/shared/tools/release-control-response.d.ts +12 -0
  100. package/dist/shared/tools/release-control-response.test.d.ts +1 -0
  101. package/dist/shared/tools/release-tools.d.ts +8 -0
  102. package/dist/shared/tools/release-tools.test.d.ts +1 -0
  103. package/dist/shared/tools/remote-dev-tools.d.ts +41 -0
  104. package/dist/shared/tools/remote-dev-tools.test.d.ts +1 -0
  105. package/dist/shared/tools/scheduled-function-tools.d.ts +7 -0
  106. package/dist/shared/tools/scheduled-function-tools.test.d.ts +1 -0
  107. package/dist/shared/tools/storage-tools.d.ts +3 -0
  108. package/dist/shared/tools/storage-tools.test.d.ts +1 -0
  109. package/dist/shared/tools/supabase-cli-tools.d.ts +49 -0
  110. package/dist/shared/tools/supabase-cli-tools.test.d.ts +1 -0
  111. package/dist/shared/transports/http.d.ts +64 -0
  112. package/dist/shared/transports/http.test.d.ts +1 -0
  113. package/dist/supabase-cli.integration.test.d.ts +1 -0
  114. package/dist/test-helpers.d.ts +1 -0
  115. package/dist/tool-runtime.d.ts +2 -0
  116. package/dist/tool-runtime.js +154 -0
  117. package/dist/tool-runtime.test.d.ts +1 -0
  118. package/package.json +19 -4
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import type { ToolSchema } from "./schema";
2
+ import type { ToolInvocation } from "./tool-server";
3
+ interface CliRunOptions {
4
+ commandName?: string;
5
+ }
6
+ interface CliToolResult {
7
+ content?: Array<{
8
+ type: string;
9
+ text?: string;
10
+ }>;
11
+ isError?: boolean;
12
+ }
13
+ export declare function cliToolResultIsError(toolResult: CliToolResult): boolean;
14
+ export declare function runCli(cliTools: Record<string, {
15
+ schema: ToolSchema;
16
+ callback: ToolInvocation;
17
+ }>, args: string[], options?: CliRunOptions): Promise<void>;
18
+ export {};
@@ -0,0 +1,26 @@
1
+ export type ContextSourceKind = "process_env" | "named_env_file" | "explicit_env_file" | "legacy_dotenv" | "none";
2
+ export type ContextCredentialScope = "management" | "project_application" | "incomplete";
3
+ export interface ContextSelection {
4
+ environmentName?: string;
5
+ envFile?: string;
6
+ }
7
+ export interface ResolvedContext {
8
+ host: string;
9
+ sshUser: string;
10
+ sshPort: number;
11
+ sshKey: string;
12
+ sshPass: string;
13
+ apiUrl: string;
14
+ apiToken: string;
15
+ projectRef: string;
16
+ readOnly: boolean;
17
+ environment: string;
18
+ production: boolean;
19
+ inferredSupabaseUrl: string;
20
+ inferredServiceRoleKey: string;
21
+ credentialScope: ContextCredentialScope;
22
+ source: ContextSourceKind;
23
+ sourcePath: string | null;
24
+ insecureTls: boolean;
25
+ }
26
+ export declare function resolveSupaCloudContext(env?: NodeJS.ProcessEnv, cwd?: string, selection?: ContextSelection): ResolvedContext;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { ToolSchema } from "./schema";
2
+ import type { ResolvedContext } from "./context";
3
+ export type ExecutionMode = "read" | "write" | "local";
4
+ export interface ExecutionAuthorization {
5
+ context: ResolvedContext;
6
+ confirmProduction?: string;
7
+ }
8
+ interface ToolCatalogEntry {
9
+ schema: ToolSchema;
10
+ }
11
+ export declare function executionMode(moduleName: string, action: string, args: Record<string, unknown>): ExecutionMode | undefined;
12
+ export declare function authorizeExecution(moduleName: string, args: Record<string, unknown>, authorization: ExecutionAuthorization): void;
13
+ export declare function validateExecutionPolicyCoverage(tools: Record<string, ToolCatalogEntry>): void;
14
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ export interface GlobalCliOptions {
2
+ environmentName?: string;
3
+ envFile?: string;
4
+ confirmProduction?: string;
5
+ args: string[];
6
+ }
7
+ export declare function normalizeEnvironmentName(name: string): string;
8
+ export declare function parseGlobalOptions(args: string[]): GlobalCliOptions;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ import type { HttpTransport } from "./transports/http";
2
+ export interface SafeMutationStatus {
3
+ project_ref: string;
4
+ mutation_id: string;
5
+ operation: string;
6
+ resource_key: string | null;
7
+ request_fingerprint: string;
8
+ principal: {
9
+ type: "master" | "admin" | "project";
10
+ id: string;
11
+ };
12
+ status: "pending" | "running" | "succeeded" | "failed_retryable" | "failed_terminal" | "outcome_unknown";
13
+ checkpoint: Record<string, unknown>;
14
+ receipt: Record<string, unknown> | null;
15
+ response_status: number | null;
16
+ failure_code: string | null;
17
+ lease: {
18
+ owner: string | null;
19
+ expires_at: string | null;
20
+ fencing_epoch: number;
21
+ };
22
+ completed_at: string | null;
23
+ created_at: string;
24
+ updated_at: string;
25
+ }
26
+ export interface MutationExpectation {
27
+ ref: string;
28
+ mutationId: string;
29
+ operation: string;
30
+ requestFingerprint: string;
31
+ }
32
+ export type MutationStatusReadback = {
33
+ kind: "available";
34
+ mutation: SafeMutationStatus;
35
+ } | {
36
+ kind: "unavailable";
37
+ httpStatus: number | null;
38
+ } | {
39
+ kind: "invalid";
40
+ };
41
+ export declare function isMutationId(candidate: unknown): candidate is string;
42
+ export declare function mutationRequestFingerprint(normalizedRequest: unknown): string;
43
+ export declare function mutationStatusPath(ref: string, mutationId: string): string;
44
+ export declare function fetchMutationStatus(http: HttpTransport, ref: string, mutationId: string): Promise<MutationStatusReadback>;
45
+ export declare function verifiedMutationReadback(http: HttpTransport, expectation: MutationExpectation): Promise<MutationStatusReadback>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare function outcomeUnknownGuidance(text: string): string | null;
@@ -0,0 +1 @@
1
+ export declare function projectRefPathSegment(ref: unknown, operation: string): string;
@@ -0,0 +1,13 @@
1
+ import { Type } from "typebox";
2
+ import type { StaticDecode, TObject, TSchema, TSchemaOptions } from "typebox";
3
+ export type ToolSchema = Record<string, TSchema>;
4
+ export type ToolArguments<S extends ToolSchema> = StaticDecode<TObject<S>>;
5
+ export declare function validateToolArguments(schema: ToolSchema, input: unknown): asserts input is Record<string, unknown>;
6
+ export declare function parseToolArguments<S extends ToolSchema>(schema: S, input: unknown): ToolArguments<S>;
7
+ export declare function stringEnum<const Values extends readonly [string, ...string[]]>(values: Values, options?: TSchemaOptions): Type.TEnum<Values[number][]>;
8
+ export declare function withDescription<T extends TSchema>(schema: T, description: string): T;
9
+ export declare function optional<T extends TSchema>(schema: T, description?: string): "~optional" extends keyof T ? T : Type.TOptional<T>;
10
+ export declare function decodedSchema<TInput extends TSchema, TOutput extends TSchema>(inputSchema: TInput, outputSchema: TOutput, decode: (input: StaticDecode<TInput>) => unknown, options?: TSchemaOptions): Type.TCodec<TInput, import("typebox").StaticType<[], "Decode", {}, {}, TOutput>>;
11
+ export declare function schemaEnumValues(schema: TSchema): string[];
12
+ export declare function schemaDescription(schema: TSchema): string;
13
+ export declare function schemaProperties(schema: TSchema | ToolSchema): ToolSchema;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { type ToolArguments, type ToolSchema } from "./schema.js";
2
+ export interface ToolTextContent {
3
+ readonly type: "text";
4
+ readonly text: string;
5
+ }
6
+ export interface ToolResult {
7
+ content: ToolTextContent[];
8
+ readonly isError?: boolean;
9
+ }
10
+ export type ToolCallback<TArgs, TResult extends ToolResult = ToolResult> = (args: TArgs) => Promise<TResult>;
11
+ export type ToolInvocation = (args: unknown) => Promise<ToolResult>;
12
+ export interface ToolServer {
13
+ tool: (name: string, description: string, schema: ToolSchema, callback: ToolInvocation) => void;
14
+ }
15
+ export declare function registerTool<S extends ToolSchema, TResult extends ToolResult = ToolResult>(server: ToolServer, name: string, description: string, schema: S, callback: ToolCallback<NoInfer<ToolArguments<S>>, TResult>): void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { type ToolServer } from "../tool-server";
2
+ import type { HttpTransport } from "../transports/http";
3
+ export declare function registerAdvancedTools(server: ToolServer, http: HttpTransport, environment?: NodeJS.ProcessEnv, options?: {
4
+ readOnly?: boolean;
5
+ }): void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { type ToolServer } from "../tool-server";
2
+ declare const SKILL_NAME = "supacloud-cli";
3
+ type InstallMode = "dry-run" | "write";
4
+ type ConflictPolicy = "reject" | "replace";
5
+ type InstallAction = "create" | "replace" | "none";
6
+ interface InstallSkillRequest {
7
+ sourceDirectory: string;
8
+ targetRoot: string;
9
+ mode: InstallMode;
10
+ conflictPolicy: ConflictPolicy;
11
+ now: Date;
12
+ }
13
+ export interface SkillInstallSummary {
14
+ name: typeof SKILL_NAME;
15
+ sourceDirectory: string;
16
+ targetRoot: string;
17
+ destinationDirectory: string;
18
+ action: InstallAction;
19
+ mode: InstallMode;
20
+ changed: boolean;
21
+ backupDirectory: string | null;
22
+ files: string[];
23
+ }
24
+ export declare function installSkill(request: InstallSkillRequest): SkillInstallSummary;
25
+ export declare function resolveDefaultCodexSkillRoot(environment?: NodeJS.ProcessEnv, homeDirectory?: string): string;
26
+ export declare function resolveBundledSkillDirectory(moduleUrl?: string): string;
27
+ export declare function registerAiTools(server: ToolServer): void;
28
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export interface AppDatabaseCheck {
2
+ name: "database-source-contracts";
3
+ ok: boolean;
4
+ detail: string;
5
+ }
6
+ /** Use the project's installed tool, not arbitrary package scripts or remote database access. */
7
+ export declare function checkAppDatabaseSources(projectRoot: string): AppDatabaseCheck | undefined;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ export interface LocalDevOptions {
2
+ root: string;
3
+ profile?: "fast" | "integration";
4
+ databaseUrl?: string;
5
+ signal?: AbortSignal;
6
+ onOutput?: (text: string) => void;
7
+ }
8
+ export declare function localDatabaseUrl(value: string | undefined): string;
9
+ /** Launch the project's one dev loop; do not add another compiler watcher. */
10
+ export declare function runLocalDevelopment(options: LocalDevOptions): Promise<{
11
+ profile: "fast" | "integration";
12
+ script: string;
13
+ exitCode: number;
14
+ state: "stopped";
15
+ }>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * One editable read resource, not a persistence implementation or a second DI
3
+ * system. Module/provider/controller wiring uses the existing compiler model.
4
+ */
5
+ export declare function resourceScaffold(name: string, className: string): Record<string, string>;
@@ -0,0 +1,33 @@
1
+ export declare class ScaffoldError extends Error {
2
+ readonly code: string;
3
+ constructor(code: string, message: string);
4
+ }
5
+ export interface ScaffoldWrite {
6
+ readonly path: string;
7
+ readonly content: string;
8
+ /** A registration edit must still match the source used by the compiler. */
9
+ readonly expected?: string;
10
+ readonly overwrite?: boolean;
11
+ }
12
+ interface FileSnapshot {
13
+ readonly content: string;
14
+ readonly mode: number;
15
+ readonly dev: number;
16
+ readonly ino: number;
17
+ }
18
+ export interface ScaffoldPlan {
19
+ readonly root: string;
20
+ readonly writes: readonly (ScaffoldWrite & {
21
+ readonly before: FileSnapshot | null;
22
+ })[];
23
+ }
24
+ export declare function scaffoldPath(root: string, path: string): string;
25
+ /** Preflight the complete set before creating any directory or file. */
26
+ export declare function planScaffoldWrites(root: string, writes: readonly ScaffoldWrite[]): Promise<ScaffoldPlan>;
27
+ /**
28
+ * Serializes generator writers and rolls back handled write failures. This is
29
+ * not a crash-atomic filesystem transaction or a sandbox against hostile local
30
+ * writers. Concurrent edits detected during validation are never overwritten.
31
+ */
32
+ export declare function applyScaffoldWrites(plan: ScaffoldPlan): Promise<void>;
33
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare const STARTER_ATTACHMENT_WORKER = "import { createWorker, type CompiledModule } from \"@supacloud/elysia\";\nimport type { SupaCloudWorkflowClaim, SupaCloudWorkflowsClient } from \"@supacloud/js\";\nimport { createReviewAttachmentAdapters, type ReviewAttachmentOptions } from \"./review-attachments\";\n\nexport interface ReviewAttachmentWorkerOptions extends ReviewAttachmentOptions {\n modules: readonly CompiledModule[];\n workflows: Pick<SupaCloudWorkflowsClient, \"claim\" | \"complete\" | \"retry\" | \"fail\">;\n workerId: string;\n queueOwnership: \"exclusive-review-attachments\";\n pollIntervalMs?: number;\n}\n\nexport async function createReviewAttachmentWorker(options: ReviewAttachmentWorkerOptions) {\n if (options.queueOwnership !== \"exclusive-review-attachments\") {\n throw new Error(\"Attachment worker requires an exclusively owned Workflow queue\");\n }\n const { store } = await createReviewAttachmentAdapters(options);\n const { workflows, workerId } = options;\n const failure = Promise.withResolvers<never>();\n // The executable attaches its fatal listener after this async factory returns.\n void failure.promise.catch(() => {});\n let halted: boolean = false;\n const fatal = () => {\n halted = true;\n failure.reject(new Error(\"Attachment worker requires recovery\"));\n };\n const attempt = (claim: SupaCloudWorkflowClaim) => ({\n stepId: claim.stepId, messageId: claim.messageId, attempt: claim.attempt, workerId: claim.workerId,\n });\n async function ownedClaim(claim: SupaCloudWorkflowClaim) {\n if (claim.workflowName !== \"review.verify-attachment\" || claim.workflowVersion !== \"1\"\n || claim.stepKey !== \"verify\" || claim.maxAttempts !== 3 || claim.workerId !== workerId) {\n throw new Error(\"Unsupported attachment workflow\");\n }\n const { reviewId, artifactId } = claim.input;\n if (typeof reviewId !== \"string\" || typeof artifactId !== \"string\") {\n throw new Error(\"Invalid attachment workflow input\");\n }\n const bound = await options.database.transaction(tx => tx.query(\n \"SELECT run_id FROM public.starter_attachments WHERE review_id=$1 AND artifact_id=$2 AND run_id=$3\",\n [reviewId, artifactId, claim.runId],\n ));\n if (!Array.isArray(bound) || bound.length !== 1) throw new Error(\"Attachment workflow ownership mismatch\");\n }\n const worker = createWorker({\n modules: options.modules, deps: { dbClient: store }, workerId, concurrency: 1,\n pollIntervalMs: options.pollIntervalMs ?? 1000,\n onError: fatal,\n transport: {\n async claim(signal): Promise<SupaCloudWorkflowClaim | null> {\n if (halted || signal.aborted) return null;\n try {\n const claim = await workflows.claim({ workerId, visibilityTimeoutSeconds: 120 });\n if (signal.aborted || halted) return null;\n if (!claim || claim.status !== \"claimed\") return null;\n await ownedClaim(claim);\n return claim;\n } catch {\n if (!signal.aborted) fatal();\n throw new Error(\"Attachment workflow claim failed\");\n }\n },\n async ack(claim, output) {\n try {\n if (!output || typeof output !== \"object\" || Array.isArray(output)) throw new Error(\"Invalid Job output\");\n // The compiled Job validates output; Workflow SDK validates the receipt.\n await workflows.complete({ ...attempt(claim), runOutput: { ...output } });\n } catch {\n fatal();\n throw new Error(\"Attachment workflow completion is unconfirmed\");\n }\n },\n async fail(claim) {\n try {\n const request: Parameters<typeof workflows.fail>[0] = { ...attempt(claim), errorMessage: \"Attachment verification failed\" };\n if (claim.attempt < claim.maxAttempts) await workflows.retry({ ...request, delaySeconds: 5 });\n else await workflows.fail(request);\n } catch {\n fatal();\n throw new Error(\"Attachment workflow failure receipt is unconfirmed\");\n }\n },\n },\n mapClaim: (claim: SupaCloudWorkflowClaim) => ({\n id: claim.stepId, jobName: claim.workflowName, input: claim.input, attempt: claim.attempt,\n requestContext: { workerId, jobId: claim.stepId, requestId: claim.runId },\n }),\n });\n if (worker.jobNames.length !== 1 || worker.jobNames[0] !== \"review.verify-attachment\") {\n throw new Error(\"Attachment worker requires its compiled reference Job\");\n }\n let closing: Promise<void> | undefined;\n return {\n failure: failure.promise,\n async start() {\n if (halted) throw new Error(\"Attachment worker is not startable\");\n await worker.start();\n },\n close() {\n halted = true;\n return closing ??= worker.stop();\n },\n };\n}\n";
2
+ export declare const STARTER_ATTACHMENT_DELIVERY_WORKER = "import { SQL } from \"bun\";\nimport { createClient } from \"@supabase/supabase-js\";\nimport { SupaCloudWorkflowsClient } from \"@supacloud/js\";\nimport { createBunCommandDatabase } from \"@supacloud/db/bun\";\nimport type { CompiledModule } from \"@supacloud/elysia\";\nimport { createReviewAttachmentWorker } from \"./host/review-attachment-worker\";\n\nfunction required(name: string): string {\n const value = process.env[name];\n if (!value?.trim()) throw new Error(\"Missing required worker setting: \" + name);\n return value;\n}\n\nfunction databaseConnection(): SQL.Options {\n const socket = process.env[\"DATABASE_SOCKET_PATH\"];\n if (socket && process.env[\"DATABASE_URL\"]) throw new Error(\"Conflicting database connection settings\");\n if (socket) {\n return { adapter: \"postgres\", path: socket, database: required(\"DATABASE_NAME\"), username: required(\"DATABASE_USER\") };\n }\n const url = new URL(required(\"DATABASE_URL\"));\n if (url.protocol !== \"postgres:\" && url.protocol !== \"postgresql:\") throw new Error(\"Invalid database URL\");\n return {\n adapter: \"postgres\", url: url.href, hostname: url.hostname.replace(/^\\[|\\]$/g, \"\"), port: Number(url.port || 5432),\n username: decodeURIComponent(url.username), password: decodeURIComponent(url.password),\n database: decodeURIComponent(url.pathname.slice(1)),\n };\n}\n\nexport async function createDeliveryWorker(modules: CompiledModule[], lifecycle: { signal: AbortSignal }) {\n lifecycle.signal.throwIfAborted();\n if (required(\"REVIEW_QUEUE_OWNERSHIP\") !== \"exclusive-review-attachments\") {\n throw new Error(\"Attachment worker requires an exclusively owned Workflow queue\");\n }\n const connection = databaseConnection();\n const settings = {\n projectId: required(\"SUPACLOUD_PROJECT_ID\"), tenantId: required(\"APP_TENANT_ID\"),\n workerId: required(\"REVIEW_WORKER_ID\"), url: required(\"SUPACLOUD_URL\"), key: required(\"SUPACLOUD_SERVICE_ROLE_KEY\"),\n };\n const service = createClient(settings.url, settings.key, {\n auth: { persistSession: false, autoRefreshToken: false, detectSessionInUrl: false },\n });\n const pool = new SQL({ ...connection, max: 4, connectionTimeout: 5 });\n let closing: Promise<void> | undefined;\n const closePool = () => closing ??= pool.close({ timeout: 5 });\n try {\n const worker = await createReviewAttachmentWorker({\n database: createBunCommandDatabase(pool), service, projectId: settings.projectId,\n tenantId: settings.tenantId, modules, workerId: settings.workerId,\n workflows: new SupaCloudWorkflowsClient(service), queueOwnership: \"exclusive-review-attachments\",\n });\n lifecycle.signal.throwIfAborted();\n return {\n start: () => worker.start(), failure: worker.failure,\n async close() { try { await worker.close(); } finally { await closePool(); } },\n };\n } catch (error) {\n await closePool();\n const code = error !== null && typeof error === \"object\" && \"code\" in error\n && typeof error.code === \"string\" && /^[A-Z0-9_]{1,64}$/.test(error.code) ? error.code : \"UNKNOWN\";\n console.error(JSON.stringify({ event: \"review-worker-initialization-failed\", code }));\n throw new Error(\"Attachment worker initialization failed\");\n }\n}\n";
@@ -0,0 +1,2 @@
1
+ export declare const STARTER_ATTACHMENT_SCHEMA = "-- Apply with the migration owner, never from an application process.\nCREATE TABLE public.starter_attachments (\n review_id text PRIMARY KEY REFERENCES public.starter_reviews(id),\n artifact_id uuid NOT NULL UNIQUE, owner_id text NOT NULL, run_id uuid NOT NULL UNIQUE,\n object_path text NOT NULL UNIQUE\n);\nCREATE TABLE public.starter_attachment_results (\n review_id text NOT NULL REFERENCES public.starter_reviews(id),\n version integer NOT NULL CHECK (version >= 2), artifact_id uuid NOT NULL,\n result jsonb NOT NULL, PRIMARY KEY(review_id,version,artifact_id)\n);\nALTER TABLE public.starter_attachments ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.starter_attachment_results ENABLE ROW LEVEL SECURITY;\n";
2
+ export declare const STARTER_ATTACHMENT_POSTGRES = "import { SupaCloudArtifactsClient } from \"@supacloud/js\";\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { CommandDatabase, CommandTransaction } from \"@supacloud/db\";\nimport type { ReviewAttachmentInput, ReviewAttachmentResult, ReviewAttachmentStore } from \"../review/attachment\";\n\nexport interface ReviewAttachmentOptions {\n database: CommandDatabase;\n // Provision this client for the same project as the database; never use a caller's bearer token.\n service: SupabaseClient;\n projectId: string;\n tenantId: string;\n}\n\nfunction rows(value: unknown): Record<string, unknown>[] {\n if (!Array.isArray(value)) throw new TypeError(\"Invalid attachment database result\");\n return value.map(record);\n}\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction record(value: unknown): Record<string, unknown> {\n if (!isRecord(value)) throw new TypeError(\"Invalid attachment record\");\n return value;\n}\nfunction durableResult(value: unknown): ReviewAttachmentResult {\n const item = record(value);\n if (typeof item[\"reviewId\"] !== \"string\" || typeof item[\"artifactId\"] !== \"string\"\n || typeof item[\"version\"] !== \"number\" || typeof item[\"sha256\"] !== \"string\"\n || typeof item[\"bytes\"] !== \"number\" || Object.keys(item).length !== 5) {\n throw new Error(\"Invalid durable attachment result\");\n }\n return { reviewId: item[\"reviewId\"], artifactId: item[\"artifactId\"], version: item[\"version\"], sha256: item[\"sha256\"], bytes: item[\"bytes\"] };\n}\n\nexport async function createReviewAttachmentAdapters(options: ReviewAttachmentOptions): Promise<{\n store: ReviewAttachmentStore;\n enqueue(tx: CommandTransaction, reviewId: string, version: number): Promise<void>;\n}> {\n const { database, service, projectId, tenantId } = options;\n if (!projectId.trim() || !tenantId.trim()) throw new TypeError(\"Attachment project/tenant is required\");\n const artifacts = new SupaCloudArtifactsClient(service);\n const binding = rows(await database.transaction(tx => tx.query(\n \"SELECT project_id,tenant_id FROM public.starter_application WHERE singleton\",\n )));\n if (binding.length !== 1 || binding[0]?.[\"project_id\"] !== projectId || binding[0]?.[\"tenant_id\"] !== tenantId) {\n throw new Error(\"Attachment database project/tenant binding mismatch\");\n }\n async function authorize(tx: CommandTransaction, input: ReviewAttachmentInput) {\n const allowed = rows(await tx.query(\n \"SELECT a.object_path,a.run_id FROM public.starter_attachments a \" +\n \"JOIN public.starter_reviews r ON r.id=a.review_id AND r.owner_id=a.owner_id \" +\n \"JOIN public.starter_members m ON m.subject=a.owner_id AND m.enabled AND m.can_approve \" +\n \"CROSS JOIN public.starter_application p \" +\n \"WHERE a.review_id=$1 AND a.artifact_id=$2 AND r.version=$3 AND r.state='approved' \" +\n \"AND p.singleton AND p.project_id=$4 AND p.tenant_id=$5 \" +\n \"FOR UPDATE OF r,a,m FOR SHARE OF p\",\n [input.reviewId, input.artifactId, input.version, projectId, tenantId],\n ));\n if (allowed.length !== 1 || typeof allowed[0]?.[\"object_path\"] !== \"string\") {\n throw new Error(\"Attachment worker authorization or review revision is no longer valid\");\n }\n return allowed[0];\n }\n return {\n async enqueue(tx, reviewId, version) {\n const found = rows(await tx.query(\n \"SELECT artifact_id FROM public.starter_attachments WHERE review_id=$1\", [reviewId],\n ))[0];\n if (!found) return;\n const input = { reviewId, version, artifactId: String(found[\"artifact_id\"]) };\n const allowed = await authorize(tx, input);\n // Bind serialized JSON as text so drivers cannot encode the string as a JSON scalar.\n await tx.query(\"SELECT supacloud_workflows.start_run($1::uuid,$2,'1','verify',$3::text::jsonb,3)\", [\n String(allowed[\"run_id\"]), \"review.verify-attachment\", JSON.stringify(input),\n ]);\n },\n store: {\n async readAttachment(input) {\n const allowed = await database.transaction(tx => authorize(tx, input));\n const artifact = await artifacts.get(input.artifactId);\n if (!artifact || artifact.bucketId !== \"review-attachments\"\n || artifact.objectPath !== allowed[\"object_path\"] || artifact.artifactType !== \"review.attachment\"\n || artifact.mimeType !== \"text/plain\" || !/^[1-9][0-9]*$/.test(artifact.sizeBytes)\n || BigInt(artifact.sizeBytes) > 1048576n) throw new Error(\"Invalid attachment artifact\");\n const downloaded = await service.storage.from(artifact.bucketId).download(artifact.objectPath);\n if (downloaded.error || !downloaded.data) throw new Error(\"Attachment download failed\");\n if (downloaded.data.size !== Number(artifact.sizeBytes)) throw new Error(\"Attachment size mismatch\");\n return { body: new Uint8Array(await downloaded.data.arrayBuffer()), sha256: artifact.sha256 };\n },\n async recordAttachment(input, result) {\n if (result.reviewId !== input.reviewId || result.version !== input.version\n || result.artifactId !== input.artifactId || !/^[a-f0-9]{64}$/.test(result.sha256)\n || !Number.isSafeInteger(result.bytes) || result.bytes < 1 || result.bytes > 1048576) {\n throw new Error(\"Invalid attachment result\");\n }\n return database.transaction(async tx => {\n await authorize(tx, input);\n await tx.query(\n \"INSERT INTO public.starter_attachment_results VALUES($1,$2,$3,$4::text::jsonb) ON CONFLICT DO NOTHING\",\n [input.reviewId, input.version, input.artifactId, JSON.stringify(result)],\n );\n const saved = rows(await tx.query(\n \"SELECT result FROM public.starter_attachment_results WHERE review_id=$1 AND version=$2 AND artifact_id=$3\",\n [input.reviewId, input.version, input.artifactId],\n ))[0]?.[\"result\"];\n const value = durableResult(saved);\n if (value.reviewId !== result.reviewId || value.version !== result.version || value.artifactId !== result.artifactId\n || value.sha256 !== result.sha256 || value.bytes !== result.bytes) {\n throw new Error(\"Conflicting durable attachment result\");\n }\n return value;\n });\n },\n },\n };\n}\n";
@@ -0,0 +1,14 @@
1
+ export declare const STARTER_DATABASE_CONFIG: {
2
+ version: number;
3
+ schema: string[];
4
+ functions: string;
5
+ migrations: string;
6
+ contracts: string;
7
+ audit: string;
8
+ consumers: string[];
9
+ auditConsumers: never[];
10
+ role: string;
11
+ };
12
+ export declare const STARTER_DATABASE_SCHEMA = "import { sql } from \"drizzle-orm\";\nimport { boolean, check, integer, jsonb, pgTable, primaryKey, text, uuid } from \"drizzle-orm/pg-core\";\n\n// Structure for the reviewed starter migrations. Grants/policies stay in those migrations.\nexport const application = pgTable.withRLS(\"starter_application\", {\n singleton: boolean().default(true).primaryKey(),\n projectId: text(\"project_id\").notNull(),\n tenantId: text(\"tenant_id\").notNull(),\n}, table => [\n check(\"starter_application_singleton_check\", sql`${table.singleton}`),\n check(\"starter_application_project_id_check\", sql`length(${table.projectId}) > 0`),\n check(\"starter_application_tenant_id_check\", sql`length(${table.tenantId}) > 0`),\n]);\n\nexport const reviews = pgTable.withRLS(\"starter_reviews\", {\n id: text().primaryKey(),\n ownerId: text(\"owner_id\").notNull(),\n state: text({ enum: [\"draft\", \"approved\"] }).notNull(),\n version: integer().notNull(),\n}, table => [\n check(\"starter_reviews_state_check\", sql`${table.state} IN ('draft', 'approved')`),\n check(\"starter_reviews_version_check\", sql`${table.version} > 0`),\n]);\n\nexport const members = pgTable.withRLS(\"starter_members\", {\n subject: text().primaryKey(),\n enabled: boolean().default(true).notNull(),\n canApprove: boolean(\"can_approve\").default(false).notNull(),\n storageSubject: uuid(\"storage_subject\").unique(),\n});\n\nexport const attachments = pgTable.withRLS(\"starter_attachments\", {\n reviewId: text(\"review_id\").primaryKey().references(() => reviews.id),\n artifactId: uuid(\"artifact_id\").notNull().unique(),\n ownerId: text(\"owner_id\").notNull(),\n runId: uuid(\"run_id\").notNull().unique(),\n objectPath: text(\"object_path\").notNull().unique(),\n});\n\nexport const attachmentResults = pgTable.withRLS(\"starter_attachment_results\", {\n reviewId: text(\"review_id\").notNull().references(() => reviews.id),\n version: integer().notNull(),\n artifactId: uuid(\"artifact_id\").notNull(),\n result: jsonb().$type<unknown>().notNull(),\n}, table => [\n primaryKey({ columns: [table.reviewId, table.version, table.artifactId] }),\n check(\"starter_attachment_results_version_check\", sql`${table.version} >= 2`),\n]);\n\nexport type ReviewRow = typeof reviews.$inferSelect;\nexport type NewReview = typeof reviews.$inferInsert;\n";
13
+ export declare const STARTER_DATABASE_GUIDE = "\n## Database Source Workflow\n\nDrizzle owns table declarations and inferred row types in db/schema.ts.\nUse the existing @supacloud/db/drizzle-bun adapters for typed queries and governed\ntransactions; never replace permission, idempotency or audit checks with plain CRUD.\nOpaque JSON still needs runtime decoding.\n\nAfter installing dependencies, run `bun run db:generate` once and commit db/contracts/.\nThe default `bun run check` and build fail on stale database contracts before any\ncompiler regeneration. After an intentional source change, run db:generate again,\nreview the diff, then run check. Ordinary checks are offline.\n\n- Maintain one schema-qualified function with explicit grants per db/functions/*.sql.\n- Preserve migrations/ and its existing executor/ledger. Add forward migrations only.\n- `bun run db:diff` generates Drizzle drafts under db/migration-candidates/, never\n into the deployment directory. The starter has existing SQL history, so establish\n and review a matching Drizzle baseline before promoting any draft; the first\n draft is not automatically an incremental production migration.\n- `bun run db:pull` requires an explicitly selected DATABASE_URL and writes only\n output/database-audit/drizzle-candidate/. Review candidates; never overwrite source blindly.\n- Full SQL dumps belong under the ignored output/database-audit/ directory. They are\n audit/recovery artifacts, never ordinary type, application or test inputs.\n- `bun run db:assess` explains adoption/findings without writing or connecting.\n- GraphQL/PostgREST exports remain legitimate protocol contracts. This check is not\n proof of deployed schema, effective role permissions or complete RLS equivalence.\n\nDo not introduce any/@ts-ignore to bypass a contract. Use Drizzle-inferred types,\npreserve bigint/numeric precision and decode unknown results at the boundary.\n";
14
+ export declare function starterDatabaseFiles(): Record<string, string>;
@@ -0,0 +1,2 @@
1
+ export declare const STARTER_ENVIRONMENT: string;
2
+ export declare const STARTER_ENVIRONMENT_TEST: string;
@@ -0,0 +1,4 @@
1
+ export declare const STARTER_REVIEW_SCHEMA = "-- Apply with the platform migration owner, never from the HTTP process.\n-- One application database belongs to one explicitly provisioned project/tenant.\nCREATE TABLE public.starter_application (\n singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),\n project_id text NOT NULL CHECK (length(project_id) > 0),\n tenant_id text NOT NULL CHECK (length(tenant_id) > 0)\n);\nCREATE TABLE public.starter_reviews (\n id text PRIMARY KEY, owner_id text NOT NULL,\n state text NOT NULL CHECK (state IN ('draft','approved')),\n version integer NOT NULL CHECK (version > 0)\n);\nCREATE TABLE public.starter_members (\n subject text PRIMARY KEY, enabled boolean NOT NULL DEFAULT true,\n can_approve boolean NOT NULL DEFAULT false\n);\nALTER TABLE public.starter_application ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.starter_reviews ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.starter_members ENABLE ROW LEVEL SECURITY;\n";
2
+ export declare const STARTER_REVIEW_POSTGRES = "import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { createTransactionalCommand, plaintextCommandInput } from \"@supacloud/commands\";\nimport { CommandError } from \"@supacloud/contracts\";\nimport { createPostgresCommandStore, type CommandDatabase, type CommandTransaction } from \"@supacloud/db\";\nimport {\n ApplicationError, createSupAuthRequestContext, requireTrustedIdentity, requireIdempotencyKey,\n type CommandInvocation, type SupAuthContextOptions,\n} from \"@supacloud/elysia\";\nimport type { AppAdapters } from \"../application\";\nimport type { ReviewStore } from \"../review/review\";\nimport type { ReviewUploadPort } from \"../review/uploads\";\n\ninterface Approval { id: string; expectedVersion: number }\ninterface Review { state: \"draft\" | \"approved\"; version: number }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction record(value: unknown): Record<string, unknown> {\n if (!isRecord(value)) throw new TypeError(\"Invalid review record\");\n return value;\n}\nfunction rows(value: unknown): Record<string, unknown>[] {\n if (!Array.isArray(value)) throw new TypeError(\"Invalid database result\");\n return value.map(record);\n}\nfunction isPublicConflict(value: unknown): boolean {\n return value instanceof ApplicationError && value.status === 409\n || isRecord(value) && value[\"expose\"] === true && value[\"status\"] === 409 && typeof value[\"code\"] === \"string\";\n}\nfunction input(value: unknown): Approval {\n const item = record(value);\n if (typeof item[\"id\"] !== \"string\" || !item[\"id\"].length || typeof item[\"expectedVersion\"] !== \"number\"\n || !Number.isSafeInteger(item[\"expectedVersion\"]) || item[\"expectedVersion\"] < 1) throw new TypeError(\"Invalid approval input\");\n return { id: item[\"id\"], expectedVersion: item[\"expectedVersion\"] };\n}\nfunction result(value: unknown): Review {\n const item = record(value);\n if ((item[\"state\"] !== \"draft\" && item[\"state\"] !== \"approved\") || typeof item[\"version\"] !== \"number\"\n || !Number.isSafeInteger(item[\"version\"]) || item[\"version\"] < 1) throw new TypeError(\"Invalid review result\");\n return { state: item[\"state\"], version: item[\"version\"] };\n}\n\nexport interface ReviewPostgresOptions {\n database: CommandDatabase;\n tenantId: string;\n identity: Omit<SupAuthContextOptions, \"resolveAccess\">;\n uploads?: ReviewUploadPort;\n // Only database effects here: they commit/rollback with the approval and receipt.\n afterApproved?(tx: CommandTransaction, id: string, version: number): Promise<void>;\n}\n\nexport async function createReviewPostgresAdapters(options: ReviewPostgresOptions): Promise<\n AppAdapters & { requestContext: ReturnType<typeof createSupAuthRequestContext> }\n> {\n const { database, tenantId, identity } = options;\n const projectId = identity.projectId;\n const afterApproved = options.afterApproved;\n if (!tenantId.trim()) throw new TypeError(\"Review tenantId is required\");\n const query = (text: string, parameters: readonly string[] = []) =>\n database.transaction(tx => tx.query(text, parameters));\n const binding = rows(await query(\n \"SELECT project_id,tenant_id FROM public.starter_application WHERE singleton\",\n ));\n if (binding.length !== 1 || binding[0]?.[\"project_id\"] !== projectId || binding[0]?.[\"tenant_id\"] !== tenantId) {\n throw new Error(\"Review database project/tenant binding mismatch\");\n }\n const requestContext = createSupAuthRequestContext({\n ...identity,\n async resolveAccess(who) {\n const found = rows(await query(\n \"SELECT m.can_approve FROM public.starter_members m,public.starter_application a \" +\n \"WHERE m.subject=$1 AND m.enabled AND a.singleton AND a.project_id=$2 AND a.tenant_id=$3\",\n [who.subject, projectId, tenantId],\n ));\n return found.length === 1 ? { projectId, tenantId,\n permissions: found[0]?.[\"can_approve\"] === true ? [\"review.approve\"] : [],\n } : null;\n },\n });\n const scope = new AsyncLocalStorage<CommandTransaction>();\n const transaction = () => {\n const tx = scope.getStore();\n if (!tx) throw new Error(\"Review storage requires its durable command transaction\");\n return tx;\n };\n const store: ReviewStore = {\n ...options.uploads,\n async get(table, id) {\n if (table !== \"reviews\") throw new Error(\"Unsupported review table\");\n return rows(await transaction().query(\n \"SELECT state,version FROM public.starter_reviews WHERE id=$1 FOR UPDATE\", [id],\n ))[0];\n },\n async set(table, id, value) {\n if (table !== \"reviews\") throw new Error(\"Unsupported review table\");\n const review = result(value);\n const updated = rows(await transaction().query(\n \"UPDATE public.starter_reviews SET state=$2,version=$3 WHERE id=$1 RETURNING id\",\n [id, review.state, review.version],\n ));\n if (updated.length !== 1) throw new Error(\"Review write did not update one record\");\n await afterApproved?.(transaction(), id, review.version);\n },\n };\n function actor(invocation: CommandInvocation): string {\n const who = requireTrustedIdentity(invocation.requestContext);\n const access = record(record(invocation.requestContext)[\"access\"]);\n if (access[\"projectId\"] !== projectId || access[\"tenantId\"] !== tenantId\n || !Array.isArray(access[\"permissions\"]) || !access[\"permissions\"].includes(\"review.approve\")) {\n throw new ApplicationError(\"Command permission denied\", { status: 403, code: \"REVIEW_PERMISSION_DENIED\" });\n }\n return who.subject;\n }\n const commandStore = createPostgresCommandStore(database);\n return {\n deps: { dbClient: store }, requestContext,\n commandGovernance: {\n authorize(invocation) {\n const command = invocation.command;\n if (command.name !== \"review.approve\" || command.permission !== \"review.approve\"\n || command.transaction !== \"required\" || command.idempotency !== \"required\"\n || command.audit !== \"review.approved\" || command.rpc !== undefined) {\n throw new ApplicationError(\"Unsupported review command\", { status: 501, code: \"REVIEW_COMMAND_UNSUPPORTED\" });\n }\n actor(invocation);\n },\n async idempotency(invocation, next) {\n const command = createTransactionalCommand({\n name: invocation.command.name, store: commandStore,\n inputCodec: plaintextCommandInput, input, result,\n async authorize(who, request, tx) {\n const found = rows(await tx.query(\n \"SELECT r.id FROM public.starter_reviews r JOIN public.starter_members m ON m.subject=r.owner_id \" +\n \"CROSS JOIN public.starter_application a \" +\n \"WHERE r.id=$1 AND r.owner_id=$2 AND m.enabled AND m.can_approve \" +\n \"AND a.singleton AND a.project_id=$3 AND a.tenant_id=$4 FOR UPDATE OF r,m FOR SHARE OF a\",\n [request.id, who.actorId, projectId, who.tenantId],\n ));\n return found.length === 1 ? \"allow\" : \"deny\";\n },\n execute: tx => scope.run(tx, async () => {\n try { return await next(); }\n catch (error) {\n if (isPublicConflict(error)) {\n throw new CommandError(\"COMMAND_IDEMPOTENCY_CONFLICT\");\n }\n throw error;\n }\n }),\n audit: { event: \"review.approved\", details: request => ({ reviewId: request.id }) },\n });\n try {\n const receipt = await command.execute(\n { actorId: actor(invocation), tenantId }, requireIdempotencyKey(invocation),\n { id: invocation.input.params[\"id\"], expectedVersion: record(invocation.input.body)[\"expectedVersion\"] },\n invocation.request.signal,\n );\n if (receipt.status !== \"confirmed\") throw new CommandError(\"COMMAND_OUTCOME_UNKNOWN\");\n return receipt.result;\n } catch (error) {\n if (!(error instanceof CommandError)) throw error;\n const status = error.code === \"COMMAND_REJECTED\" ? 403\n : error.code === \"COMMAND_IDEMPOTENCY_CONFLICT\" ? 409 : 503;\n throw new ApplicationError(error.code, { code: error.code, status });\n }\n },\n transaction(_invocation, next) { transaction(); return next(); },\n // The durable command owns the authoritative audit and receipt in the same transaction.\n audit: { succeeded() { transaction(); }, failed() {} },\n },\n };\n}\n";
3
+ export declare const STARTER_REVIEW_POSTGRES_TEST = "import { expect, test } from \"bun:test\";\nimport { createReviewPostgresAdapters } from \"../src/host/review-postgres\";\nimport type { ReviewStore } from \"../src/review/review\";\n\nconst identity = {\n issuer: \"https://identity.example.test\", audience: \"review-test\", clientId: \"review-client\",\n projectId: \"review-test\", jwksUrl: \"https://identity.example.test/jwks\",\n};\nfunction database(binding: unknown) {\n const queries: string[] = [];\n return {\n queries,\n async transaction<T>(run: (tx: { query(text: string): Promise<unknown> }) => Promise<T>): Promise<T> {\n return run({ async query(text) { queries.push(text); return binding; } });\n },\n };\n}\n\ntest(\"PostgreSQL host rejects missing or mismatched application ownership without creating data\", async () => {\n for (const binding of [[], [{ project_id: \"other\", tenant_id: \"review-test\" }],\n [{ project_id: \"review-test\", tenant_id: \"other\" }]]) {\n const db = database(binding);\n await expect(createReviewPostgresAdapters({ database: db, identity, tenantId: \"review-test\" }))\n .rejects.toThrow(\"binding mismatch\");\n expect(db.queries).toEqual([\"SELECT project_id,tenant_id FROM public.starter_application WHERE singleton\"]);\n }\n});\n\ntest(\"PostgreSQL host never falls back to demo identity or unbound business storage\", async () => {\n const db = database([{ project_id: \"review-test\", tenant_id: \"review-test\" }]);\n const adapters = await createReviewPostgresAdapters({ database: db, identity, tenantId: \"review-test\" });\n await expect(adapters.requestContext(new Request(\"http://localhost/reviews/health\")))\n .rejects.toMatchObject({ code: \"AUTHENTICATION_REQUIRED\" });\n const store = adapters.deps[\"dbClient\"] as ReviewStore;\n await expect(store.get(\"reviews\", \"example\")).rejects.toThrow(\"durable command transaction\");\n await expect(store.set(\"reviews\", \"example\", { state: \"approved\", version: 2 }))\n .rejects.toThrow(\"durable command transaction\");\n expect(db.queries).toHaveLength(1);\n});\n";
4
+ export declare const STARTER_REVIEW_DELIVERY_HOST = "import { SQL } from \"bun\";\nimport { createClient } from \"@supabase/supabase-js\";\nimport { createBunCommandDatabase } from \"@supacloud/db/bun\";\nimport { createApplication, type CompiledModule } from \"@supacloud/elysia\";\nimport { createReviewPostgresAdapters } from \"./host/review-postgres\";\nimport { createReviewAttachmentAdapters } from \"./host/review-attachments\";\nimport { createReviewUploadAdapters } from \"./host/review-uploads\";\n\nfunction required(name: string): string {\n const value = process.env[name];\n if (!value?.trim()) throw new Error(\"Missing required host setting: \" + name);\n return value;\n}\n\nfunction databaseConnection(): SQL.Options {\n const socket = process.env[\"DATABASE_SOCKET_PATH\"];\n if (socket && process.env[\"DATABASE_URL\"]) throw new Error(\"Conflicting database connection settings\");\n if (socket) {\n return { adapter: \"postgres\", path: socket, database: required(\"DATABASE_NAME\"), username: required(\"DATABASE_USER\") };\n }\n const url = new URL(required(\"DATABASE_URL\"));\n if (url.protocol !== \"postgres:\" && url.protocol !== \"postgresql:\") throw new Error(\"Invalid database URL\");\n return {\n adapter: \"postgres\", url: url.href, hostname: url.hostname.replace(/^\\[|\\]$/g, \"\"), port: Number(url.port || 5432),\n username: decodeURIComponent(url.username), password: decodeURIComponent(url.password),\n database: decodeURIComponent(url.pathname.slice(1)),\n };\n}\n\nexport async function createDeliveryApplication(modules: CompiledModule[], lifecycle: { signal: AbortSignal }) {\n lifecycle.signal.throwIfAborted();\n const connection = databaseConnection();\n const attachments = process.env[\"REVIEW_ATTACHMENTS\"];\n if (attachments !== undefined && attachments !== \"enabled\") throw new Error(\"Invalid attachment configuration\");\n const settings = {\n tenantId: required(\"APP_TENANT_ID\"),\n identity: {\n issuer: required(\"SUPAUTH_ISSUER\"), audience: required(\"SUPAUTH_AUDIENCE\"),\n clientId: required(\"SUPAUTH_CLIENT_ID\"), projectId: required(\"SUPACLOUD_PROJECT_ID\"),\n jwksUrl: required(\"SUPAUTH_JWKS_URL\"),\n },\n };\n const pool = new SQL({ ...connection, max: 12, connectionTimeout: 5 });\n let closing: Promise<void> | undefined;\n const close = () => closing ??= pool.close({ timeout: 5 });\n let stage: \"attachments\" | \"uploads\" | \"postgres\" | \"application\" = \"attachments\";\n try {\n const database = createBunCommandDatabase(pool);\n const attachmentOptions = attachments === \"enabled\" ? {\n database, tenantId: settings.tenantId, projectId: settings.identity.projectId,\n service: createClient(required(\"SUPACLOUD_URL\"), required(\"SUPACLOUD_SERVICE_ROLE_KEY\"), {\n auth: { persistSession: false, autoRefreshToken: false, detectSessionInUrl: false },\n }),\n } : undefined;\n const durable = attachmentOptions ? await createReviewAttachmentAdapters(attachmentOptions) : undefined;\n stage = \"uploads\";\n const uploads = attachmentOptions ? await createReviewUploadAdapters(attachmentOptions) : undefined;\n stage = \"postgres\";\n const adapters = await createReviewPostgresAdapters({\n database, tenantId: settings.tenantId, identity: settings.identity,\n ...(uploads ? { uploads } : {}),\n ...(durable ? { afterApproved: durable.enqueue } : {}),\n });\n lifecycle.signal.throwIfAborted();\n stage = \"application\";\n const app = createApplication({ name: \"review\", modules, ...adapters });\n return { fetch: (request: Request) => app.handle(request), close };\n } catch (error) {\n await close();\n const code = error !== null && typeof error === \"object\" && \"code\" in error\n && typeof error.code === \"string\" && /^[A-Z0-9_]{1,64}$/.test(error.code) ? error.code : \"UNKNOWN\";\n console.error(JSON.stringify({ event: \"review-host-initialization-failed\", stage, code }));\n // Database/identity errors can contain connection details; never echo the underlying cause.\n throw new Error(\"Review host initialization failed\");\n }\n}\n";
@@ -0,0 +1,4 @@
1
+ /** Shared reactive conventions for command, HTTP and edge starters. */
2
+ export declare const STARTER_REACTIVE_GUIDE = "# Reactive development / \u54CD\u5E94\u5F0F\u5F00\u53D1\n\nRxJS is installed by default. Ordinary queries and business commands still use\nasync/await. Use RxJS for ongoing events and timing/composition, not as a new\ntransaction, retry or durable-delivery system.\n\nHost defaults:\n- In a native Angular host, use signal/resource APIs from @supacloud/app/angular\n and Angular's scheduler. Do not mix them with the legacy root signal graph.\n- In framework-neutral code, use async/await for one result and RxJS for event\n composition. The host owns cancellation through an explicit signal.\n\n- Import native operators from rxjs; do not invent another event/operator library.\n- Import takeUntilAborted / toReadableStream from @supacloud/app/reactive.\n- Every subscription belongs to a request, connection, task or application owner.\n Bind the owner's AbortSignal before starting work; release it on exit.\n- toReadableStream has a bounded item buffer and fails on overflow. It is not\n an event journal. Do not silently drop durable business events or audit records.\n- Never wrap command submission in defer/retry/repeat or submit from subscribe.\n Await the existing governed command and then observe its existing receipt.\n- Do not shareReplay authenticated values globally across projects or sessions.\n- subscribe(async ...) does not await processing. Keep acknowledged task-output\n consumers on the SDK's for-await watch path; do not advance onCursor via next().\n\n## SupaCloud JS\n\nThe platform SDK is @supacloud/js, not a replacement Supabase client. Keep the\nclient from createSupaCloudClient. Database/Auth/Storage calls use client.supabase;\nplatform commands/tasks use the existing client.commands / client.tasks APIs.\nFor applications using this SDK, import observeQuery and observeTask from\n@supacloud/js/reactive. Pass the request signal to the native query builder:\n\n observeQuery(signal => client.supabase.from(\"widgets\").select(\"id\").abortSignal(signal), { signal: owner.signal })\n\nThe query result keeps data/error/count/status; non-null errors enter error().\nThe query factory runs once per subscriber. This is intended for reads, not\nreplaying writes. A PromiseLike is supported; an already-started Promise is not\nan appropriate factory. Unsubscribe aborts cooperative I/O, not committed work.\n\nSubmit a background task ONCE with the existing SDK, then observe the receipt:\n\n const task = await client.functions.invokeBackground(\"render\", { body: input });\n const progress = observeTask(task, { signal: owner.signal });\n\nUnsubscribing progress does not cancel the server task. Task read/decoder errors\nterminate observation; there is no automatic business retry. Realtime-to-polling\nfallback and project validation remain the SDK's responsibilities. Do not invent\na public.tasks table or copy service credentials into browser code.\n\nRun bun run check. Add cancellation, error, overflow and cross-owner isolation\ncoverage whenever adding streaming behavior. These APIs require the matching\nframework/SDK release or the packed candidate packages used by repository CI.\n";
3
+ export declare const STARTER_REACTIVE_AGENTS = "# Application development rules\n\nRead README.md and REACTIVE.md before adding asynchronous features. If the\napplication is hosted by Angular, read the @supacloud/app/angular guidance\nbefore choosing a signal API.\nPrefer the generated contracts and existing project-bound @supacloud/js client.\nUse async/await for a single result; RxJS is the default event-composition library.\nBind subscriptions to an explicit owner and propagate cancellation to native I/O.\nNever automatically retry a write or turn observable delivery into durable ack.\nDo not bypass authorization, transactions, idempotency or audit to simplify code.\nRun bun run check; include negative and cleanup tests. Never store live credentials.\n";
4
+ export declare const STARTER_REACTIVE_TEST = "import { expect, test } from \"bun:test\";\nimport { Observable } from \"rxjs\";\nimport { takeUntilAborted, toReadableStream } from \"@supacloud/app/reactive\";\n\ntest(\"the owner's signal releases reactive work exactly once\", () => {\n const owner = new AbortController();\n let starts = 0, stops = 0;\n const source = new Observable<number>(() => { starts++; return () => { stops++; }; });\n source.pipe(takeUntilAborted(owner.signal)).subscribe();\n owner.abort();\n source.pipe(takeUntilAborted(owner.signal)).subscribe();\n expect(starts).toBe(1);\n expect(stops).toBe(1);\n});\n\ntest(\"cancelled stream consumers release their subscription\", async () => {\n let stops = 0;\n const source = new Observable<number>(() => () => { stops++; });\n const stream = toReadableStream(source);\n await stream.cancel();\n expect(stops).toBe(1);\n});\n";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare const STARTER_REVIEW_JOB = "import { DB_CLIENT, Inject, Injectable, Job } from \"@supacloud/app\";\nimport { ApplicationError } from \"@supacloud/elysia\";\nimport { t } from \"elysia\";\nimport type { Static } from \"elysia/type\";\n\nexport const AttachmentInput = t.Object({\n reviewId: t.String({ minLength: 1, maxLength: 100 }),\n version: t.Integer({ minimum: 2 }),\n artifactId: t.String({ format: \"uuid\" }),\n}, { additionalProperties: false });\n\nexport const AttachmentResult = t.Object({\n ...AttachmentInput.properties,\n sha256: t.String({ pattern: \"^[a-f0-9]{64}$\" }),\n bytes: t.Integer({ minimum: 1, maximum: 1048576 }),\n}, { additionalProperties: false });\n\nexport type ReviewAttachmentInput = Static<typeof AttachmentInput>;\nexport type ReviewAttachmentResult = Static<typeof AttachmentResult>;\n\nexport interface ReviewAttachmentStore {\n // Resolve the immutable artifact under the worker's own authorization and current review revision.\n readAttachment(input: ReviewAttachmentInput): Promise<{ body: Uint8Array; sha256: string }>;\n // Revalidate revision/ownership and persist idempotently in a transaction; return the durable result.\n recordAttachment(input: ReviewAttachmentInput, result: ReviewAttachmentResult): Promise<ReviewAttachmentResult>;\n}\n\n@Injectable({ scope: \"job\" })\n@Job({\n name: \"review.verify-attachment\", mode: \"task\", idempotency: \"required\",\n timeoutSec: 30, maxAttempts: 3, input: AttachmentInput, output: AttachmentResult,\n})\nexport class VerifyReviewAttachment {\n constructor(@Inject(DB_CLIENT) private readonly store: ReviewAttachmentStore) {}\n\n async run(input: ReviewAttachmentInput): Promise<ReviewAttachmentResult> {\n const source = await this.store.readAttachment(input);\n if (source.body.byteLength < 1 || source.body.byteLength > 1048576) {\n throw new ApplicationError(\"Attachment size is outside the supported range\", { code: \"ATTACHMENT_SIZE_INVALID\" });\n }\n const digest = await crypto.subtle.digest(\"SHA-256\", new Uint8Array(source.body));\n const sha256 = Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, \"0\")).join(\"\");\n if (sha256 !== source.sha256) {\n throw new ApplicationError(\"Attachment integrity check failed\", { code: \"ATTACHMENT_INTEGRITY_FAILED\" });\n }\n return this.store.recordAttachment(input, { ...input, sha256, bytes: source.body.byteLength });\n }\n}\n";
2
+ export declare const STARTER_REVIEW_JOB_TEST = "import { expect, test } from \"bun:test\";\nimport { VerifyReviewAttachment, type ReviewAttachmentResult } from \"../src/review/attachment\";\n\nconst input = { reviewId: \"review\", version: 2, artifactId: \"11111111-1111-4111-8111-111111111111\" };\nconst body = new TextEncoder().encode(\"review attachment\");\nconst sha256 = Array.from(new Uint8Array(await crypto.subtle.digest(\"SHA-256\", body)),\n byte => byte.toString(16).padStart(2, \"0\")).join(\"\");\n\ntest(\"attachment job awaits the authorized reader and the committed result\", async () => {\n let recorded: ReviewAttachmentResult | undefined;\n const job = new VerifyReviewAttachment({\n async readAttachment() { return { body, sha256 }; },\n async recordAttachment(_input, result) { await Promise.resolve(); recorded = result; return result; },\n });\n expect(await job.run(input)).toEqual({ ...input, sha256, bytes: body.byteLength });\n expect(recorded).toEqual({ ...input, sha256, bytes: body.byteLength });\n});\n\ntest(\"missing authorization, corrupted bytes and persistence failures are not successful jobs\", async () => {\n let records = 0;\n for (const readAttachment of [\n async () => { throw new Error(\"Access denied\"); },\n async () => ({ body, sha256: \"0\".repeat(64) }),\n async () => ({ body: new Uint8Array(), sha256 }),\n async () => ({ body: new Uint8Array(1048577), sha256 }),\n ]) {\n const job = new VerifyReviewAttachment({\n readAttachment, async recordAttachment(_input, result) { records++; return result; },\n });\n await expect(job.run(input)).rejects.toThrow();\n }\n expect(records).toBe(0);\n await expect(new VerifyReviewAttachment({\n async readAttachment() { return { body, sha256 }; },\n async recordAttachment() { throw new Error(\"Commit failed\"); },\n }).run(input)).rejects.toThrow(\"Commit failed\");\n});\n";
@@ -0,0 +1,2 @@
1
+ export declare function renderStarterRuntimeRolesSchema(httpRole: string, workerRole: string): string;
2
+ export declare const STARTER_RUNTIME_ROLES_SCHEMA: string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export type StarterTemplate = "minimal" | "http" | "command" | "edge";
2
+ export interface StarterSdkDependencies {
3
+ "@supacloud/js": string;
4
+ "@supabase/supabase-js": string;
5
+ }
6
+ /** Explicit recipes retain their existing layouts; new projects default to minimal. */
7
+ export declare function appTemplateFiles(name: string, template: StarterTemplate, sdkDependencies?: StarterSdkDependencies): Record<string, string>;
@@ -0,0 +1,3 @@
1
+ export declare const STARTER_UPLOAD_FEATURE = "import { Effect } from \"effect\";\nimport { Body, Controller, Inject, Injectable, Param, Post, DB_CLIENT, REQUEST_CONTEXT } from \"@supacloud/app\";\nimport { ApplicationError } from \"@supacloud/elysia\";\nimport { UploadBody, UploadLocation, UploadParams, UploadResult } from \"./contracts\";\n\nexport interface ReviewUploadInput { reviewId: string; artifactId: string; expectedVersion: number }\nexport interface ReviewUploadLocation { artifactId: string; bucketId: string; objectPath: string }\nexport interface ReviewUploadResult { artifactId: string; runId: string; objectPath: string; sha256: string; bytes: number }\ntype PreviewUploadError =\n | { readonly _tag: \"AUTHENTICATION_REQUIRED\" }\n | { readonly _tag: \"UPLOAD_INPUT_INVALID\" }\n | { readonly _tag: \"UPLOAD_DENIED\" }\n | { readonly _tag: \"UPLOADS_UNAVAILABLE\" };\ntype RegisterUploadError =\n | PreviewUploadError\n | { readonly _tag: \"UPLOAD_CONTENT_INVALID\" }\n | { readonly _tag: \"COMMAND_IDEMPOTENCY_CONFLICT\" }\n | { readonly _tag: \"COMMAND_REJECTED\" }\n | { readonly _tag: \"UPLOAD_OUTCOME_UNKNOWN\" };\nexport interface ReviewUploadPort {\n previewUpload(context: unknown, input: ReviewUploadInput): Promise<ReviewUploadLocation>;\n registerUpload(context: unknown, input: ReviewUploadInput): Promise<ReviewUploadResult>;\n}\n@Injectable({ scope: \"request\" })\nexport class ReviewUploads {\n constructor(@Inject(DB_CLIENT) private readonly store: Partial<ReviewUploadPort>,\n @Inject(REQUEST_CONTEXT) private readonly context: unknown) {}\n\n preview(input: ReviewUploadInput): Promise<ReviewUploadLocation> {\n if (!this.store.previewUpload) throw new ApplicationError(\"Attachment uploads are not configured\", { status: 501, code: \"UPLOADS_UNAVAILABLE\" });\n return this.store.previewUpload(this.context, input);\n }\n register(input: ReviewUploadInput): Promise<ReviewUploadResult> {\n if (!this.store.registerUpload) throw new ApplicationError(\"Attachment uploads are not configured\", { status: 501, code: \"UPLOADS_UNAVAILABLE\" });\n return this.store.registerUpload(this.context, input);\n }\n}\n\n@Controller(\"/reviews\")\nexport class ReviewUploadsController {\n constructor(@Inject(ReviewUploads) private readonly uploads: ReviewUploads) {}\n\n @Post(\"/:id/attachment-upload\", {\n params: UploadParams,\n body: UploadBody,\n responses: { 200: UploadLocation },\n effect: {\n required: true,\n dependencies: [],\n errors: [\n { tag: \"AUTHENTICATION_REQUIRED\", status: 401, code: \"AUTHENTICATION_REQUIRED\" },\n { tag: \"UPLOAD_INPUT_INVALID\", status: 400, code: \"UPLOAD_INPUT_INVALID\" },\n { tag: \"UPLOAD_DENIED\", status: 403, code: \"UPLOAD_DENIED\" },\n { tag: \"UPLOADS_UNAVAILABLE\", status: 501, code: \"UPLOADS_UNAVAILABLE\" },\n ],\n retry: \"none\",\n },\n })\n preview(@Param(\"id\") id: string, @Body() body: { artifactId: string; expectedVersion: number }): Effect.Effect<ReviewUploadLocation, PreviewUploadError, never> {\n return Effect.promise(() => this.uploads.preview({ reviewId: id, artifactId: body.artifactId, expectedVersion: body.expectedVersion }));\n }\n @Post(\"/:id/attachment-registration\", {\n params: UploadParams,\n body: UploadBody,\n responses: { 200: UploadResult },\n effect: {\n required: true,\n dependencies: [],\n errors: [\n { tag: \"AUTHENTICATION_REQUIRED\", status: 401, code: \"AUTHENTICATION_REQUIRED\" },\n { tag: \"UPLOAD_INPUT_INVALID\", status: 400, code: \"UPLOAD_INPUT_INVALID\" },\n { tag: \"UPLOAD_DENIED\", status: 403, code: \"UPLOAD_DENIED\" },\n { tag: \"UPLOAD_CONTENT_INVALID\", status: 400, code: \"UPLOAD_CONTENT_INVALID\" },\n { tag: \"COMMAND_IDEMPOTENCY_CONFLICT\", status: 409, code: \"COMMAND_IDEMPOTENCY_CONFLICT\" },\n { tag: \"COMMAND_REJECTED\", status: 403, code: \"COMMAND_REJECTED\" },\n { tag: \"UPLOADS_UNAVAILABLE\", status: 501, code: \"UPLOADS_UNAVAILABLE\" },\n { tag: \"UPLOAD_OUTCOME_UNKNOWN\", status: 503, code: \"UPLOAD_OUTCOME_UNKNOWN\" },\n ],\n retry: \"none\",\n },\n })\n register(@Param(\"id\") id: string, @Body() body: { artifactId: string; expectedVersion: number }): Effect.Effect<ReviewUploadResult, RegisterUploadError, never> {\n return Effect.promise(() => this.uploads.register({ reviewId: id, artifactId: body.artifactId, expectedVersion: body.expectedVersion }));\n }\n}\n";
2
+ export declare const STARTER_UPLOAD_SCHEMA = "-- Apply explicitly with the migration owner.\nALTER TABLE public.starter_members ADD COLUMN storage_subject uuid UNIQUE;\nGRANT SELECT (subject,enabled,can_approve,storage_subject) ON public.starter_members TO authenticated;\nCREATE POLICY starter_member_read ON public.starter_members FOR SELECT TO authenticated\n USING (subject=COALESCE(NULLIF(auth.jwt()->>'external_sub',''),auth.jwt()->>'sub') AND storage_subject=auth.uid());\nGRANT SELECT ON public.starter_reviews TO authenticated;\nCREATE POLICY starter_review_read ON public.starter_reviews FOR SELECT TO authenticated\n USING (owner_id=COALESCE(NULLIF(auth.jwt()->>'external_sub',''),auth.jwt()->>'sub') AND EXISTS (\n SELECT 1 FROM public.starter_members m WHERE m.subject=owner_id\n AND m.enabled AND m.can_approve AND m.storage_subject=auth.uid()\n ));\nINSERT INTO storage.buckets(id,name,public,file_size_limit,allowed_mime_types)\n VALUES ('review-attachments','review-attachments',false,1048576,ARRAY['text/plain']);\nCREATE POLICY starter_attachment_read ON storage.objects FOR SELECT TO authenticated\n USING (bucket_id='review-attachments');\nCREATE POLICY starter_attachment_insert ON storage.objects FOR INSERT TO authenticated\n WITH CHECK (bucket_id='review-attachments');\n-- Restrictive fences also apply when the host already has permissive Storage policies.\nCREATE POLICY starter_attachment_owner_fence ON storage.objects AS RESTRICTIVE FOR ALL TO authenticated\n USING (bucket_id<>'review-attachments' OR (split_part(name,'/',1)=auth.uid()::text AND EXISTS (\n SELECT 1 FROM public.starter_reviews r JOIN public.starter_members m ON m.subject=r.owner_id\n WHERE r.id=split_part(name,'/',2) AND m.subject=COALESCE(NULLIF(auth.jwt()->>'external_sub',''),auth.jwt()->>'sub')\n AND m.storage_subject=auth.uid() AND m.enabled AND m.can_approve\n )))\n WITH CHECK (bucket_id<>'review-attachments' OR (split_part(name,'/',1)=auth.uid()::text\n AND name ~ '^[a-f0-9-]{36}/[a-f0-9-]{36}/[a-f0-9-]{36}[.]txt$' AND EXISTS (\n SELECT 1 FROM public.starter_reviews r JOIN public.starter_members m ON m.subject=r.owner_id\n WHERE r.id=split_part(name,'/',2) AND r.state='draft' AND m.subject=COALESCE(NULLIF(auth.jwt()->>'external_sub',''),auth.jwt()->>'sub')\n AND m.storage_subject=auth.uid() AND m.enabled AND m.can_approve\n )));\nCREATE POLICY starter_attachment_update_fence ON storage.objects AS RESTRICTIVE FOR UPDATE TO authenticated\n USING (bucket_id<>'review-attachments') WITH CHECK (bucket_id<>'review-attachments');\nCREATE POLICY starter_attachment_delete_fence ON storage.objects AS RESTRICTIVE FOR DELETE TO authenticated\n USING (bucket_id<>'review-attachments');\n";
3
+ export declare const STARTER_UPLOAD_ADAPTER = "import { MIMEType } from \"node:util\";\nimport { SupaCloudArtifactsClient } from \"@supacloud/js\";\nimport { createTransactionalCommand, plaintextCommandInput } from \"@supacloud/commands\";\nimport { CommandError } from \"@supacloud/contracts\";\nimport { createPostgresCommandStore, type CommandTransaction } from \"@supacloud/db\";\nimport { ApplicationError, requireTrustedIdentity } from \"@supacloud/elysia\";\nimport type { ReviewAttachmentOptions } from \"./review-attachments\";\nimport type { ReviewUploadInput, ReviewUploadPort, ReviewUploadResult } from \"../review/uploads\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction record(value: unknown): Record<string, unknown> {\n if (!isRecord(value)) throw new TypeError(\"Invalid upload record\");\n return value;\n}\nfunction rows(value: unknown): Record<string, unknown>[] {\n if (!Array.isArray(value)) throw new TypeError(\"Invalid upload database result\");\n return value.map(record);\n}\nfunction capture(value: unknown): ReviewUploadInput {\n const item = record(value), uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (typeof item[\"reviewId\"] !== \"string\" || !uuid.test(item[\"reviewId\"])\n || typeof item[\"artifactId\"] !== \"string\" || !uuid.test(item[\"artifactId\"])\n || typeof item[\"expectedVersion\"] !== \"number\" || !Number.isSafeInteger(item[\"expectedVersion\"]) || item[\"expectedVersion\"] < 1) {\n throw new ApplicationError(\"Invalid upload input\", { status: 400, code: \"UPLOAD_INPUT_INVALID\" });\n }\n return { reviewId: item[\"reviewId\"].toLowerCase(), artifactId: item[\"artifactId\"].toLowerCase(), expectedVersion: item[\"expectedVersion\"] };\n}\nfunction result(value: unknown): ReviewUploadResult {\n const item = record(value);\n if (typeof item[\"artifactId\"] !== \"string\" || typeof item[\"runId\"] !== \"string\" || typeof item[\"objectPath\"] !== \"string\"\n || typeof item[\"sha256\"] !== \"string\" || typeof item[\"bytes\"] !== \"number\") throw new TypeError(\"Invalid upload receipt\");\n return { artifactId: item[\"artifactId\"], runId: item[\"runId\"], objectPath: item[\"objectPath\"], sha256: item[\"sha256\"], bytes: item[\"bytes\"] };\n}\n\nexport async function createReviewUploadAdapters(options: ReviewAttachmentOptions): Promise<ReviewUploadPort> {\n const { database, service, projectId, tenantId } = options;\n const binding = rows(await database.transaction(tx => tx.query(\n \"SELECT project_id,tenant_id FROM public.starter_application WHERE singleton\",\n )));\n if (!projectId.trim() || !tenantId.trim() || binding.length !== 1\n || binding[0]?.[\"project_id\"] !== projectId || binding[0]?.[\"tenant_id\"] !== tenantId) {\n throw new Error(\"Upload database project/tenant binding mismatch\");\n }\n const artifacts = new SupaCloudArtifactsClient(service);\n function actor(context: unknown): string {\n const who = requireTrustedIdentity(context);\n const access = record(record(context)[\"access\"]);\n if (access[\"projectId\"] !== projectId || access[\"tenantId\"] !== tenantId\n || !Array.isArray(access[\"permissions\"]) || !access[\"permissions\"].includes(\"review.approve\")) {\n throw new ApplicationError(\"Upload permission denied\", { status: 403, code: \"UPLOAD_DENIED\" });\n }\n return who.subject;\n }\n async function authorize(tx: CommandTransaction, subject: string, input: ReviewUploadInput, replay = false) {\n const allowed = rows(await tx.query(\n \"SELECT m.storage_subject::text AS storage_subject FROM public.starter_reviews r \" +\n \"JOIN public.starter_members m ON m.subject=r.owner_id CROSS JOIN public.starter_application p \" +\n \"WHERE r.id=$1 AND r.owner_id=$2 AND ((r.state='draft' AND r.version=$3) OR \" +\n \"($6::boolean AND r.state='approved' AND r.version=$3+1 AND EXISTS (\" +\n \"SELECT 1 FROM public.starter_attachments b WHERE b.review_id=r.id AND b.artifact_id=$7::uuid AND b.owner_id=$2 \" +\n \"AND b.object_path=m.storage_subject::text || '/' || r.id || '/' || $7::text || '.txt'))) \" +\n \"AND m.enabled AND m.can_approve AND m.storage_subject IS NOT NULL \" +\n \"AND p.singleton AND p.project_id=$4 AND p.tenant_id=$5 FOR UPDATE OF r,m FOR SHARE OF p\",\n [input.reviewId, subject, input.expectedVersion, projectId, tenantId, replay, input.artifactId],\n ));\n if (allowed.length !== 1 || typeof allowed[0]?.[\"storage_subject\"] !== \"string\") {\n throw new ApplicationError(\"Upload ownership, permission or revision changed\", { status: 403, code: \"UPLOAD_DENIED\" });\n }\n return allowed[0][\"storage_subject\"] + \"/\" + input.reviewId + \"/\" + input.artifactId + \".txt\";\n }\n return {\n async previewUpload(context, value) {\n const input = capture(value), subject = actor(context);\n const objectPath = await database.transaction(tx => authorize(tx, subject, input));\n return { artifactId: input.artifactId, bucketId: \"review-attachments\", objectPath };\n },\n async registerUpload(context, value) {\n const input = capture(value), subject = actor(context);\n const objectPath = await database.transaction(tx => authorize(tx, subject, input, true));\n try {\n const downloaded = await service.storage.from(\"review-attachments\").download(objectPath);\n if (downloaded.error || !downloaded.data) throw new Error(\"Upload unavailable\");\n const blob = downloaded.data;\n if (blob.size < 1 || blob.size > 1048576 || new MIMEType(blob.type).essence !== \"text/plain\") {\n throw new ApplicationError(\"Unsupported upload\", { status: 400, code: \"UPLOAD_CONTENT_INVALID\" });\n }\n const sha256 = Array.from(new Uint8Array(await crypto.subtle.digest(\"SHA-256\", await blob.arrayBuffer())),\n byte => byte.toString(16).padStart(2, \"0\")).join(\"\");\n await artifacts.register({ artifactId: input.artifactId, bucketId: \"review-attachments\", objectPath,\n artifactType: \"review.attachment\", sha256, sizeBytes: blob.size, mimeType: \"text/plain\" });\n const command = createTransactionalCommand({\n name: \"review.attach\", store: createPostgresCommandStore(database), inputCodec: plaintextCommandInput,\n input: capture, result,\n async authorize(who, request, tx) {\n try {\n const currentPath = await authorize(tx, who.actorId, request, true);\n return currentPath === objectPath ? \"allow\" : \"deny\";\n } catch (error) {\n if (error instanceof ApplicationError && error.code === \"UPLOAD_DENIED\") return \"deny\";\n throw error;\n }\n },\n async execute(tx, request) {\n const existing = rows(await tx.query(\"SELECT artifact_id FROM public.starter_attachments WHERE review_id=$1\", [request.reviewId]));\n if (existing.length) throw new CommandError(\"COMMAND_IDEMPOTENCY_CONFLICT\");\n const bound: ReviewUploadResult = { artifactId: request.artifactId, runId: request.artifactId, objectPath, sha256, bytes: blob.size };\n await tx.query(\"INSERT INTO public.starter_attachments VALUES($1,$2,$3,$4,$5)\",\n [request.reviewId, bound.artifactId, subject, bound.runId, bound.objectPath]);\n return bound;\n },\n audit: { event: \"review.attachment-bound\", details: request => ({ reviewId: request.reviewId, artifactId: request.artifactId }) },\n });\n const receipt = await command.execute({ actorId: subject, tenantId }, input.artifactId, input);\n if (receipt.status !== \"confirmed\") throw new CommandError(\"COMMAND_OUTCOME_UNKNOWN\");\n return receipt.result;\n } catch (error) {\n if (error instanceof ApplicationError) throw error;\n if (error instanceof CommandError && error.code === \"COMMAND_IDEMPOTENCY_CONFLICT\") {\n throw new ApplicationError(\"Conflicting attachment binding\", { status: 409, code: error.code });\n }\n if (error instanceof CommandError && error.code === \"COMMAND_REJECTED\") {\n throw new ApplicationError(\"Upload permission denied\", { status: 403, code: error.code });\n }\n // Registration may have committed even when binding failed; never delete immutable evidence here.\n throw new ApplicationError(\"Upload registration could not be confirmed\", { status: 503, code: \"UPLOAD_OUTCOME_UNKNOWN\" });\n }\n },\n };\n}\n";
@@ -0,0 +1,12 @@
1
+ import { type StarterTemplate } from "./app-starter-templates";
2
+ /** Embedded source strings are included in both the npm CLI and standalone binary. */
3
+ export declare function appStarterFiles(name: string): Record<string, string>;
4
+ export declare function initializeAppProject(options: {
5
+ root?: string;
6
+ name?: string;
7
+ template?: StarterTemplate;
8
+ }): Promise<{
9
+ root: string;
10
+ name: string;
11
+ files: string[];
12
+ }>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ import type { ModuleNode } from "@supacloud/compiler";
2
+ export interface AppManifest {
3
+ version: number;
4
+ modules: ModuleNode[];
5
+ externalTokens: string[];
6
+ }
7
+ export interface ToolSchemaProperty {
8
+ type: string;
9
+ description: string;
10
+ additionalProperties?: boolean;
11
+ }
12
+ export interface ToolInputSchema {
13
+ type: "object";
14
+ properties: Record<string, ToolSchemaProperty>;
15
+ required: string[];
16
+ additionalProperties?: boolean;
17
+ }
18
+ export interface OpenAIToolDefinition {
19
+ type: "function";
20
+ function: {
21
+ name: string;
22
+ description: string;
23
+ parameters: ToolInputSchema;
24
+ };
25
+ }
26
+ export interface McpToolDefinition {
27
+ name: string;
28
+ description: string;
29
+ inputSchema: ToolInputSchema;
30
+ annotations: {
31
+ readOnly: boolean;
32
+ audited: boolean;
33
+ permission: string;
34
+ httpPath?: string;
35
+ httpMethod?: string;
36
+ };
37
+ }
38
+ export interface ExportedToolDefinitions {
39
+ openai: OpenAIToolDefinition[];
40
+ mcp: McpToolDefinition[];
41
+ }
42
+ export declare function buildToolDefinitions(manifest: AppManifest): ExportedToolDefinitions;