Switch the control plane fully to local file-backed state and delete the old SQL persistence layer. This keeps runtime storage aligned with the local-first architecture and removes the legacy migration path.
Unified · Split
| @@ -161,19 +161,19 @@ Because of that, these surfaces share: · 160 unmodified lines | |||
| 161 | 161 | ||
| 162 | ## Persistence model | 162 | ## Persistence model |
| 163 | 163 | ||
| 164 | The persistence layer is SQL-backed. | 164 | The persistence layer is local-file-backed. |
| 165 | 165 | ||
| 166 | Default behavior: | 166 | Default behavior: |
| 167 | 167 | ||
| 168 | - embedded PGlite for local use | 168 | - workspace config and state are stored in local files |
| 169 | 169 | ||
| 170 | Optional behavior: | 170 | Optional behavior: |
| 171 | 171 | ||
| 172 | - Postgres when a Postgres URL is supplied | 172 | - future cloud backends can plug in behind Effect service boundaries |
| 173 | 173 | ||
| 174 | At a high level, the database stores these domains: | 174 | At a high level, the local control plane stores these domains: |
| 175 | 175 | ||
| 176 | - installation identity: local installation, accounts, organizations, memberships, workspaces | 176 | - installation identity: local installation identity for the workspace |
| 177 | - source state: sources, auth sessions, source credential bindings | 177 | - source state: sources, auth sessions, source credential bindings |
| 178 | - tool model: tool artifacts and related metadata extracted from sources | 178 | - tool model: tool artifacts and related metadata extracted from sources |
| 179 | - secret state: credentials and secret materials | 179 | - secret state: credentials and secret materials |
| @@ -31,7 +31,7 @@ We are on v3 of this codebase which is a completely fresh start, the original co · 30 unmodified lines | |||
| 31 | Architecture I like: | 31 | Architecture I like: |
| 32 | 32 | ||
| 33 | Database: | 33 | Database: |
| 34 | Postgres SQLIte for local, Postgres SQL for cloud hosted version | 34 | Local-file-backed control plane for local use, with room for future hosted backends |
| 35 | 35 | ||
| 36 | Server: | 36 | Server: |
| 37 | 37 | ||
| @@ -69,7 +69,7 @@ Runtimes: · 31 unmodified lines | |||
| 69 | Other: | 69 | Other: |
| 70 | 70 | ||
| 71 | - Avoid hardcoding strings in the core libraries, prefer programming to interfaces, this is because it allows people to build custom adapters without having to modify the core | 71 | - Avoid hardcoding strings in the core libraries, prefer programming to interfaces, this is because it allows people to build custom adapters without having to modify the core |
| 72 | - Aim for composability of adapters. For example, the cloud product may be able to leverage storing secrets both encrypted in postgres and also BYO 1password setup. This does not need to be implemented today but worth noting | 72 | - Aim for composability of adapters. For example, the cloud product may eventually store secrets in a hosted backend while still supporting BYO 1Password. This does not need to be implemented today but is worth noting. |
| 73 | - This is a fresh start so we can make whatever changes we need. | 73 | - This is a fresh start so we can make whatever changes we need. |
| 74 | 74 | ||
| 75 | Rough architecture is: Turborepo monorepo, effect-vitest for testing, bun for package manager / running apps (but we leverage Effect wherever possible rather than bun's apis i.e for the server), Next.js for web app | 75 | Rough architecture is: Turborepo monorepo, effect-vitest for testing, bun for package manager / running apps (but we leverage Effect wherever possible rather than bun's apis i.e for the server), Next.js for web app |
| @@ -280,18 +280,16 @@ The server also maintains local PID and log files in its runtime directory. · 279 unmodified lines | |||
| 280 | 280 | ||
| 281 | ## Persistence and data | 281 | ## Persistence and data |
| 282 | 282 | ||
| 283 | `executor` persists the local control plane to SQL. | 283 | `executor` persists the local control plane to local files. |
| 284 | |||
| 285 | By default it uses embedded PGlite for a local install, which keeps the product self-contained. If you provide a Postgres URL, the persistence layer can target real Postgres instead. | ||
| 286 | 284 | ||
| 287 | Persisted concepts include: | 285 | Persisted concepts include: |
| 288 | 286 | ||
| 289 | - the local installation record | 287 | - local installation identity |
| 290 | - organizations and workspaces provisioned for that install | ||
| 291 | - connected sources | 288 | - connected sources |
| 292 | - indexed tool artifacts and related metadata | 289 | - indexed tool artifacts and related metadata |
| 293 | - credentials and secret material bindings | 290 | - credentials and secret material bindings |
| 294 | - source auth sessions | 291 | - source auth sessions |
| 292 | - execution and interaction state | ||
| 295 | - executions and execution interactions | 293 | - executions and execution interactions |
| 296 | - policies | 294 | - policies |
| 297 | 295 | ||
| @@ -324,6 +322,14 @@ If you are exploring the repo, these are the directories that matter most: · 26 unmodified lines | |||
| 324 | - `packages/executor-mcp`: MCP bridge for `execute` and `resume` | 322 | - `packages/executor-mcp`: MCP bridge for `execute` and `resume` |
| 325 | - `packages/codemode-*`: core tool abstractions plus MCP and OpenAPI adapters | 323 | - `packages/codemode-*`: core tool abstractions plus MCP and OpenAPI adapters |
| 326 | 324 | ||
| 325 | ## Releasing | ||
| 326 | |||
| 327 | - `bun run --cwd apps/executor release:publish` is the only supported publish command. | ||
| 328 | - Stable releases use a normal semver like `1.2.3` and publish to npm under `latest`. | ||
| 329 | - Beta releases use a prerelease semver like `1.3.0-beta.1` and publish to npm under `beta`. | ||
| 330 | - Push a matching git tag such as `v1.2.3` or `v1.3.0-beta.1` to trigger `.github/workflows/publish-executor-package.yml`. | ||
| 331 | - To build and pack the publish artifact locally without publishing, run `bun run --cwd apps/executor release:publish:dry-run`. | ||
| 332 | |||
| 327 | ## Project status | 333 | ## Project status |
| 328 | 334 | ||
| 329 | This repository is explicitly on its third major architecture iteration. | 335 | This repository is explicitly on its third major architecture iteration. |
| @@ -44,7 +44,6 @@ import { · 43 unmodified lines | |||
| 44 | seedGithubOpenApiSourceInWorkspace, | 44 | seedGithubOpenApiSourceInWorkspace, |
| 45 | } from "./dev"; | 45 | } from "./dev"; |
| 46 | import { | 46 | import { |
| 47 | resolveRuntimeMigrationsDir, | ||
| 48 | resolveRuntimeWebAssetsDir, | 47 | resolveRuntimeWebAssetsDir, |
| 49 | resolveSelfCommand, | 48 | resolveSelfCommand, |
| 50 | } from "./runtime-paths"; | 49 | } from "./runtime-paths"; |
| @@ -274,9 +273,7 @@ const buildRunWorkflowText = ( · 223 unmodified lines | |||
| 274 | 273 | ||
| 275 | const loadRunWorkflowText = (): Effect.Effect<string, Error, never> => | 274 | const loadRunWorkflowText = (): Effect.Effect<string, Error, never> => |
| 276 | Effect.acquireUseRelease( | 275 | Effect.acquireUseRelease( |
| 277 | createControlPlaneRuntime({ | 276 | createControlPlaneRuntime({}).pipe(Effect.mapError(toError)), |
| 278 | localDataDir: DEFAULT_LOCAL_DATA_DIR, | ||
| 279 | }).pipe(Effect.mapError(toError)), | ||
| 280 | (runtime) => | 277 | (runtime) => |
| 281 | Effect.gen(function* () { | 278 | Effect.gen(function* () { |
| 282 | const environment = yield* Effect.gen(function* () { | 279 | const environment = yield* Effect.gen(function* () { |
| @@ -414,13 +411,11 @@ const isServerReachable = (baseUrl: string) => · 131 unmodified lines | |||
| 414 | 411 | ||
| 415 | const getDefaultServerOptions = (port: number = DEFAULT_SERVER_PORT) => { | 412 | const getDefaultServerOptions = (port: number = DEFAULT_SERVER_PORT) => { |
| 416 | const assetsDir = resolveRuntimeWebAssetsDir(); | 413 | const assetsDir = resolveRuntimeWebAssetsDir(); |
| 417 | const migrationsFolder = resolveRuntimeMigrationsDir(); | ||
| 418 | 414 | ||
| 419 | return { | 415 | return { |
| 420 | host: DEFAULT_SERVER_HOST, | 416 | host: DEFAULT_SERVER_HOST, |
| 421 | port, | 417 | port, |
| 422 | localDataDir: DEFAULT_LOCAL_DATA_DIR, | 418 | localDataDir: DEFAULT_LOCAL_DATA_DIR, |
| 423 | migrationsFolder: migrationsFolder ?? undefined, | ||
| 424 | pidFile: DEFAULT_SERVER_PID_FILE, | 419 | pidFile: DEFAULT_SERVER_PID_FILE, |
| 425 | ui: assetsDir ? { assetsDir } : undefined, | 420 | ui: assetsDir ? { assetsDir } : undefined, |
| 426 | }; | 421 | }; |
| @@ -533,7 +528,6 @@ type LocalServerStatus = { · 106 unmodified lines | |||
| 533 | logFile: string; | 528 | logFile: string; |
| 534 | localDataDir: string; | 529 | localDataDir: string; |
| 535 | webAssetsDir: string | null; | 530 | webAssetsDir: string | null; |
| 536 | migrationsDir: string | null; | ||
| 537 | installation: { | 531 | installation: { |
| 538 | accountId: string; | 532 | accountId: string; |
| 539 | workspaceId: string; | 533 | workspaceId: string; |
| @@ -571,7 +565,6 @@ const getServerStatus = (baseUrl: string): Effect.Effect<LocalServerStatus, Erro · 31 unmodified lines | |||
| 571 | logFile, | 565 | logFile, |
| 572 | localDataDir: DEFAULT_LOCAL_DATA_DIR, | 566 | localDataDir: DEFAULT_LOCAL_DATA_DIR, |
| 573 | webAssetsDir: resolveRuntimeWebAssetsDir(), | 567 | webAssetsDir: resolveRuntimeWebAssetsDir(), |
| 574 | migrationsDir: resolveRuntimeMigrationsDir(), | ||
| 575 | installation, | 568 | installation, |
| 576 | denoVersion, | 569 | denoVersion, |
| 577 | } satisfies LocalServerStatus; | 570 | } satisfies LocalServerStatus; |
| @@ -587,7 +580,6 @@ const renderStatus = (status: LocalServerStatus): string => · 9 unmodified lines | |||
| 587 | `logFile: ${status.logFile}`, | 580 | `logFile: ${status.logFile}`, |
| 588 | `localDataDir: ${status.localDataDir}`, | 581 | `localDataDir: ${status.localDataDir}`, |
| 589 | `webAssetsDir: ${status.webAssetsDir ?? "missing"}`, | 582 | `webAssetsDir: ${status.webAssetsDir ?? "missing"}`, |
| 590 | `migrationsDir: ${status.migrationsDir ?? "missing"}`, | ||
| 591 | `workspaceId: ${status.installation?.workspaceId ?? "unavailable"}`, | 583 | `workspaceId: ${status.installation?.workspaceId ?? "unavailable"}`, |
| 592 | `denoSandbox: ${renderDenoSandboxDetail(status.denoVersion)}`, | 584 | `denoSandbox: ${renderDenoSandboxDetail(status.denoVersion)}`, |
| 593 | ].join("\n"); | 585 | ].join("\n"); |
| @@ -616,10 +608,6 @@ const getDoctorReport = (baseUrl: string) => · 22 unmodified lines | |||
| 616 | ok: status.webAssetsDir !== null, | 608 | ok: status.webAssetsDir !== null, |
| 617 | detail: status.webAssetsDir ?? "missing bundled web assets", | 609 | detail: status.webAssetsDir ?? "missing bundled web assets", |
| 618 | }, | 610 | }, |
| 619 | migrations: { | ||
| 620 | ok: status.migrationsDir !== null, | ||
| 621 | detail: status.migrationsDir ?? "missing migrations directory", | ||
| 622 | }, | ||
| 623 | installation: { | 611 | installation: { |
| 624 | ok: status.installation !== null, | 612 | ok: status.installation !== null, |
| 625 | detail: status.installation | 613 | detail: status.installation |
| @@ -3,7 +3,6 @@ import { dirname, extname, resolve } from "node:path"; · 2 unmodified lines | |||
| 3 | import { fileURLToPath } from "node:url"; | 3 | import { fileURLToPath } from "node:url"; |
| 4 | 4 | ||
| 5 | import { | 5 | import { |
| 6 | EXECUTOR_MIGRATIONS_DIR_ENV, | ||
| 7 | EXECUTOR_WEB_ASSETS_DIR_ENV, | 6 | EXECUTOR_WEB_ASSETS_DIR_ENV, |
| 8 | } from "@executor/server"; | 7 | } from "@executor/server"; |
| 9 | 8 | ||
| @@ -93,20 +92,3 @@ export const resolveRuntimeWebAssetsDir = (): string | null => { · 83 unmodified lines | |||
| 93 | 92 | ||
| 94 | return resolveIfExists(resolve(sourceDir, "../../../web/dist")); | 93 | return resolveIfExists(resolve(sourceDir, "../../../web/dist")); |
| 95 | }; | 94 | }; |
| 96 | |||
| 97 | export const resolveRuntimeMigrationsDir = (): string | null => { | ||
| 98 | const explicit = resolveIfExists(process.env[EXECUTOR_MIGRATIONS_DIR_ENV]); | ||
| 99 | if (explicit) { | ||
| 100 | return explicit; | ||
| 101 | } | ||
| 102 | |||
| 103 | const resourcesRoot = resolveRuntimeResourcesRoot(); | ||
| 104 | const bundled = resourcesRoot | ||
| 105 | ? resolveIfExists(resolve(resourcesRoot, "migrations")) | ||
| 106 | : null; | ||
| 107 | if (bundled) { | ||
| 108 | return bundled; | ||
| 109 | } | ||
| 110 | |||
| 111 | return resolveIfExists(resolve(sourceDir, "../../../../packages/control-plane/drizzle")); | ||
| 112 | }; | ||
| @@ -1,13 +0,0 @@ | |||
| 1 | const databaseUrl = | ||
| 2 | process.env.DATABASE_URL ?? "postgres://localhost:5432/executor_v3"; | ||
| 3 | |||
| 4 | export default { | ||
| 5 | dialect: "postgresql", | ||
| 6 | schema: "./src/persistence/schema.ts", | ||
| 7 | out: "./drizzle", | ||
| 8 | dbCredentials: { | ||
| 9 | url: databaseUrl, | ||
| 10 | }, | ||
| 11 | strict: true, | ||
| 12 | verbose: true, | ||
| 13 | }; | ||
| @@ -1,158 +0,0 @@ | |||
| 1 | CREATE TABLE "accounts" ( | ||
| 2 | "id" text PRIMARY KEY, | ||
| 3 | "provider" text NOT NULL, | ||
| 4 | "subject" text NOT NULL, | ||
| 5 | "email" text, | ||
| 6 | "display_name" text, | ||
| 7 | "created_at" bigint NOT NULL, | ||
| 8 | "updated_at" bigint NOT NULL, | ||
| 9 | CONSTRAINT "accounts_provider_check" CHECK ("provider" in ('local', 'workos', 'service')) | ||
| 10 | ); | ||
| 11 | --> statement-breakpoint | ||
| 12 | CREATE TABLE "execution_interactions" ( | ||
| 13 | "id" text PRIMARY KEY, | ||
| 14 | "execution_id" text NOT NULL, | ||
| 15 | "status" text NOT NULL, | ||
| 16 | "kind" text NOT NULL, | ||
| 17 | "payload_json" text NOT NULL, | ||
| 18 | "response_json" text, | ||
| 19 | "created_at" bigint NOT NULL, | ||
| 20 | "updated_at" bigint NOT NULL, | ||
| 21 | CONSTRAINT "execution_interactions_status_check" CHECK ("status" in ('pending', 'resolved', 'cancelled')) | ||
| 22 | ); | ||
| 23 | --> statement-breakpoint | ||
| 24 | CREATE TABLE "executions" ( | ||
| 25 | "id" text PRIMARY KEY, | ||
| 26 | "workspace_id" text NOT NULL, | ||
| 27 | "created_by_account_id" text NOT NULL, | ||
| 28 | "status" text NOT NULL, | ||
| 29 | "code" text NOT NULL, | ||
| 30 | "result_json" text, | ||
| 31 | "error_text" text, | ||
| 32 | "logs_json" text, | ||
| 33 | "started_at" bigint, | ||
| 34 | "completed_at" bigint, | ||
| 35 | "created_at" bigint NOT NULL, | ||
| 36 | "updated_at" bigint NOT NULL, | ||
| 37 | CONSTRAINT "executions_status_check" CHECK ("status" in ('pending', 'running', 'waiting_for_interaction', 'completed', 'failed', 'cancelled')) | ||
| 38 | ); | ||
| 39 | --> statement-breakpoint | ||
| 40 | CREATE TABLE "local_installations" ( | ||
| 41 | "id" text PRIMARY KEY, | ||
| 42 | "account_id" text NOT NULL, | ||
| 43 | "organization_id" text NOT NULL, | ||
| 44 | "workspace_id" text NOT NULL, | ||
| 45 | "created_at" bigint NOT NULL, | ||
| 46 | "updated_at" bigint NOT NULL | ||
| 47 | ); | ||
| 48 | --> statement-breakpoint | ||
| 49 | CREATE TABLE "organization_memberships" ( | ||
| 50 | "id" text PRIMARY KEY, | ||
| 51 | "organization_id" text NOT NULL, | ||
| 52 | "account_id" text NOT NULL, | ||
| 53 | "role" text NOT NULL, | ||
| 54 | "status" text NOT NULL, | ||
| 55 | "billable" boolean NOT NULL, | ||
| 56 | "invited_by_account_id" text, | ||
| 57 | "joined_at" bigint, | ||
| 58 | "created_at" bigint NOT NULL, | ||
| 59 | "updated_at" bigint NOT NULL, | ||
| 60 | CONSTRAINT "organization_memberships_role_check" CHECK ("role" in ('viewer', 'editor', 'admin', 'owner')), | ||
| 61 | CONSTRAINT "organization_memberships_status_check" CHECK ("status" in ('invited', 'active', 'suspended', 'removed')) | ||
| 62 | ); | ||
| 63 | --> statement-breakpoint | ||
| 64 | CREATE TABLE "organizations" ( | ||
| 65 | "id" text PRIMARY KEY, | ||
| 66 | "slug" text NOT NULL, | ||
| 67 | "name" text NOT NULL, | ||
| 68 | "status" text NOT NULL, | ||
| 69 | "created_by_account_id" text, | ||
| 70 | "created_at" bigint NOT NULL, | ||
| 71 | "updated_at" bigint NOT NULL, | ||
| 72 | CONSTRAINT "organizations_status_check" CHECK ("status" in ('active', 'suspended', 'archived')) | ||
| 73 | ); | ||
| 74 | --> statement-breakpoint | ||
| 75 | CREATE TABLE "policies" ( | ||
| 76 | "id" text PRIMARY KEY, | ||
| 77 | "workspace_id" text NOT NULL, | ||
| 78 | "target_account_id" text, | ||
| 79 | "client_id" text, | ||
| 80 | "resource_type" text NOT NULL, | ||
| 81 | "resource_pattern" text NOT NULL, | ||
| 82 | "match_type" text NOT NULL, | ||
| 83 | "effect" text NOT NULL, | ||
| 84 | "approval_mode" text NOT NULL, | ||
| 85 | "argument_conditions_json" text, | ||
| 86 | "priority" bigint NOT NULL, | ||
| 87 | "enabled" boolean NOT NULL, | ||
| 88 | "created_at" bigint NOT NULL, | ||
| 89 | "updated_at" bigint NOT NULL, | ||
| 90 | CONSTRAINT "policies_resource_type_check" CHECK ("resource_type" in ('all_tools', 'source', 'namespace', 'tool_path')), | ||
| 91 | CONSTRAINT "policies_match_type_check" CHECK ("match_type" in ('glob', 'exact')), | ||
| 92 | CONSTRAINT "policies_effect_check" CHECK ("effect" in ('allow', 'deny')), | ||
| 93 | CONSTRAINT "policies_approval_mode_check" CHECK ("approval_mode" in ('auto', 'required')) | ||
| 94 | ); | ||
| 95 | --> statement-breakpoint | ||
| 96 | CREATE TABLE "source_credential_bindings" ( | ||
| 97 | "workspace_id" text, | ||
| 98 | "source_id" text, | ||
| 99 | "token_provider_id" text, | ||
| 100 | "token_handle" text, | ||
| 101 | "refresh_token_provider_id" text, | ||
| 102 | "refresh_token_handle" text, | ||
| 103 | "created_at" bigint NOT NULL, | ||
| 104 | "updated_at" bigint NOT NULL, | ||
| 105 | CONSTRAINT "source_credential_bindings_pkey" PRIMARY KEY("workspace_id","source_id") | ||
| 106 | ); | ||
| 107 | --> statement-breakpoint | ||
| 108 | CREATE TABLE "sources" ( | ||
| 109 | "workspace_id" text, | ||
| 110 | "source_id" text, | ||
| 111 | "name" text NOT NULL, | ||
| 112 | "kind" text NOT NULL, | ||
| 113 | "endpoint" text NOT NULL, | ||
| 114 | "status" text NOT NULL, | ||
| 115 | "enabled" boolean NOT NULL, | ||
| 116 | "namespace" text, | ||
| 117 | "transport" text, | ||
| 118 | "query_params_json" text, | ||
| 119 | "headers_json" text, | ||
| 120 | "spec_url" text, | ||
| 121 | "default_headers_json" text, | ||
| 122 | "auth_kind" text NOT NULL, | ||
| 123 | "auth_header_name" text, | ||
| 124 | "auth_prefix" text, | ||
| 125 | "source_hash" text, | ||
| 126 | "last_error" text, | ||
| 127 | "created_at" bigint NOT NULL, | ||
| 128 | "updated_at" bigint NOT NULL, | ||
| 129 | CONSTRAINT "sources_pkey" PRIMARY KEY("workspace_id","source_id"), | ||
| 130 | CONSTRAINT "sources_kind_check" CHECK ("kind" in ('mcp', 'openapi', 'graphql', 'internal')), | ||
| 131 | CONSTRAINT "sources_status_check" CHECK ("status" in ('draft', 'probing', 'auth_required', 'connected', 'error')), | ||
| 132 | CONSTRAINT "sources_transport_check" CHECK ("transport" is null or "transport" in ('auto', 'streamable-http', 'sse')), | ||
| 133 | CONSTRAINT "sources_auth_kind_check" CHECK ("auth_kind" in ('none', 'bearer', 'oauth2')) | ||
| 134 | ); | ||
| 135 | --> statement-breakpoint | ||
| 136 | CREATE TABLE "workspaces" ( | ||
| 137 | "id" text PRIMARY KEY, | ||
| 138 | "organization_id" text NOT NULL, | ||
| 139 | "name" text NOT NULL, | ||
| 140 | "created_by_account_id" text, | ||
| 141 | "created_at" bigint NOT NULL, | ||
| 142 | "updated_at" bigint NOT NULL | ||
| 143 | ); | ||
| 144 | --> statement-breakpoint | ||
| 145 | CREATE UNIQUE INDEX "accounts_provider_subject_idx" ON "accounts" ("provider","subject");--> statement-breakpoint | ||
| 146 | CREATE INDEX "accounts_updated_idx" ON "accounts" ("updated_at","id");--> statement-breakpoint | ||
| 147 | CREATE INDEX "execution_interactions_execution_idx" ON "execution_interactions" ("execution_id","updated_at","id");--> statement-breakpoint | ||
| 148 | CREATE INDEX "executions_workspace_idx" ON "executions" ("workspace_id","updated_at","id");--> statement-breakpoint | ||
| 149 | CREATE INDEX "organization_memberships_org_idx" ON "organization_memberships" ("organization_id");--> statement-breakpoint | ||
| 150 | CREATE INDEX "organization_memberships_account_idx" ON "organization_memberships" ("account_id");--> statement-breakpoint | ||
| 151 | CREATE UNIQUE INDEX "organization_memberships_org_account_idx" ON "organization_memberships" ("organization_id","account_id");--> statement-breakpoint | ||
| 152 | CREATE UNIQUE INDEX "organizations_slug_idx" ON "organizations" ("slug");--> statement-breakpoint | ||
| 153 | CREATE INDEX "organizations_updated_idx" ON "organizations" ("updated_at","id");--> statement-breakpoint | ||
| 154 | CREATE INDEX "policies_workspace_idx" ON "policies" ("workspace_id","updated_at","id");--> statement-breakpoint | ||
| 155 | CREATE INDEX "source_credential_bindings_workspace_idx" ON "source_credential_bindings" ("workspace_id","updated_at","source_id");--> statement-breakpoint | ||
| 156 | CREATE UNIQUE INDEX "sources_workspace_name_idx" ON "sources" ("workspace_id","name");--> statement-breakpoint | ||
| 157 | CREATE INDEX "workspaces_org_idx" ON "workspaces" ("organization_id");--> statement-breakpoint | ||
| 158 | CREATE UNIQUE INDEX "workspaces_org_name_idx" ON "workspaces" ("organization_id","name"); | ||
| @@ -1,1937 +0,0 @@ | |||
| 1 | { | ||
| 2 | "version": "8", | ||
| 3 | "dialect": "postgres", | ||
| 4 | "id": "bac1651d-7a30-4187-a7e2-74bc2b099486", | ||
| 5 | "prevIds": [ | ||
| 6 | "00000000-0000-0000-0000-000000000000" | ||
| 7 | ], | ||
| 8 | "ddl": [ | ||
| 9 | { | ||
| 10 | "isRlsEnabled": false, | ||
| 11 | "name": "accounts", | ||
| 12 | "entityType": "tables", | ||
| 13 | "schema": "public" | ||
| 14 | }, | ||
| 15 | { | ||
| 16 | "isRlsEnabled": false, | ||
| 17 | "name": "execution_interactions", | ||
| 18 | "entityType": "tables", | ||
| 19 | "schema": "public" | ||
| 20 | }, | ||
| 21 | { | ||
| 22 | "isRlsEnabled": false, | ||
| 23 | "name": "executions", | ||
| 24 | "entityType": "tables", | ||
| 25 | "schema": "public" | ||
| 26 | }, | ||
| 27 | { | ||
| 28 | "isRlsEnabled": false, | ||
| 29 | "name": "local_installations", | ||
| 30 | "entityType": "tables", | ||
| 31 | "schema": "public" | ||
| 32 | }, | ||
| 33 | { | ||
| 34 | "isRlsEnabled": false, | ||
| 35 | "name": "organization_memberships", | ||
| 36 | "entityType": "tables", | ||
| 37 | "schema": "public" | ||
| 38 | }, | ||
| 39 | { | ||
| 40 | "isRlsEnabled": false, | ||
| 41 | "name": "organizations", | ||
| 42 | "entityType": "tables", | ||
| 43 | "schema": "public" | ||
| 44 | }, | ||
| 45 | { | ||
| 46 | "isRlsEnabled": false, | ||
| 47 | "name": "policies", | ||
| 48 | "entityType": "tables", | ||
| 49 | "schema": "public" | ||
| 50 | }, | ||
| 51 | { | ||
| 52 | "isRlsEnabled": false, | ||
| 53 | "name": "source_credential_bindings", | ||
| 54 | "entityType": "tables", | ||
| 55 | "schema": "public" | ||
| 56 | }, | ||
| 57 | { | ||
| 58 | "isRlsEnabled": false, | ||
| 59 | "name": "sources", | ||
| 60 | "entityType": "tables", | ||
| 61 | "schema": "public" | ||
| 62 | }, | ||
| 63 | { | ||
| 64 | "isRlsEnabled": false, | ||
| 65 | "name": "workspaces", | ||
| 66 | "entityType": "tables", | ||
| 67 | "schema": "public" | ||
| 68 | }, | ||
| 69 | { | ||
| 70 | "type": "text", | ||
| 71 | "typeSchema": null, | ||
| 72 | "notNull": true, | ||
| 73 | "dimensions": 0, | ||
| 74 | "default": null, | ||
| 75 | "generated": null, | ||
| 76 | "identity": null, | ||
| 77 | "name": "id", | ||
| 78 | "entityType": "columns", | ||
| 79 | "schema": "public", | ||
| 80 | "table": "accounts" | ||
| 81 | }, | ||
| 82 | { | ||
| 83 | "type": "text", | ||
| 84 | "typeSchema": null, | ||
| 85 | "notNull": true, | ||
| 86 | "dimensions": 0, | ||
| 87 | "default": null, | ||
| 88 | "generated": null, | ||
| 89 | "identity": null, | ||
| 90 | "name": "provider", | ||
| 91 | "entityType": "columns", | ||
| 92 | "schema": "public", | ||
| 93 | "table": "accounts" | ||
| 94 | }, | ||
| 95 | { | ||
| 96 | "type": "text", | ||
| 97 | "typeSchema": null, | ||
| 98 | "notNull": true, | ||
| 99 | "dimensions": 0, | ||
| 100 | "default": null, | ||
| 101 | "generated": null, | ||
| 102 | "identity": null, | ||
| 103 | "name": "subject", | ||
| 104 | "entityType": "columns", | ||
| 105 | "schema": "public", | ||
| 106 | "table": "accounts" | ||
| 107 | }, | ||
| 108 | { | ||
| 109 | "type": "text", | ||
| 110 | "typeSchema": null, | ||
| 111 | "notNull": false, | ||
| 112 | "dimensions": 0, | ||
| 113 | "default": null, | ||
| 114 | "generated": null, | ||
| 115 | "identity": null, | ||
| 116 | "name": "email", | ||
| 117 | "entityType": "columns", | ||
| 118 | "schema": "public", | ||
| 119 | "table": "accounts" | ||
| 120 | }, | ||
| 121 | { | ||
| 122 | "type": "text", | ||
| 123 | "typeSchema": null, | ||
| 124 | "notNull": false, | ||
| 125 | "dimensions": 0, | ||
| 126 | "default": null, | ||
| 127 | "generated": null, | ||
| 128 | "identity": null, | ||
| 129 | "name": "display_name", | ||
| 130 | "entityType": "columns", | ||
| 131 | "schema": "public", | ||
| 132 | "table": "accounts" | ||
| 133 | }, | ||
| 134 | { | ||
| 135 | "type": "bigint", | ||
| 136 | "typeSchema": null, | ||
| 137 | "notNull": true, | ||
| 138 | "dimensions": 0, | ||
| 139 | "default": null, | ||
| 140 | "generated": null, | ||
| 141 | "identity": null, | ||
| 142 | "name": "created_at", | ||
| 143 | "entityType": "columns", | ||
| 144 | "schema": "public", | ||
| 145 | "table": "accounts" | ||
| 146 | }, | ||
| 147 | { | ||
| 148 | "type": "bigint", | ||
| 149 | "typeSchema": null, | ||
| 150 | "notNull": true, | ||
| 151 | "dimensions": 0, | ||
| 152 | "default": null, | ||
| 153 | "generated": null, | ||
| 154 | "identity": null, | ||
| 155 | "name": "updated_at", | ||
| 156 | "entityType": "columns", | ||
| 157 | "schema": "public", | ||
| 158 | "table": "accounts" | ||
| 159 | }, | ||
| 160 | { | ||
| 161 | "type": "text", | ||
| 162 | "typeSchema": null, | ||
| 163 | "notNull": true, | ||
| 164 | "dimensions": 0, | ||
| 165 | "default": null, | ||
| 166 | "generated": null, | ||
| 167 | "identity": null, | ||
| 168 | "name": "id", | ||
| 169 | "entityType": "columns", | ||
| 170 | "schema": "public", | ||
| 171 | "table": "execution_interactions" | ||
| 172 | }, | ||
| 173 | { | ||
| 174 | "type": "text", | ||
| 175 | "typeSchema": null, | ||
| 176 | "notNull": true, | ||
| 177 | "dimensions": 0, | ||
| 178 | "default": null, | ||
| 179 | "generated": null, | ||
| 180 | "identity": null, | ||
| 181 | "name": "execution_id", | ||
| 182 | "entityType": "columns", | ||
| 183 | "schema": "public", | ||
| 184 | "table": "execution_interactions" | ||
| 185 | }, | ||
| 186 | { | ||
| 187 | "type": "text", | ||
| 188 | "typeSchema": null, | ||
| 189 | "notNull": true, | ||
| 190 | "dimensions": 0, | ||
| 191 | "default": null, | ||
| 192 | "generated": null, | ||
| 193 | "identity": null, | ||
| 194 | "name": "status", | ||
| 195 | "entityType": "columns", | ||
| 196 | "schema": "public", | ||
| 197 | "table": "execution_interactions" | ||
| 198 | }, | ||
| 199 | { | ||
| 200 | "type": "text", | ||
| 201 | "typeSchema": null, | ||
| 202 | "notNull": true, | ||
| 203 | "dimensions": 0, | ||
| 204 | "default": null, | ||
| 205 | "generated": null, | ||
| 206 | "identity": null, | ||
| 207 | "name": "kind", | ||
| 208 | "entityType": "columns", | ||
| 209 | "schema": "public", | ||
| 210 | "table": "execution_interactions" | ||
| 211 | }, | ||
| 212 | { | ||
| 213 | "type": "text", | ||
| 214 | "typeSchema": null, | ||
| 215 | "notNull": true, | ||
| 216 | "dimensions": 0, | ||
| 217 | "default": null, | ||
| 218 | "generated": null, | ||
| 219 | "identity": null, | ||
| 220 | "name": "payload_json", | ||
| 221 | "entityType": "columns", | ||
| 222 | "schema": "public", | ||
| 223 | "table": "execution_interactions" | ||
| 224 | }, | ||
| 225 | { | ||
| 226 | "type": "text", | ||
| 227 | "typeSchema": null, | ||
| 228 | "notNull": false, | ||
| 229 | "dimensions": 0, | ||
| 230 | "default": null, | ||
| 231 | "generated": null, | ||
| 232 | "identity": null, | ||
| 233 | "name": "response_json", | ||
| 234 | "entityType": "columns", | ||
| 235 | "schema": "public", | ||
| 236 | "table": "execution_interactions" | ||
| 237 | }, | ||
| 238 | { | ||
| 239 | "type": "bigint", | ||
| 240 | "typeSchema": null, | ||
| 241 | "notNull": true, | ||
| 242 | "dimensions": 0, | ||
| 243 | "default": null, | ||
| 244 | "generated": null, | ||
| 245 | "identity": null, | ||
| 246 | "name": "created_at", | ||
| 247 | "entityType": "columns", | ||
| 248 | "schema": "public", | ||
| 249 | "table": "execution_interactions" | ||
| 250 | }, | ||
| 251 | { | ||
| 252 | "type": "bigint", | ||
| 253 | "typeSchema": null, | ||
| 254 | "notNull": true, | ||
| 255 | "dimensions": 0, | ||
| 256 | "default": null, | ||
| 257 | "generated": null, | ||
| 258 | "identity": null, | ||
| 259 | "name": "updated_at", | ||
| 260 | "entityType": "columns", | ||
| 261 | "schema": "public", | ||
| 262 | "table": "execution_interactions" | ||
| 263 | }, | ||
| 264 | { | ||
| 265 | "type": "text", | ||
| 266 | "typeSchema": null, | ||
| 267 | "notNull": true, | ||
| 268 | "dimensions": 0, | ||
| 269 | "default": null, | ||
| 270 | "generated": null, | ||
| 271 | "identity": null, | ||
| 272 | "name": "id", | ||
| 273 | "entityType": "columns", | ||
| 274 | "schema": "public", | ||
| 275 | "table": "executions" | ||
| 276 | }, | ||
| 277 | { | ||
| 278 | "type": "text", | ||
| 279 | "typeSchema": null, | ||
| 280 | "notNull": true, | ||
| 281 | "dimensions": 0, | ||
| 282 | "default": null, | ||
| 283 | "generated": null, | ||
| 284 | "identity": null, | ||
| 285 | "name": "workspace_id", | ||
| 286 | "entityType": "columns", | ||
| 287 | "schema": "public", | ||
| 288 | "table": "executions" | ||
| 289 | }, | ||
| 290 | { | ||
| 291 | "type": "text", | ||
| 292 | "typeSchema": null, | ||
| 293 | "notNull": true, | ||
| 294 | "dimensions": 0, | ||
| 295 | "default": null, | ||
| 296 | "generated": null, | ||
| 297 | "identity": null, | ||
| 298 | "name": "created_by_account_id", | ||
| 299 | "entityType": "columns", | ||
| 300 | "schema": "public", | ||
| 301 | "table": "executions" | ||
| 302 | }, | ||
| 303 | { | ||
| 304 | "type": "text", | ||
| 305 | "typeSchema": null, | ||
| 306 | "notNull": true, | ||
| 307 | "dimensions": 0, | ||
| 308 | "default": null, | ||
| 309 | "generated": null, | ||
| 310 | "identity": null, | ||
| 311 | "name": "status", | ||
| 312 | "entityType": "columns", | ||
| 313 | "schema": "public", | ||
| 314 | "table": "executions" | ||
| 315 | }, | ||
| 316 | { | ||
| 317 | "type": "text", | ||
| 318 | "typeSchema": null, | ||
| 319 | "notNull": true, | ||
| 320 | "dimensions": 0, | ||
| 321 | "default": null, | ||
| 322 | "generated": null, | ||
| 323 | "identity": null, | ||
| 324 | "name": "code", | ||
| 325 | "entityType": "columns", | ||
| 326 | "schema": "public", | ||
| 327 | "table": "executions" | ||
| 328 | }, | ||
| 329 | { | ||
| 330 | "type": "text", | ||
| 331 | "typeSchema": null, | ||
| 332 | "notNull": false, | ||
| 333 | "dimensions": 0, | ||
| 334 | "default": null, | ||
| 335 | "generated": null, | ||
| 336 | "identity": null, | ||
| 337 | "name": "result_json", | ||
| 338 | "entityType": "columns", | ||
| 339 | "schema": "public", | ||
| 340 | "table": "executions" | ||
| 341 | }, | ||
| 342 | { | ||
| 343 | "type": "text", | ||
| 344 | "typeSchema": null, | ||
| 345 | "notNull": false, | ||
| 346 | "dimensions": 0, | ||
| 347 | "default": null, | ||
| 348 | "generated": null, | ||
| 349 | "identity": null, | ||
| 350 | "name": "error_text", | ||
| 351 | "entityType": "columns", | ||
| 352 | "schema": "public", | ||
| 353 | "table": "executions" | ||
| 354 | }, | ||
| 355 | { | ||
| 356 | "type": "text", | ||
| 357 | "typeSchema": null, | ||
| 358 | "notNull": false, | ||
| 359 | "dimensions": 0, | ||
| 360 | "default": null, | ||
| 361 | "generated": null, | ||
| 362 | "identity": null, | ||
| 363 | "name": "logs_json", | ||
| 364 | "entityType": "columns", | ||
| 365 | "schema": "public", | ||
| 366 | "table": "executions" | ||
| 367 | }, | ||
| 368 | { | ||
| 369 | "type": "bigint", | ||
| 370 | "typeSchema": null, | ||
| 371 | "notNull": false, | ||
| 372 | "dimensions": 0, | ||
| 373 | "default": null, | ||
| 374 | "generated": null, | ||
| 375 | "identity": null, | ||
| 376 | "name": "started_at", | ||
| 377 | "entityType": "columns", | ||
| 378 | "schema": "public", | ||
| 379 | "table": "executions" | ||
| 380 | }, | ||
| 381 | { | ||
| 382 | "type": "bigint", | ||
| 383 | "typeSchema": null, | ||
| 384 | "notNull": false, | ||
| 385 | "dimensions": 0, | ||
| 386 | "default": null, | ||
| 387 | "generated": null, | ||
| 388 | "identity": null, | ||
| 389 | "name": "completed_at", | ||
| 390 | "entityType": "columns", | ||
| 391 | "schema": "public", | ||
| 392 | "table": "executions" | ||
| 393 | }, | ||
| 394 | { | ||
| 395 | "type": "bigint", | ||
| 396 | "typeSchema": null, | ||
| 397 | "notNull": true, | ||
| 398 | "dimensions": 0, | ||
| 399 | "default": null, | ||
| 400 | "generated": null, | ||
| 401 | "identity": null, | ||
| 402 | "name": "created_at", | ||
| 403 | "entityType": "columns", | ||
| 404 | "schema": "public", | ||
| 405 | "table": "executions" | ||
| 406 | }, | ||
| 407 | { | ||
| 408 | "type": "bigint", | ||
| 409 | "typeSchema": null, | ||
| 410 | "notNull": true, | ||
| 411 | "dimensions": 0, | ||
| 412 | "default": null, | ||
| 413 | "generated": null, | ||
| 414 | "identity": null, | ||
| 415 | "name": "updated_at", | ||
| 416 | "entityType": "columns", | ||
| 417 | "schema": "public", | ||
| 418 | "table": "executions" | ||
| 419 | }, | ||
| 420 | { | ||
| 421 | "type": "text", | ||
| 422 | "typeSchema": null, | ||
| 423 | "notNull": true, | ||
| 424 | "dimensions": 0, | ||
| 425 | "default": null, | ||
| 426 | "generated": null, | ||
| 427 | "identity": null, | ||
| 428 | "name": "id", | ||
| 429 | "entityType": "columns", | ||
| 430 | "schema": "public", | ||
| 431 | "table": "local_installations" | ||
| 432 | }, | ||
| 433 | { | ||
| 434 | "type": "text", | ||
| 435 | "typeSchema": null, | ||
| 436 | "notNull": true, | ||
| 437 | "dimensions": 0, | ||
| 438 | "default": null, | ||
| 439 | "generated": null, | ||
| 440 | "identity": null, | ||
| 441 | "name": "account_id", | ||
| 442 | "entityType": "columns", | ||
| 443 | "schema": "public", | ||
| 444 | "table": "local_installations" | ||
| 445 | }, | ||
| 446 | { | ||
| 447 | "type": "text", | ||
| 448 | "typeSchema": null, | ||
| 449 | "notNull": true, | ||
| 450 | "dimensions": 0, | ||
| 451 | "default": null, | ||
| 452 | "generated": null, | ||
| 453 | "identity": null, | ||
| 454 | "name": "organization_id", | ||
| 455 | "entityType": "columns", | ||
| 456 | "schema": "public", | ||
| 457 | "table": "local_installations" | ||
| 458 | }, | ||
| 459 | { | ||
| 460 | "type": "text", | ||
| 461 | "typeSchema": null, | ||
| 462 | "notNull": true, | ||
| 463 | "dimensions": 0, | ||
| 464 | "default": null, | ||
| 465 | "generated": null, | ||
| 466 | "identity": null, | ||
| 467 | "name": "workspace_id", | ||
| 468 | "entityType": "columns", | ||
| 469 | "schema": "public", | ||
| 470 | "table": "local_installations" | ||
| 471 | }, | ||
| 472 | { | ||
| 473 | "type": "bigint", | ||
| 474 | "typeSchema": null, | ||
| 475 | "notNull": true, | ||
| 476 | "dimensions": 0, | ||
| 477 | "default": null, | ||
| 478 | "generated": null, | ||
| 479 | "identity": null, | ||
| 480 | "name": "created_at", | ||
| 481 | "entityType": "columns", | ||
| 482 | "schema": "public", | ||
| 483 | "table": "local_installations" | ||
| 484 | }, | ||
| 485 | { | ||
| 486 | "type": "bigint", | ||
| 487 | "typeSchema": null, | ||
| 488 | "notNull": true, | ||
| 489 | "dimensions": 0, | ||
| 490 | "default": null, | ||
| 491 | "generated": null, | ||
| 492 | "identity": null, | ||
| 493 | "name": "updated_at", | ||
| 494 | "entityType": "columns", | ||
| 495 | "schema": "public", | ||
| 496 | "table": "local_installations" | ||
| 497 | }, | ||
| 498 | { | ||
| 499 | "type": "text", | ||
| 500 | "typeSchema": null, | ||
| 501 | "notNull": true, | ||
| 502 | "dimensions": 0, | ||
| 503 | "default": null, | ||
| 504 | "generated": null, | ||
| 505 | "identity": null, | ||
| 506 | "name": "id", | ||
| 507 | "entityType": "columns", | ||
| 508 | "schema": "public", | ||
| 509 | "table": "organization_memberships" | ||
| 510 | }, | ||
| 511 | { | ||
| 512 | "type": "text", | ||
| 513 | "typeSchema": null, | ||
| 514 | "notNull": true, | ||
| 515 | "dimensions": 0, | ||
| 516 | "default": null, | ||
| 517 | "generated": null, | ||
| 518 | "identity": null, | ||
| 519 | "name": "organization_id", | ||
| 520 | "entityType": "columns", | ||
| 521 | "schema": "public", | ||
| 522 | "table": "organization_memberships" | ||
| 523 | }, | ||
| 524 | { | ||
| 525 | "type": "text", | ||
| 526 | "typeSchema": null, | ||
| 527 | "notNull": true, | ||
| 528 | "dimensions": 0, | ||
| 529 | "default": null, | ||
| 530 | "generated": null, | ||
| 531 | "identity": null, | ||
| 532 | "name": "account_id", | ||
| 533 | "entityType": "columns", | ||
| 534 | "schema": "public", | ||
| 535 | "table": "organization_memberships" | ||
| 536 | }, | ||
| 537 | { | ||
| 538 | "type": "text", | ||
| 539 | "typeSchema": null, | ||
| 540 | "notNull": true, | ||
| 541 | "dimensions": 0, | ||
| 542 | "default": null, | ||
| 543 | "generated": null, | ||
| 544 | "identity": null, | ||
| 545 | "name": "role", | ||
| 546 | "entityType": "columns", | ||
| 547 | "schema": "public", | ||
| 548 | "table": "organization_memberships" | ||
| 549 | }, | ||
| 550 | { | ||
| 551 | "type": "text", | ||
| 552 | "typeSchema": null, | ||
| 553 | "notNull": true, | ||
| 554 | "dimensions": 0, | ||
| 555 | "default": null, | ||
| 556 | "generated": null, | ||
| 557 | "identity": null, | ||
| 558 | "name": "status", | ||
| 559 | "entityType": "columns", | ||
| 560 | "schema": "public", | ||
| 561 | "table": "organization_memberships" | ||
| 562 | }, | ||
| 563 | { | ||
| 564 | "type": "boolean", | ||
| 565 | "typeSchema": null, | ||
| 566 | "notNull": true, | ||
| 567 | "dimensions": 0, | ||
| 568 | "default": null, | ||
| 569 | "generated": null, | ||
| 570 | "identity": null, | ||
| 571 | "name": "billable", | ||
| 572 | "entityType": "columns", | ||
| 573 | "schema": "public", | ||
| 574 | "table": "organization_memberships" | ||
| 575 | }, | ||
| 576 | { | ||
| 577 | "type": "text", | ||
| 578 | "typeSchema": null, | ||
| 579 | "notNull": false, | ||
| 580 | "dimensions": 0, | ||
| 581 | "default": null, | ||
| 582 | "generated": null, | ||
| 583 | "identity": null, | ||
| 584 | "name": "invited_by_account_id", | ||
| 585 | "entityType": "columns", | ||
| 586 | "schema": "public", | ||
| 587 | "table": "organization_memberships" | ||
| 588 | }, | ||
| 589 | { | ||
| 590 | "type": "bigint", | ||
| 591 | "typeSchema": null, | ||
| 592 | "notNull": false, | ||
| 593 | "dimensions": 0, | ||
| 594 | "default": null, | ||
| 595 | "generated": null, | ||
| 596 | "identity": null, | ||
| 597 | "name": "joined_at", | ||
| 598 | "entityType": "columns", | ||
| 599 | "schema": "public", | ||
| 600 | "table": "organization_memberships" | ||
| 601 | }, | ||
| 602 | { | ||
| 603 | "type": "bigint", | ||
| 604 | "typeSchema": null, | ||
| 605 | "notNull": true, | ||
| 606 | "dimensions": 0, | ||
| 607 | "default": null, | ||
| 608 | "generated": null, | ||
| 609 | "identity": null, | ||
| 610 | "name": "created_at", | ||
| 611 | "entityType": "columns", | ||
| 612 | "schema": "public", | ||
| 613 | "table": "organization_memberships" | ||
| 614 | }, | ||
| 615 | { | ||
| 616 | "type": "bigint", | ||
| 617 | "typeSchema": null, | ||
| 618 | "notNull": true, | ||
| 619 | "dimensions": 0, | ||
| 620 | "default": null, | ||
| 621 | "generated": null, | ||
| 622 | "identity": null, | ||
| 623 | "name": "updated_at", | ||
| 624 | "entityType": "columns", | ||
| 625 | "schema": "public", | ||
| 626 | "table": "organization_memberships" | ||
| 627 | }, | ||
| 628 | { | ||
| 629 | "type": "text", | ||
| 630 | "typeSchema": null, | ||
| 631 | "notNull": true, | ||
| 632 | "dimensions": 0, | ||
| 633 | "default": null, | ||
| 634 | "generated": null, | ||
| 635 | "identity": null, | ||
| 636 | "name": "id", | ||
| 637 | "entityType": "columns", | ||
| 638 | "schema": "public", | ||
| 639 | "table": "organizations" | ||
| 640 | }, | ||
| 641 | { | ||
| 642 | "type": "text", | ||
| 643 | "typeSchema": null, | ||
| 644 | "notNull": true, | ||
| 645 | "dimensions": 0, | ||
| 646 | "default": null, | ||
| 647 | "generated": null, | ||
| 648 | "identity": null, | ||
| 649 | "name": "slug", | ||
| 650 | "entityType": "columns", | ||
| 651 | "schema": "public", | ||
| 652 | "table": "organizations" | ||
| 653 | }, | ||
| 654 | { | ||
| 655 | "type": "text", | ||
| 656 | "typeSchema": null, | ||
| 657 | "notNull": true, | ||
| 658 | "dimensions": 0, | ||
| 659 | "default": null, | ||
| 660 | "generated": null, | ||
| 661 | "identity": null, | ||
| 662 | "name": "name", | ||
| 663 | "entityType": "columns", | ||
| 664 | "schema": "public", | ||
| 665 | "table": "organizations" | ||
| 666 | }, | ||
| 667 | { | ||
| 668 | "type": "text", | ||
| 669 | "typeSchema": null, | ||
| 670 | "notNull": true, | ||
| 671 | "dimensions": 0, | ||
| 672 | "default": null, | ||
| 673 | "generated": null, | ||
| 674 | "identity": null, | ||
| 675 | "name": "status", | ||
| 676 | "entityType": "columns", | ||
| 677 | "schema": "public", | ||
| 678 | "table": "organizations" | ||
| 679 | }, | ||
| 680 | { | ||
| 681 | "type": "text", | ||
| 682 | "typeSchema": null, | ||
| 683 | "notNull": false, | ||
| 684 | "dimensions": 0, | ||
| 685 | "default": null, | ||
| 686 | "generated": null, | ||
| 687 | "identity": null, | ||
| 688 | "name": "created_by_account_id", | ||
| 689 | "entityType": "columns", | ||
| 690 | "schema": "public", | ||
| 691 | "table": "organizations" | ||
| 692 | }, | ||
| 693 | { | ||
| 694 | "type": "bigint", | ||
| 695 | "typeSchema": null, | ||
| 696 | "notNull": true, | ||
| 697 | "dimensions": 0, | ||
| 698 | "default": null, | ||
| 699 | "generated": null, | ||
| 700 | "identity": null, | ||
| 701 | "name": "created_at", | ||
| 702 | "entityType": "columns", | ||
| 703 | "schema": "public", | ||
| 704 | "table": "organizations" | ||
| 705 | }, | ||
| 706 | { | ||
| 707 | "type": "bigint", | ||
| 708 | "typeSchema": null, | ||
| 709 | "notNull": true, | ||
| 710 | "dimensions": 0, | ||
| 711 | "default": null, | ||
| 712 | "generated": null, | ||
| 713 | "identity": null, | ||
| 714 | "name": "updated_at", | ||
| 715 | "entityType": "columns", | ||
| 716 | "schema": "public", | ||
| 717 | "table": "organizations" | ||
| 718 | }, | ||
| 719 | { | ||
| 720 | "type": "text", | ||
| 721 | "typeSchema": null, | ||
| 722 | "notNull": true, | ||
| 723 | "dimensions": 0, | ||
| 724 | "default": null, | ||
| 725 | "generated": null, | ||
| 726 | "identity": null, | ||
| 727 | "name": "id", | ||
| 728 | "entityType": "columns", | ||
| 729 | "schema": "public", | ||
| 730 | "table": "policies" | ||
| 731 | }, | ||
| 732 | { | ||
| 733 | "type": "text", | ||
| 734 | "typeSchema": null, | ||
| 735 | "notNull": true, | ||
| 736 | "dimensions": 0, | ||
| 737 | "default": null, | ||
| 738 | "generated": null, | ||
| 739 | "identity": null, | ||
| 740 | "name": "workspace_id", | ||
| 741 | "entityType": "columns", | ||
| 742 | "schema": "public", | ||
| 743 | "table": "policies" | ||
| 744 | }, | ||
| 745 | { | ||
| 746 | "type": "text", | ||
| 747 | "typeSchema": null, | ||
| 748 | "notNull": false, | ||
| 749 | "dimensions": 0, | ||
| 750 | "default": null, | ||
| 751 | "generated": null, | ||
| 752 | "identity": null, | ||
| 753 | "name": "target_account_id", | ||
| 754 | "entityType": "columns", | ||
| 755 | "schema": "public", | ||
| 756 | "table": "policies" | ||
| 757 | }, | ||
| 758 | { | ||
| 759 | "type": "text", | ||
| 760 | "typeSchema": null, | ||
| 761 | "notNull": false, | ||
| 762 | "dimensions": 0, | ||
| 763 | "default": null, | ||
| 764 | "generated": null, | ||
| 765 | "identity": null, | ||
| 766 | "name": "client_id", | ||
| 767 | "entityType": "columns", | ||
| 768 | "schema": "public", | ||
| 769 | "table": "policies" | ||
| 770 | }, | ||
| 771 | { | ||
| 772 | "type": "text", | ||
| 773 | "typeSchema": null, | ||
| 774 | "notNull": true, | ||
| 775 | "dimensions": 0, | ||
| 776 | "default": null, | ||
| 777 | "generated": null, | ||
| 778 | "identity": null, | ||
| 779 | "name": "resource_type", | ||
| 780 | "entityType": "columns", | ||
| 781 | "schema": "public", | ||
| 782 | "table": "policies" | ||
| 783 | }, | ||
| 784 | { | ||
| 785 | "type": "text", | ||
| 786 | "typeSchema": null, | ||
| 787 | "notNull": true, | ||
| 788 | "dimensions": 0, | ||
| 789 | "default": null, | ||
| 790 | "generated": null, | ||
| 791 | "identity": null, | ||
| 792 | "name": "resource_pattern", | ||
| 793 | "entityType": "columns", | ||
| 794 | "schema": "public", | ||
| 795 | "table": "policies" | ||
| 796 | }, | ||
| 797 | { | ||
| 798 | "type": "text", | ||
| 799 | "typeSchema": null, | ||
| 800 | "notNull": true, | ||
| 801 | "dimensions": 0, | ||
| 802 | "default": null, | ||
| 803 | "generated": null, | ||
| 804 | "identity": null, | ||
| 805 | "name": "match_type", | ||
| 806 | "entityType": "columns", | ||
| 807 | "schema": "public", | ||
| 808 | "table": "policies" | ||
| 809 | }, | ||
| 810 | { | ||
| 811 | "type": "text", | ||
| 812 | "typeSchema": null, | ||
| 813 | "notNull": true, | ||
| 814 | "dimensions": 0, | ||
| 815 | "default": null, | ||
| 816 | "generated": null, | ||
| 817 | "identity": null, | ||
| 818 | "name": "effect", | ||
| 819 | "entityType": "columns", | ||
| 820 | "schema": "public", | ||
| 821 | "table": "policies" | ||
| 822 | }, | ||
| 823 | { | ||
| 824 | "type": "text", | ||
| 825 | "typeSchema": null, | ||
| 826 | "notNull": true, | ||
| 827 | "dimensions": 0, | ||
| 828 | "default": null, | ||
| 829 | "generated": null, | ||
| 830 | "identity": null, | ||
| 831 | "name": "approval_mode", | ||
| 832 | "entityType": "columns", | ||
| 833 | "schema": "public", | ||
| 834 | "table": "policies" | ||
| 835 | }, | ||
| 836 | { | ||
| 837 | "type": "text", | ||
| 838 | "typeSchema": null, | ||
| 839 | "notNull": false, | ||
| 840 | "dimensions": 0, | ||
| 841 | "default": null, | ||
| 842 | "generated": null, | ||
| 843 | "identity": null, | ||
| 844 | "name": "argument_conditions_json", | ||
| 845 | "entityType": "columns", | ||
| 846 | "schema": "public", | ||
| 847 | "table": "policies" | ||
| 848 | }, | ||
| 849 | { | ||
| 850 | "type": "bigint", | ||
| 851 | "typeSchema": null, | ||
| 852 | "notNull": true, | ||
| 853 | "dimensions": 0, | ||
| 854 | "default": null, | ||
| 855 | "generated": null, | ||
| 856 | "identity": null, | ||
| 857 | "name": "priority", | ||
| 858 | "entityType": "columns", | ||
| 859 | "schema": "public", | ||
| 860 | "table": "policies" | ||
| 861 | }, | ||
| 862 | { | ||
| 863 | "type": "boolean", | ||
| 864 | "typeSchema": null, | ||
| 865 | "notNull": true, | ||
| 866 | "dimensions": 0, | ||
| 867 | "default": null, | ||
| 868 | "generated": null, | ||
| 869 | "identity": null, | ||
| 870 | "name": "enabled", | ||
| 871 | "entityType": "columns", | ||
| 872 | "schema": "public", | ||
| 873 | "table": "policies" | ||
| 874 | }, | ||
| 875 | { | ||
| 876 | "type": "bigint", | ||
| 877 | "typeSchema": null, | ||
| 878 | "notNull": true, | ||
| 879 | "dimensions": 0, | ||
| 880 | "default": null, | ||
| 881 | "generated": null, | ||
| 882 | "identity": null, | ||
| 883 | "name": "created_at", | ||
| 884 | "entityType": "columns", | ||
| 885 | "schema": "public", | ||
| 886 | "table": "policies" | ||
| 887 | }, | ||
| 888 | { | ||
| 889 | "type": "bigint", | ||
| 890 | "typeSchema": null, | ||
| 891 | "notNull": true, | ||
| 892 | "dimensions": 0, | ||
| 893 | "default": null, | ||
| 894 | "generated": null, | ||
| 895 | "identity": null, | ||
| 896 | "name": "updated_at", | ||
| 897 | "entityType": "columns", | ||
| 898 | "schema": "public", | ||
| 899 | "table": "policies" | ||
| 900 | }, | ||
| 901 | { | ||
| 902 | "type": "text", | ||
| 903 | "typeSchema": null, | ||
| 904 | "notNull": true, | ||
| 905 | "dimensions": 0, | ||
| 906 | "default": null, | ||
| 907 | "generated": null, | ||
| 908 | "identity": null, | ||
| 909 | "name": "workspace_id", | ||
| 910 | "entityType": "columns", | ||
| 911 | "schema": "public", | ||
| 912 | "table": "source_credential_bindings" | ||
| 913 | }, | ||
| 914 | { | ||
| 915 | "type": "text", | ||
| 916 | "typeSchema": null, | ||
| 917 | "notNull": true, | ||
| 918 | "dimensions": 0, | ||
| 919 | "default": null, | ||
| 920 | "generated": null, | ||
| 921 | "identity": null, | ||
| 922 | "name": "source_id", | ||
| 923 | "entityType": "columns", | ||
| 924 | "schema": "public", | ||
| 925 | "table": "source_credential_bindings" | ||
| 926 | }, | ||
| 927 | { | ||
| 928 | "type": "text", | ||
| 929 | "typeSchema": null, | ||
| 930 | "notNull": false, | ||
| 931 | "dimensions": 0, | ||
| 932 | "default": null, | ||
| 933 | "generated": null, | ||
| 934 | "identity": null, | ||
| 935 | "name": "token_provider_id", | ||
| 936 | "entityType": "columns", | ||
| 937 | "schema": "public", | ||
| 938 | "table": "source_credential_bindings" | ||
| 939 | }, | ||
| 940 | { | ||
| 941 | "type": "text", | ||
| 942 | "typeSchema": null, | ||
| 943 | "notNull": false, | ||
| 944 | "dimensions": 0, | ||
| 945 | "default": null, | ||
| 946 | "generated": null, | ||
| 947 | "identity": null, | ||
| 948 | "name": "token_handle", | ||
| 949 | "entityType": "columns", | ||
| 950 | "schema": "public", | ||
| 951 | "table": "source_credential_bindings" | ||
| 952 | }, | ||
| 953 | { | ||
| 954 | "type": "text", | ||
| 955 | "typeSchema": null, | ||
| 956 | "notNull": false, | ||
| 957 | "dimensions": 0, | ||
| 958 | "default": null, | ||
| 959 | "generated": null, | ||
| 960 | "identity": null, | ||
| 961 | "name": "refresh_token_provider_id", | ||
| 962 | "entityType": "columns", | ||
| 963 | "schema": "public", | ||
| 964 | "table": "source_credential_bindings" | ||
| 965 | }, | ||
| 966 | { | ||
| 967 | "type": "text", | ||
| 968 | "typeSchema": null, | ||
| 969 | "notNull": false, | ||
| 970 | "dimensions": 0, | ||
| 971 | "default": null, | ||
| 972 | "generated": null, | ||
| 973 | "identity": null, | ||
| 974 | "name": "refresh_token_handle", | ||
| 975 | "entityType": "columns", | ||
| 976 | "schema": "public", | ||
| 977 | "table": "source_credential_bindings" | ||
| 978 | }, | ||
| 979 | { | ||
| 980 | "type": "bigint", | ||
| 981 | "typeSchema": null, | ||
| 982 | "notNull": true, | ||
| 983 | "dimensions": 0, | ||
| 984 | "default": null, | ||
| 985 | "generated": null, | ||
| 986 | "identity": null, | ||
| 987 | "name": "created_at", | ||
| 988 | "entityType": "columns", | ||
| 989 | "schema": "public", | ||
| 990 | "table": "source_credential_bindings" | ||
| 991 | }, | ||
| 992 | { | ||
| 993 | "type": "bigint", | ||
| 994 | "typeSchema": null, | ||
| 995 | "notNull": true, | ||
| 996 | "dimensions": 0, | ||
| 997 | "default": null, | ||
| 998 | "generated": null, | ||
| 999 | "identity": null, | ||
| 1000 | "name": "updated_at", | ||
| 1001 | "entityType": "columns", | ||
| 1002 | "schema": "public", | ||
| 1003 | "table": "source_credential_bindings" | ||
| 1004 | }, | ||
| 1005 | { | ||
| 1006 | "type": "text", | ||
| 1007 | "typeSchema": null, | ||
| 1008 | "notNull": true, | ||
| 1009 | "dimensions": 0, | ||
| 1010 | "default": null, | ||
| 1011 | "generated": null, | ||
| 1012 | "identity": null, | ||
| 1013 | "name": "workspace_id", | ||
| 1014 | "entityType": "columns", | ||
| 1015 | "schema": "public", | ||
| 1016 | "table": "sources" | ||
| 1017 | }, | ||
| 1018 | { | ||
| 1019 | "type": "text", | ||
| 1020 | "typeSchema": null, | ||
| 1021 | "notNull": true, | ||
| 1022 | "dimensions": 0, | ||
| 1023 | "default": null, | ||
| 1024 | "generated": null, | ||
| 1025 | "identity": null, | ||
| 1026 | "name": "source_id", | ||
| 1027 | "entityType": "columns", | ||
| 1028 | "schema": "public", | ||
| 1029 | "table": "sources" | ||
| 1030 | }, | ||
| 1031 | { | ||
| 1032 | "type": "text", | ||
| 1033 | "typeSchema": null, | ||
| 1034 | "notNull": true, | ||
| 1035 | "dimensions": 0, | ||
| 1036 | "default": null, | ||
| 1037 | "generated": null, | ||
| 1038 | "identity": null, | ||
| 1039 | "name": "name", | ||
| 1040 | "entityType": "columns", | ||
| 1041 | "schema": "public", | ||
| 1042 | "table": "sources" | ||
| 1043 | }, | ||
| 1044 | { | ||
| 1045 | "type": "text", | ||
| 1046 | "typeSchema": null, | ||
| 1047 | "notNull": true, | ||
| 1048 | "dimensions": 0, | ||
| 1049 | "default": null, | ||
| 1050 | "generated": null, | ||
| 1051 | "identity": null, | ||
| 1052 | "name": "kind", | ||
| 1053 | "entityType": "columns", | ||
| 1054 | "schema": "public", | ||
| 1055 | "table": "sources" | ||
| 1056 | }, | ||
| 1057 | { | ||
| 1058 | "type": "text", | ||
| 1059 | "typeSchema": null, | ||
| 1060 | "notNull": true, | ||
| 1061 | "dimensions": 0, | ||
| 1062 | "default": null, | ||
| 1063 | "generated": null, | ||
| 1064 | "identity": null, | ||
| 1065 | "name": "endpoint", | ||
| 1066 | "entityType": "columns", | ||
| 1067 | "schema": "public", | ||
| 1068 | "table": "sources" | ||
| 1069 | }, | ||
| 1070 | { | ||
| 1071 | "type": "text", | ||
| 1072 | "typeSchema": null, | ||
| 1073 | "notNull": true, | ||
| 1074 | "dimensions": 0, | ||
| 1075 | "default": null, | ||
| 1076 | "generated": null, | ||
| 1077 | "identity": null, | ||
| 1078 | "name": "status", | ||
| 1079 | "entityType": "columns", | ||
| 1080 | "schema": "public", | ||
| 1081 | "table": "sources" | ||
| 1082 | }, | ||
| 1083 | { | ||
| 1084 | "type": "boolean", | ||
| 1085 | "typeSchema": null, | ||
| 1086 | "notNull": true, | ||
| 1087 | "dimensions": 0, | ||
| 1088 | "default": null, | ||
| 1089 | "generated": null, | ||
| 1090 | "identity": null, | ||
| 1091 | "name": "enabled", | ||
| 1092 | "entityType": "columns", | ||
| 1093 | "schema": "public", | ||
| 1094 | "table": "sources" | ||
| 1095 | }, | ||
| 1096 | { | ||
| 1097 | "type": "text", | ||
| 1098 | "typeSchema": null, | ||
| 1099 | "notNull": false, | ||
| 1100 | "dimensions": 0, | ||
| 1101 | "default": null, | ||
| 1102 | "generated": null, | ||
| 1103 | "identity": null, | ||
| 1104 | "name": "namespace", | ||
| 1105 | "entityType": "columns", | ||
| 1106 | "schema": "public", | ||
| 1107 | "table": "sources" | ||
| 1108 | }, | ||
| 1109 | { | ||
| 1110 | "type": "text", | ||
| 1111 | "typeSchema": null, | ||
| 1112 | "notNull": false, | ||
| 1113 | "dimensions": 0, | ||
| 1114 | "default": null, | ||
| 1115 | "generated": null, | ||
| 1116 | "identity": null, | ||
| 1117 | "name": "transport", | ||
| 1118 | "entityType": "columns", | ||
| 1119 | "schema": "public", | ||
| 1120 | "table": "sources" | ||
| 1121 | }, | ||
| 1122 | { | ||
| 1123 | "type": "text", | ||
| 1124 | "typeSchema": null, | ||
| 1125 | "notNull": false, | ||
| 1126 | "dimensions": 0, | ||
| 1127 | "default": null, | ||
| 1128 | "generated": null, | ||
| 1129 | "identity": null, | ||
| 1130 | "name": "query_params_json", | ||
| 1131 | "entityType": "columns", | ||
| 1132 | "schema": "public", | ||
| 1133 | "table": "sources" | ||
| 1134 | }, | ||
| 1135 | { | ||
| 1136 | "type": "text", | ||
| 1137 | "typeSchema": null, | ||
| 1138 | "notNull": false, | ||
| 1139 | "dimensions": 0, | ||
| 1140 | "default": null, | ||
| 1141 | "generated": null, | ||
| 1142 | "identity": null, | ||
| 1143 | "name": "headers_json", | ||
| 1144 | "entityType": "columns", | ||
| 1145 | "schema": "public", | ||
| 1146 | "table": "sources" | ||
| 1147 | }, | ||
| 1148 | { | ||
| 1149 | "type": "text", | ||
| 1150 | "typeSchema": null, | ||
| 1151 | "notNull": false, | ||
| 1152 | "dimensions": 0, | ||
| 1153 | "default": null, | ||
| 1154 | "generated": null, | ||
| 1155 | "identity": null, | ||
| 1156 | "name": "spec_url", | ||
| 1157 | "entityType": "columns", | ||
| 1158 | "schema": "public", | ||
| 1159 | "table": "sources" | ||
| 1160 | }, | ||
| 1161 | { | ||
| 1162 | "type": "text", | ||
| 1163 | "typeSchema": null, | ||
| 1164 | "notNull": false, | ||
| 1165 | "dimensions": 0, | ||
| 1166 | "default": null, | ||
| 1167 | "generated": null, | ||
| 1168 | "identity": null, | ||
| 1169 | "name": "default_headers_json", | ||
| 1170 | "entityType": "columns", | ||
| 1171 | "schema": "public", | ||
| 1172 | "table": "sources" | ||
| 1173 | }, | ||
| 1174 | { | ||
| 1175 | "type": "text", | ||
| 1176 | "typeSchema": null, | ||
| 1177 | "notNull": true, | ||
| 1178 | "dimensions": 0, | ||
| 1179 | "default": null, | ||
| 1180 | "generated": null, | ||
| 1181 | "identity": null, | ||
| 1182 | "name": "auth_kind", | ||
| 1183 | "entityType": "columns", | ||
| 1184 | "schema": "public", | ||
| 1185 | "table": "sources" | ||
| 1186 | }, | ||
| 1187 | { | ||
| 1188 | "type": "text", | ||
| 1189 | "typeSchema": null, | ||
| 1190 | "notNull": false, | ||
| 1191 | "dimensions": 0, | ||
| 1192 | "default": null, | ||
| 1193 | "generated": null, | ||
| 1194 | "identity": null, | ||
| 1195 | "name": "auth_header_name", | ||
| 1196 | "entityType": "columns", | ||
| 1197 | "schema": "public", | ||
| 1198 | "table": "sources" | ||
| 1199 | }, | ||
| 1200 | { | ||
| 1201 | "type": "text", | ||
| 1202 | "typeSchema": null, | ||
| 1203 | "notNull": false, | ||
| 1204 | "dimensions": 0, | ||
| 1205 | "default": null, | ||
| 1206 | "generated": null, | ||
| 1207 | "identity": null, | ||
| 1208 | "name": "auth_prefix", | ||
| 1209 | "entityType": "columns", | ||
| 1210 | "schema": "public", | ||
| 1211 | "table": "sources" | ||
| 1212 | }, | ||
| 1213 | { | ||
| 1214 | "type": "text", | ||
| 1215 | "typeSchema": null, | ||
| 1216 | "notNull": false, | ||
| 1217 | "dimensions": 0, | ||
| 1218 | "default": null, | ||
| 1219 | "generated": null, | ||
| 1220 | "identity": null, | ||
| 1221 | "name": "source_hash", | ||
| 1222 | "entityType": "columns", | ||
| 1223 | "schema": "public", | ||
| 1224 | "table": "sources" | ||
| 1225 | }, | ||
| 1226 | { | ||
| 1227 | "type": "text", | ||
| 1228 | "typeSchema": null, | ||
| 1229 | "notNull": false, | ||
| 1230 | "dimensions": 0, | ||
| 1231 | "default": null, | ||
| 1232 | "generated": null, | ||
| 1233 | "identity": null, | ||
| 1234 | "name": "last_error", | ||
| 1235 | "entityType": "columns", | ||
| 1236 | "schema": "public", | ||
| 1237 | "table": "sources" | ||
| 1238 | }, | ||
| 1239 | { | ||
| 1240 | "type": "bigint", | ||
| 1241 | "typeSchema": null, | ||
| 1242 | "notNull": true, | ||
| 1243 | "dimensions": 0, | ||
| 1244 | "default": null, | ||
| 1245 | "generated": null, | ||
| 1246 | "identity": null, | ||
| 1247 | "name": "created_at", | ||
| 1248 | "entityType": "columns", | ||
| 1249 | "schema": "public", | ||
| 1250 | "table": "sources" | ||
| 1251 | }, | ||
| 1252 | { | ||
| 1253 | "type": "bigint", | ||
| 1254 | "typeSchema": null, | ||
| 1255 | "notNull": true, | ||
| 1256 | "dimensions": 0, | ||
| 1257 | "default": null, | ||
| 1258 | "generated": null, | ||
| 1259 | "identity": null, | ||
| 1260 | "name": "updated_at", | ||
| 1261 | "entityType": "columns", | ||
| 1262 | "schema": "public", | ||
| 1263 | "table": "sources" | ||
| 1264 | }, | ||
| 1265 | { | ||
| 1266 | "type": "text", | ||
| 1267 | "typeSchema": null, | ||
| 1268 | "notNull": true, | ||
| 1269 | "dimensions": 0, | ||
| 1270 | "default": null, | ||
| 1271 | "generated": null, | ||
| 1272 | "identity": null, | ||
| 1273 | "name": "id", | ||
| 1274 | "entityType": "columns", | ||
| 1275 | "schema": "public", | ||
| 1276 | "table": "workspaces" | ||
| 1277 | }, | ||
| 1278 | { | ||
| 1279 | "type": "text", | ||
| 1280 | "typeSchema": null, | ||
| 1281 | "notNull": true, | ||
| 1282 | "dimensions": 0, | ||
| 1283 | "default": null, | ||
| 1284 | "generated": null, | ||
| 1285 | "identity": null, | ||
| 1286 | "name": "organization_id", | ||
| 1287 | "entityType": "columns", | ||
| 1288 | "schema": "public", | ||
| 1289 | "table": "workspaces" | ||
| 1290 | }, | ||
| 1291 | { | ||
| 1292 | "type": "text", | ||
| 1293 | "typeSchema": null, | ||
| 1294 | "notNull": true, | ||
| 1295 | "dimensions": 0, | ||
| 1296 | "default": null, | ||
| 1297 | "generated": null, | ||
| 1298 | "identity": null, | ||
| 1299 | "name": "name", | ||
| 1300 | "entityType": "columns", | ||
| 1301 | "schema": "public", | ||
| 1302 | "table": "workspaces" | ||
| 1303 | }, | ||
| 1304 | { | ||
| 1305 | "type": "text", | ||
| 1306 | "typeSchema": null, | ||
| 1307 | "notNull": false, | ||
| 1308 | "dimensions": 0, | ||
| 1309 | "default": null, | ||
| 1310 | "generated": null, | ||
| 1311 | "identity": null, | ||
| 1312 | "name": "created_by_account_id", | ||
| 1313 | "entityType": "columns", | ||
| 1314 | "schema": "public", | ||
| 1315 | "table": "workspaces" | ||
| 1316 | }, | ||
| 1317 | { | ||
| 1318 | "type": "bigint", | ||
| 1319 | "typeSchema": null, | ||
| 1320 | "notNull": true, | ||
| 1321 | "dimensions": 0, | ||
| 1322 | "default": null, | ||
| 1323 | "generated": null, | ||
| 1324 | "identity": null, | ||
| 1325 | "name": "created_at", | ||
| 1326 | "entityType": "columns", | ||
| 1327 | "schema": "public", | ||
| 1328 | "table": "workspaces" | ||
| 1329 | }, | ||
| 1330 | { | ||
| 1331 | "type": "bigint", | ||
| 1332 | "typeSchema": null, | ||
| 1333 | "notNull": true, | ||
| 1334 | "dimensions": 0, | ||
| 1335 | "default": null, | ||
| 1336 | "generated": null, | ||
| 1337 | "identity": null, | ||
| 1338 | "name": "updated_at", | ||
| 1339 | "entityType": "columns", | ||
| 1340 | "schema": "public", | ||
| 1341 | "table": "workspaces" | ||
| 1342 | }, | ||
| 1343 | { | ||
| 1344 | "nameExplicit": true, | ||
| 1345 | "columns": [ | ||
| 1346 | { | ||
| 1347 | "value": "provider", | ||
| 1348 | "isExpression": false, | ||
| 1349 | "asc": true, | ||
| 1350 | "nullsFirst": false, | ||
| 1351 | "opclass": null | ||
| 1352 | }, | ||
| 1353 | { | ||
| 1354 | "value": "subject", | ||
| 1355 | "isExpression": false, | ||
| 1356 | "asc": true, | ||
| 1357 | "nullsFirst": false, | ||
| 1358 | "opclass": null | ||
| 1359 | } | ||
| 1360 | ], | ||
| 1361 | "isUnique": true, | ||
| 1362 | "where": null, | ||
| 1363 | "with": "", | ||
| 1364 | "method": "btree", | ||
| 1365 | "concurrently": false, | ||
| 1366 | "name": "accounts_provider_subject_idx", | ||
| 1367 | "entityType": "indexes", | ||
| 1368 | "schema": "public", | ||
| 1369 | "table": "accounts" | ||
| 1370 | }, | ||
| 1371 | { | ||
| 1372 | "nameExplicit": true, | ||
| 1373 | "columns": [ | ||
| 1374 | { | ||
| 1375 | "value": "updated_at", | ||
| 1376 | "isExpression": false, | ||
| 1377 | "asc": true, | ||
| 1378 | "nullsFirst": false, | ||
| 1379 | "opclass": null | ||
| 1380 | }, | ||
| 1381 | { | ||
| 1382 | "value": "id", | ||
| 1383 | "isExpression": false, | ||
| 1384 | "asc": true, | ||
| 1385 | "nullsFirst": false, | ||
| 1386 | "opclass": null | ||
| 1387 | } | ||
| 1388 | ], | ||
| 1389 | "isUnique": false, | ||
| 1390 | "where": null, | ||
| 1391 | "with": "", | ||
| 1392 | "method": "btree", | ||
| 1393 | "concurrently": false, | ||
| 1394 | "name": "accounts_updated_idx", | ||
| 1395 | "entityType": "indexes", | ||
| 1396 | "schema": "public", | ||
| 1397 | "table": "accounts" | ||
| 1398 | }, | ||
| 1399 | { | ||
| 1400 | "nameExplicit": true, | ||
| 1401 | "columns": [ | ||
| 1402 | { | ||
| 1403 | "value": "execution_id", | ||
| 1404 | "isExpression": false, | ||
| 1405 | "asc": true, | ||
| 1406 | "nullsFirst": false, | ||
| 1407 | "opclass": null | ||
| 1408 | }, | ||
| 1409 | { | ||
| 1410 | "value": "updated_at", | ||
| 1411 | "isExpression": false, | ||
| 1412 | "asc": true, | ||
| 1413 | "nullsFirst": false, | ||
| 1414 | "opclass": null | ||
| 1415 | }, | ||
| 1416 | { | ||
| 1417 | "value": "id", | ||
| 1418 | "isExpression": false, | ||
| 1419 | "asc": true, | ||
| 1420 | "nullsFirst": false, | ||
| 1421 | "opclass": null | ||
| 1422 | } | ||
| 1423 | ], | ||
| 1424 | "isUnique": false, | ||
| 1425 | "where": null, | ||
| 1426 | "with": "", | ||
| 1427 | "method": "btree", | ||
| 1428 | "concurrently": false, | ||
| 1429 | "name": "execution_interactions_execution_idx", | ||
| 1430 | "entityType": "indexes", | ||
| 1431 | "schema": "public", | ||
| 1432 | "table": "execution_interactions" | ||
| 1433 | }, | ||
| 1434 | { | ||
| 1435 | "nameExplicit": true, | ||
| 1436 | "columns": [ | ||
| 1437 | { | ||
| 1438 | "value": "workspace_id", | ||
| 1439 | "isExpression": false, | ||
| 1440 | "asc": true, | ||
| 1441 | "nullsFirst": false, | ||
| 1442 | "opclass": null | ||
| 1443 | }, | ||
| 1444 | { | ||
| 1445 | "value": "updated_at", | ||
| 1446 | "isExpression": false, | ||
| 1447 | "asc": true, | ||
| 1448 | "nullsFirst": false, | ||
| 1449 | "opclass": null | ||
| 1450 | }, | ||
| 1451 | { | ||
| 1452 | "value": "id", | ||
| 1453 | "isExpression": false, | ||
| 1454 | "asc": true, | ||
| 1455 | "nullsFirst": false, | ||
| 1456 | "opclass": null | ||
| 1457 | } | ||
| 1458 | ], | ||
| 1459 | "isUnique": false, | ||
| 1460 | "where": null, | ||
| 1461 | "with": "", | ||
| 1462 | "method": "btree", | ||
| 1463 | "concurrently": false, | ||
| 1464 | "name": "executions_workspace_idx", | ||
| 1465 | "entityType": "indexes", | ||
| 1466 | "schema": "public", | ||
| 1467 | "table": "executions" | ||
| 1468 | }, | ||
| 1469 | { | ||
| 1470 | "nameExplicit": true, | ||
| 1471 | "columns": [ | ||
| 1472 | { | ||
| 1473 | "value": "organization_id", | ||
| 1474 | "isExpression": false, | ||
| 1475 | "asc": true, | ||
| 1476 | "nullsFirst": false, | ||
| 1477 | "opclass": null | ||
| 1478 | } | ||
| 1479 | ], | ||
| 1480 | "isUnique": false, | ||
| 1481 | "where": null, | ||
| 1482 | "with": "", | ||
| 1483 | "method": "btree", | ||
| 1484 | "concurrently": false, | ||
| 1485 | "name": "organization_memberships_org_idx", | ||
| 1486 | "entityType": "indexes", | ||
| 1487 | "schema": "public", | ||
| 1488 | "table": "organization_memberships" | ||
| 1489 | }, | ||
| 1490 | { | ||
| 1491 | "nameExplicit": true, | ||
| 1492 | "columns": [ | ||
| 1493 | { | ||
| 1494 | "value": "account_id", | ||
| 1495 | "isExpression": false, | ||
| 1496 | "asc": true, | ||
| 1497 | "nullsFirst": false, | ||
| 1498 | "opclass": null | ||
| 1499 | } | ||
| 1500 | ], | ||
| 1501 | "isUnique": false, | ||
| 1502 | "where": null, | ||
| 1503 | "with": "", | ||
| 1504 | "method": "btree", | ||
| 1505 | "concurrently": false, | ||
| 1506 | "name": "organization_memberships_account_idx", | ||
| 1507 | "entityType": "indexes", | ||
| 1508 | "schema": "public", | ||
| 1509 | "table": "organization_memberships" | ||
| 1510 | }, | ||
| 1511 | { | ||
| 1512 | "nameExplicit": true, | ||
| 1513 | "columns": [ | ||
| 1514 | { | ||
| 1515 | "value": "organization_id", | ||
| 1516 | "isExpression": false, | ||
| 1517 | "asc": true, | ||
| 1518 | "nullsFirst": false, | ||
| 1519 | "opclass": null | ||
| 1520 | }, | ||
| 1521 | { | ||
| 1522 | "value": "account_id", | ||
| 1523 | "isExpression": false, | ||
| 1524 | "asc": true, | ||
| 1525 | "nullsFirst": false, | ||
| 1526 | "opclass": null | ||
| 1527 | } | ||
| 1528 | ], | ||
| 1529 | "isUnique": true, | ||
| 1530 | "where": null, | ||
| 1531 | "with": "", | ||
| 1532 | "method": "btree", | ||
| 1533 | "concurrently": false, | ||
| 1534 | "name": "organization_memberships_org_account_idx", | ||
| 1535 | "entityType": "indexes", | ||
| 1536 | "schema": "public", | ||
| 1537 | "table": "organization_memberships" | ||
| 1538 | }, | ||
| 1539 | { | ||
| 1540 | "nameExplicit": true, | ||
| 1541 | "columns": [ | ||
| 1542 | { | ||
| 1543 | "value": "slug", | ||
| 1544 | "isExpression": false, | ||
| 1545 | "asc": true, | ||
| 1546 | "nullsFirst": false, | ||
| 1547 | "opclass": null | ||
| 1548 | } | ||
| 1549 | ], | ||
| 1550 | "isUnique": true, | ||
| 1551 | "where": null, | ||
| 1552 | "with": "", | ||
| 1553 | "method": "btree", | ||
| 1554 | "concurrently": false, | ||
| 1555 | "name": "organizations_slug_idx", | ||
| 1556 | "entityType": "indexes", | ||
| 1557 | "schema": "public", | ||
| 1558 | "table": "organizations" | ||
| 1559 | }, | ||
| 1560 | { | ||
| 1561 | "nameExplicit": true, | ||
| 1562 | "columns": [ | ||
| 1563 | { | ||
| 1564 | "value": "updated_at", | ||
| 1565 | "isExpression": false, | ||
| 1566 | "asc": true, | ||
| 1567 | "nullsFirst": false, | ||
| 1568 | "opclass": null | ||
| 1569 | }, | ||
| 1570 | { | ||
| 1571 | "value": "id", | ||
| 1572 | "isExpression": false, | ||
| 1573 | "asc": true, | ||
| 1574 | "nullsFirst": false, | ||
| 1575 | "opclass": null | ||
| 1576 | } | ||
| 1577 | ], | ||
| 1578 | "isUnique": false, | ||
| 1579 | "where": null, | ||
| 1580 | "with": "", | ||
| 1581 | "method": "btree", | ||
| 1582 | "concurrently": false, | ||
| 1583 | "name": "organizations_updated_idx", | ||
| 1584 | "entityType": "indexes", | ||
| 1585 | "schema": "public", | ||
| 1586 | "table": "organizations" | ||
| 1587 | }, | ||
| 1588 | { | ||
| 1589 | "nameExplicit": true, | ||
| 1590 | "columns": [ | ||
| 1591 | { | ||
| 1592 | "value": "workspace_id", | ||
| 1593 | "isExpression": false, | ||
| 1594 | "asc": true, | ||
| 1595 | "nullsFirst": false, | ||
| 1596 | "opclass": null | ||
| 1597 | }, | ||
| 1598 | { | ||
| 1599 | "value": "updated_at", | ||
| 1600 | "isExpression": false, | ||
| 1601 | "asc": true, | ||
| 1602 | "nullsFirst": false, | ||
| 1603 | "opclass": null | ||
| 1604 | }, | ||
| 1605 | { | ||
| 1606 | "value": "id", | ||
| 1607 | "isExpression": false, | ||
| 1608 | "asc": true, | ||
| 1609 | "nullsFirst": false, | ||
| 1610 | "opclass": null | ||
| 1611 | } | ||
| 1612 | ], | ||
| 1613 | "isUnique": false, | ||
| 1614 | "where": null, | ||
| 1615 | "with": "", | ||
| 1616 | "method": "btree", | ||
| 1617 | "concurrently": false, | ||
| 1618 | "name": "policies_workspace_idx", | ||
| 1619 | "entityType": "indexes", | ||
| 1620 | "schema": "public", | ||
| 1621 | "table": "policies" | ||
| 1622 | }, | ||
| 1623 | { | ||
| 1624 | "nameExplicit": true, | ||
| 1625 | "columns": [ | ||
| 1626 | { | ||
| 1627 | "value": "workspace_id", | ||
| 1628 | "isExpression": false, | ||
| 1629 | "asc": true, | ||
| 1630 | "nullsFirst": false, | ||
| 1631 | "opclass": null | ||
| 1632 | }, | ||
| 1633 | { | ||
| 1634 | "value": "updated_at", | ||
| 1635 | "isExpression": false, | ||
| 1636 | "asc": true, | ||
| 1637 | "nullsFirst": false, | ||
| 1638 | "opclass": null | ||
| 1639 | }, | ||
| 1640 | { | ||
| 1641 | "value": "source_id", | ||
| 1642 | "isExpression": false, | ||
| 1643 | "asc": true, | ||
| 1644 | "nullsFirst": false, | ||
| 1645 | "opclass": null | ||
| 1646 | } | ||
| 1647 | ], | ||
| 1648 | "isUnique": false, | ||
| 1649 | "where": null, | ||
| 1650 | "with": "", | ||
| 1651 | "method": "btree", | ||
| 1652 | "concurrently": false, | ||
| 1653 | "name": "source_credential_bindings_workspace_idx", | ||
| 1654 | "entityType": "indexes", | ||
| 1655 | "schema": "public", | ||
| 1656 | "table": "source_credential_bindings" | ||
| 1657 | }, | ||
| 1658 | { | ||
| 1659 | "nameExplicit": true, | ||
| 1660 | "columns": [ | ||
| 1661 | { | ||
| 1662 | "value": "workspace_id", | ||
| 1663 | "isExpression": false, | ||
| 1664 | "asc": true, | ||
| 1665 | "nullsFirst": false, | ||
| 1666 | "opclass": null | ||
| 1667 | }, | ||
| 1668 | { | ||
| 1669 | "value": "name", | ||
| 1670 | "isExpression": false, | ||
| 1671 | "asc": true, | ||
| 1672 | "nullsFirst": false, | ||
| 1673 | "opclass": null | ||
| 1674 | } | ||
| 1675 | ], | ||
| 1676 | "isUnique": true, | ||
| 1677 | "where": null, | ||
| 1678 | "with": "", | ||
| 1679 | "method": "btree", | ||
| 1680 | "concurrently": false, | ||
| 1681 | "name": "sources_workspace_name_idx", | ||
| 1682 | "entityType": "indexes", | ||
| 1683 | "schema": "public", | ||
| 1684 | "table": "sources" | ||
| 1685 | }, | ||
| 1686 | { | ||
| 1687 | "nameExplicit": true, | ||
| 1688 | "columns": [ | ||
| 1689 | { | ||
| 1690 | "value": "organization_id", | ||
| 1691 | "isExpression": false, | ||
| 1692 | "asc": true, | ||
| 1693 | "nullsFirst": false, | ||
| 1694 | "opclass": null | ||
| 1695 | } | ||
| 1696 | ], | ||
| 1697 | "isUnique": false, | ||
| 1698 | "where": null, | ||
| 1699 | "with": "", | ||
| 1700 | "method": "btree", | ||
| 1701 | "concurrently": false, | ||
| 1702 | "name": "workspaces_org_idx", | ||
| 1703 | "entityType": "indexes", | ||
| 1704 | "schema": "public", | ||
| 1705 | "table": "workspaces" | ||
| 1706 | }, | ||
| 1707 | { | ||
| 1708 | "nameExplicit": true, | ||
| 1709 | "columns": [ | ||
| 1710 | { | ||
| 1711 | "value": "organization_id", | ||
| 1712 | "isExpression": false, | ||
| 1713 | "asc": true, | ||
| 1714 | "nullsFirst": false, | ||
| 1715 | "opclass": null | ||
| 1716 | }, | ||
| 1717 | { | ||
| 1718 | "value": "name", | ||
| 1719 | "isExpression": false, | ||
| 1720 | "asc": true, | ||
| 1721 | "nullsFirst": false, | ||
| 1722 | "opclass": null | ||
| 1723 | } | ||
| 1724 | ], | ||
| 1725 | "isUnique": true, | ||
| 1726 | "where": null, | ||
| 1727 | "with": "", | ||
| 1728 | "method": "btree", | ||
| 1729 | "concurrently": false, | ||
| 1730 | "name": "workspaces_org_name_idx", | ||
| 1731 | "entityType": "indexes", | ||
| 1732 | "schema": "public", | ||
| 1733 | "table": "workspaces" | ||
| 1734 | }, | ||
| 1735 | { | ||
| 1736 | "columns": [ | ||
| 1737 | "workspace_id", | ||
| 1738 | "source_id" | ||
| 1739 | ], | ||
| 1740 | "nameExplicit": false, | ||
| 1741 | "name": "source_credential_bindings_pkey", | ||
| 1742 | "entityType": "pks", | ||
| 1743 | "schema": "public", | ||
| 1744 | "table": "source_credential_bindings" | ||
| 1745 | }, | ||
| 1746 | { | ||
| 1747 | "columns": [ | ||
| 1748 | "workspace_id", | ||
| 1749 | "source_id" | ||
| 1750 | ], | ||
| 1751 | "nameExplicit": false, | ||
| 1752 | "name": "sources_pkey", | ||
| 1753 | "entityType": "pks", | ||
| 1754 | "schema": "public", | ||
| 1755 | "table": "sources" | ||
| 1756 | }, | ||
| 1757 | { | ||
| 1758 | "columns": [ | ||
| 1759 | "id" | ||
| 1760 | ], | ||
| 1761 | "nameExplicit": false, | ||
| 1762 | "name": "accounts_pkey", | ||
| 1763 | "schema": "public", | ||
| 1764 | "table": "accounts", | ||
| 1765 | "entityType": "pks" | ||
| 1766 | }, | ||
| 1767 | { | ||
| 1768 | "columns": [ | ||
| 1769 | "id" | ||
| 1770 | ], | ||
| 1771 | "nameExplicit": false, | ||
| 1772 | "name": "execution_interactions_pkey", | ||
| 1773 | "schema": "public", | ||
| 1774 | "table": "execution_interactions", | ||
| 1775 | "entityType": "pks" | ||
| 1776 | }, | ||
| 1777 | { | ||
| 1778 | "columns": [ | ||
| 1779 | "id" | ||
| 1780 | ], | ||
| 1781 | "nameExplicit": false, | ||
| 1782 | "name": "executions_pkey", | ||
| 1783 | "schema": "public", | ||
| 1784 | "table": "executions", | ||
| 1785 | "entityType": "pks" | ||
| 1786 | }, | ||
| 1787 | { | ||
| 1788 | "columns": [ | ||
| 1789 | "id" | ||
| 1790 | ], | ||
| 1791 | "nameExplicit": false, | ||
| 1792 | "name": "local_installations_pkey", | ||
| 1793 | "schema": "public", | ||
| 1794 | "table": "local_installations", | ||
| 1795 | "entityType": "pks" | ||
| 1796 | }, | ||
| 1797 | { | ||
| 1798 | "columns": [ | ||
| 1799 | "id" | ||
| 1800 | ], | ||
| 1801 | "nameExplicit": false, | ||
| 1802 | "name": "organization_memberships_pkey", | ||
| 1803 | "schema": "public", | ||
| 1804 | "table": "organization_memberships", | ||
| 1805 | "entityType": "pks" | ||
| 1806 | }, | ||
| 1807 | { | ||
| 1808 | "columns": [ | ||
| 1809 | "id" | ||
| 1810 | ], | ||
| 1811 | "nameExplicit": false, | ||
| 1812 | "name": "organizations_pkey", | ||
| 1813 | "schema": "public", | ||
| 1814 | "table": "organizations", | ||
| 1815 | "entityType": "pks" | ||
| 1816 | }, | ||
| 1817 | { | ||
| 1818 | "columns": [ | ||
| 1819 | "id" | ||
| 1820 | ], | ||
| 1821 | "nameExplicit": false, | ||
| 1822 | "name": "policies_pkey", | ||
| 1823 | "schema": "public", | ||
| 1824 | "table": "policies", | ||
| 1825 | "entityType": "pks" | ||
| 1826 | }, | ||
| 1827 | { | ||
| 1828 | "columns": [ | ||
| 1829 | "id" | ||
| 1830 | ], | ||
| 1831 | "nameExplicit": false, | ||
| 1832 | "name": "workspaces_pkey", | ||
| 1833 | "schema": "public", | ||
| 1834 | "table": "workspaces", | ||
| 1835 | "entityType": "pks" | ||
| 1836 | }, | ||
| 1837 | { | ||
| 1838 | "value": "\"provider\" in ('local', 'workos', 'service')", | ||
| 1839 | "name": "accounts_provider_check", | ||
| 1840 | "entityType": "checks", | ||
| 1841 | "schema": "public", | ||
| 1842 | "table": "accounts" | ||
| 1843 | }, | ||
| 1844 | { | ||
| 1845 | "value": "\"status\" in ('pending', 'resolved', 'cancelled')", | ||
| 1846 | "name": "execution_interactions_status_check", | ||
| 1847 | "entityType": "checks", | ||
| 1848 | "schema": "public", | ||
| 1849 | "table": "execution_interactions" | ||
| 1850 | }, | ||
| 1851 | { | ||
| 1852 | "value": "\"status\" in ('pending', 'running', 'waiting_for_interaction', 'completed', 'failed', 'cancelled')", | ||
| 1853 | "name": "executions_status_check", | ||
| 1854 | "entityType": "checks", | ||
| 1855 | "schema": "public", | ||
| 1856 | "table": "executions" | ||
| 1857 | }, | ||
| 1858 | { | ||
| 1859 | "value": "\"role\" in ('viewer', 'editor', 'admin', 'owner')", | ||
| 1860 | "name": "organization_memberships_role_check", | ||
| 1861 | "entityType": "checks", | ||
| 1862 | "schema": "public", | ||
| 1863 | "table": "organization_memberships" | ||
| 1864 | }, | ||
| 1865 | { | ||
| 1866 | "value": "\"status\" in ('invited', 'active', 'suspended', 'removed')", | ||
| 1867 | "name": "organization_memberships_status_check", | ||
| 1868 | "entityType": "checks", | ||
| 1869 | "schema": "public", | ||
| 1870 | "table": "organization_memberships" | ||
| 1871 | }, | ||
| 1872 | { | ||
| 1873 | "value": "\"status\" in ('active', 'suspended', 'archived')", | ||
| 1874 | "name": "organizations_status_check", | ||
| 1875 | "entityType": "checks", | ||
| 1876 | "schema": "public", | ||
| 1877 | "table": "organizations" | ||
| 1878 | }, | ||
| 1879 | { | ||
| 1880 | "value": "\"resource_type\" in ('all_tools', 'source', 'namespace', 'tool_path')", | ||
| 1881 | "name": "policies_resource_type_check", | ||
| 1882 | "entityType": "checks", | ||
| 1883 | "schema": "public", | ||
| 1884 | "table": "policies" | ||
| 1885 | }, | ||
| 1886 | { | ||
| 1887 | "value": "\"match_type\" in ('glob', 'exact')", | ||
| 1888 | "name": "policies_match_type_check", | ||
| 1889 | "entityType": "checks", | ||
| 1890 | "schema": "public", | ||
| 1891 | "table": "policies" | ||
| 1892 | }, | ||
| 1893 | { | ||
| 1894 | "value": "\"effect\" in ('allow', 'deny')", | ||
| 1895 | "name": "policies_effect_check", | ||
| 1896 | "entityType": "checks", | ||
| 1897 | "schema": "public", | ||
| 1898 | "table": "policies" | ||
| 1899 | }, | ||
| 1900 | { | ||
| 1901 | "value": "\"approval_mode\" in ('auto', 'required')", | ||
| 1902 | "name": "policies_approval_mode_check", | ||
| 1903 | "entityType": "checks", | ||
| 1904 | "schema": "public", | ||
| 1905 | "table": "policies" | ||
| 1906 | }, | ||
| 1907 | { | ||
| 1908 | "value": "\"kind\" in ('mcp', 'openapi', 'graphql', 'internal')", | ||
| 1909 | "name": "sources_kind_check", | ||
| 1910 | "entityType": "checks", | ||
| 1911 | "schema": "public", | ||
| 1912 | "table": "sources" | ||
| 1913 | }, | ||
| 1914 | { | ||
| 1915 | "value": "\"status\" in ('draft', 'probing', 'auth_required', 'connected', 'error')", | ||
| 1916 | "name": "sources_status_check", | ||
| 1917 | "entityType": "checks", | ||
| 1918 | "schema": "public", | ||
| 1919 | "table": "sources" | ||
| 1920 | }, | ||
| 1921 | { | ||
| 1922 | "value": "\"transport\" is null or \"transport\" in ('auto', 'streamable-http', 'sse')", | ||
| 1923 | "name": "sources_transport_check", | ||
| 1924 | "entityType": "checks", | ||
| 1925 | "schema": "public", | ||
| 1926 | "table": "sources" | ||
| 1927 | }, | ||
| 1928 | { | ||
| 1929 | "value": "\"auth_kind\" in ('none', 'bearer', 'oauth2')", | ||
| 1930 | "name": "sources_auth_kind_check", | ||
| 1931 | "entityType": "checks", | ||
| 1932 | "schema": "public", | ||
| 1933 | "table": "sources" | ||
| 1934 | } | ||
| 1935 | ], | ||
| 1936 | "renames": [] | ||
| 1937 | } | ||
| @@ -1,39 +0,0 @@ | |||
| 1 | CREATE TABLE "secret_materials" ( | ||
| 2 | "id" text PRIMARY KEY, | ||
| 3 | "purpose" text NOT NULL, | ||
| 4 | "value" text NOT NULL, | ||
| 5 | "created_at" bigint NOT NULL, | ||
| 6 | "updated_at" bigint NOT NULL, | ||
| 7 | CONSTRAINT "secret_materials_purpose_check" CHECK ("purpose" in ('auth_material', 'oauth_access_token', 'oauth_refresh_token', 'oauth_client_info')) | ||
| 8 | ); | ||
| 9 | --> statement-breakpoint | ||
| 10 | CREATE TABLE "source_auth_sessions" ( | ||
| 11 | "id" text PRIMARY KEY, | ||
| 12 | "workspace_id" text NOT NULL, | ||
| 13 | "source_id" text NOT NULL, | ||
| 14 | "execution_id" text, | ||
| 15 | "interaction_id" text, | ||
| 16 | "strategy" text NOT NULL, | ||
| 17 | "status" text NOT NULL, | ||
| 18 | "endpoint" text NOT NULL, | ||
| 19 | "state" text NOT NULL, | ||
| 20 | "redirect_uri" text NOT NULL, | ||
| 21 | "scope" text, | ||
| 22 | "resource_metadata_url" text, | ||
| 23 | "authorization_server_url" text, | ||
| 24 | "resource_metadata_json" text, | ||
| 25 | "authorization_server_metadata_json" text, | ||
| 26 | "client_information_json" text, | ||
| 27 | "code_verifier" text, | ||
| 28 | "authorization_url" text, | ||
| 29 | "error_text" text, | ||
| 30 | "completed_at" bigint, | ||
| 31 | "created_at" bigint NOT NULL, | ||
| 32 | "updated_at" bigint NOT NULL, | ||
| 33 | CONSTRAINT "source_auth_sessions_strategy_check" CHECK ("strategy" in ('oauth2_authorization_code')), | ||
| 34 | CONSTRAINT "source_auth_sessions_status_check" CHECK ("status" in ('pending', 'completed', 'failed', 'cancelled')) | ||
| 35 | ); | ||
| 36 | --> statement-breakpoint | ||
| 37 | CREATE INDEX "secret_materials_updated_idx" ON "secret_materials" ("updated_at","id");--> statement-breakpoint | ||
| 38 | CREATE INDEX "source_auth_sessions_workspace_idx" ON "source_auth_sessions" ("workspace_id","updated_at","id");--> statement-breakpoint | ||
| 39 | CREATE UNIQUE INDEX "source_auth_sessions_state_idx" ON "source_auth_sessions" ("state"); | ||
| @@ -1,2425 +0,0 @@ | |||
| 1 | { | ||
| 2 | "version": "8", | ||
| 3 | "dialect": "postgres", | ||
| 4 | "id": "1141497b-c500-44ff-87db-6a440062becb", | ||
| 5 | "prevIds": [ | ||
| 6 | "bac1651d-7a30-4187-a7e2-74bc2b099486" | ||
| 7 | ], | ||
| 8 | "ddl": [ | ||
| 9 | { | ||
| 10 | "isRlsEnabled": false, | ||
| 11 | "name": "accounts", | ||
| 12 | "entityType": "tables", | ||
| 13 | "schema": "public" | ||
| 14 | }, | ||
| 15 | { | ||
| 16 | "isRlsEnabled": false, | ||
| 17 | "name": "execution_interactions", | ||
| 18 | "entityType": "tables", | ||
| 19 | "schema": "public" | ||
| 20 | }, | ||
| 21 | { | ||
| 22 | "isRlsEnabled": false, | ||
| 23 | "name": "executions", | ||
| 24 | "entityType": "tables", | ||
| 25 | "schema": "public" | ||
| 26 | }, | ||
| 27 | { | ||
| 28 | "isRlsEnabled": false, | ||
| 29 | "name": "local_installations", | ||
| 30 | "entityType": "tables", | ||
| 31 | "schema": "public" | ||
| 32 | }, | ||
| 33 | { | ||
| 34 | "isRlsEnabled": false, | ||
| 35 | "name": "organization_memberships", | ||
| 36 | "entityType": "tables", | ||
| 37 | "schema": "public" | ||
| 38 | }, | ||
| 39 | { | ||
| 40 | "isRlsEnabled": false, | ||
| 41 | "name": "organizations", | ||
| 42 | "entityType": "tables", | ||
| 43 | "schema": "public" | ||
| 44 | }, | ||
| 45 | { | ||
| 46 | "isRlsEnabled": false, | ||
| 47 | "name": "policies", | ||
| 48 | "entityType": "tables", | ||
| 49 | "schema": "public" | ||
| 50 | }, | ||
| 51 | { | ||
| 52 | "isRlsEnabled": false, | ||
| 53 | "name": "secret_materials", | ||
| 54 | "entityType": "tables", | ||
| 55 | "schema": "public" | ||
| 56 | }, | ||
| 57 | { | ||
| 58 | "isRlsEnabled": false, | ||
| 59 | "name": "source_auth_sessions", | ||
| 60 | "entityType": "tables", | ||
| 61 | "schema": "public" | ||
| 62 | }, | ||
| 63 | { | ||
| 64 | "isRlsEnabled": false, | ||
| 65 | "name": "source_credential_bindings", | ||
| 66 | "entityType": "tables", | ||
| 67 | "schema": "public" | ||
| 68 | }, | ||
| 69 | { | ||
| 70 | "isRlsEnabled": false, | ||
| 71 | "name": "sources", | ||
| 72 | "entityType": "tables", | ||
| 73 | "schema": "public" | ||
| 74 | }, | ||
| 75 | { | ||
| 76 | "isRlsEnabled": false, | ||
| 77 | "name": "workspaces", | ||
| 78 | "entityType": "tables", | ||
| 79 | "schema": "public" | ||
| 80 | }, | ||
| 81 | { | ||
| 82 | "type": "text", | ||
| 83 | "typeSchema": null, | ||
| 84 | "notNull": true, | ||
| 85 | "dimensions": 0, | ||
| 86 | "default": null, | ||
| 87 | "generated": null, | ||
| 88 | "identity": null, | ||
| 89 | "name": "id", | ||
| 90 | "entityType": "columns", | ||
| 91 | "schema": "public", | ||
| 92 | "table": "accounts" | ||
| 93 | }, | ||
| 94 | { | ||
| 95 | "type": "text", | ||
| 96 | "typeSchema": null, | ||
| 97 | "notNull": true, | ||
| 98 | "dimensions": 0, | ||
| 99 | "default": null, | ||
| 100 | "generated": null, | ||
| 101 | "identity": null, | ||
| 102 | "name": "provider", | ||
| 103 | "entityType": "columns", | ||
| 104 | "schema": "public", | ||
| 105 | "table": "accounts" | ||
| 106 | }, | ||
| 107 | { | ||
| 108 | "type": "text", | ||
| 109 | "typeSchema": null, | ||
| 110 | "notNull": true, | ||
| 111 | "dimensions": 0, | ||
| 112 | "default": null, | ||
| 113 | "generated": null, | ||
| 114 | "identity": null, | ||
| 115 | "name": "subject", | ||
| 116 | "entityType": "columns", | ||
| 117 | "schema": "public", | ||
| 118 | "table": "accounts" | ||
| 119 | }, | ||
| 120 | { | ||
| 121 | "type": "text", | ||
| 122 | "typeSchema": null, | ||
| 123 | "notNull": false, | ||
| 124 | "dimensions": 0, | ||
| 125 | "default": null, | ||
| 126 | "generated": null, | ||
| 127 | "identity": null, | ||
| 128 | "name": "email", | ||
| 129 | "entityType": "columns", | ||
| 130 | "schema": "public", | ||
| 131 | "table": "accounts" | ||
| 132 | }, | ||
| 133 | { | ||
| 134 | "type": "text", | ||
| 135 | "typeSchema": null, | ||
| 136 | "notNull": false, | ||
| 137 | "dimensions": 0, | ||
| 138 | "default": null, | ||
| 139 | "generated": null, | ||
| 140 | "identity": null, | ||
| 141 | "name": "display_name", | ||
| 142 | "entityType": "columns", | ||
| 143 | "schema": "public", | ||
| 144 | "table": "accounts" | ||
| 145 | }, | ||
| 146 | { | ||
| 147 | "type": "bigint", | ||
| 148 | "typeSchema": null, | ||
| 149 | "notNull": true, | ||
| 150 | "dimensions": 0, | ||
| 151 | "default": null, | ||
| 152 | "generated": null, | ||
| 153 | "identity": null, | ||
| 154 | "name": "created_at", | ||
| 155 | "entityType": "columns", | ||
| 156 | "schema": "public", | ||
| 157 | "table": "accounts" | ||
| 158 | }, | ||
| 159 | { | ||
| 160 | "type": "bigint", | ||
| 161 | "typeSchema": null, | ||
| 162 | "notNull": true, | ||
| 163 | "dimensions": 0, | ||
| 164 | "default": null, | ||
| 165 | "generated": null, | ||
| 166 | "identity": null, | ||
| 167 | "name": "updated_at", | ||
| 168 | "entityType": "columns", | ||
| 169 | "schema": "public", | ||
| 170 | "table": "accounts" | ||
| 171 | }, | ||
| 172 | { | ||
| 173 | "type": "text", | ||
| 174 | "typeSchema": null, | ||
| 175 | "notNull": true, | ||
| 176 | "dimensions": 0, | ||
| 177 | "default": null, | ||
| 178 | "generated": null, | ||
| 179 | "identity": null, | ||
| 180 | "name": "id", | ||
| 181 | "entityType": "columns", | ||
| 182 | "schema": "public", | ||
| 183 | "table": "execution_interactions" | ||
| 184 | }, | ||
| 185 | { | ||
| 186 | "type": "text", | ||
| 187 | "typeSchema": null, | ||
| 188 | "notNull": true, | ||
| 189 | "dimensions": 0, | ||
| 190 | "default": null, | ||
| 191 | "generated": null, | ||
| 192 | "identity": null, | ||
| 193 | "name": "execution_id", | ||
| 194 | "entityType": "columns", | ||
| 195 | "schema": "public", | ||
| 196 | "table": "execution_interactions" | ||
| 197 | }, | ||
| 198 | { | ||
| 199 | "type": "text", | ||
| 200 | "typeSchema": null, | ||
| 201 | "notNull": true, | ||
| 202 | "dimensions": 0, | ||
| 203 | "default": null, | ||
| 204 | "generated": null, | ||
| 205 | "identity": null, | ||
| 206 | "name": "status", | ||
| 207 | "entityType": "columns", | ||
| 208 | "schema": "public", | ||
| 209 | "table": "execution_interactions" | ||
| 210 | }, | ||
| 211 | { | ||
| 212 | "type": "text", | ||
| 213 | "typeSchema": null, | ||
| 214 | "notNull": true, | ||
| 215 | "dimensions": 0, | ||
| 216 | "default": null, | ||
| 217 | "generated": null, | ||
| 218 | "identity": null, | ||
| 219 | "name": "kind", | ||
| 220 | "entityType": "columns", | ||
| 221 | "schema": "public", | ||
| 222 | "table": "execution_interactions" | ||
| 223 | }, | ||
| 224 | { | ||
| 225 | "type": "text", | ||
| 226 | "typeSchema": null, | ||
| 227 | "notNull": true, | ||
| 228 | "dimensions": 0, | ||
| 229 | "default": null, | ||
| 230 | "generated": null, | ||
| 231 | "identity": null, | ||
| 232 | "name": "payload_json", | ||
| 233 | "entityType": "columns", | ||
| 234 | "schema": "public", | ||
| 235 | "table": "execution_interactions" | ||
| 236 | }, | ||
| 237 | { | ||
| 238 | "type": "text", | ||
| 239 | "typeSchema": null, | ||
| 240 | "notNull": false, | ||
| 241 | "dimensions": 0, | ||
| 242 | "default": null, | ||
| 243 | "generated": null, | ||
| 244 | "identity": null, | ||
| 245 | "name": "response_json", | ||
| 246 | "entityType": "columns", | ||
| 247 | "schema": "public", | ||
| 248 | "table": "execution_interactions" | ||
| 249 | }, | ||
| 250 | { | ||
| 251 | "type": "bigint", | ||
| 252 | "typeSchema": null, | ||
| 253 | "notNull": true, | ||
| 254 | "dimensions": 0, | ||
| 255 | "default": null, | ||
| 256 | "generated": null, | ||
| 257 | "identity": null, | ||
| 258 | "name": "created_at", | ||
| 259 | "entityType": "columns", | ||
| 260 | "schema": "public", | ||
| 261 | "table": "execution_interactions" | ||
| 262 | }, | ||
| 263 | { | ||
| 264 | "type": "bigint", | ||
| 265 | "typeSchema": null, | ||
| 266 | "notNull": true, | ||
| 267 | "dimensions": 0, | ||
| 268 | "default": null, | ||
| 269 | "generated": null, | ||
| 270 | "identity": null, | ||
| 271 | "name": "updated_at", | ||
| 272 | "entityType": "columns", | ||
| 273 | "schema": "public", | ||
| 274 | "table": "execution_interactions" | ||
| 275 | }, | ||
| 276 | { | ||
| 277 | "type": "text", | ||
| 278 | "typeSchema": null, | ||
| 279 | "notNull": true, | ||
| 280 | "dimensions": 0, | ||
| 281 | "default": null, | ||
| 282 | "generated": null, | ||
| 283 | "identity": null, | ||
| 284 | "name": "id", | ||
| 285 | "entityType": "columns", | ||
| 286 | "schema": "public", | ||
| 287 | "table": "executions" | ||
| 288 | }, | ||
| 289 | { | ||
| 290 | "type": "text", | ||
| 291 | "typeSchema": null, | ||
| 292 | "notNull": true, | ||
| 293 | "dimensions": 0, | ||
| 294 | "default": null, | ||
| 295 | "generated": null, | ||
| 296 | "identity": null, | ||
| 297 | "name": "workspace_id", | ||
| 298 | "entityType": "columns", | ||
| 299 | "schema": "public", | ||
| 300 | "table": "executions" | ||
| 301 | }, | ||
| 302 | { | ||
| 303 | "type": "text", | ||
| 304 | "typeSchema": null, | ||
| 305 | "notNull": true, | ||
| 306 | "dimensions": 0, | ||
| 307 | "default": null, | ||
| 308 | "generated": null, | ||
| 309 | "identity": null, | ||
| 310 | "name": "created_by_account_id", | ||
| 311 | "entityType": "columns", | ||
| 312 | "schema": "public", | ||
| 313 | "table": "executions" | ||
| 314 | }, | ||
| 315 | { | ||
| 316 | "type": "text", | ||
| 317 | "typeSchema": null, | ||
| 318 | "notNull": true, | ||
| 319 | "dimensions": 0, | ||
| 320 | "default": null, | ||
| 321 | "generated": null, | ||
| 322 | "identity": null, | ||
| 323 | "name": "status", | ||
| 324 | "entityType": "columns", | ||
| 325 | "schema": "public", | ||
| 326 | "table": "executions" | ||
| 327 | }, | ||
| 328 | { | ||
| 329 | "type": "text", | ||
| 330 | "typeSchema": null, | ||
| 331 | "notNull": true, | ||
| 332 | "dimensions": 0, | ||
| 333 | "default": null, | ||
| 334 | "generated": null, | ||
| 335 | "identity": null, | ||
| 336 | "name": "code", | ||
| 337 | "entityType": "columns", | ||
| 338 | "schema": "public", | ||
| 339 | "table": "executions" | ||
| 340 | }, | ||
| 341 | { | ||
| 342 | "type": "text", | ||
| 343 | "typeSchema": null, | ||
| 344 | "notNull": false, | ||
| 345 | "dimensions": 0, | ||
| 346 | "default": null, | ||
| 347 | "generated": null, | ||
| 348 | "identity": null, | ||
| 349 | "name": "result_json", | ||
| 350 | "entityType": "columns", | ||
| 351 | "schema": "public", | ||
| 352 | "table": "executions" | ||
| 353 | }, | ||
| 354 | { | ||
| 355 | "type": "text", | ||
| 356 | "typeSchema": null, | ||
| 357 | "notNull": false, | ||
| 358 | "dimensions": 0, | ||
| 359 | "default": null, | ||
| 360 | "generated": null, | ||
| 361 | "identity": null, | ||
| 362 | "name": "error_text", | ||
| 363 | "entityType": "columns", | ||
| 364 | "schema": "public", | ||
| 365 | "table": "executions" | ||
| 366 | }, | ||
| 367 | { | ||
| 368 | "type": "text", | ||
| 369 | "typeSchema": null, | ||
| 370 | "notNull": false, | ||
| 371 | "dimensions": 0, | ||
| 372 | "default": null, | ||
| 373 | "generated": null, | ||
| 374 | "identity": null, | ||
| 375 | "name": "logs_json", | ||
| 376 | "entityType": "columns", | ||
| 377 | "schema": "public", | ||
| 378 | "table": "executions" | ||
| 379 | }, | ||
| 380 | { | ||
| 381 | "type": "bigint", | ||
| 382 | "typeSchema": null, | ||
| 383 | "notNull": false, | ||
| 384 | "dimensions": 0, | ||
| 385 | "default": null, | ||
| 386 | "generated": null, | ||
| 387 | "identity": null, | ||
| 388 | "name": "started_at", | ||
| 389 | "entityType": "columns", | ||
| 390 | "schema": "public", | ||
| 391 | "table": "executions" | ||
| 392 | }, | ||
| 393 | { | ||
| 394 | "type": "bigint", | ||
| 395 | "typeSchema": null, | ||
| 396 | "notNull": false, | ||
| 397 | "dimensions": 0, | ||
| 398 | "default": null, | ||
| 399 | "generated": null, | ||
| 400 | "identity": null, | ||
| 401 | "name": "completed_at", | ||
| 402 | "entityType": "columns", | ||
| 403 | "schema": "public", | ||
| 404 | "table": "executions" | ||
| 405 | }, | ||
| 406 | { | ||
| 407 | "type": "bigint", | ||
| 408 | "typeSchema": null, | ||
| 409 | "notNull": true, | ||
| 410 | "dimensions": 0, | ||
| 411 | "default": null, | ||
| 412 | "generated": null, | ||
| 413 | "identity": null, | ||
| 414 | "name": "created_at", | ||
| 415 | "entityType": "columns", | ||
| 416 | "schema": "public", | ||
| 417 | "table": "executions" | ||
| 418 | }, | ||
| 419 | { | ||
| 420 | "type": "bigint", | ||
| 421 | "typeSchema": null, | ||
| 422 | "notNull": true, | ||
| 423 | "dimensions": 0, | ||
| 424 | "default": null, | ||
| 425 | "generated": null, | ||
| 426 | "identity": null, | ||
| 427 | "name": "updated_at", | ||
| 428 | "entityType": "columns", | ||
| 429 | "schema": "public", | ||
| 430 | "table": "executions" | ||
| 431 | }, | ||
| 432 | { | ||
| 433 | "type": "text", | ||
| 434 | "typeSchema": null, | ||
| 435 | "notNull": true, | ||
| 436 | "dimensions": 0, | ||
| 437 | "default": null, | ||
| 438 | "generated": null, | ||
| 439 | "identity": null, | ||
| 440 | "name": "id", | ||
| 441 | "entityType": "columns", | ||
| 442 | "schema": "public", | ||
| 443 | "table": "local_installations" | ||
| 444 | }, | ||
| 445 | { | ||
| 446 | "type": "text", | ||
| 447 | "typeSchema": null, | ||
| 448 | "notNull": true, | ||
| 449 | "dimensions": 0, | ||
| 450 | "default": null, | ||
| 451 | "generated": null, | ||
| 452 | "identity": null, | ||
| 453 | "name": "account_id", | ||
| 454 | "entityType": "columns", | ||
| 455 | "schema": "public", | ||
| 456 | "table": "local_installations" | ||
| 457 | }, | ||
| 458 | { | ||
| 459 | "type": "text", | ||
| 460 | "typeSchema": null, | ||
| 461 | "notNull": true, | ||
| 462 | "dimensions": 0, | ||
| 463 | "default": null, | ||
| 464 | "generated": null, | ||
| 465 | "identity": null, | ||
| 466 | "name": "organization_id", | ||
| 467 | "entityType": "columns", | ||
| 468 | "schema": "public", | ||
| 469 | "table": "local_installations" | ||
| 470 | }, | ||
| 471 | { | ||
| 472 | "type": "text", | ||
| 473 | "typeSchema": null, | ||
| 474 | "notNull": true, | ||
| 475 | "dimensions": 0, | ||
| 476 | "default": null, | ||
| 477 | "generated": null, | ||
| 478 | "identity": null, | ||
| 479 | "name": "workspace_id", | ||
| 480 | "entityType": "columns", | ||
| 481 | "schema": "public", | ||
| 482 | "table": "local_installations" | ||
| 483 | }, | ||
| 484 | { | ||
| 485 | "type": "bigint", | ||
| 486 | "typeSchema": null, | ||
| 487 | "notNull": true, | ||
| 488 | "dimensions": 0, | ||
| 489 | "default": null, | ||
| 490 | "generated": null, | ||
| 491 | "identity": null, | ||
| 492 | "name": "created_at", | ||
| 493 | "entityType": "columns", | ||
| 494 | "schema": "public", | ||
| 495 | "table": "local_installations" | ||
| 496 | }, | ||
| 497 | { | ||
| 498 | "type": "bigint", | ||
| 499 | "typeSchema": null, | ||
| 500 | "notNull": true, | ||
| 501 | "dimensions": 0, | ||
| 502 | "default": null, | ||
| 503 | "generated": null, | ||
| 504 | "identity": null, | ||
| 505 | "name": "updated_at", | ||
| 506 | "entityType": "columns", | ||
| 507 | "schema": "public", | ||
| 508 | "table": "local_installations" | ||
| 509 | }, | ||
| 510 | { | ||
| 511 | "type": "text", | ||
| 512 | "typeSchema": null, | ||
| 513 | "notNull": true, | ||
| 514 | "dimensions": 0, | ||
| 515 | "default": null, | ||
| 516 | "generated": null, | ||
| 517 | "identity": null, | ||
| 518 | "name": "id", | ||
| 519 | "entityType": "columns", | ||
| 520 | "schema": "public", | ||
| 521 | "table": "organization_memberships" | ||
| 522 | }, | ||
| 523 | { | ||
| 524 | "type": "text", | ||
| 525 | "typeSchema": null, | ||
| 526 | "notNull": true, | ||
| 527 | "dimensions": 0, | ||
| 528 | "default": null, | ||
| 529 | "generated": null, | ||
| 530 | "identity": null, | ||
| 531 | "name": "organization_id", | ||
| 532 | "entityType": "columns", | ||
| 533 | "schema": "public", | ||
| 534 | "table": "organization_memberships" | ||
| 535 | }, | ||
| 536 | { | ||
| 537 | "type": "text", | ||
| 538 | "typeSchema": null, | ||
| 539 | "notNull": true, | ||
| 540 | "dimensions": 0, | ||
| 541 | "default": null, | ||
| 542 | "generated": null, | ||
| 543 | "identity": null, | ||
| 544 | "name": "account_id", | ||
| 545 | "entityType": "columns", | ||
| 546 | "schema": "public", | ||
| 547 | "table": "organization_memberships" | ||
| 548 | }, | ||
| 549 | { | ||
| 550 | "type": "text", | ||
| 551 | "typeSchema": null, | ||
| 552 | "notNull": true, | ||
| 553 | "dimensions": 0, | ||
| 554 | "default": null, | ||
| 555 | "generated": null, | ||
| 556 | "identity": null, | ||
| 557 | "name": "role", | ||
| 558 | "entityType": "columns", | ||
| 559 | "schema": "public", | ||
| 560 | "table": "organization_memberships" | ||
| 561 | }, | ||
| 562 | { | ||
| 563 | "type": "text", | ||
| 564 | "typeSchema": null, | ||
| 565 | "notNull": true, | ||
| 566 | "dimensions": 0, | ||
| 567 | "default": null, | ||
| 568 | "generated": null, | ||
| 569 | "identity": null, | ||
| 570 | "name": "status", | ||
| 571 | "entityType": "columns", | ||
| 572 | "schema": "public", | ||
| 573 | "table": "organization_memberships" | ||
| 574 | }, | ||
| 575 | { | ||
| 576 | "type": "boolean", | ||
| 577 | "typeSchema": null, | ||
| 578 | "notNull": true, | ||
| 579 | "dimensions": 0, | ||
| 580 | "default": null, | ||
| 581 | "generated": null, | ||
| 582 | "identity": null, | ||
| 583 | "name": "billable", | ||
| 584 | "entityType": "columns", | ||
| 585 | "schema": "public", | ||
| 586 | "table": "organization_memberships" | ||
| 587 | }, | ||
| 588 | { | ||
| 589 | "type": "text", | ||
| 590 | "typeSchema": null, | ||
| 591 | "notNull": false, | ||
| 592 | "dimensions": 0, | ||
| 593 | "default": null, | ||
| 594 | "generated": null, | ||
| 595 | "identity": null, | ||
| 596 | "name": "invited_by_account_id", | ||
| 597 | "entityType": "columns", | ||
| 598 | "schema": "public", | ||
| 599 | "table": "organization_memberships" | ||
| 600 | }, | ||
| 601 | { | ||
| 602 | "type": "bigint", | ||
| 603 | "typeSchema": null, | ||
| 604 | "notNull": false, | ||
| 605 | "dimensions": 0, | ||
| 606 | "default": null, | ||
| 607 | "generated": null, | ||
| 608 | "identity": null, | ||
| 609 | "name": "joined_at", | ||
| 610 | "entityType": "columns", | ||
| 611 | "schema": "public", | ||
| 612 | "table": "organization_memberships" | ||
| 613 | }, | ||
| 614 | { | ||
| 615 | "type": "bigint", | ||
| 616 | "typeSchema": null, | ||
| 617 | "notNull": true, | ||
| 618 | "dimensions": 0, | ||
| 619 | "default": null, | ||
| 620 | "generated": null, | ||
| 621 | "identity": null, | ||
| 622 | "name": "created_at", | ||
| 623 | "entityType": "columns", | ||
| 624 | "schema": "public", | ||
| 625 | "table": "organization_memberships" | ||
| 626 | }, | ||
| 627 | { | ||
| 628 | "type": "bigint", | ||
| 629 | "typeSchema": null, | ||
| 630 | "notNull": true, | ||
| 631 | "dimensions": 0, | ||
| 632 | "default": null, | ||
| 633 | "generated": null, | ||
| 634 | "identity": null, | ||
| 635 | "name": "updated_at", | ||
| 636 | "entityType": "columns", | ||
| 637 | "schema": "public", | ||
| 638 | "table": "organization_memberships" | ||
| 639 | }, | ||
| 640 | { | ||
| 641 | "type": "text", | ||
| 642 | "typeSchema": null, | ||
| 643 | "notNull": true, | ||
| 644 | "dimensions": 0, | ||
| 645 | "default": null, | ||
| 646 | "generated": null, | ||
| 647 | "identity": null, | ||
| 648 | "name": "id", | ||
| 649 | "entityType": "columns", | ||
| 650 | "schema": "public", | ||
| 651 | "table": "organizations" | ||
| 652 | }, | ||
| 653 | { | ||
| 654 | "type": "text", | ||
| 655 | "typeSchema": null, | ||
| 656 | "notNull": true, | ||
| 657 | "dimensions": 0, | ||
| 658 | "default": null, | ||
| 659 | "generated": null, | ||
| 660 | "identity": null, | ||
| 661 | "name": "slug", | ||
| 662 | "entityType": "columns", | ||
| 663 | "schema": "public", | ||
| 664 | "table": "organizations" | ||
| 665 | }, | ||
| 666 | { | ||
| 667 | "type": "text", | ||
| 668 | "typeSchema": null, | ||
| 669 | "notNull": true, | ||
| 670 | "dimensions": 0, | ||
| 671 | "default": null, | ||
| 672 | "generated": null, | ||
| 673 | "identity": null, | ||
| 674 | "name": "name", | ||
| 675 | "entityType": "columns", | ||
| 676 | "schema": "public", | ||
| 677 | "table": "organizations" | ||
| 678 | }, | ||
| 679 | { | ||
| 680 | "type": "text", | ||
| 681 | "typeSchema": null, | ||
| 682 | "notNull": true, | ||
| 683 | "dimensions": 0, | ||
| 684 | "default": null, | ||
| 685 | "generated": null, | ||
| 686 | "identity": null, | ||
| 687 | "name": "status", | ||
| 688 | "entityType": "columns", | ||
| 689 | "schema": "public", | ||
| 690 | "table": "organizations" | ||
| 691 | }, | ||
| 692 | { | ||
| 693 | "type": "text", | ||
| 694 | "typeSchema": null, | ||
| 695 | "notNull": false, | ||
| 696 | "dimensions": 0, | ||
| 697 | "default": null, | ||
| 698 | "generated": null, | ||
| 699 | "identity": null, | ||
| 700 | "name": "created_by_account_id", | ||
| 701 | "entityType": "columns", | ||
| 702 | "schema": "public", | ||
| 703 | "table": "organizations" | ||
| 704 | }, | ||
| 705 | { | ||
| 706 | "type": "bigint", | ||
| 707 | "typeSchema": null, | ||
| 708 | "notNull": true, | ||
| 709 | "dimensions": 0, | ||
| 710 | "default": null, | ||
| 711 | "generated": null, | ||
| 712 | "identity": null, | ||
| 713 | "name": "created_at", | ||
| 714 | "entityType": "columns", | ||
| 715 | "schema": "public", | ||
| 716 | "table": "organizations" | ||
| 717 | }, | ||
| 718 | { | ||
| 719 | "type": "bigint", | ||
| 720 | "typeSchema": null, | ||
| 721 | "notNull": true, | ||
| 722 | "dimensions": 0, | ||
| 723 | "default": null, | ||
| 724 | "generated": null, | ||
| 725 | "identity": null, | ||
| 726 | "name": "updated_at", | ||
| 727 | "entityType": "columns", | ||
| 728 | "schema": "public", | ||
| 729 | "table": "organizations" | ||
| 730 | }, | ||
| 731 | { | ||
| 732 | "type": "text", | ||
| 733 | "typeSchema": null, | ||
| 734 | "notNull": true, | ||
| 735 | "dimensions": 0, | ||
| 736 | "default": null, | ||
| 737 | "generated": null, | ||
| 738 | "identity": null, | ||
| 739 | "name": "id", | ||
| 740 | "entityType": "columns", | ||
| 741 | "schema": "public", | ||
| 742 | "table": "policies" | ||
| 743 | }, | ||
| 744 | { | ||
| 745 | "type": "text", | ||
| 746 | "typeSchema": null, | ||
| 747 | "notNull": true, | ||
| 748 | "dimensions": 0, | ||
| 749 | "default": null, | ||
| 750 | "generated": null, | ||
| 751 | "identity": null, | ||
| 752 | "name": "workspace_id", | ||
| 753 | "entityType": "columns", | ||
| 754 | "schema": "public", | ||
| 755 | "table": "policies" | ||
| 756 | }, | ||
| 757 | { | ||
| 758 | "type": "text", | ||
| 759 | "typeSchema": null, | ||
| 760 | "notNull": false, | ||
| 761 | "dimensions": 0, | ||
| 762 | "default": null, | ||
| 763 | "generated": null, | ||
| 764 | "identity": null, | ||
| 765 | "name": "target_account_id", | ||
| 766 | "entityType": "columns", | ||
| 767 | "schema": "public", | ||
| 768 | "table": "policies" | ||
| 769 | }, | ||
| 770 | { | ||
| 771 | "type": "text", | ||
| 772 | "typeSchema": null, | ||
| 773 | "notNull": false, | ||
| 774 | "dimensions": 0, | ||
| 775 | "default": null, | ||
| 776 | "generated": null, | ||
| 777 | "identity": null, | ||
| 778 | "name": "client_id", | ||
| 779 | "entityType": "columns", | ||
| 780 | "schema": "public", | ||
| 781 | "table": "policies" | ||
| 782 | }, | ||
| 783 | { | ||
| 784 | "type": "text", | ||
| 785 | "typeSchema": null, | ||
| 786 | "notNull": true, | ||
| 787 | "dimensions": 0, | ||
| 788 | "default": null, | ||
| 789 | "generated": null, | ||
| 790 | "identity": null, | ||
| 791 | "name": "resource_type", | ||
| 792 | "entityType": "columns", | ||
| 793 | "schema": "public", | ||
| 794 | "table": "policies" | ||
| 795 | }, | ||
| 796 | { | ||
| 797 | "type": "text", | ||
| 798 | "typeSchema": null, | ||
| 799 | "notNull": true, | ||
| 800 | "dimensions": 0, | ||
| 801 | "default": null, | ||
| 802 | "generated": null, | ||
| 803 | "identity": null, | ||
| 804 | "name": "resource_pattern", | ||
| 805 | "entityType": "columns", | ||
| 806 | "schema": "public", | ||
| 807 | "table": "policies" | ||
| 808 | }, | ||
| 809 | { | ||
| 810 | "type": "text", | ||
| 811 | "typeSchema": null, | ||
| 812 | "notNull": true, | ||
| 813 | "dimensions": 0, | ||
| 814 | "default": null, | ||
| 815 | "generated": null, | ||
| 816 | "identity": null, | ||
| 817 | "name": "match_type", | ||
| 818 | "entityType": "columns", | ||
| 819 | "schema": "public", | ||
| 820 | "table": "policies" | ||
| 821 | }, | ||
| 822 | { | ||
| 823 | "type": "text", | ||
| 824 | "typeSchema": null, | ||
| 825 | "notNull": true, | ||
| 826 | "dimensions": 0, | ||
| 827 | "default": null, | ||
| 828 | "generated": null, | ||
| 829 | "identity": null, | ||
| 830 | "name": "effect", | ||
| 831 | "entityType": "columns", | ||
| 832 | "schema": "public", | ||
| 833 | "table": "policies" | ||
| 834 | }, | ||
| 835 | { | ||
| 836 | "type": "text", | ||
| 837 | "typeSchema": null, | ||
| 838 | "notNull": true, | ||
| 839 | "dimensions": 0, | ||
| 840 | "default": null, | ||
| 841 | "generated": null, | ||
| 842 | "identity": null, | ||
| 843 | "name": "approval_mode", | ||
| 844 | "entityType": "columns", | ||
| 845 | "schema": "public", | ||
| 846 | "table": "policies" | ||
| 847 | }, | ||
| 848 | { | ||
| 849 | "type": "text", | ||
| 850 | "typeSchema": null, | ||
| 851 | "notNull": false, | ||
| 852 | "dimensions": 0, | ||
| 853 | "default": null, | ||
| 854 | "generated": null, | ||
| 855 | "identity": null, | ||
| 856 | "name": "argument_conditions_json", | ||
| 857 | "entityType": "columns", | ||
| 858 | "schema": "public", | ||
| 859 | "table": "policies" | ||
| 860 | }, | ||
| 861 | { | ||
| 862 | "type": "bigint", | ||
| 863 | "typeSchema": null, | ||
| 864 | "notNull": true, | ||
| 865 | "dimensions": 0, | ||
| 866 | "default": null, | ||
| 867 | "generated": null, | ||
| 868 | "identity": null, | ||
| 869 | "name": "priority", | ||
| 870 | "entityType": "columns", | ||
| 871 | "schema": "public", | ||
| 872 | "table": "policies" | ||
| 873 | }, | ||
| 874 | { | ||
| 875 | "type": "boolean", | ||
| 876 | "typeSchema": null, | ||
| 877 | "notNull": true, | ||
| 878 | "dimensions": 0, | ||
| 879 | "default": null, | ||
| 880 | "generated": null, | ||
| 881 | "identity": null, | ||
| 882 | "name": "enabled", | ||
| 883 | "entityType": "columns", | ||
| 884 | "schema": "public", | ||
| 885 | "table": "policies" | ||
| 886 | }, | ||
| 887 | { | ||
| 888 | "type": "bigint", | ||
| 889 | "typeSchema": null, | ||
| 890 | "notNull": true, | ||
| 891 | "dimensions": 0, | ||
| 892 | "default": null, | ||
| 893 | "generated": null, | ||
| 894 | "identity": null, | ||
| 895 | "name": "created_at", | ||
| 896 | "entityType": "columns", | ||
| 897 | "schema": "public", | ||
| 898 | "table": "policies" | ||
| 899 | }, | ||
| 900 | { | ||
| 901 | "type": "bigint", | ||
| 902 | "typeSchema": null, | ||
| 903 | "notNull": true, | ||
| 904 | "dimensions": 0, | ||
| 905 | "default": null, | ||
| 906 | "generated": null, | ||
| 907 | "identity": null, | ||
| 908 | "name": "updated_at", | ||
| 909 | "entityType": "columns", | ||
| 910 | "schema": "public", | ||
| 911 | "table": "policies" | ||
| 912 | }, | ||
| 913 | { | ||
| 914 | "type": "text", | ||
| 915 | "typeSchema": null, | ||
| 916 | "notNull": true, | ||
| 917 | "dimensions": 0, | ||
| 918 | "default": null, | ||
| 919 | "generated": null, | ||
| 920 | "identity": null, | ||
| 921 | "name": "id", | ||
| 922 | "entityType": "columns", | ||
| 923 | "schema": "public", | ||
| 924 | "table": "secret_materials" | ||
| 925 | }, | ||
| 926 | { | ||
| 927 | "type": "text", | ||
| 928 | "typeSchema": null, | ||
| 929 | "notNull": true, | ||
| 930 | "dimensions": 0, | ||
| 931 | "default": null, | ||
| 932 | "generated": null, | ||
| 933 | "identity": null, | ||
| 934 | "name": "purpose", | ||
| 935 | "entityType": "columns", | ||
| 936 | "schema": "public", | ||
| 937 | "table": "secret_materials" | ||
| 938 | }, | ||
| 939 | { | ||
| 940 | "type": "text", | ||
| 941 | "typeSchema": null, | ||
| 942 | "notNull": true, | ||
| 943 | "dimensions": 0, | ||
| 944 | "default": null, | ||
| 945 | "generated": null, | ||
| 946 | "identity": null, | ||
| 947 | "name": "value", | ||
| 948 | "entityType": "columns", | ||
| 949 | "schema": "public", | ||
| 950 | "table": "secret_materials" | ||
| 951 | }, | ||
| 952 | { | ||
| 953 | "type": "bigint", | ||
| 954 | "typeSchema": null, | ||
| 955 | "notNull": true, | ||
| 956 | "dimensions": 0, | ||
| 957 | "default": null, | ||
| 958 | "generated": null, | ||
| 959 | "identity": null, | ||
| 960 | "name": "created_at", | ||
| 961 | "entityType": "columns", | ||
| 962 | "schema": "public", | ||
| 963 | "table": "secret_materials" | ||
| 964 | }, | ||
| 965 | { | ||
| 966 | "type": "bigint", | ||
| 967 | "typeSchema": null, | ||
| 968 | "notNull": true, | ||
| 969 | "dimensions": 0, | ||
| 970 | "default": null, | ||
| 971 | "generated": null, | ||
| 972 | "identity": null, | ||
| 973 | "name": "updated_at", | ||
| 974 | "entityType": "columns", | ||
| 975 | "schema": "public", | ||
| 976 | "table": "secret_materials" | ||
| 977 | }, | ||
| 978 | { | ||
| 979 | "type": "text", | ||
| 980 | "typeSchema": null, | ||
| 981 | "notNull": true, | ||
| 982 | "dimensions": 0, | ||
| 983 | "default": null, | ||
| 984 | "generated": null, | ||
| 985 | "identity": null, | ||
| 986 | "name": "id", | ||
| 987 | "entityType": "columns", | ||
| 988 | "schema": "public", | ||
| 989 | "table": "source_auth_sessions" | ||
| 990 | }, | ||
| 991 | { | ||
| 992 | "type": "text", | ||
| 993 | "typeSchema": null, | ||
| 994 | "notNull": true, | ||
| 995 | "dimensions": 0, | ||
| 996 | "default": null, | ||
| 997 | "generated": null, | ||
| 998 | "identity": null, | ||
| 999 | "name": "workspace_id", | ||
| 1000 | "entityType": "columns", | ||
| 1001 | "schema": "public", | ||
| 1002 | "table": "source_auth_sessions" | ||
| 1003 | }, | ||
| 1004 | { | ||
| 1005 | "type": "text", | ||
| 1006 | "typeSchema": null, | ||
| 1007 | "notNull": true, | ||
| 1008 | "dimensions": 0, | ||
| 1009 | "default": null, | ||
| 1010 | "generated": null, | ||
| 1011 | "identity": null, | ||
| 1012 | "name": "source_id", | ||
| 1013 | "entityType": "columns", | ||
| 1014 | "schema": "public", | ||
| 1015 | "table": "source_auth_sessions" | ||
| 1016 | }, | ||
| 1017 | { | ||
| 1018 | "type": "text", | ||
| 1019 | "typeSchema": null, | ||
| 1020 | "notNull": false, | ||
| 1021 | "dimensions": 0, | ||
| 1022 | "default": null, | ||
| 1023 | "generated": null, | ||
| 1024 | "identity": null, | ||
| 1025 | "name": "execution_id", | ||
| 1026 | "entityType": "columns", | ||
| 1027 | "schema": "public", | ||
| 1028 | "table": "source_auth_sessions" | ||
| 1029 | }, | ||
| 1030 | { | ||
| 1031 | "type": "text", | ||
| 1032 | "typeSchema": null, | ||
| 1033 | "notNull": false, | ||
| 1034 | "dimensions": 0, | ||
| 1035 | "default": null, | ||
| 1036 | "generated": null, | ||
| 1037 | "identity": null, | ||
| 1038 | "name": "interaction_id", | ||
| 1039 | "entityType": "columns", | ||
| 1040 | "schema": "public", | ||
| 1041 | "table": "source_auth_sessions" | ||
| 1042 | }, | ||
| 1043 | { | ||
| 1044 | "type": "text", | ||
| 1045 | "typeSchema": null, | ||
| 1046 | "notNull": true, | ||
| 1047 | "dimensions": 0, | ||
| 1048 | "default": null, | ||
| 1049 | "generated": null, | ||
| 1050 | "identity": null, | ||
| 1051 | "name": "strategy", | ||
| 1052 | "entityType": "columns", | ||
| 1053 | "schema": "public", | ||
| 1054 | "table": "source_auth_sessions" | ||
| 1055 | }, | ||
| 1056 | { | ||
| 1057 | "type": "text", | ||
| 1058 | "typeSchema": null, | ||
| 1059 | "notNull": true, | ||
| 1060 | "dimensions": 0, | ||
| 1061 | "default": null, | ||
| 1062 | "generated": null, | ||
| 1063 | "identity": null, | ||
| 1064 | "name": "status", | ||
| 1065 | "entityType": "columns", | ||
| 1066 | "schema": "public", | ||
| 1067 | "table": "source_auth_sessions" | ||
| 1068 | }, | ||
| 1069 | { | ||
| 1070 | "type": "text", | ||
| 1071 | "typeSchema": null, | ||
| 1072 | "notNull": true, | ||
| 1073 | "dimensions": 0, | ||
| 1074 | "default": null, | ||
| 1075 | "generated": null, | ||
| 1076 | "identity": null, | ||
| 1077 | "name": "endpoint", | ||
| 1078 | "entityType": "columns", | ||
| 1079 | "schema": "public", | ||
| 1080 | "table": "source_auth_sessions" | ||
| 1081 | }, | ||
| 1082 | { | ||
| 1083 | "type": "text", | ||
| 1084 | "typeSchema": null, | ||
| 1085 | "notNull": true, | ||
| 1086 | "dimensions": 0, | ||
| 1087 | "default": null, | ||
| 1088 | "generated": null, | ||
| 1089 | "identity": null, | ||
| 1090 | "name": "state", | ||
| 1091 | "entityType": "columns", | ||
| 1092 | "schema": "public", | ||
| 1093 | "table": "source_auth_sessions" | ||
| 1094 | }, | ||
| 1095 | { | ||
| 1096 | "type": "text", | ||
| 1097 | "typeSchema": null, | ||
| 1098 | "notNull": true, | ||
| 1099 | "dimensions": 0, | ||
| 1100 | "default": null, | ||
| 1101 | "generated": null, | ||
| 1102 | "identity": null, | ||
| 1103 | "name": "redirect_uri", | ||
| 1104 | "entityType": "columns", | ||
| 1105 | "schema": "public", | ||
| 1106 | "table": "source_auth_sessions" | ||
| 1107 | }, | ||
| 1108 | { | ||
| 1109 | "type": "text", | ||
| 1110 | "typeSchema": null, | ||
| 1111 | "notNull": false, | ||
| 1112 | "dimensions": 0, | ||
| 1113 | "default": null, | ||
| 1114 | "generated": null, | ||
| 1115 | "identity": null, | ||
| 1116 | "name": "scope", | ||
| 1117 | "entityType": "columns", | ||
| 1118 | "schema": "public", | ||
| 1119 | "table": "source_auth_sessions" | ||
| 1120 | }, | ||
| 1121 | { | ||
| 1122 | "type": "text", | ||
| 1123 | "typeSchema": null, | ||
| 1124 | "notNull": false, | ||
| 1125 | "dimensions": 0, | ||
| 1126 | "default": null, | ||
| 1127 | "generated": null, | ||
| 1128 | "identity": null, | ||
| 1129 | "name": "resource_metadata_url", | ||
| 1130 | "entityType": "columns", | ||
| 1131 | "schema": "public", | ||
| 1132 | "table": "source_auth_sessions" | ||
| 1133 | }, | ||
| 1134 | { | ||
| 1135 | "type": "text", | ||
| 1136 | "typeSchema": null, | ||
| 1137 | "notNull": false, | ||
| 1138 | "dimensions": 0, | ||
| 1139 | "default": null, | ||
| 1140 | "generated": null, | ||
| 1141 | "identity": null, | ||
| 1142 | "name": "authorization_server_url", | ||
| 1143 | "entityType": "columns", | ||
| 1144 | "schema": "public", | ||
| 1145 | "table": "source_auth_sessions" | ||
| 1146 | }, | ||
| 1147 | { | ||
| 1148 | "type": "text", | ||
| 1149 | "typeSchema": null, | ||
| 1150 | "notNull": false, | ||
| 1151 | "dimensions": 0, | ||
| 1152 | "default": null, | ||
| 1153 | "generated": null, | ||
| 1154 | "identity": null, | ||
| 1155 | "name": "resource_metadata_json", | ||
| 1156 | "entityType": "columns", | ||
| 1157 | "schema": "public", | ||
| 1158 | "table": "source_auth_sessions" | ||
| 1159 | }, | ||
| 1160 | { | ||
| 1161 | "type": "text", | ||
| 1162 | "typeSchema": null, | ||
| 1163 | "notNull": false, | ||
| 1164 | "dimensions": 0, | ||
| 1165 | "default": null, | ||
| 1166 | "generated": null, | ||
| 1167 | "identity": null, | ||
| 1168 | "name": "authorization_server_metadata_json", | ||
| 1169 | "entityType": "columns", | ||
| 1170 | "schema": "public", | ||
| 1171 | "table": "source_auth_sessions" | ||
| 1172 | }, | ||
| 1173 | { | ||
| 1174 | "type": "text", | ||
| 1175 | "typeSchema": null, | ||
| 1176 | "notNull": false, | ||
| 1177 | "dimensions": 0, | ||
| 1178 | "default": null, | ||
| 1179 | "generated": null, | ||
| 1180 | "identity": null, | ||
| 1181 | "name": "client_information_json", | ||
| 1182 | "entityType": "columns", | ||
| 1183 | "schema": "public", | ||
| 1184 | "table": "source_auth_sessions" | ||
| 1185 | }, | ||
| 1186 | { | ||
| 1187 | "type": "text", | ||
| 1188 | "typeSchema": null, | ||
| 1189 | "notNull": false, | ||
| 1190 | "dimensions": 0, | ||
| 1191 | "default": null, | ||
| 1192 | "generated": null, | ||
| 1193 | "identity": null, | ||
| 1194 | "name": "code_verifier", | ||
| 1195 | "entityType": "columns", | ||
| 1196 | "schema": "public", | ||
| 1197 | "table": "source_auth_sessions" | ||
| 1198 | }, | ||
| 1199 | { | ||
| 1200 | "type": "text", | ||
| 1201 | "typeSchema": null, | ||
| 1202 | "notNull": false, | ||
| 1203 | "dimensions": 0, | ||
| 1204 | "default": null, | ||
| 1205 | "generated": null, | ||
| 1206 | "identity": null, | ||
| 1207 | "name": "authorization_url", | ||
| 1208 | "entityType": "columns", | ||
| 1209 | "schema": "public", | ||
| 1210 | "table": "source_auth_sessions" | ||
| 1211 | }, | ||
| 1212 | { | ||
| 1213 | "type": "text", | ||
| 1214 | "typeSchema": null, | ||
| 1215 | "notNull": false, | ||
| 1216 | "dimensions": 0, | ||
| 1217 | "default": null, | ||
| 1218 | "generated": null, | ||
| 1219 | "identity": null, | ||
| 1220 | "name": "error_text", | ||
| 1221 | "entityType": "columns", | ||
| 1222 | "schema": "public", | ||
| 1223 | "table": "source_auth_sessions" | ||
| 1224 | }, | ||
| 1225 | { | ||
| 1226 | "type": "bigint", | ||
| 1227 | "typeSchema": null, | ||
| 1228 | "notNull": false, | ||
| 1229 | "dimensions": 0, | ||
| 1230 | "default": null, | ||
| 1231 | "generated": null, | ||
| 1232 | "identity": null, | ||
| 1233 | "name": "completed_at", | ||
| 1234 | "entityType": "columns", | ||
| 1235 | "schema": "public", | ||
| 1236 | "table": "source_auth_sessions" | ||
| 1237 | }, | ||
| 1238 | { | ||
| 1239 | "type": "bigint", | ||
| 1240 | "typeSchema": null, | ||
| 1241 | "notNull": true, | ||
| 1242 | "dimensions": 0, | ||
| 1243 | "default": null, | ||
| 1244 | "generated": null, | ||
| 1245 | "identity": null, | ||
| 1246 | "name": "created_at", | ||
| 1247 | "entityType": "columns", | ||
| 1248 | "schema": "public", | ||
| 1249 | "table": "source_auth_sessions" | ||
| 1250 | }, | ||
| 1251 | { | ||
| 1252 | "type": "bigint", | ||
| 1253 | "typeSchema": null, | ||
| 1254 | "notNull": true, | ||
| 1255 | "dimensions": 0, | ||
| 1256 | "default": null, | ||
| 1257 | "generated": null, | ||
| 1258 | "identity": null, | ||
| 1259 | "name": "updated_at", | ||
| 1260 | "entityType": "columns", | ||
| 1261 | "schema": "public", | ||
| 1262 | "table": "source_auth_sessions" | ||
| 1263 | }, | ||
| 1264 | { | ||
| 1265 | "type": "text", | ||
| 1266 | "typeSchema": null, | ||
| 1267 | "notNull": true, | ||
| 1268 | "dimensions": 0, | ||
| 1269 | "default": null, | ||
| 1270 | "generated": null, | ||
| 1271 | "identity": null, | ||
| 1272 | "name": "workspace_id", | ||
| 1273 | "entityType": "columns", | ||
| 1274 | "schema": "public", | ||
| 1275 | "table": "source_credential_bindings" | ||
| 1276 | }, | ||
| 1277 | { | ||
| 1278 | "type": "text", | ||
| 1279 | "typeSchema": null, | ||
| 1280 | "notNull": true, | ||
| 1281 | "dimensions": 0, | ||
| 1282 | "default": null, | ||
| 1283 | "generated": null, | ||
| 1284 | "identity": null, | ||
| 1285 | "name": "source_id", | ||
| 1286 | "entityType": "columns", | ||
| 1287 | "schema": "public", | ||
| 1288 | "table": "source_credential_bindings" | ||
| 1289 | }, | ||
| 1290 | { | ||
| 1291 | "type": "text", | ||
| 1292 | "typeSchema": null, | ||
| 1293 | "notNull": false, | ||
| 1294 | "dimensions": 0, | ||
| 1295 | "default": null, | ||
| 1296 | "generated": null, | ||
| 1297 | "identity": null, | ||
| 1298 | "name": "token_provider_id", | ||
| 1299 | "entityType": "columns", | ||
| 1300 | "schema": "public", | ||
| 1301 | "table": "source_credential_bindings" | ||
| 1302 | }, | ||
| 1303 | { | ||
| 1304 | "type": "text", | ||
| 1305 | "typeSchema": null, | ||
| 1306 | "notNull": false, | ||
| 1307 | "dimensions": 0, | ||
| 1308 | "default": null, | ||
| 1309 | "generated": null, | ||
| 1310 | "identity": null, | ||
| 1311 | "name": "token_handle", | ||
| 1312 | "entityType": "columns", | ||
| 1313 | "schema": "public", | ||
| 1314 | "table": "source_credential_bindings" | ||
| 1315 | }, | ||
| 1316 | { | ||
| 1317 | "type": "text", | ||
| 1318 | "typeSchema": null, | ||
| 1319 | "notNull": false, | ||
| 1320 | "dimensions": 0, | ||
| 1321 | "default": null, | ||
| 1322 | "generated": null, | ||
| 1323 | "identity": null, | ||
| 1324 | "name": "refresh_token_provider_id", | ||
| 1325 | "entityType": "columns", | ||
| 1326 | "schema": "public", | ||
| 1327 | "table": "source_credential_bindings" | ||
| 1328 | }, | ||
| 1329 | { | ||
| 1330 | "type": "text", | ||
| 1331 | "typeSchema": null, | ||
| 1332 | "notNull": false, | ||
| 1333 | "dimensions": 0, | ||
| 1334 | "default": null, | ||
| 1335 | "generated": null, | ||
| 1336 | "identity": null, | ||
| 1337 | "name": "refresh_token_handle", | ||
| 1338 | "entityType": "columns", | ||
| 1339 | "schema": "public", | ||
| 1340 | "table": "source_credential_bindings" | ||
| 1341 | }, | ||
| 1342 | { | ||
| 1343 | "type": "bigint", | ||
| 1344 | "typeSchema": null, | ||
| 1345 | "notNull": true, | ||
| 1346 | "dimensions": 0, | ||
| 1347 | "default": null, | ||
| 1348 | "generated": null, | ||
| 1349 | "identity": null, | ||
| 1350 | "name": "created_at", | ||
| 1351 | "entityType": "columns", | ||
| 1352 | "schema": "public", | ||
| 1353 | "table": "source_credential_bindings" | ||
| 1354 | }, | ||
| 1355 | { | ||
| 1356 | "type": "bigint", | ||
| 1357 | "typeSchema": null, | ||
| 1358 | "notNull": true, | ||
| 1359 | "dimensions": 0, | ||
| 1360 | "default": null, | ||
| 1361 | "generated": null, | ||
| 1362 | "identity": null, | ||
| 1363 | "name": "updated_at", | ||
| 1364 | "entityType": "columns", | ||
| 1365 | "schema": "public", | ||
| 1366 | "table": "source_credential_bindings" | ||
| 1367 | }, | ||
| 1368 | { | ||
| 1369 | "type": "text", | ||
| 1370 | "typeSchema": null, | ||
| 1371 | "notNull": true, | ||
| 1372 | "dimensions": 0, | ||
| 1373 | "default": null, | ||
| 1374 | "generated": null, | ||
| 1375 | "identity": null, | ||
| 1376 | "name": "workspace_id", | ||
| 1377 | "entityType": "columns", | ||
| 1378 | "schema": "public", | ||
| 1379 | "table": "sources" | ||
| 1380 | }, | ||
| 1381 | { | ||
| 1382 | "type": "text", | ||
| 1383 | "typeSchema": null, | ||
| 1384 | "notNull": true, | ||
| 1385 | "dimensions": 0, | ||
| 1386 | "default": null, | ||
| 1387 | "generated": null, | ||
| 1388 | "identity": null, | ||
| 1389 | "name": "source_id", | ||
| 1390 | "entityType": "columns", | ||
| 1391 | "schema": "public", | ||
| 1392 | "table": "sources" | ||
| 1393 | }, | ||
| 1394 | { | ||
| 1395 | "type": "text", | ||
| 1396 | "typeSchema": null, | ||
| 1397 | "notNull": true, | ||
| 1398 | "dimensions": 0, | ||
| 1399 | "default": null, | ||
| 1400 | "generated": null, | ||
| 1401 | "identity": null, | ||
| 1402 | "name": "name", | ||
| 1403 | "entityType": "columns", | ||
| 1404 | "schema": "public", | ||
| 1405 | "table": "sources" | ||
| 1406 | }, | ||
| 1407 | { | ||
| 1408 | "type": "text", | ||
| 1409 | "typeSchema": null, | ||
| 1410 | "notNull": true, | ||
| 1411 | "dimensions": 0, | ||
| 1412 | "default": null, | ||
| 1413 | "generated": null, | ||
| 1414 | "identity": null, | ||
| 1415 | "name": "kind", | ||
| 1416 | "entityType": "columns", | ||
| 1417 | "schema": "public", | ||
| 1418 | "table": "sources" | ||
| 1419 | }, | ||
| 1420 | { | ||
| 1421 | "type": "text", | ||
| 1422 | "typeSchema": null, | ||
| 1423 | "notNull": true, | ||
| 1424 | "dimensions": 0, | ||
| 1425 | "default": null, | ||
| 1426 | "generated": null, | ||
| 1427 | "identity": null, | ||
| 1428 | "name": "endpoint", | ||
| 1429 | "entityType": "columns", | ||
| 1430 | "schema": "public", | ||
| 1431 | "table": "sources" | ||
| 1432 | }, | ||
| 1433 | { | ||
| 1434 | "type": "text", | ||
| 1435 | "typeSchema": null, | ||
| 1436 | "notNull": true, | ||
| 1437 | "dimensions": 0, | ||
| 1438 | "default": null, | ||
| 1439 | "generated": null, | ||
| 1440 | "identity": null, | ||
| 1441 | "name": "status", | ||
| 1442 | "entityType": "columns", | ||
| 1443 | "schema": "public", | ||
| 1444 | "table": "sources" | ||
| 1445 | }, | ||
| 1446 | { | ||
| 1447 | "type": "boolean", | ||
| 1448 | "typeSchema": null, | ||
| 1449 | "notNull": true, | ||
| 1450 | "dimensions": 0, | ||
| 1451 | "default": null, | ||
| 1452 | "generated": null, | ||
| 1453 | "identity": null, | ||
| 1454 | "name": "enabled", | ||
| 1455 | "entityType": "columns", | ||
| 1456 | "schema": "public", | ||
| 1457 | "table": "sources" | ||
| 1458 | }, | ||
| 1459 | { | ||
| 1460 | "type": "text", | ||
| 1461 | "typeSchema": null, | ||
| 1462 | "notNull": false, | ||
| 1463 | "dimensions": 0, | ||
| 1464 | "default": null, | ||
| 1465 | "generated": null, | ||
| 1466 | "identity": null, | ||
| 1467 | "name": "namespace", | ||
| 1468 | "entityType": "columns", | ||
| 1469 | "schema": "public", | ||
| 1470 | "table": "sources" | ||
| 1471 | }, | ||
| 1472 | { | ||
| 1473 | "type": "text", | ||
| 1474 | "typeSchema": null, | ||
| 1475 | "notNull": false, | ||
| 1476 | "dimensions": 0, | ||
| 1477 | "default": null, | ||
| 1478 | "generated": null, | ||
| 1479 | "identity": null, | ||
| 1480 | "name": "transport", | ||
| 1481 | "entityType": "columns", | ||
| 1482 | "schema": "public", | ||
| 1483 | "table": "sources" | ||
| 1484 | }, | ||
| 1485 | { | ||
| 1486 | "type": "text", | ||
| 1487 | "typeSchema": null, | ||
| 1488 | "notNull": false, | ||
| 1489 | "dimensions": 0, | ||
| 1490 | "default": null, | ||
| 1491 | "generated": null, | ||
| 1492 | "identity": null, | ||
| 1493 | "name": "query_params_json", | ||
| 1494 | "entityType": "columns", | ||
| 1495 | "schema": "public", | ||
| 1496 | "table": "sources" | ||
| 1497 | }, | ||
| 1498 | { | ||
| 1499 | "type": "text", | ||
| 1500 | "typeSchema": null, | ||
| 1501 | "notNull": false, | ||
| 1502 | "dimensions": 0, | ||
| 1503 | "default": null, | ||
| 1504 | "generated": null, | ||
| 1505 | "identity": null, | ||
| 1506 | "name": "headers_json", | ||
| 1507 | "entityType": "columns", | ||
| 1508 | "schema": "public", | ||
| 1509 | "table": "sources" | ||
| 1510 | }, | ||
| 1511 | { | ||
| 1512 | "type": "text", | ||
| 1513 | "typeSchema": null, | ||
| 1514 | "notNull": false, | ||
| 1515 | "dimensions": 0, | ||
| 1516 | "default": null, | ||
| 1517 | "generated": null, | ||
| 1518 | "identity": null, | ||
| 1519 | "name": "spec_url", | ||
| 1520 | "entityType": "columns", | ||
| 1521 | "schema": "public", | ||
| 1522 | "table": "sources" | ||
| 1523 | }, | ||
| 1524 | { | ||
| 1525 | "type": "text", | ||
| 1526 | "typeSchema": null, | ||
| 1527 | "notNull": false, | ||
| 1528 | "dimensions": 0, | ||
| 1529 | "default": null, | ||
| 1530 | "generated": null, | ||
| 1531 | "identity": null, | ||
| 1532 | "name": "default_headers_json", | ||
| 1533 | "entityType": "columns", | ||
| 1534 | "schema": "public", | ||
| 1535 | "table": "sources" | ||
| 1536 | }, | ||
| 1537 | { | ||
| 1538 | "type": "text", | ||
| 1539 | "typeSchema": null, | ||
| 1540 | "notNull": true, | ||
| 1541 | "dimensions": 0, | ||
| 1542 | "default": null, | ||
| 1543 | "generated": null, | ||
| 1544 | "identity": null, | ||
| 1545 | "name": "auth_kind", | ||
| 1546 | "entityType": "columns", | ||
| 1547 | "schema": "public", | ||
| 1548 | "table": "sources" | ||
| 1549 | }, | ||
| 1550 | { | ||
| 1551 | "type": "text", | ||
| 1552 | "typeSchema": null, | ||
| 1553 | "notNull": false, | ||
| 1554 | "dimensions": 0, | ||
| 1555 | "default": null, | ||
| 1556 | "generated": null, | ||
| 1557 | "identity": null, | ||
| 1558 | "name": "auth_header_name", | ||
| 1559 | "entityType": "columns", | ||
| 1560 | "schema": "public", | ||
| 1561 | "table": "sources" | ||
| 1562 | }, | ||
| 1563 | { | ||
| 1564 | "type": "text", | ||
| 1565 | "typeSchema": null, | ||
| 1566 | "notNull": false, | ||
| 1567 | "dimensions": 0, | ||
| 1568 | "default": null, | ||
| 1569 | "generated": null, | ||
| 1570 | "identity": null, | ||
| 1571 | "name": "auth_prefix", | ||
| 1572 | "entityType": "columns", | ||
| 1573 | "schema": "public", | ||
| 1574 | "table": "sources" | ||
| 1575 | }, | ||
| 1576 | { | ||
| 1577 | "type": "text", | ||
| 1578 | "typeSchema": null, | ||
| 1579 | "notNull": false, | ||
| 1580 | "dimensions": 0, | ||
| 1581 | "default": null, | ||
| 1582 | "generated": null, | ||
| 1583 | "identity": null, | ||
| 1584 | "name": "source_hash", | ||
| 1585 | "entityType": "columns", | ||
| 1586 | "schema": "public", | ||
| 1587 | "table": "sources" | ||
| 1588 | }, | ||
| 1589 | { | ||
| 1590 | "type": "text", | ||
| 1591 | "typeSchema": null, | ||
| 1592 | "notNull": false, | ||
| 1593 | "dimensions": 0, | ||
| 1594 | "default": null, | ||
| 1595 | "generated": null, | ||
| 1596 | "identity": null, | ||
| 1597 | "name": "last_error", | ||
| 1598 | "entityType": "columns", | ||
| 1599 | "schema": "public", | ||
| 1600 | "table": "sources" | ||
| 1601 | }, | ||
| 1602 | { | ||
| 1603 | "type": "bigint", | ||
| 1604 | "typeSchema": null, | ||
| 1605 | "notNull": true, | ||
| 1606 | "dimensions": 0, | ||
| 1607 | "default": null, | ||
| 1608 | "generated": null, | ||
| 1609 | "identity": null, | ||
| 1610 | "name": "created_at", | ||
| 1611 | "entityType": "columns", | ||
| 1612 | "schema": "public", | ||
| 1613 | "table": "sources" | ||
| 1614 | }, | ||
| 1615 | { | ||
| 1616 | "type": "bigint", | ||
| 1617 | "typeSchema": null, | ||
| 1618 | "notNull": true, | ||
| 1619 | "dimensions": 0, | ||
| 1620 | "default": null, | ||
| 1621 | "generated": null, | ||
| 1622 | "identity": null, | ||
| 1623 | "name": "updated_at", | ||
| 1624 | "entityType": "columns", | ||
| 1625 | "schema": "public", | ||
| 1626 | "table": "sources" | ||
| 1627 | }, | ||
| 1628 | { | ||
| 1629 | "type": "text", | ||
| 1630 | "typeSchema": null, | ||
| 1631 | "notNull": true, | ||
| 1632 | "dimensions": 0, | ||
| 1633 | "default": null, | ||
| 1634 | "generated": null, | ||
| 1635 | "identity": null, | ||
| 1636 | "name": "id", | ||
| 1637 | "entityType": "columns", | ||
| 1638 | "schema": "public", | ||
| 1639 | "table": "workspaces" | ||
| 1640 | }, | ||
| 1641 | { | ||
| 1642 | "type": "text", | ||
| 1643 | "typeSchema": null, | ||
| 1644 | "notNull": true, | ||
| 1645 | "dimensions": 0, | ||
| 1646 | "default": null, | ||
| 1647 | "generated": null, | ||
| 1648 | "identity": null, | ||
| 1649 | "name": "organization_id", | ||
| 1650 | "entityType": "columns", | ||
| 1651 | "schema": "public", | ||
| 1652 | "table": "workspaces" | ||
| 1653 | }, | ||
| 1654 | { | ||
| 1655 | "type": "text", | ||
| 1656 | "typeSchema": null, | ||
| 1657 | "notNull": true, | ||
| 1658 | "dimensions": 0, | ||
| 1659 | "default": null, | ||
| 1660 | "generated": null, | ||
| 1661 | "identity": null, | ||
| 1662 | "name": "name", | ||
| 1663 | "entityType": "columns", | ||
| 1664 | "schema": "public", | ||
| 1665 | "table": "workspaces" | ||
| 1666 | }, | ||
| 1667 | { | ||
| 1668 | "type": "text", | ||
| 1669 | "typeSchema": null, | ||
| 1670 | "notNull": false, | ||
| 1671 | "dimensions": 0, | ||
| 1672 | "default": null, | ||
| 1673 | "generated": null, | ||
| 1674 | "identity": null, | ||
| 1675 | "name": "created_by_account_id", | ||
| 1676 | "entityType": "columns", | ||
| 1677 | "schema": "public", | ||
| 1678 | "table": "workspaces" | ||
| 1679 | }, | ||
| 1680 | { | ||
| 1681 | "type": "bigint", | ||
| 1682 | "typeSchema": null, | ||
| 1683 | "notNull": true, | ||
| 1684 | "dimensions": 0, | ||
| 1685 | "default": null, | ||
| 1686 | "generated": null, | ||
| 1687 | "identity": null, | ||
| 1688 | "name": "created_at", | ||
| 1689 | "entityType": "columns", | ||
| 1690 | "schema": "public", | ||
| 1691 | "table": "workspaces" | ||
| 1692 | }, | ||
| 1693 | { | ||
| 1694 | "type": "bigint", | ||
| 1695 | "typeSchema": null, | ||
| 1696 | "notNull": true, | ||
| 1697 | "dimensions": 0, | ||
| 1698 | "default": null, | ||
| 1699 | "generated": null, | ||
| 1700 | "identity": null, | ||
| 1701 | "name": "updated_at", | ||
| 1702 | "entityType": "columns", | ||
| 1703 | "schema": "public", | ||
| 1704 | "table": "workspaces" | ||
| 1705 | }, | ||
| 1706 | { | ||
| 1707 | "nameExplicit": true, | ||
| 1708 | "columns": [ | ||
| 1709 | { | ||
| 1710 | "value": "provider", | ||
| 1711 | "isExpression": false, | ||
| 1712 | "asc": true, | ||
| 1713 | "nullsFirst": false, | ||
| 1714 | "opclass": null | ||
| 1715 | }, | ||
| 1716 | { | ||
| 1717 | "value": "subject", | ||
| 1718 | "isExpression": false, | ||
| 1719 | "asc": true, | ||
| 1720 | "nullsFirst": false, | ||
| 1721 | "opclass": null | ||
| 1722 | } | ||
| 1723 | ], | ||
| 1724 | "isUnique": true, | ||
| 1725 | "where": null, | ||
| 1726 | "with": "", | ||
| 1727 | "method": "btree", | ||
| 1728 | "concurrently": false, | ||
| 1729 | "name": "accounts_provider_subject_idx", | ||
| 1730 | "entityType": "indexes", | ||
| 1731 | "schema": "public", | ||
| 1732 | "table": "accounts" | ||
| 1733 | }, | ||
| 1734 | { | ||
| 1735 | "nameExplicit": true, | ||
| 1736 | "columns": [ | ||
| 1737 | { | ||
| 1738 | "value": "updated_at", | ||
| 1739 | "isExpression": false, | ||
| 1740 | "asc": true, | ||
| 1741 | "nullsFirst": false, | ||
| 1742 | "opclass": null | ||
| 1743 | }, | ||
| 1744 | { | ||
| 1745 | "value": "id", | ||
| 1746 | "isExpression": false, | ||
| 1747 | "asc": true, | ||
| 1748 | "nullsFirst": false, | ||
| 1749 | "opclass": null | ||
| 1750 | } | ||
| 1751 | ], | ||
| 1752 | "isUnique": false, | ||
| 1753 | "where": null, | ||
| 1754 | "with": "", | ||
| 1755 | "method": "btree", | ||
| 1756 | "concurrently": false, | ||
| 1757 | "name": "accounts_updated_idx", | ||
| 1758 | "entityType": "indexes", | ||
| 1759 | "schema": "public", | ||
| 1760 | "table": "accounts" | ||
| 1761 | }, | ||
| 1762 | { | ||
| 1763 | "nameExplicit": true, | ||
| 1764 | "columns": [ | ||
| 1765 | { | ||
| 1766 | "value": "execution_id", | ||
| 1767 | "isExpression": false, | ||
| 1768 | "asc": true, | ||
| 1769 | "nullsFirst": false, | ||
| 1770 | "opclass": null | ||
| 1771 | }, | ||
| 1772 | { | ||
| 1773 | "value": "updated_at", | ||
| 1774 | "isExpression": false, | ||
| 1775 | "asc": true, | ||
| 1776 | "nullsFirst": false, | ||
| 1777 | "opclass": null | ||
| 1778 | }, | ||
| 1779 | { | ||
| 1780 | "value": "id", | ||
| 1781 | "isExpression": false, | ||
| 1782 | "asc": true, | ||
| 1783 | "nullsFirst": false, | ||
| 1784 | "opclass": null | ||
| 1785 | } | ||
| 1786 | ], | ||
| 1787 | "isUnique": false, | ||
| 1788 | "where": null, | ||
| 1789 | "with": "", | ||
| 1790 | "method": "btree", | ||
| 1791 | "concurrently": false, | ||
| 1792 | "name": "execution_interactions_execution_idx", | ||
| 1793 | "entityType": "indexes", | ||
| 1794 | "schema": "public", | ||
| 1795 | "table": "execution_interactions" | ||
| 1796 | }, | ||
| 1797 | { | ||
| 1798 | "nameExplicit": true, | ||
| 1799 | "columns": [ | ||
| 1800 | { | ||
| 1801 | "value": "workspace_id", | ||
| 1802 | "isExpression": false, | ||
| 1803 | "asc": true, | ||
| 1804 | "nullsFirst": false, | ||
| 1805 | "opclass": null | ||
| 1806 | }, | ||
| 1807 | { | ||
| 1808 | "value": "updated_at", | ||
| 1809 | "isExpression": false, | ||
| 1810 | "asc": true, | ||
| 1811 | "nullsFirst": false, | ||
| 1812 | "opclass": null | ||
| 1813 | }, | ||
| 1814 | { | ||
| 1815 | "value": "id", | ||
| 1816 | "isExpression": false, | ||
| 1817 | "asc": true, | ||
| 1818 | "nullsFirst": false, | ||
| 1819 | "opclass": null | ||
| 1820 | } | ||
| 1821 | ], | ||
| 1822 | "isUnique": false, | ||
| 1823 | "where": null, | ||
| 1824 | "with": "", | ||
| 1825 | "method": "btree", | ||
| 1826 | "concurrently": false, | ||
| 1827 | "name": "executions_workspace_idx", | ||
| 1828 | "entityType": "indexes", | ||
| 1829 | "schema": "public", | ||
| 1830 | "table": "executions" | ||
| 1831 | }, | ||
| 1832 | { | ||
| 1833 | "nameExplicit": true, | ||
| 1834 | "columns": [ | ||
| 1835 | { | ||
| 1836 | "value": "organization_id", | ||
| 1837 | "isExpression": false, | ||
| 1838 | "asc": true, | ||
| 1839 | "nullsFirst": false, | ||
| 1840 | "opclass": null | ||
| 1841 | } | ||
| 1842 | ], | ||
| 1843 | "isUnique": false, | ||
| 1844 | "where": null, | ||
| 1845 | "with": "", | ||
| 1846 | "method": "btree", | ||
| 1847 | "concurrently": false, | ||
| 1848 | "name": "organization_memberships_org_idx", | ||
| 1849 | "entityType": "indexes", | ||
| 1850 | "schema": "public", | ||
| 1851 | "table": "organization_memberships" | ||
| 1852 | }, | ||
| 1853 | { | ||
| 1854 | "nameExplicit": true, | ||
| 1855 | "columns": [ | ||
| 1856 | { | ||
| 1857 | "value": "account_id", | ||
| 1858 | "isExpression": false, | ||
| 1859 | "asc": true, | ||
| 1860 | "nullsFirst": false, | ||
| 1861 | "opclass": null | ||
| 1862 | } | ||
| 1863 | ], | ||
| 1864 | "isUnique": false, | ||
| 1865 | "where": null, | ||
| 1866 | "with": "", | ||
| 1867 | "method": "btree", | ||
| 1868 | "concurrently": false, | ||
| 1869 | "name": "organization_memberships_account_idx", | ||
| 1870 | "entityType": "indexes", | ||
| 1871 | "schema": "public", | ||
| 1872 | "table": "organization_memberships" | ||
| 1873 | }, | ||
| 1874 | { | ||
| 1875 | "nameExplicit": true, | ||
| 1876 | "columns": [ | ||
| 1877 | { | ||
| 1878 | "value": "organization_id", | ||
| 1879 | "isExpression": false, | ||
| 1880 | "asc": true, | ||
| 1881 | "nullsFirst": false, | ||
| 1882 | "opclass": null | ||
| 1883 | }, | ||
| 1884 | { | ||
| 1885 | "value": "account_id", | ||
| 1886 | "isExpression": false, | ||
| 1887 | "asc": true, | ||
| 1888 | "nullsFirst": false, | ||
| 1889 | "opclass": null | ||
| 1890 | } | ||
| 1891 | ], | ||
| 1892 | "isUnique": true, | ||
| 1893 | "where": null, | ||
| 1894 | "with": "", | ||
| 1895 | "method": "btree", | ||
| 1896 | "concurrently": false, | ||
| 1897 | "name": "organization_memberships_org_account_idx", | ||
| 1898 | "entityType": "indexes", | ||
| 1899 | "schema": "public", | ||
| 1900 | "table": "organization_memberships" | ||
| 1901 | }, | ||
| 1902 | { | ||
| 1903 | "nameExplicit": true, | ||
| 1904 | "columns": [ | ||
| 1905 | { | ||
| 1906 | "value": "slug", | ||
| 1907 | "isExpression": false, | ||
| 1908 | "asc": true, | ||
| 1909 | "nullsFirst": false, | ||
| 1910 | "opclass": null | ||
| 1911 | } | ||
| 1912 | ], | ||
| 1913 | "isUnique": true, | ||
| 1914 | "where": null, | ||
| 1915 | "with": "", | ||
| 1916 | "method": "btree", | ||
| 1917 | "concurrently": false, | ||
| 1918 | "name": "organizations_slug_idx", | ||
| 1919 | "entityType": "indexes", | ||
| 1920 | "schema": "public", | ||
| 1921 | "table": "organizations" | ||
| 1922 | }, | ||
| 1923 | { | ||
| 1924 | "nameExplicit": true, | ||
| 1925 | "columns": [ | ||
| 1926 | { | ||
| 1927 | "value": "updated_at", | ||
| 1928 | "isExpression": false, | ||
| 1929 | "asc": true, | ||
| 1930 | "nullsFirst": false, | ||
| 1931 | "opclass": null | ||
| 1932 | }, | ||
| 1933 | { | ||
| 1934 | "value": "id", | ||
| 1935 | "isExpression": false, | ||
| 1936 | "asc": true, | ||
| 1937 | "nullsFirst": false, | ||
| 1938 | "opclass": null | ||
| 1939 | } | ||
| 1940 | ], | ||
| 1941 | "isUnique": false, | ||
| 1942 | "where": null, | ||
| 1943 | "with": "", | ||
| 1944 | "method": "btree", | ||
| 1945 | "concurrently": false, | ||
| 1946 | "name": "organizations_updated_idx", | ||
| 1947 | "entityType": "indexes", | ||
| 1948 | "schema": "public", | ||
| 1949 | "table": "organizations" | ||
| 1950 | }, | ||
| 1951 | { | ||
| 1952 | "nameExplicit": true, | ||
| 1953 | "columns": [ | ||
| 1954 | { | ||
| 1955 | "value": "workspace_id", | ||
| 1956 | "isExpression": false, | ||
| 1957 | "asc": true, | ||
| 1958 | "nullsFirst": false, | ||
| 1959 | "opclass": null | ||
| 1960 | }, | ||
| 1961 | { | ||
| 1962 | "value": "updated_at", | ||
| 1963 | "isExpression": false, | ||
| 1964 | "asc": true, | ||
| 1965 | "nullsFirst": false, | ||
| 1966 | "opclass": null | ||
| 1967 | }, | ||
| 1968 | { | ||
| 1969 | "value": "id", | ||
| 1970 | "isExpression": false, | ||
| 1971 | "asc": true, | ||
| 1972 | "nullsFirst": false, | ||
| 1973 | "opclass": null | ||
| 1974 | } | ||
| 1975 | ], | ||
| 1976 | "isUnique": false, | ||
| 1977 | "where": null, | ||
| 1978 | "with": "", | ||
| 1979 | "method": "btree", | ||
| 1980 | "concurrently": false, | ||
| 1981 | "name": "policies_workspace_idx", | ||
| 1982 | "entityType": "indexes", | ||
| 1983 | "schema": "public", | ||
| 1984 | "table": "policies" | ||
| 1985 | }, | ||
| 1986 | { | ||
| 1987 | "nameExplicit": true, | ||
| 1988 | "columns": [ | ||
| 1989 | { | ||
| 1990 | "value": "updated_at", | ||
| 1991 | "isExpression": false, | ||
| 1992 | "asc": true, | ||
| 1993 | "nullsFirst": false, | ||
| 1994 | "opclass": null | ||
| 1995 | }, | ||
| 1996 | { | ||
| 1997 | "value": "id", | ||
| 1998 | "isExpression": false, | ||
| 1999 | "asc": true, | ||
| 2000 | "nullsFirst": false, | ||
| 2001 | "opclass": null | ||
| 2002 | } | ||
| 2003 | ], | ||
| 2004 | "isUnique": false, | ||
| 2005 | "where": null, | ||
| 2006 | "with": "", | ||
| 2007 | "method": "btree", | ||
| 2008 | "concurrently": false, | ||
| 2009 | "name": "secret_materials_updated_idx", | ||
| 2010 | "entityType": "indexes", | ||
| 2011 | "schema": "public", | ||
| 2012 | "table": "secret_materials" | ||
| 2013 | }, | ||
| 2014 | { | ||
| 2015 | "nameExplicit": true, | ||
| 2016 | "columns": [ | ||
| 2017 | { | ||
| 2018 | "value": "workspace_id", | ||
| 2019 | "isExpression": false, | ||
| 2020 | "asc": true, | ||
| 2021 | "nullsFirst": false, | ||
| 2022 | "opclass": null | ||
| 2023 | }, | ||
| 2024 | { | ||
| 2025 | "value": "updated_at", | ||
| 2026 | "isExpression": false, | ||
| 2027 | "asc": true, | ||
| 2028 | "nullsFirst": false, | ||
| 2029 | "opclass": null | ||
| 2030 | }, | ||
| 2031 | { | ||
| 2032 | "value": "id", | ||
| 2033 | "isExpression": false, | ||
| 2034 | "asc": true, | ||
| 2035 | "nullsFirst": false, | ||
| 2036 | "opclass": null | ||
| 2037 | } | ||
| 2038 | ], | ||
| 2039 | "isUnique": false, | ||
| 2040 | "where": null, | ||
| 2041 | "with": "", | ||
| 2042 | "method": "btree", | ||
| 2043 | "concurrently": false, | ||
| 2044 | "name": "source_auth_sessions_workspace_idx", | ||
| 2045 | "entityType": "indexes", | ||
| 2046 | "schema": "public", | ||
| 2047 | "table": "source_auth_sessions" | ||
| 2048 | }, | ||
| 2049 | { | ||
| 2050 | "nameExplicit": true, | ||
| 2051 | "columns": [ | ||
| 2052 | { | ||
| 2053 | "value": "state", | ||
| 2054 | "isExpression": false, | ||
| 2055 | "asc": true, | ||
| 2056 | "nullsFirst": false, | ||
| 2057 | "opclass": null | ||
| 2058 | } | ||
| 2059 | ], | ||
| 2060 | "isUnique": true, | ||
| 2061 | "where": null, | ||
| 2062 | "with": "", | ||
| 2063 | "method": "btree", | ||
| 2064 | "concurrently": false, | ||
| 2065 | "name": "source_auth_sessions_state_idx", | ||
| 2066 | "entityType": "indexes", | ||
| 2067 | "schema": "public", | ||
| 2068 | "table": "source_auth_sessions" | ||
| 2069 | }, | ||
| 2070 | { | ||
| 2071 | "nameExplicit": true, | ||
| 2072 | "columns": [ | ||
| 2073 | { | ||
| 2074 | "value": "workspace_id", | ||
| 2075 | "isExpression": false, | ||
| 2076 | "asc": true, | ||
| 2077 | "nullsFirst": false, | ||
| 2078 | "opclass": null | ||
| 2079 | }, | ||
| 2080 | { | ||
| 2081 | "value": "updated_at", | ||
| 2082 | "isExpression": false, | ||
| 2083 | "asc": true, | ||
| 2084 | "nullsFirst": false, | ||
| 2085 | "opclass": null | ||
| 2086 | }, | ||
| 2087 | { | ||
| 2088 | "value": "source_id", | ||
| 2089 | "isExpression": false, | ||
| 2090 | "asc": true, | ||
| 2091 | "nullsFirst": false, | ||
| 2092 | "opclass": null | ||
| 2093 | } | ||
| 2094 | ], | ||
| 2095 | "isUnique": false, | ||
| 2096 | "where": null, | ||
| 2097 | "with": "", | ||
| 2098 | "method": "btree", | ||
| 2099 | "concurrently": false, | ||
| 2100 | "name": "source_credential_bindings_workspace_idx", | ||
| 2101 | "entityType": "indexes", | ||
| 2102 | "schema": "public", | ||
| 2103 | "table": "source_credential_bindings" | ||
| 2104 | }, | ||
| 2105 | { | ||
| 2106 | "nameExplicit": true, | ||
| 2107 | "columns": [ | ||
| 2108 | { | ||
| 2109 | "value": "workspace_id", | ||
| 2110 | "isExpression": false, | ||
| 2111 | "asc": true, | ||
| 2112 | "nullsFirst": false, | ||
| 2113 | "opclass": null | ||
| 2114 | }, | ||
| 2115 | { | ||
| 2116 | "value": "name", | ||
| 2117 | "isExpression": false, | ||
| 2118 | "asc": true, | ||
| 2119 | "nullsFirst": false, | ||
| 2120 | "opclass": null | ||
| 2121 | } | ||
| 2122 | ], | ||
| 2123 | "isUnique": true, | ||
| 2124 | "where": null, | ||
| 2125 | "with": "", | ||
| 2126 | "method": "btree", | ||
| 2127 | "concurrently": false, | ||
| 2128 | "name": "sources_workspace_name_idx", | ||
| 2129 | "entityType": "indexes", | ||
| 2130 | "schema": "public", | ||
| 2131 | "table": "sources" | ||
| 2132 | }, | ||
| 2133 | { | ||
| 2134 | "nameExplicit": true, | ||
| 2135 | "columns": [ | ||
| 2136 | { | ||
| 2137 | "value": "organization_id", | ||
| 2138 | "isExpression": false, | ||
| 2139 | "asc": true, | ||
| 2140 | "nullsFirst": false, | ||
| 2141 | "opclass": null | ||
| 2142 | } | ||
| 2143 | ], | ||
| 2144 | "isUnique": false, | ||
| 2145 | "where": null, | ||
| 2146 | "with": "", | ||
| 2147 | "method": "btree", | ||
| 2148 | "concurrently": false, | ||
| 2149 | "name": "workspaces_org_idx", | ||
| 2150 | "entityType": "indexes", | ||
| 2151 | "schema": "public", | ||
| 2152 | "table": "workspaces" | ||
| 2153 | }, | ||
| 2154 | { | ||
| 2155 | "nameExplicit": true, | ||
| 2156 | "columns": [ | ||
| 2157 | { | ||
| 2158 | "value": "organization_id", | ||
| 2159 | "isExpression": false, | ||
| 2160 | "asc": true, | ||
| 2161 | "nullsFirst": false, | ||
| 2162 | "opclass": null | ||
| 2163 | }, | ||
| 2164 | { | ||
| 2165 | "value": "name", | ||
| 2166 | "isExpression": false, | ||
| 2167 | "asc": true, | ||
| 2168 | "nullsFirst": false, | ||
| 2169 | "opclass": null | ||
| 2170 | } | ||
| 2171 | ], | ||
| 2172 | "isUnique": true, | ||
| 2173 | "where": null, | ||
| 2174 | "with": "", | ||
| 2175 | "method": "btree", | ||
| 2176 | "concurrently": false, | ||
| 2177 | "name": "workspaces_org_name_idx", | ||
| 2178 | "entityType": "indexes", | ||
| 2179 | "schema": "public", | ||
| 2180 | "table": "workspaces" | ||
| 2181 | }, | ||
| 2182 | { | ||
| 2183 | "columns": [ | ||
| 2184 | "workspace_id", | ||
| 2185 | "source_id" | ||
| 2186 | ], | ||
| 2187 | "nameExplicit": false, | ||
| 2188 | "name": "source_credential_bindings_pkey", | ||
| 2189 | "entityType": "pks", | ||
| 2190 | "schema": "public", | ||
| 2191 | "table": "source_credential_bindings" | ||
| 2192 | }, | ||
| 2193 | { | ||
| 2194 | "columns": [ | ||
| 2195 | "workspace_id", | ||
| 2196 | "source_id" | ||
| 2197 | ], | ||
| 2198 | "nameExplicit": false, | ||
| 2199 | "name": "sources_pkey", | ||
| 2200 | "entityType": "pks", | ||
| 2201 | "schema": "public", | ||
| 2202 | "table": "sources" | ||
| 2203 | }, | ||
| 2204 | { | ||
| 2205 | "columns": [ | ||
| 2206 | "id" | ||
| 2207 | ], | ||
| 2208 | "nameExplicit": false, | ||
| 2209 | "name": "accounts_pkey", | ||
| 2210 | "schema": "public", | ||
| 2211 | "table": "accounts", | ||
| 2212 | "entityType": "pks" | ||
| 2213 | }, | ||
| 2214 | { | ||
| 2215 | "columns": [ | ||
| 2216 | "id" | ||
| 2217 | ], | ||
| 2218 | "nameExplicit": false, | ||
| 2219 | "name": "execution_interactions_pkey", | ||
| 2220 | "schema": "public", | ||
| 2221 | "table": "execution_interactions", | ||
| 2222 | "entityType": "pks" | ||
| 2223 | }, | ||
| 2224 | { | ||
| 2225 | "columns": [ | ||
| 2226 | "id" | ||
| 2227 | ], | ||
| 2228 | "nameExplicit": false, | ||
| 2229 | "name": "executions_pkey", | ||
| 2230 | "schema": "public", | ||
| 2231 | "table": "executions", | ||
| 2232 | "entityType": "pks" | ||
| 2233 | }, | ||
| 2234 | { | ||
| 2235 | "columns": [ | ||
| 2236 | "id" | ||
| 2237 | ], | ||
| 2238 | "nameExplicit": false, | ||
| 2239 | "name": "local_installations_pkey", | ||
| 2240 | "schema": "public", | ||
| 2241 | "table": "local_installations", | ||
| 2242 | "entityType": "pks" | ||
| 2243 | }, | ||
| 2244 | { | ||
| 2245 | "columns": [ | ||
| 2246 | "id" | ||
| 2247 | ], | ||
| 2248 | "nameExplicit": false, | ||
| 2249 | "name": "organization_memberships_pkey", | ||
| 2250 | "schema": "public", | ||
| 2251 | "table": "organization_memberships", | ||
| 2252 | "entityType": "pks" | ||
| 2253 | }, | ||
| 2254 | { | ||
| 2255 | "columns": [ | ||
| 2256 | "id" | ||
| 2257 | ], | ||
| 2258 | "nameExplicit": false, | ||
| 2259 | "name": "organizations_pkey", | ||
| 2260 | "schema": "public", | ||
| 2261 | "table": "organizations", | ||
| 2262 | "entityType": "pks" | ||
| 2263 | }, | ||
| 2264 | { | ||
| 2265 | "columns": [ | ||
| 2266 | "id" | ||
| 2267 | ], | ||
| 2268 | "nameExplicit": false, | ||
| 2269 | "name": "policies_pkey", | ||
| 2270 | "schema": "public", | ||
| 2271 | "table": "policies", | ||
| 2272 | "entityType": "pks" | ||
| 2273 | }, | ||
| 2274 | { | ||
| 2275 | "columns": [ | ||
| 2276 | "id" | ||
| 2277 | ], | ||
| 2278 | "nameExplicit": false, | ||
| 2279 | "name": "secret_materials_pkey", | ||
| 2280 | "schema": "public", | ||
| 2281 | "table": "secret_materials", | ||
| 2282 | "entityType": "pks" | ||
| 2283 | }, | ||
| 2284 | { | ||
| 2285 | "columns": [ | ||
| 2286 | "id" | ||
| 2287 | ], | ||
| 2288 | "nameExplicit": false, | ||
| 2289 | "name": "source_auth_sessions_pkey", | ||
| 2290 | "schema": "public", | ||
| 2291 | "table": "source_auth_sessions", | ||
| 2292 | "entityType": "pks" | ||
| 2293 | }, | ||
| 2294 | { | ||
| 2295 | "columns": [ | ||
| 2296 | "id" | ||
| 2297 | ], | ||
| 2298 | "nameExplicit": false, | ||
| 2299 | "name": "workspaces_pkey", | ||
| 2300 | "schema": "public", | ||
| 2301 | "table": "workspaces", | ||
| 2302 | "entityType": "pks" | ||
| 2303 | }, | ||
| 2304 | { | ||
| 2305 | "value": "\"provider\" in ('local', 'workos', 'service')", | ||
| 2306 | "name": "accounts_provider_check", | ||
| 2307 | "entityType": "checks", | ||
| 2308 | "schema": "public", | ||
| 2309 | "table": "accounts" | ||
| 2310 | }, | ||
| 2311 | { | ||
| 2312 | "value": "\"status\" in ('pending', 'resolved', 'cancelled')", | ||
| 2313 | "name": "execution_interactions_status_check", | ||
| 2314 | "entityType": "checks", | ||
| 2315 | "schema": "public", | ||
| 2316 | "table": "execution_interactions" | ||
| 2317 | }, | ||
| 2318 | { | ||
| 2319 | "value": "\"status\" in ('pending', 'running', 'waiting_for_interaction', 'completed', 'failed', 'cancelled')", | ||
| 2320 | "name": "executions_status_check", | ||
| 2321 | "entityType": "checks", | ||
| 2322 | "schema": "public", | ||
| 2323 | "table": "executions" | ||
| 2324 | }, | ||
| 2325 | { | ||
| 2326 | "value": "\"role\" in ('viewer', 'editor', 'admin', 'owner')", | ||
| 2327 | "name": "organization_memberships_role_check", | ||
| 2328 | "entityType": "checks", | ||
| 2329 | "schema": "public", | ||
| 2330 | "table": "organization_memberships" | ||
| 2331 | }, | ||
| 2332 | { | ||
| 2333 | "value": "\"status\" in ('invited', 'active', 'suspended', 'removed')", | ||
| 2334 | "name": "organization_memberships_status_check", | ||
| 2335 | "entityType": "checks", | ||
| 2336 | "schema": "public", | ||
| 2337 | "table": "organization_memberships" | ||
| 2338 | }, | ||
| 2339 | { | ||
| 2340 | "value": "\"status\" in ('active', 'suspended', 'archived')", | ||
| 2341 | "name": "organizations_status_check", | ||
| 2342 | "entityType": "checks", | ||
| 2343 | "schema": "public", | ||
| 2344 | "table": "organizations" | ||
| 2345 | }, | ||
| 2346 | { | ||
| 2347 | "value": "\"resource_type\" in ('all_tools', 'source', 'namespace', 'tool_path')", | ||
| 2348 | "name": "policies_resource_type_check", | ||
| 2349 | "entityType": "checks", | ||
| 2350 | "schema": "public", | ||
| 2351 | "table": "policies" | ||
| 2352 | }, | ||
| 2353 | { | ||
| 2354 | "value": "\"match_type\" in ('glob', 'exact')", | ||
| 2355 | "name": "policies_match_type_check", | ||
| 2356 | "entityType": "checks", | ||
| 2357 | "schema": "public", | ||
| 2358 | "table": "policies" | ||
| 2359 | }, | ||
| 2360 | { | ||
| 2361 | "value": "\"effect\" in ('allow', 'deny')", | ||
| 2362 | "name": "policies_effect_check", | ||
| 2363 | "entityType": "checks", | ||
| 2364 | "schema": "public", | ||
| 2365 | "table": "policies" | ||
| 2366 | }, | ||
| 2367 | { | ||
| 2368 | "value": "\"approval_mode\" in ('auto', 'required')", | ||
| 2369 | "name": "policies_approval_mode_check", | ||
| 2370 | "entityType": "checks", | ||
| 2371 | "schema": "public", | ||
| 2372 | "table": "policies" | ||
| 2373 | }, | ||
| 2374 | { | ||
| 2375 | "value": "\"purpose\" in ('auth_material', 'oauth_access_token', 'oauth_refresh_token', 'oauth_client_info')", | ||
| 2376 | "name": "secret_materials_purpose_check", | ||
| 2377 | "entityType": "checks", | ||
| 2378 | "schema": "public", | ||
| 2379 | "table": "secret_materials" | ||
| 2380 | }, | ||
| 2381 | { | ||
| 2382 | "value": "\"strategy\" in ('oauth2_authorization_code')", | ||
| 2383 | "name": "source_auth_sessions_strategy_check", | ||
| 2384 | "entityType": "checks", | ||
| 2385 | "schema": "public", | ||
| 2386 | "table": "source_auth_sessions" | ||
| 2387 | }, | ||
| 2388 | { | ||
| 2389 | "value": "\"status\" in ('pending', 'completed', 'failed', 'cancelled')", | ||
| 2390 | "name": "source_auth_sessions_status_check", | ||
| 2391 | "entityType": "checks", | ||
| 2392 | "schema": "public", | ||
| 2393 | "table": "source_auth_sessions" | ||
| 2394 | }, | ||
| 2395 | { | ||
| 2396 | "value": "\"kind\" in ('mcp', 'openapi', 'graphql', 'internal')", | ||
| 2397 | "name": "sources_kind_check", | ||
| 2398 | "entityType": "checks", | ||
| 2399 | "schema": "public", | ||
| 2400 | "table": "sources" | ||
| 2401 | }, | ||
| 2402 | { | ||
| 2403 | "value": "\"status\" in ('draft', 'probing', 'auth_required', 'connected', 'error')", | ||
| 2404 | "name": "sources_status_check", | ||
| 2405 | "entityType": "checks", | ||
| 2406 | "schema": "public", | ||
| 2407 | "table": "sources" | ||
| 2408 | }, | ||
| 2409 | { | ||
| 2410 | "value": "\"transport\" is null or \"transport\" in ('auto', 'streamable-http', 'sse')", | ||
| 2411 | "name": "sources_transport_check", | ||
| 2412 | "entityType": "checks", | ||
| 2413 | "schema": "public", | ||
| 2414 | "table": "sources" | ||
| 2415 | }, | ||
| 2416 | { | ||
| 2417 | "value": "\"auth_kind\" in ('none', 'bearer', 'oauth2')", | ||
| 2418 | "name": "sources_auth_kind_check", | ||
| 2419 | "entityType": "checks", | ||
| 2420 | "schema": "public", | ||
| 2421 | "table": "sources" | ||
| 2422 | } | ||
| 2423 | ], | ||
| 2424 | "renames": [] | ||
| 2425 | } | ||
| @@ -1,70 +0,0 @@ | |||
| 1 | CREATE TABLE "tool_artifact_parameters" ( | ||
| 2 | "workspace_id" text, | ||
| 3 | "path" text, | ||
| 4 | "position" bigint, | ||
| 5 | "name" text NOT NULL, | ||
| 6 | "location" text NOT NULL, | ||
| 7 | "required" boolean NOT NULL, | ||
| 8 | CONSTRAINT "tool_artifact_parameters_pkey" PRIMARY KEY("workspace_id","path","position"), | ||
| 9 | CONSTRAINT "tool_artifact_parameters_location_check" CHECK ("location" in ('path', 'query', 'header', 'cookie')) | ||
| 10 | ); | ||
| 11 | --> statement-breakpoint | ||
| 12 | CREATE TABLE "tool_artifact_ref_hint_keys" ( | ||
| 13 | "workspace_id" text, | ||
| 14 | "path" text, | ||
| 15 | "position" bigint, | ||
| 16 | "ref_hint_key" text NOT NULL, | ||
| 17 | CONSTRAINT "tool_artifact_ref_hint_keys_pkey" PRIMARY KEY("workspace_id","path","position") | ||
| 18 | ); | ||
| 19 | --> statement-breakpoint | ||
| 20 | CREATE TABLE "tool_artifact_request_body_content_types" ( | ||
| 21 | "workspace_id" text, | ||
| 22 | "path" text, | ||
| 23 | "position" bigint, | ||
| 24 | "content_type" text NOT NULL, | ||
| 25 | CONSTRAINT "tool_artifact_request_body_content_types_pkey" PRIMARY KEY("workspace_id","path","position") | ||
| 26 | ); | ||
| 27 | --> statement-breakpoint | ||
| 28 | CREATE TABLE "tool_artifacts" ( | ||
| 29 | "workspace_id" text, | ||
| 30 | "path" text, | ||
| 31 | "tool_id" text NOT NULL, | ||
| 32 | "source_id" text NOT NULL, | ||
| 33 | "title" text, | ||
| 34 | "description" text, | ||
| 35 | "search_namespace" text NOT NULL, | ||
| 36 | "search_text" text NOT NULL, | ||
| 37 | "input_schema_json" text, | ||
| 38 | "output_schema_json" text, | ||
| 39 | "provider_kind" text NOT NULL, | ||
| 40 | "mcp_tool_name" text, | ||
| 41 | "openapi_method" text, | ||
| 42 | "openapi_path_template" text, | ||
| 43 | "openapi_operation_hash" text, | ||
| 44 | "openapi_request_body_required" boolean, | ||
| 45 | "created_at" bigint NOT NULL, | ||
| 46 | "updated_at" bigint NOT NULL, | ||
| 47 | CONSTRAINT "tool_artifacts_pkey" PRIMARY KEY("workspace_id","path"), | ||
| 48 | CONSTRAINT "tool_artifacts_provider_kind_check" CHECK ("provider_kind" in ('mcp', 'openapi')), | ||
| 49 | CONSTRAINT "tool_artifacts_mcp_shape_check" CHECK ("provider_kind" <> 'mcp' | ||
| 50 | or ( | ||
| 51 | "mcp_tool_name" is not null | ||
| 52 | and "openapi_method" is null | ||
| 53 | and "openapi_path_template" is null | ||
| 54 | and "openapi_operation_hash" is null | ||
| 55 | and "openapi_request_body_required" is null | ||
| 56 | )), | ||
| 57 | CONSTRAINT "tool_artifacts_openapi_shape_check" CHECK ("provider_kind" <> 'openapi' | ||
| 58 | or ( | ||
| 59 | "mcp_tool_name" is null | ||
| 60 | and "openapi_method" in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace') | ||
| 61 | and "openapi_path_template" is not null | ||
| 62 | and "openapi_operation_hash" is not null | ||
| 63 | )) | ||
| 64 | ); | ||
| 65 | --> statement-breakpoint | ||
| 66 | CREATE INDEX "tool_artifact_parameters_lookup_idx" ON "tool_artifact_parameters" ("workspace_id","path","position");--> statement-breakpoint | ||
| 67 | CREATE INDEX "tool_artifact_ref_hint_keys_lookup_idx" ON "tool_artifact_ref_hint_keys" ("workspace_id","path","position");--> statement-breakpoint | ||
| 68 | CREATE INDEX "tool_artifact_request_body_content_types_lookup_idx" ON "tool_artifact_request_body_content_types" ("workspace_id","path","position");--> statement-breakpoint | ||
| 69 | CREATE INDEX "tool_artifacts_workspace_source_idx" ON "tool_artifacts" ("workspace_id","source_id","updated_at","path");--> statement-breakpoint | ||
| 70 | CREATE INDEX "tool_artifacts_workspace_namespace_idx" ON "tool_artifacts" ("workspace_id","search_namespace","path"); | ||
| @@ -1,3122 +0,0 @@ | |||
| 1 | { | ||
| 2 | "version": "8", | ||
| 3 | "dialect": "postgres", | ||
| 4 | "id": "3e9aa0ec-194c-4364-9e57-4539db8fb2ad", | ||
| 5 | "prevIds": [ | ||
| 6 | "1141497b-c500-44ff-87db-6a440062becb" | ||
| 7 | ], | ||
| 8 | "ddl": [ | ||
| 9 | { | ||
| 10 | "isRlsEnabled": false, | ||
| 11 | "name": "accounts", | ||
| 12 | "entityType": "tables", | ||
| 13 | "schema": "public" | ||
| 14 | }, | ||
| 15 | { | ||
| 16 | "isRlsEnabled": false, | ||
| 17 | "name": "execution_interactions", | ||
| 18 | "entityType": "tables", | ||
| 19 | "schema": "public" | ||
| 20 | }, | ||
| 21 | { | ||
| 22 | "isRlsEnabled": false, | ||
| 23 | "name": "executions", | ||
| 24 | "entityType": "tables", | ||
| 25 | "schema": "public" | ||
| 26 | }, | ||
| 27 | { | ||
| 28 | "isRlsEnabled": false, | ||
| 29 | "name": "local_installations", | ||
| 30 | "entityType": "tables", | ||
| 31 | "schema": "public" | ||
| 32 | }, | ||
| 33 | { | ||
| 34 | "isRlsEnabled": false, | ||
| 35 | "name": "organization_memberships", | ||
| 36 | "entityType": "tables", | ||
| 37 | "schema": "public" | ||
| 38 | }, | ||
| 39 | { | ||
| 40 | "isRlsEnabled": false, | ||
| 41 | "name": "organizations", | ||
| 42 | "entityType": "tables", | ||
| 43 | "schema": "public" | ||
| 44 | }, | ||
| 45 | { | ||
| 46 | "isRlsEnabled": false, | ||
| 47 | "name": "policies", | ||
| 48 | "entityType": "tables", | ||
| 49 | "schema": "public" | ||
| 50 | }, | ||
| 51 | { | ||
| 52 | "isRlsEnabled": false, | ||
| 53 | "name": "secret_materials", | ||
| 54 | "entityType": "tables", | ||
| 55 | "schema": "public" | ||
| 56 | }, | ||
| 57 | { | ||
| 58 | "isRlsEnabled": false, | ||
| 59 | "name": "source_auth_sessions", | ||
| 60 | "entityType": "tables", | ||
| 61 | "schema": "public" | ||
| 62 | }, | ||
| 63 | { | ||
| 64 | "isRlsEnabled": false, | ||
| 65 | "name": "source_credential_bindings", | ||
| 66 | "entityType": "tables", | ||
| 67 | "schema": "public" | ||
| 68 | }, | ||
| 69 | { | ||
| 70 | "isRlsEnabled": false, | ||
| 71 | "name": "sources", | ||
| 72 | "entityType": "tables", | ||
| 73 | "schema": "public" | ||
| 74 | }, | ||
| 75 | { | ||
| 76 | "isRlsEnabled": false, | ||
| 77 | "name": "tool_artifact_parameters", | ||
| 78 | "entityType": "tables", | ||
| 79 | "schema": "public" | ||
| 80 | }, | ||
| 81 | { | ||
| 82 | "isRlsEnabled": false, | ||
| 83 | "name": "tool_artifact_ref_hint_keys", | ||
| 84 | "entityType": "tables", | ||
| 85 | "schema": "public" | ||
| 86 | }, | ||
| 87 | { | ||
| 88 | "isRlsEnabled": false, | ||
| 89 | "name": "tool_artifact_request_body_content_types", | ||
| 90 | "entityType": "tables", | ||
| 91 | "schema": "public" | ||
| 92 | }, | ||
| 93 | { | ||
| 94 | "isRlsEnabled": false, | ||
| 95 | "name": "tool_artifacts", | ||
| 96 | "entityType": "tables", | ||
| 97 | "schema": "public" | ||
| 98 | }, | ||
| 99 | { | ||
| 100 | "isRlsEnabled": false, | ||
| 101 | "name": "workspaces", | ||
| 102 | "entityType": "tables", | ||
| 103 | "schema": "public" | ||
| 104 | }, | ||
| 105 | { | ||
| 106 | "type": "text", | ||
| 107 | "typeSchema": null, | ||
| 108 | "notNull": true, | ||
| 109 | "dimensions": 0, | ||
| 110 | "default": null, | ||
| 111 | "generated": null, | ||
| 112 | "identity": null, | ||
| 113 | "name": "id", | ||
| 114 | "entityType": "columns", | ||
| 115 | "schema": "public", | ||
| 116 | "table": "accounts" | ||
| 117 | }, | ||
| 118 | { | ||
| 119 | "type": "text", | ||
| 120 | "typeSchema": null, | ||
| 121 | "notNull": true, | ||
| 122 | "dimensions": 0, | ||
| 123 | "default": null, | ||
| 124 | "generated": null, | ||
| 125 | "identity": null, | ||
| 126 | "name": "provider", | ||
| 127 | "entityType": "columns", | ||
| 128 | "schema": "public", | ||
| 129 | "table": "accounts" | ||
| 130 | }, | ||
| 131 | { | ||
| 132 | "type": "text", | ||
| 133 | "typeSchema": null, | ||
| 134 | "notNull": true, | ||
| 135 | "dimensions": 0, | ||
| 136 | "default": null, | ||
| 137 | "generated": null, | ||
| 138 | "identity": null, | ||
| 139 | "name": "subject", | ||
| 140 | "entityType": "columns", | ||
| 141 | "schema": "public", | ||
| 142 | "table": "accounts" | ||
| 143 | }, | ||
| 144 | { | ||
| 145 | "type": "text", | ||
| 146 | "typeSchema": null, | ||
| 147 | "notNull": false, | ||
| 148 | "dimensions": 0, | ||
| 149 | "default": null, | ||
| 150 | "generated": null, | ||
| 151 | "identity": null, | ||
| 152 | "name": "email", | ||
| 153 | "entityType": "columns", | ||
| 154 | "schema": "public", | ||
| 155 | "table": "accounts" | ||
| 156 | }, | ||
| 157 | { | ||
| 158 | "type": "text", | ||
| 159 | "typeSchema": null, | ||
| 160 | "notNull": false, | ||
| 161 | "dimensions": 0, | ||
| 162 | "default": null, | ||
| 163 | "generated": null, | ||
| 164 | "identity": null, | ||
| 165 | "name": "display_name", | ||
| 166 | "entityType": "columns", | ||
| 167 | "schema": "public", | ||
| 168 | "table": "accounts" | ||
| 169 | }, | ||
| 170 | { | ||
| 171 | "type": "bigint", | ||
| 172 | "typeSchema": null, | ||
| 173 | "notNull": true, | ||
| 174 | "dimensions": 0, | ||
| 175 | "default": null, | ||
| 176 | "generated": null, | ||
| 177 | "identity": null, | ||
| 178 | "name": "created_at", | ||
| 179 | "entityType": "columns", | ||
| 180 | "schema": "public", | ||
| 181 | "table": "accounts" | ||
| 182 | }, | ||
| 183 | { | ||
| 184 | "type": "bigint", | ||
| 185 | "typeSchema": null, | ||
| 186 | "notNull": true, | ||
| 187 | "dimensions": 0, | ||
| 188 | "default": null, | ||
| 189 | "generated": null, | ||
| 190 | "identity": null, | ||
| 191 | "name": "updated_at", | ||
| 192 | "entityType": "columns", | ||
| 193 | "schema": "public", | ||
| 194 | "table": "accounts" | ||
| 195 | }, | ||
| 196 | { | ||
| 197 | "type": "text", | ||
| 198 | "typeSchema": null, | ||
| 199 | "notNull": true, | ||
| 200 | "dimensions": 0, | ||
| 201 | "default": null, | ||
| 202 | "generated": null, | ||
| 203 | "identity": null, | ||
| 204 | "name": "id", | ||
| 205 | "entityType": "columns", | ||
| 206 | "schema": "public", | ||
| 207 | "table": "execution_interactions" | ||
| 208 | }, | ||
| 209 | { | ||
| 210 | "type": "text", | ||
| 211 | "typeSchema": null, | ||
| 212 | "notNull": true, | ||
| 213 | "dimensions": 0, | ||
| 214 | "default": null, | ||
| 215 | "generated": null, | ||
| 216 | "identity": null, | ||
| 217 | "name": "execution_id", | ||
| 218 | "entityType": "columns", | ||
| 219 | "schema": "public", | ||
| 220 | "table": "execution_interactions" | ||
| 221 | }, | ||
| 222 | { | ||
| 223 | "type": "text", | ||
| 224 | "typeSchema": null, | ||
| 225 | "notNull": true, | ||
| 226 | "dimensions": 0, | ||
| 227 | "default": null, | ||
| 228 | "generated": null, | ||
| 229 | "identity": null, | ||
| 230 | "name": "status", | ||
| 231 | "entityType": "columns", | ||
| 232 | "schema": "public", | ||
| 233 | "table": "execution_interactions" | ||
| 234 | }, | ||
| 235 | { | ||
| 236 | "type": "text", | ||
| 237 | "typeSchema": null, | ||
| 238 | "notNull": true, | ||
| 239 | "dimensions": 0, | ||
| 240 | "default": null, | ||
| 241 | "generated": null, | ||
| 242 | "identity": null, | ||
| 243 | "name": "kind", | ||
| 244 | "entityType": "columns", | ||
| 245 | "schema": "public", | ||
| 246 | "table": "execution_interactions" | ||
| 247 | }, | ||
| 248 | { | ||
| 249 | "type": "text", | ||
| 250 | "typeSchema": null, | ||
| 251 | "notNull": true, | ||
| 252 | "dimensions": 0, | ||
| 253 | "default": null, | ||
| 254 | "generated": null, | ||
| 255 | "identity": null, | ||
| 256 | "name": "payload_json", | ||
| 257 | "entityType": "columns", | ||
| 258 | "schema": "public", | ||
| 259 | "table": "execution_interactions" | ||
| 260 | }, | ||
| 261 | { | ||
| 262 | "type": "text", | ||
| 263 | "typeSchema": null, | ||
| 264 | "notNull": false, | ||
| 265 | "dimensions": 0, | ||
| 266 | "default": null, | ||
| 267 | "generated": null, | ||
| 268 | "identity": null, | ||
| 269 | "name": "response_json", | ||
| 270 | "entityType": "columns", | ||
| 271 | "schema": "public", | ||
| 272 | "table": "execution_interactions" | ||
| 273 | }, | ||
| 274 | { | ||
| 275 | "type": "bigint", | ||
| 276 | "typeSchema": null, | ||
| 277 | "notNull": true, | ||
| 278 | "dimensions": 0, | ||
| 279 | "default": null, | ||
| 280 | "generated": null, | ||
| 281 | "identity": null, | ||
| 282 | "name": "created_at", | ||
| 283 | "entityType": "columns", | ||
| 284 | "schema": "public", | ||
| 285 | "table": "execution_interactions" | ||
| 286 | }, | ||
| 287 | { | ||
| 288 | "type": "bigint", | ||
| 289 | "typeSchema": null, | ||
| 290 | "notNull": true, | ||
| 291 | "dimensions": 0, | ||
| 292 | "default": null, | ||
| 293 | "generated": null, | ||
| 294 | "identity": null, | ||
| 295 | "name": "updated_at", | ||
| 296 | "entityType": "columns", | ||
| 297 | "schema": "public", | ||
| 298 | "table": "execution_interactions" | ||
| 299 | }, | ||
| 300 | { | ||
| 301 | "type": "text", | ||
| 302 | "typeSchema": null, | ||
| 303 | "notNull": true, | ||
| 304 | "dimensions": 0, | ||
| 305 | "default": null, | ||
| 306 | "generated": null, | ||
| 307 | "identity": null, | ||
| 308 | "name": "id", | ||
| 309 | "entityType": "columns", | ||
| 310 | "schema": "public", | ||
| 311 | "table": "executions" | ||
| 312 | }, | ||
| 313 | { | ||
| 314 | "type": "text", | ||
| 315 | "typeSchema": null, | ||
| 316 | "notNull": true, | ||
| 317 | "dimensions": 0, | ||
| 318 | "default": null, | ||
| 319 | "generated": null, | ||
| 320 | "identity": null, | ||
| 321 | "name": "workspace_id", | ||
| 322 | "entityType": "columns", | ||
| 323 | "schema": "public", | ||
| 324 | "table": "executions" | ||
| 325 | }, | ||
| 326 | { | ||
| 327 | "type": "text", | ||
| 328 | "typeSchema": null, | ||
| 329 | "notNull": true, | ||
| 330 | "dimensions": 0, | ||
| 331 | "default": null, | ||
| 332 | "generated": null, | ||
| 333 | "identity": null, | ||
| 334 | "name": "created_by_account_id", | ||
| 335 | "entityType": "columns", | ||
| 336 | "schema": "public", | ||
| 337 | "table": "executions" | ||
| 338 | }, | ||
| 339 | { | ||
| 340 | "type": "text", | ||
| 341 | "typeSchema": null, | ||
| 342 | "notNull": true, | ||
| 343 | "dimensions": 0, | ||
| 344 | "default": null, | ||
| 345 | "generated": null, | ||
| 346 | "identity": null, | ||
| 347 | "name": "status", | ||
| 348 | "entityType": "columns", | ||
| 349 | "schema": "public", | ||
| 350 | "table": "executions" | ||
| 351 | }, | ||
| 352 | { | ||
| 353 | "type": "text", | ||
| 354 | "typeSchema": null, | ||
| 355 | "notNull": true, | ||
| 356 | "dimensions": 0, | ||
| 357 | "default": null, | ||
| 358 | "generated": null, | ||
| 359 | "identity": null, | ||
| 360 | "name": "code", | ||
| 361 | "entityType": "columns", | ||
| 362 | "schema": "public", | ||
| 363 | "table": "executions" | ||
| 364 | }, | ||
| 365 | { | ||
| 366 | "type": "text", | ||
| 367 | "typeSchema": null, | ||
| 368 | "notNull": false, | ||
| 369 | "dimensions": 0, | ||
| 370 | "default": null, | ||
| 371 | "generated": null, | ||
| 372 | "identity": null, | ||
| 373 | "name": "result_json", | ||
| 374 | "entityType": "columns", | ||
| 375 | "schema": "public", | ||
| 376 | "table": "executions" | ||
| 377 | }, | ||
| 378 | { | ||
| 379 | "type": "text", | ||
| 380 | "typeSchema": null, | ||
| 381 | "notNull": false, | ||
| 382 | "dimensions": 0, | ||
| 383 | "default": null, | ||
| 384 | "generated": null, | ||
| 385 | "identity": null, | ||
| 386 | "name": "error_text", | ||
| 387 | "entityType": "columns", | ||
| 388 | "schema": "public", | ||
| 389 | "table": "executions" | ||
| 390 | }, | ||
| 391 | { | ||
| 392 | "type": "text", | ||
| 393 | "typeSchema": null, | ||
| 394 | "notNull": false, | ||
| 395 | "dimensions": 0, | ||
| 396 | "default": null, | ||
| 397 | "generated": null, | ||
| 398 | "identity": null, | ||
| 399 | "name": "logs_json", | ||
| 400 | "entityType": "columns", | ||
| 401 | "schema": "public", | ||
| 402 | "table": "executions" | ||
| 403 | }, | ||
| 404 | { | ||
| 405 | "type": "bigint", | ||
| 406 | "typeSchema": null, | ||
| 407 | "notNull": false, | ||
| 408 | "dimensions": 0, | ||
| 409 | "default": null, | ||
| 410 | "generated": null, | ||
| 411 | "identity": null, | ||
| 412 | "name": "started_at", | ||
| 413 | "entityType": "columns", | ||
| 414 | "schema": "public", | ||
| 415 | "table": "executions" | ||
| 416 | }, | ||
| 417 | { | ||
| 418 | "type": "bigint", | ||
| 419 | "typeSchema": null, | ||
| 420 | "notNull": false, | ||
| 421 | "dimensions": 0, | ||
| 422 | "default": null, | ||
| 423 | "generated": null, | ||
| 424 | "identity": null, | ||
| 425 | "name": "completed_at", | ||
| 426 | "entityType": "columns", | ||
| 427 | "schema": "public", | ||
| 428 | "table": "executions" | ||
| 429 | }, | ||
| 430 | { | ||
| 431 | "type": "bigint", | ||
| 432 | "typeSchema": null, | ||
| 433 | "notNull": true, | ||
| 434 | "dimensions": 0, | ||
| 435 | "default": null, | ||
| 436 | "generated": null, | ||
| 437 | "identity": null, | ||
| 438 | "name": "created_at", | ||
| 439 | "entityType": "columns", | ||
| 440 | "schema": "public", | ||
| 441 | "table": "executions" | ||
| 442 | }, | ||
| 443 | { | ||
| 444 | "type": "bigint", | ||
| 445 | "typeSchema": null, | ||
| 446 | "notNull": true, | ||
| 447 | "dimensions": 0, | ||
| 448 | "default": null, | ||
| 449 | "generated": null, | ||
| 450 | "identity": null, | ||
| 451 | "name": "updated_at", | ||
| 452 | "entityType": "columns", | ||
| 453 | "schema": "public", | ||
| 454 | "table": "executions" | ||
| 455 | }, | ||
| 456 | { | ||
| 457 | "type": "text", | ||
| 458 | "typeSchema": null, | ||
| 459 | "notNull": true, | ||
| 460 | "dimensions": 0, | ||
| 461 | "default": null, | ||
| 462 | "generated": null, | ||
| 463 | "identity": null, | ||
| 464 | "name": "id", | ||
| 465 | "entityType": "columns", | ||
| 466 | "schema": "public", | ||
| 467 | "table": "local_installations" | ||
| 468 | }, | ||
| 469 | { | ||
| 470 | "type": "text", | ||
| 471 | "typeSchema": null, | ||
| 472 | "notNull": true, | ||
| 473 | "dimensions": 0, | ||
| 474 | "default": null, | ||
| 475 | "generated": null, | ||
| 476 | "identity": null, | ||
| 477 | "name": "account_id", | ||
| 478 | "entityType": "columns", | ||
| 479 | "schema": "public", | ||
| 480 | "table": "local_installations" | ||
| 481 | }, | ||
| 482 | { | ||
| 483 | "type": "text", | ||
| 484 | "typeSchema": null, | ||
| 485 | "notNull": true, | ||
| 486 | "dimensions": 0, | ||
| 487 | "default": null, | ||
| 488 | "generated": null, | ||
| 489 | "identity": null, | ||
| 490 | "name": "organization_id", | ||
| 491 | "entityType": "columns", | ||
| 492 | "schema": "public", | ||
| 493 | "table": "local_installations" | ||
| 494 | }, | ||
| 495 | { | ||
| 496 | "type": "text", | ||
| 497 | "typeSchema": null, | ||
| 498 | "notNull": true, | ||
| 499 | "dimensions": 0, | ||
| 500 | "default": null, | ||
| 501 | "generated": null, | ||
| 502 | "identity": null, | ||
| 503 | "name": "workspace_id", | ||
| 504 | "entityType": "columns", | ||
| 505 | "schema": "public", | ||
| 506 | "table": "local_installations" | ||
| 507 | }, | ||
| 508 | { | ||
| 509 | "type": "bigint", | ||
| 510 | "typeSchema": null, | ||
| 511 | "notNull": true, | ||
| 512 | "dimensions": 0, | ||
| 513 | "default": null, | ||
| 514 | "generated": null, | ||
| 515 | "identity": null, | ||
| 516 | "name": "created_at", | ||
| 517 | "entityType": "columns", | ||
| 518 | "schema": "public", | ||
| 519 | "table": "local_installations" | ||
| 520 | }, | ||
| 521 | { | ||
| 522 | "type": "bigint", | ||
| 523 | "typeSchema": null, | ||
| 524 | "notNull": true, | ||
| 525 | "dimensions": 0, | ||
| 526 | "default": null, | ||
| 527 | "generated": null, | ||
| 528 | "identity": null, | ||
| 529 | "name": "updated_at", | ||
| 530 | "entityType": "columns", | ||
| 531 | "schema": "public", | ||
| 532 | "table": "local_installations" | ||
| 533 | }, | ||
| 534 | { | ||
| 535 | "type": "text", | ||
| 536 | "typeSchema": null, | ||
| 537 | "notNull": true, | ||
| 538 | "dimensions": 0, | ||
| 539 | "default": null, | ||
| 540 | "generated": null, | ||
| 541 | "identity": null, | ||
| 542 | "name": "id", | ||
| 543 | "entityType": "columns", | ||
| 544 | "schema": "public", | ||
| 545 | "table": "organization_memberships" | ||
| 546 | }, | ||
| 547 | { | ||
| 548 | "type": "text", | ||
| 549 | "typeSchema": null, | ||
| 550 | "notNull": true, | ||
| 551 | "dimensions": 0, | ||
| 552 | "default": null, | ||
| 553 | "generated": null, | ||
| 554 | "identity": null, | ||
| 555 | "name": "organization_id", | ||
| 556 | "entityType": "columns", | ||
| 557 | "schema": "public", | ||
| 558 | "table": "organization_memberships" | ||
| 559 | }, | ||
| 560 | { | ||
| 561 | "type": "text", | ||
| 562 | "typeSchema": null, | ||
| 563 | "notNull": true, | ||
| 564 | "dimensions": 0, | ||
| 565 | "default": null, | ||
| 566 | "generated": null, | ||
| 567 | "identity": null, | ||
| 568 | "name": "account_id", | ||
| 569 | "entityType": "columns", | ||
| 570 | "schema": "public", | ||
| 571 | "table": "organization_memberships" | ||
| 572 | }, | ||
| 573 | { | ||
| 574 | "type": "text", | ||
| 575 | "typeSchema": null, | ||
| 576 | "notNull": true, | ||
| 577 | "dimensions": 0, | ||
| 578 | "default": null, | ||
| 579 | "generated": null, | ||
| 580 | "identity": null, | ||
| 581 | "name": "role", | ||
| 582 | "entityType": "columns", | ||
| 583 | "schema": "public", | ||
| 584 | "table": "organization_memberships" | ||
| 585 | }, | ||
| 586 | { | ||
| 587 | "type": "text", | ||
| 588 | "typeSchema": null, | ||
| 589 | "notNull": true, | ||
| 590 | "dimensions": 0, | ||
| 591 | "default": null, | ||
| 592 | "generated": null, | ||
| 593 | "identity": null, | ||
| 594 | "name": "status", | ||
| 595 | "entityType": "columns", | ||
| 596 | "schema": "public", | ||
| 597 | "table": "organization_memberships" | ||
| 598 | }, | ||
| 599 | { | ||
| 600 | "type": "boolean", | ||
| 601 | "typeSchema": null, | ||
| 602 | "notNull": true, | ||
| 603 | "dimensions": 0, | ||
| 604 | "default": null, | ||
| 605 | "generated": null, | ||
| 606 | "identity": null, | ||
| 607 | "name": "billable", | ||
| 608 | "entityType": "columns", | ||
| 609 | "schema": "public", | ||
| 610 | "table": "organization_memberships" | ||
| 611 | }, | ||
| 612 | { | ||
| 613 | "type": "text", | ||
| 614 | "typeSchema": null, | ||
| 615 | "notNull": false, | ||
| 616 | "dimensions": 0, | ||
| 617 | "default": null, | ||
| 618 | "generated": null, | ||
| 619 | "identity": null, | ||
| 620 | "name": "invited_by_account_id", | ||
| 621 | "entityType": "columns", | ||
| 622 | "schema": "public", | ||
| 623 | "table": "organization_memberships" | ||
| 624 | }, | ||
| 625 | { | ||
| 626 | "type": "bigint", | ||
| 627 | "typeSchema": null, | ||
| 628 | "notNull": false, | ||
| 629 | "dimensions": 0, | ||
| 630 | "default": null, | ||
| 631 | "generated": null, | ||
| 632 | "identity": null, | ||
| 633 | "name": "joined_at", | ||
| 634 | "entityType": "columns", | ||
| 635 | "schema": "public", | ||
| 636 | "table": "organization_memberships" | ||
| 637 | }, | ||
| 638 | { | ||
| 639 | "type": "bigint", | ||
| 640 | "typeSchema": null, | ||
| 641 | "notNull": true, | ||
| 642 | "dimensions": 0, | ||
| 643 | "default": null, | ||
| 644 | "generated": null, | ||
| 645 | "identity": null, | ||
| 646 | "name": "created_at", | ||
| 647 | "entityType": "columns", | ||
| 648 | "schema": "public", | ||
| 649 | "table": "organization_memberships" | ||
| 650 | }, | ||
| 651 | { | ||
| 652 | "type": "bigint", | ||
| 653 | "typeSchema": null, | ||
| 654 | "notNull": true, | ||
| 655 | "dimensions": 0, | ||
| 656 | "default": null, | ||
| 657 | "generated": null, | ||
| 658 | "identity": null, | ||
| 659 | "name": "updated_at", | ||
| 660 | "entityType": "columns", | ||
| 661 | "schema": "public", | ||
| 662 | "table": "organization_memberships" | ||
| 663 | }, | ||
| 664 | { | ||
| 665 | "type": "text", | ||
| 666 | "typeSchema": null, | ||
| 667 | "notNull": true, | ||
| 668 | "dimensions": 0, | ||
| 669 | "default": null, | ||
| 670 | "generated": null, | ||
| 671 | "identity": null, | ||
| 672 | "name": "id", | ||
| 673 | "entityType": "columns", | ||
| 674 | "schema": "public", | ||
| 675 | "table": "organizations" | ||
| 676 | }, | ||
| 677 | { | ||
| 678 | "type": "text", | ||
| 679 | "typeSchema": null, | ||
| 680 | "notNull": true, | ||
| 681 | "dimensions": 0, | ||
| 682 | "default": null, | ||
| 683 | "generated": null, | ||
| 684 | "identity": null, | ||
| 685 | "name": "slug", | ||
| 686 | "entityType": "columns", | ||
| 687 | "schema": "public", | ||
| 688 | "table": "organizations" | ||
| 689 | }, | ||
| 690 | { | ||
| 691 | "type": "text", | ||
| 692 | "typeSchema": null, | ||
| 693 | "notNull": true, | ||
| 694 | "dimensions": 0, | ||
| 695 | "default": null, | ||
| 696 | "generated": null, | ||
| 697 | "identity": null, | ||
| 698 | "name": "name", | ||
| 699 | "entityType": "columns", | ||
| 700 | "schema": "public", | ||
| 701 | "table": "organizations" | ||
| 702 | }, | ||
| 703 | { | ||
| 704 | "type": "text", | ||
| 705 | "typeSchema": null, | ||
| 706 | "notNull": true, | ||
| 707 | "dimensions": 0, | ||
| 708 | "default": null, | ||
| 709 | "generated": null, | ||
| 710 | "identity": null, | ||
| 711 | "name": "status", | ||
| 712 | "entityType": "columns", | ||
| 713 | "schema": "public", | ||
| 714 | "table": "organizations" | ||
| 715 | }, | ||
| 716 | { | ||
| 717 | "type": "text", | ||
| 718 | "typeSchema": null, | ||
| 719 | "notNull": false, | ||
| 720 | "dimensions": 0, | ||
| 721 | "default": null, | ||
| 722 | "generated": null, | ||
| 723 | "identity": null, | ||
| 724 | "name": "created_by_account_id", | ||
| 725 | "entityType": "columns", | ||
| 726 | "schema": "public", | ||
| 727 | "table": "organizations" | ||
| 728 | }, | ||
| 729 | { | ||
| 730 | "type": "bigint", | ||
| 731 | "typeSchema": null, | ||
| 732 | "notNull": true, | ||
| 733 | "dimensions": 0, | ||
| 734 | "default": null, | ||
| 735 | "generated": null, | ||
| 736 | "identity": null, | ||
| 737 | "name": "created_at", | ||
| 738 | "entityType": "columns", | ||
| 739 | "schema": "public", | ||
| 740 | "table": "organizations" | ||
| 741 | }, | ||
| 742 | { | ||
| 743 | "type": "bigint", | ||
| 744 | "typeSchema": null, | ||
| 745 | "notNull": true, | ||
| 746 | "dimensions": 0, | ||
| 747 | "default": null, | ||
| 748 | "generated": null, | ||
| 749 | "identity": null, | ||
| 750 | "name": "updated_at", | ||
| 751 | "entityType": "columns", | ||
| 752 | "schema": "public", | ||
| 753 | "table": "organizations" | ||
| 754 | }, | ||
| 755 | { | ||
| 756 | "type": "text", | ||
| 757 | "typeSchema": null, | ||
| 758 | "notNull": true, | ||
| 759 | "dimensions": 0, | ||
| 760 | "default": null, | ||
| 761 | "generated": null, | ||
| 762 | "identity": null, | ||
| 763 | "name": "id", | ||
| 764 | "entityType": "columns", | ||
| 765 | "schema": "public", | ||
| 766 | "table": "policies" | ||
| 767 | }, | ||
| 768 | { | ||
| 769 | "type": "text", | ||
| 770 | "typeSchema": null, | ||
| 771 | "notNull": true, | ||
| 772 | "dimensions": 0, | ||
| 773 | "default": null, | ||
| 774 | "generated": null, | ||
| 775 | "identity": null, | ||
| 776 | "name": "workspace_id", | ||
| 777 | "entityType": "columns", | ||
| 778 | "schema": "public", | ||
| 779 | "table": "policies" | ||
| 780 | }, | ||
| 781 | { | ||
| 782 | "type": "text", | ||
| 783 | "typeSchema": null, | ||
| 784 | "notNull": false, | ||
| 785 | "dimensions": 0, | ||
| 786 | "default": null, | ||
| 787 | "generated": null, | ||
| 788 | "identity": null, | ||
| 789 | "name": "target_account_id", | ||
| 790 | "entityType": "columns", | ||
| 791 | "schema": "public", | ||
| 792 | "table": "policies" | ||
| 793 | }, | ||
| 794 | { | ||
| 795 | "type": "text", | ||
| 796 | "typeSchema": null, | ||
| 797 | "notNull": false, | ||
| 798 | "dimensions": 0, | ||
| 799 | "default": null, | ||
| 800 | "generated": null, | ||
| 801 | "identity": null, | ||
| 802 | "name": "client_id", | ||
| 803 | "entityType": "columns", | ||
| 804 | "schema": "public", | ||
| 805 | "table": "policies" | ||
| 806 | }, | ||
| 807 | { | ||
| 808 | "type": "text", | ||
| 809 | "typeSchema": null, | ||
| 810 | "notNull": true, | ||
| 811 | "dimensions": 0, | ||
| 812 | "default": null, | ||
| 813 | "generated": null, | ||
| 814 | "identity": null, | ||
| 815 | "name": "resource_type", | ||
| 816 | "entityType": "columns", | ||
| 817 | "schema": "public", | ||
| 818 | "table": "policies" | ||
| 819 | }, | ||
| 820 | { | ||
| 821 | "type": "text", | ||
| 822 | "typeSchema": null, | ||
| 823 | "notNull": true, | ||
| 824 | "dimensions": 0, | ||
| 825 | "default": null, | ||
| 826 | "generated": null, | ||
| 827 | "identity": null, | ||
| 828 | "name": "resource_pattern", | ||
| 829 | "entityType": "columns", | ||
| 830 | "schema": "public", | ||
| 831 | "table": "policies" | ||
| 832 | }, | ||
| 833 | { | ||
| 834 | "type": "text", | ||
| 835 | "typeSchema": null, | ||
| 836 | "notNull": true, | ||
| 837 | "dimensions": 0, | ||
| 838 | "default": null, | ||
| 839 | "generated": null, | ||
| 840 | "identity": null, | ||
| 841 | "name": "match_type", | ||
| 842 | "entityType": "columns", | ||
| 843 | "schema": "public", | ||
| 844 | "table": "policies" | ||
| 845 | }, | ||
| 846 | { | ||
| 847 | "type": "text", | ||
| 848 | "typeSchema": null, | ||
| 849 | "notNull": true, | ||
| 850 | "dimensions": 0, | ||
| 851 | "default": null, | ||
| 852 | "generated": null, | ||
| 853 | "identity": null, | ||
| 854 | "name": "effect", | ||
| 855 | "entityType": "columns", | ||
| 856 | "schema": "public", | ||
| 857 | "table": "policies" | ||
| 858 | }, | ||
| 859 | { | ||
| 860 | "type": "text", | ||
| 861 | "typeSchema": null, | ||
| 862 | "notNull": true, | ||
| 863 | "dimensions": 0, | ||
| 864 | "default": null, | ||
| 865 | "generated": null, | ||
| 866 | "identity": null, | ||
| 867 | "name": "approval_mode", | ||
| 868 | "entityType": "columns", | ||
| 869 | "schema": "public", | ||
| 870 | "table": "policies" | ||
| 871 | }, | ||
| 872 | { | ||
| 873 | "type": "text", | ||
| 874 | "typeSchema": null, | ||
| 875 | "notNull": false, | ||
| 876 | "dimensions": 0, | ||
| 877 | "default": null, | ||
| 878 | "generated": null, | ||
| 879 | "identity": null, | ||
| 880 | "name": "argument_conditions_json", | ||
| 881 | "entityType": "columns", | ||
| 882 | "schema": "public", | ||
| 883 | "table": "policies" | ||
| 884 | }, | ||
| 885 | { | ||
| 886 | "type": "bigint", | ||
| 887 | "typeSchema": null, | ||
| 888 | "notNull": true, | ||
| 889 | "dimensions": 0, | ||
| 890 | "default": null, | ||
| 891 | "generated": null, | ||
| 892 | "identity": null, | ||
| 893 | "name": "priority", | ||
| 894 | "entityType": "columns", | ||
| 895 | "schema": "public", | ||
| 896 | "table": "policies" | ||
| 897 | }, | ||
| 898 | { | ||
| 899 | "type": "boolean", | ||
| 900 | "typeSchema": null, | ||
| 901 | "notNull": true, | ||
| 902 | "dimensions": 0, | ||
| 903 | "default": null, | ||
| 904 | "generated": null, | ||
| 905 | "identity": null, | ||
| 906 | "name": "enabled", | ||
| 907 | "entityType": "columns", | ||
| 908 | "schema": "public", | ||
| 909 | "table": "policies" | ||
| 910 | }, | ||
| 911 | { | ||
| 912 | "type": "bigint", | ||
| 913 | "typeSchema": null, | ||
| 914 | "notNull": true, | ||
| 915 | "dimensions": 0, | ||
| 916 | "default": null, | ||
| 917 | "generated": null, | ||
| 918 | "identity": null, | ||
| 919 | "name": "created_at", | ||
| 920 | "entityType": "columns", | ||
| 921 | "schema": "public", | ||
| 922 | "table": "policies" | ||
| 923 | }, | ||
| 924 | { | ||
| 925 | "type": "bigint", | ||
| 926 | "typeSchema": null, | ||
| 927 | "notNull": true, | ||
| 928 | "dimensions": 0, | ||
| 929 | "default": null, | ||
| 930 | "generated": null, | ||
| 931 | "identity": null, | ||
| 932 | "name": "updated_at", | ||
| 933 | "entityType": "columns", | ||
| 934 | "schema": "public", | ||
| 935 | "table": "policies" | ||
| 936 | }, | ||
| 937 | { | ||
| 938 | "type": "text", | ||
| 939 | "typeSchema": null, | ||
| 940 | "notNull": true, | ||
| 941 | "dimensions": 0, | ||
| 942 | "default": null, | ||
| 943 | "generated": null, | ||
| 944 | "identity": null, | ||
| 945 | "name": "id", | ||
| 946 | "entityType": "columns", | ||
| 947 | "schema": "public", | ||
| 948 | "table": "secret_materials" | ||
| 949 | }, | ||
| 950 | { | ||
| 951 | "type": "text", | ||
| 952 | "typeSchema": null, | ||
| 953 | "notNull": true, | ||
| 954 | "dimensions": 0, | ||
| 955 | "default": null, | ||
| 956 | "generated": null, | ||
| 957 | "identity": null, | ||
| 958 | "name": "purpose", | ||
| 959 | "entityType": "columns", | ||
| 960 | "schema": "public", | ||
| 961 | "table": "secret_materials" | ||
| 962 | }, | ||
| 963 | { | ||
| 964 | "type": "text", | ||
| 965 | "typeSchema": null, | ||
| 966 | "notNull": true, | ||
| 967 | "dimensions": 0, | ||
| 968 | "default": null, | ||
| 969 | "generated": null, | ||
| 970 | "identity": null, | ||
| 971 | "name": "value", | ||
| 972 | "entityType": "columns", | ||
| 973 | "schema": "public", | ||
| 974 | "table": "secret_materials" | ||
| 975 | }, | ||
| 976 | { | ||
| 977 | "type": "bigint", | ||
| 978 | "typeSchema": null, | ||
| 979 | "notNull": true, | ||
| 980 | "dimensions": 0, | ||
| 981 | "default": null, | ||
| 982 | "generated": null, | ||
| 983 | "identity": null, | ||
| 984 | "name": "created_at", | ||
| 985 | "entityType": "columns", | ||
| 986 | "schema": "public", | ||
| 987 | "table": "secret_materials" | ||
| 988 | }, | ||
| 989 | { | ||
| 990 | "type": "bigint", | ||
| 991 | "typeSchema": null, | ||
| 992 | "notNull": true, | ||
| 993 | "dimensions": 0, | ||
| 994 | "default": null, | ||
| 995 | "generated": null, | ||
| 996 | "identity": null, | ||
| 997 | "name": "updated_at", | ||
| 998 | "entityType": "columns", | ||
| 999 | "schema": "public", | ||
| 1000 | "table": "secret_materials" | ||
| 1001 | }, | ||
| 1002 | { | ||
| 1003 | "type": "text", | ||
| 1004 | "typeSchema": null, | ||
| 1005 | "notNull": true, | ||
| 1006 | "dimensions": 0, | ||
| 1007 | "default": null, | ||
| 1008 | "generated": null, | ||
| 1009 | "identity": null, | ||
| 1010 | "name": "id", | ||
| 1011 | "entityType": "columns", | ||
| 1012 | "schema": "public", | ||
| 1013 | "table": "source_auth_sessions" | ||
| 1014 | }, | ||
| 1015 | { | ||
| 1016 | "type": "text", | ||
| 1017 | "typeSchema": null, | ||
| 1018 | "notNull": true, | ||
| 1019 | "dimensions": 0, | ||
| 1020 | "default": null, | ||
| 1021 | "generated": null, | ||
| 1022 | "identity": null, | ||
| 1023 | "name": "workspace_id", | ||
| 1024 | "entityType": "columns", | ||
| 1025 | "schema": "public", | ||
| 1026 | "table": "source_auth_sessions" | ||
| 1027 | }, | ||
| 1028 | { | ||
| 1029 | "type": "text", | ||
| 1030 | "typeSchema": null, | ||
| 1031 | "notNull": true, | ||
| 1032 | "dimensions": 0, | ||
| 1033 | "default": null, | ||
| 1034 | "generated": null, | ||
| 1035 | "identity": null, | ||
| 1036 | "name": "source_id", | ||
| 1037 | "entityType": "columns", | ||
| 1038 | "schema": "public", | ||
| 1039 | "table": "source_auth_sessions" | ||
| 1040 | }, | ||
| 1041 | { | ||
| 1042 | "type": "text", | ||
| 1043 | "typeSchema": null, | ||
| 1044 | "notNull": false, | ||
| 1045 | "dimensions": 0, | ||
| 1046 | "default": null, | ||
| 1047 | "generated": null, | ||
| 1048 | "identity": null, | ||
| 1049 | "name": "execution_id", | ||
| 1050 | "entityType": "columns", | ||
| 1051 | "schema": "public", | ||
| 1052 | "table": "source_auth_sessions" | ||
| 1053 | }, | ||
| 1054 | { | ||
| 1055 | "type": "text", | ||
| 1056 | "typeSchema": null, | ||
| 1057 | "notNull": false, | ||
| 1058 | "dimensions": 0, | ||
| 1059 | "default": null, | ||
| 1060 | "generated": null, | ||
| 1061 | "identity": null, | ||
| 1062 | "name": "interaction_id", | ||
| 1063 | "entityType": "columns", | ||
| 1064 | "schema": "public", | ||
| 1065 | "table": "source_auth_sessions" | ||
| 1066 | }, | ||
| 1067 | { | ||
| 1068 | "type": "text", | ||
| 1069 | "typeSchema": null, | ||
| 1070 | "notNull": true, | ||
| 1071 | "dimensions": 0, | ||
| 1072 | "default": null, | ||
| 1073 | "generated": null, | ||
| 1074 | "identity": null, | ||
| 1075 | "name": "strategy", | ||
| 1076 | "entityType": "columns", | ||
| 1077 | "schema": "public", | ||
| 1078 | "table": "source_auth_sessions" | ||
| 1079 | }, | ||
| 1080 | { | ||
| 1081 | "type": "text", | ||
| 1082 | "typeSchema": null, | ||
| 1083 | "notNull": true, | ||
| 1084 | "dimensions": 0, | ||
| 1085 | "default": null, | ||
| 1086 | "generated": null, | ||
| 1087 | "identity": null, | ||
| 1088 | "name": "status", | ||
| 1089 | "entityType": "columns", | ||
| 1090 | "schema": "public", | ||
| 1091 | "table": "source_auth_sessions" | ||
| 1092 | }, | ||
| 1093 | { | ||
| 1094 | "type": "text", | ||
| 1095 | "typeSchema": null, | ||
| 1096 | "notNull": true, | ||
| 1097 | "dimensions": 0, | ||
| 1098 | "default": null, | ||
| 1099 | "generated": null, | ||
| 1100 | "identity": null, | ||
| 1101 | "name": "endpoint", | ||
| 1102 | "entityType": "columns", | ||
| 1103 | "schema": "public", | ||
| 1104 | "table": "source_auth_sessions" | ||
| 1105 | }, | ||
| 1106 | { | ||
| 1107 | "type": "text", | ||
| 1108 | "typeSchema": null, | ||
| 1109 | "notNull": true, | ||
| 1110 | "dimensions": 0, | ||
| 1111 | "default": null, | ||
| 1112 | "generated": null, | ||
| 1113 | "identity": null, | ||
| 1114 | "name": "state", | ||
| 1115 | "entityType": "columns", | ||
| 1116 | "schema": "public", | ||
| 1117 | "table": "source_auth_sessions" | ||
| 1118 | }, | ||
| 1119 | { | ||
| 1120 | "type": "text", | ||
| 1121 | "typeSchema": null, | ||
| 1122 | "notNull": true, | ||
| 1123 | "dimensions": 0, | ||
| 1124 | "default": null, | ||
| 1125 | "generated": null, | ||
| 1126 | "identity": null, | ||
| 1127 | "name": "redirect_uri", | ||
| 1128 | "entityType": "columns", | ||
| 1129 | "schema": "public", | ||
| 1130 | "table": "source_auth_sessions" | ||
| 1131 | }, | ||
| 1132 | { | ||
| 1133 | "type": "text", | ||
| 1134 | "typeSchema": null, | ||
| 1135 | "notNull": false, | ||
| 1136 | "dimensions": 0, | ||
| 1137 | "default": null, | ||
| 1138 | "generated": null, | ||
| 1139 | "identity": null, | ||
| 1140 | "name": "scope", | ||
| 1141 | "entityType": "columns", | ||
| 1142 | "schema": "public", | ||
| 1143 | "table": "source_auth_sessions" | ||
| 1144 | }, | ||
| 1145 | { | ||
| 1146 | "type": "text", | ||
| 1147 | "typeSchema": null, | ||
| 1148 | "notNull": false, | ||
| 1149 | "dimensions": 0, | ||
| 1150 | "default": null, | ||
| 1151 | "generated": null, | ||
| 1152 | "identity": null, | ||
| 1153 | "name": "resource_metadata_url", | ||
| 1154 | "entityType": "columns", | ||
| 1155 | "schema": "public", | ||
| 1156 | "table": "source_auth_sessions" | ||
| 1157 | }, | ||
| 1158 | { | ||
| 1159 | "type": "text", | ||
| 1160 | "typeSchema": null, | ||
| 1161 | "notNull": false, | ||
| 1162 | "dimensions": 0, | ||
| 1163 | "default": null, | ||
| 1164 | "generated": null, | ||
| 1165 | "identity": null, | ||
| 1166 | "name": "authorization_server_url", | ||
| 1167 | "entityType": "columns", | ||
| 1168 | "schema": "public", | ||
| 1169 | "table": "source_auth_sessions" | ||
| 1170 | }, | ||
| 1171 | { | ||
| 1172 | "type": "text", | ||
| 1173 | "typeSchema": null, | ||
| 1174 | "notNull": false, | ||
| 1175 | "dimensions": 0, | ||
| 1176 | "default": null, | ||
| 1177 | "generated": null, | ||
| 1178 | "identity": null, | ||
| 1179 | "name": "resource_metadata_json", | ||
| 1180 | "entityType": "columns", | ||
| 1181 | "schema": "public", | ||
| 1182 | "table": "source_auth_sessions" | ||
| 1183 | }, | ||
| 1184 | { | ||
| 1185 | "type": "text", | ||
| 1186 | "typeSchema": null, | ||
| 1187 | "notNull": false, | ||
| 1188 | "dimensions": 0, | ||
| 1189 | "default": null, | ||
| 1190 | "generated": null, | ||
| 1191 | "identity": null, | ||
| 1192 | "name": "authorization_server_metadata_json", | ||
| 1193 | "entityType": "columns", | ||
| 1194 | "schema": "public", | ||
| 1195 | "table": "source_auth_sessions" | ||
| 1196 | }, | ||
| 1197 | { | ||
| 1198 | "type": "text", | ||
| 1199 | "typeSchema": null, | ||
| 1200 | "notNull": false, | ||
| 1201 | "dimensions": 0, | ||
| 1202 | "default": null, | ||
| 1203 | "generated": null, | ||
| 1204 | "identity": null, | ||
| 1205 | "name": "client_information_json", | ||
| 1206 | "entityType": "columns", | ||
| 1207 | "schema": "public", | ||
| 1208 | "table": "source_auth_sessions" | ||
| 1209 | }, | ||
| 1210 | { | ||
| 1211 | "type": "text", | ||
| 1212 | "typeSchema": null, | ||
| 1213 | "notNull": false, | ||
| 1214 | "dimensions": 0, | ||
| 1215 | "default": null, | ||
| 1216 | "generated": null, | ||
| 1217 | "identity": null, | ||
| 1218 | "name": "code_verifier", | ||
| 1219 | "entityType": "columns", | ||
| 1220 | "schema": "public", | ||
| 1221 | "table": "source_auth_sessions" | ||
| 1222 | }, | ||
| 1223 | { | ||
| 1224 | "type": "text", | ||
| 1225 | "typeSchema": null, | ||
| 1226 | "notNull": false, | ||
| 1227 | "dimensions": 0, | ||
| 1228 | "default": null, | ||
| 1229 | "generated": null, | ||
| 1230 | "identity": null, | ||
| 1231 | "name": "authorization_url", | ||
| 1232 | "entityType": "columns", | ||
| 1233 | "schema": "public", | ||
| 1234 | "table": "source_auth_sessions" | ||
| 1235 | }, | ||
| 1236 | { | ||
| 1237 | "type": "text", | ||
| 1238 | "typeSchema": null, | ||
| 1239 | "notNull": false, | ||
| 1240 | "dimensions": 0, | ||
| 1241 | "default": null, | ||
| 1242 | "generated": null, | ||
| 1243 | "identity": null, | ||
| 1244 | "name": "error_text", | ||
| 1245 | "entityType": "columns", | ||
| 1246 | "schema": "public", | ||
| 1247 | "table": "source_auth_sessions" | ||
| 1248 | }, | ||
| 1249 | { | ||
| 1250 | "type": "bigint", | ||
| 1251 | "typeSchema": null, | ||
| 1252 | "notNull": false, | ||
| 1253 | "dimensions": 0, | ||
| 1254 | "default": null, | ||
| 1255 | "generated": null, | ||
| 1256 | "identity": null, | ||
| 1257 | "name": "completed_at", | ||
| 1258 | "entityType": "columns", | ||
| 1259 | "schema": "public", | ||
| 1260 | "table": "source_auth_sessions" | ||
| 1261 | }, | ||
| 1262 | { | ||
| 1263 | "type": "bigint", | ||
| 1264 | "typeSchema": null, | ||
| 1265 | "notNull": true, | ||
| 1266 | "dimensions": 0, | ||
| 1267 | "default": null, | ||
| 1268 | "generated": null, | ||
| 1269 | "identity": null, | ||
| 1270 | "name": "created_at", | ||
| 1271 | "entityType": "columns", | ||
| 1272 | "schema": "public", | ||
| 1273 | "table": "source_auth_sessions" | ||
| 1274 | }, | ||
| 1275 | { | ||
| 1276 | "type": "bigint", | ||
| 1277 | "typeSchema": null, | ||
| 1278 | "notNull": true, | ||
| 1279 | "dimensions": 0, | ||
| 1280 | "default": null, | ||
| 1281 | "generated": null, | ||
| 1282 | "identity": null, | ||
| 1283 | "name": "updated_at", | ||
| 1284 | "entityType": "columns", | ||
| 1285 | "schema": "public", | ||
| 1286 | "table": "source_auth_sessions" | ||
| 1287 | }, | ||
| 1288 | { | ||
| 1289 | "type": "text", | ||
| 1290 | "typeSchema": null, | ||
| 1291 | "notNull": true, | ||
| 1292 | "dimensions": 0, | ||
| 1293 | "default": null, | ||
| 1294 | "generated": null, | ||
| 1295 | "identity": null, | ||
| 1296 | "name": "workspace_id", | ||
| 1297 | "entityType": "columns", | ||
| 1298 | "schema": "public", | ||
| 1299 | "table": "source_credential_bindings" | ||
| 1300 | }, | ||
| 1301 | { | ||
| 1302 | "type": "text", | ||
| 1303 | "typeSchema": null, | ||
| 1304 | "notNull": true, | ||
| 1305 | "dimensions": 0, | ||
| 1306 | "default": null, | ||
| 1307 | "generated": null, | ||
| 1308 | "identity": null, | ||
| 1309 | "name": "source_id", | ||
| 1310 | "entityType": "columns", | ||
| 1311 | "schema": "public", | ||
| 1312 | "table": "source_credential_bindings" | ||
| 1313 | }, | ||
| 1314 | { | ||
| 1315 | "type": "text", | ||
| 1316 | "typeSchema": null, | ||
| 1317 | "notNull": false, | ||
| 1318 | "dimensions": 0, | ||
| 1319 | "default": null, | ||
| 1320 | "generated": null, | ||
| 1321 | "identity": null, | ||
| 1322 | "name": "token_provider_id", | ||
| 1323 | "entityType": "columns", | ||
| 1324 | "schema": "public", | ||
| 1325 | "table": "source_credential_bindings" | ||
| 1326 | }, | ||
| 1327 | { | ||
| 1328 | "type": "text", | ||
| 1329 | "typeSchema": null, | ||
| 1330 | "notNull": false, | ||
| 1331 | "dimensions": 0, | ||
| 1332 | "default": null, | ||
| 1333 | "generated": null, | ||
| 1334 | "identity": null, | ||
| 1335 | "name": "token_handle", | ||
| 1336 | "entityType": "columns", | ||
| 1337 | "schema": "public", | ||
| 1338 | "table": "source_credential_bindings" | ||
| 1339 | }, | ||
| 1340 | { | ||
| 1341 | "type": "text", | ||
| 1342 | "typeSchema": null, | ||
| 1343 | "notNull": false, | ||
| 1344 | "dimensions": 0, | ||
| 1345 | "default": null, | ||
| 1346 | "generated": null, | ||
| 1347 | "identity": null, | ||
| 1348 | "name": "refresh_token_provider_id", | ||
| 1349 | "entityType": "columns", | ||
| 1350 | "schema": "public", | ||
| 1351 | "table": "source_credential_bindings" | ||
| 1352 | }, | ||
| 1353 | { | ||
| 1354 | "type": "text", | ||
| 1355 | "typeSchema": null, | ||
| 1356 | "notNull": false, | ||
| 1357 | "dimensions": 0, | ||
| 1358 | "default": null, | ||
| 1359 | "generated": null, | ||
| 1360 | "identity": null, | ||
| 1361 | "name": "refresh_token_handle", | ||
| 1362 | "entityType": "columns", | ||
| 1363 | "schema": "public", | ||
| 1364 | "table": "source_credential_bindings" | ||
| 1365 | }, | ||
| 1366 | { | ||
| 1367 | "type": "bigint", | ||
| 1368 | "typeSchema": null, | ||
| 1369 | "notNull": true, | ||
| 1370 | "dimensions": 0, | ||
| 1371 | "default": null, | ||
| 1372 | "generated": null, | ||
| 1373 | "identity": null, | ||
| 1374 | "name": "created_at", | ||
| 1375 | "entityType": "columns", | ||
| 1376 | "schema": "public", | ||
| 1377 | "table": "source_credential_bindings" | ||
| 1378 | }, | ||
| 1379 | { | ||
| 1380 | "type": "bigint", | ||
| 1381 | "typeSchema": null, | ||
| 1382 | "notNull": true, | ||
| 1383 | "dimensions": 0, | ||
| 1384 | "default": null, | ||
| 1385 | "generated": null, | ||
| 1386 | "identity": null, | ||
| 1387 | "name": "updated_at", | ||
| 1388 | "entityType": "columns", | ||
| 1389 | "schema": "public", | ||
| 1390 | "table": "source_credential_bindings" | ||
| 1391 | }, | ||
| 1392 | { | ||
| 1393 | "type": "text", | ||
| 1394 | "typeSchema": null, | ||
| 1395 | "notNull": true, | ||
| 1396 | "dimensions": 0, | ||
| 1397 | "default": null, | ||
| 1398 | "generated": null, | ||
| 1399 | "identity": null, | ||
| 1400 | "name": "workspace_id", | ||
| 1401 | "entityType": "columns", | ||
| 1402 | "schema": "public", | ||
| 1403 | "table": "sources" | ||
| 1404 | }, | ||
| 1405 | { | ||
| 1406 | "type": "text", | ||
| 1407 | "typeSchema": null, | ||
| 1408 | "notNull": true, | ||
| 1409 | "dimensions": 0, | ||
| 1410 | "default": null, | ||
| 1411 | "generated": null, | ||
| 1412 | "identity": null, | ||
| 1413 | "name": "source_id", | ||
| 1414 | "entityType": "columns", | ||
| 1415 | "schema": "public", | ||
| 1416 | "table": "sources" | ||
| 1417 | }, | ||
| 1418 | { | ||
| 1419 | "type": "text", | ||
| 1420 | "typeSchema": null, | ||
| 1421 | "notNull": true, | ||
| 1422 | "dimensions": 0, | ||
| 1423 | "default": null, | ||
| 1424 | "generated": null, | ||
| 1425 | "identity": null, | ||
| 1426 | "name": "name", | ||
| 1427 | "entityType": "columns", | ||
| 1428 | "schema": "public", | ||
| 1429 | "table": "sources" | ||
| 1430 | }, | ||
| 1431 | { | ||
| 1432 | "type": "text", | ||
| 1433 | "typeSchema": null, | ||
| 1434 | "notNull": true, | ||
| 1435 | "dimensions": 0, | ||
| 1436 | "default": null, | ||
| 1437 | "generated": null, | ||
| 1438 | "identity": null, | ||
| 1439 | "name": "kind", | ||
| 1440 | "entityType": "columns", | ||
| 1441 | "schema": "public", | ||
| 1442 | "table": "sources" | ||
| 1443 | }, | ||
| 1444 | { | ||
| 1445 | "type": "text", | ||
| 1446 | "typeSchema": null, | ||
| 1447 | "notNull": true, | ||
| 1448 | "dimensions": 0, | ||
| 1449 | "default": null, | ||
| 1450 | "generated": null, | ||
| 1451 | "identity": null, | ||
| 1452 | "name": "endpoint", | ||
| 1453 | "entityType": "columns", | ||
| 1454 | "schema": "public", | ||
| 1455 | "table": "sources" | ||
| 1456 | }, | ||
| 1457 | { | ||
| 1458 | "type": "text", | ||
| 1459 | "typeSchema": null, | ||
| 1460 | "notNull": true, | ||
| 1461 | "dimensions": 0, | ||
| 1462 | "default": null, | ||
| 1463 | "generated": null, | ||
| 1464 | "identity": null, | ||
| 1465 | "name": "status", | ||
| 1466 | "entityType": "columns", | ||
| 1467 | "schema": "public", | ||
| 1468 | "table": "sources" | ||
| 1469 | }, | ||
| 1470 | { | ||
| 1471 | "type": "boolean", | ||
| 1472 | "typeSchema": null, | ||
| 1473 | "notNull": true, | ||
| 1474 | "dimensions": 0, | ||
| 1475 | "default": null, | ||
| 1476 | "generated": null, | ||
| 1477 | "identity": null, | ||
| 1478 | "name": "enabled", | ||
| 1479 | "entityType": "columns", | ||
| 1480 | "schema": "public", | ||
| 1481 | "table": "sources" | ||
| 1482 | }, | ||
| 1483 | { | ||
| 1484 | "type": "text", | ||
| 1485 | "typeSchema": null, | ||
| 1486 | "notNull": false, | ||
| 1487 | "dimensions": 0, | ||
| 1488 | "default": null, | ||
| 1489 | "generated": null, | ||
| 1490 | "identity": null, | ||
| 1491 | "name": "namespace", | ||
| 1492 | "entityType": "columns", | ||
| 1493 | "schema": "public", | ||
| 1494 | "table": "sources" | ||
| 1495 | }, | ||
| 1496 | { | ||
| 1497 | "type": "text", | ||
| 1498 | "typeSchema": null, | ||
| 1499 | "notNull": false, | ||
| 1500 | "dimensions": 0, | ||
| 1501 | "default": null, | ||
| 1502 | "generated": null, | ||
| 1503 | "identity": null, | ||
| 1504 | "name": "transport", | ||
| 1505 | "entityType": "columns", | ||
| 1506 | "schema": "public", | ||
| 1507 | "table": "sources" | ||
| 1508 | }, | ||
| 1509 | { | ||
| 1510 | "type": "text", | ||
| 1511 | "typeSchema": null, | ||
| 1512 | "notNull": false, | ||
| 1513 | "dimensions": 0, | ||
| 1514 | "default": null, | ||
| 1515 | "generated": null, | ||
| 1516 | "identity": null, | ||
| 1517 | "name": "query_params_json", | ||
| 1518 | "entityType": "columns", | ||
| 1519 | "schema": "public", | ||
| 1520 | "table": "sources" | ||
| 1521 | }, | ||
| 1522 | { | ||
| 1523 | "type": "text", | ||
| 1524 | "typeSchema": null, | ||
| 1525 | "notNull": false, | ||
| 1526 | "dimensions": 0, | ||
| 1527 | "default": null, | ||
| 1528 | "generated": null, | ||
| 1529 | "identity": null, | ||
| 1530 | "name": "headers_json", | ||
| 1531 | "entityType": "columns", | ||
| 1532 | "schema": "public", | ||
| 1533 | "table": "sources" | ||
| 1534 | }, | ||
| 1535 | { | ||
| 1536 | "type": "text", | ||
| 1537 | "typeSchema": null, | ||
| 1538 | "notNull": false, | ||
| 1539 | "dimensions": 0, | ||
| 1540 | "default": null, | ||
| 1541 | "generated": null, | ||
| 1542 | "identity": null, | ||
| 1543 | "name": "spec_url", | ||
| 1544 | "entityType": "columns", | ||
| 1545 | "schema": "public", | ||
| 1546 | "table": "sources" | ||
| 1547 | }, | ||
| 1548 | { | ||
| 1549 | "type": "text", | ||
| 1550 | "typeSchema": null, | ||
| 1551 | "notNull": false, | ||
| 1552 | "dimensions": 0, | ||
| 1553 | "default": null, | ||
| 1554 | "generated": null, | ||
| 1555 | "identity": null, | ||
| 1556 | "name": "default_headers_json", | ||
| 1557 | "entityType": "columns", | ||
| 1558 | "schema": "public", | ||
| 1559 | "table": "sources" | ||
| 1560 | }, | ||
| 1561 | { | ||
| 1562 | "type": "text", | ||
| 1563 | "typeSchema": null, | ||
| 1564 | "notNull": true, | ||
| 1565 | "dimensions": 0, | ||
| 1566 | "default": null, | ||
| 1567 | "generated": null, | ||
| 1568 | "identity": null, | ||
| 1569 | "name": "auth_kind", | ||
| 1570 | "entityType": "columns", | ||
| 1571 | "schema": "public", | ||
| 1572 | "table": "sources" | ||
| 1573 | }, | ||
| 1574 | { | ||
| 1575 | "type": "text", | ||
| 1576 | "typeSchema": null, | ||
| 1577 | "notNull": false, | ||
| 1578 | "dimensions": 0, | ||
| 1579 | "default": null, | ||
| 1580 | "generated": null, | ||
| 1581 | "identity": null, | ||
| 1582 | "name": "auth_header_name", | ||
| 1583 | "entityType": "columns", | ||
| 1584 | "schema": "public", | ||
| 1585 | "table": "sources" | ||
| 1586 | }, | ||
| 1587 | { | ||
| 1588 | "type": "text", | ||
| 1589 | "typeSchema": null, | ||
| 1590 | "notNull": false, | ||
| 1591 | "dimensions": 0, | ||
| 1592 | "default": null, | ||
| 1593 | "generated": null, | ||
| 1594 | "identity": null, | ||
| 1595 | "name": "auth_prefix", | ||
| 1596 | "entityType": "columns", | ||
| 1597 | "schema": "public", | ||
| 1598 | "table": "sources" | ||
| 1599 | }, | ||
| 1600 | { | ||
| 1601 | "type": "text", | ||
| 1602 | "typeSchema": null, | ||
| 1603 | "notNull": false, | ||
| 1604 | "dimensions": 0, | ||
| 1605 | "default": null, | ||
| 1606 | "generated": null, | ||
| 1607 | "identity": null, | ||
| 1608 | "name": "source_hash", | ||
| 1609 | "entityType": "columns", | ||
| 1610 | "schema": "public", | ||
| 1611 | "table": "sources" | ||
| 1612 | }, | ||
| 1613 | { | ||
| 1614 | "type": "text", | ||
| 1615 | "typeSchema": null, | ||
| 1616 | "notNull": false, | ||
| 1617 | "dimensions": 0, | ||
| 1618 | "default": null, | ||
| 1619 | "generated": null, | ||
| 1620 | "identity": null, | ||
| 1621 | "name": "last_error", | ||
| 1622 | "entityType": "columns", | ||
| 1623 | "schema": "public", | ||
| 1624 | "table": "sources" | ||
| 1625 | }, | ||
| 1626 | { | ||
| 1627 | "type": "bigint", | ||
| 1628 | "typeSchema": null, | ||
| 1629 | "notNull": true, | ||
| 1630 | "dimensions": 0, | ||
| 1631 | "default": null, | ||
| 1632 | "generated": null, | ||
| 1633 | "identity": null, | ||
| 1634 | "name": "created_at", | ||
| 1635 | "entityType": "columns", | ||
| 1636 | "schema": "public", | ||
| 1637 | "table": "sources" | ||
| 1638 | }, | ||
| 1639 | { | ||
| 1640 | "type": "bigint", | ||
| 1641 | "typeSchema": null, | ||
| 1642 | "notNull": true, | ||
| 1643 | "dimensions": 0, | ||
| 1644 | "default": null, | ||
| 1645 | "generated": null, | ||
| 1646 | "identity": null, | ||
| 1647 | "name": "updated_at", | ||
| 1648 | "entityType": "columns", | ||
| 1649 | "schema": "public", | ||
| 1650 | "table": "sources" | ||
| 1651 | }, | ||
| 1652 | { | ||
| 1653 | "type": "text", | ||
| 1654 | "typeSchema": null, | ||
| 1655 | "notNull": true, | ||
| 1656 | "dimensions": 0, | ||
| 1657 | "default": null, | ||
| 1658 | "generated": null, | ||
| 1659 | "identity": null, | ||
| 1660 | "name": "workspace_id", | ||
| 1661 | "entityType": "columns", | ||
| 1662 | "schema": "public", | ||
| 1663 | "table": "tool_artifact_parameters" | ||
| 1664 | }, | ||
| 1665 | { | ||
| 1666 | "type": "text", | ||
| 1667 | "typeSchema": null, | ||
| 1668 | "notNull": true, | ||
| 1669 | "dimensions": 0, | ||
| 1670 | "default": null, | ||
| 1671 | "generated": null, | ||
| 1672 | "identity": null, | ||
| 1673 | "name": "path", | ||
| 1674 | "entityType": "columns", | ||
| 1675 | "schema": "public", | ||
| 1676 | "table": "tool_artifact_parameters" | ||
| 1677 | }, | ||
| 1678 | { | ||
| 1679 | "type": "bigint", | ||
| 1680 | "typeSchema": null, | ||
| 1681 | "notNull": true, | ||
| 1682 | "dimensions": 0, | ||
| 1683 | "default": null, | ||
| 1684 | "generated": null, | ||
| 1685 | "identity": null, | ||
| 1686 | "name": "position", | ||
| 1687 | "entityType": "columns", | ||
| 1688 | "schema": "public", | ||
| 1689 | "table": "tool_artifact_parameters" | ||
| 1690 | }, | ||
| 1691 | { | ||
| 1692 | "type": "text", | ||
| 1693 | "typeSchema": null, | ||
| 1694 | "notNull": true, | ||
| 1695 | "dimensions": 0, | ||
| 1696 | "default": null, | ||
| 1697 | "generated": null, | ||
| 1698 | "identity": null, | ||
| 1699 | "name": "name", | ||
| 1700 | "entityType": "columns", | ||
| 1701 | "schema": "public", | ||
| 1702 | "table": "tool_artifact_parameters" | ||
| 1703 | }, | ||
| 1704 | { | ||
| 1705 | "type": "text", | ||
| 1706 | "typeSchema": null, | ||
| 1707 | "notNull": true, | ||
| 1708 | "dimensions": 0, | ||
| 1709 | "default": null, | ||
| 1710 | "generated": null, | ||
| 1711 | "identity": null, | ||
| 1712 | "name": "location", | ||
| 1713 | "entityType": "columns", | ||
| 1714 | "schema": "public", | ||
| 1715 | "table": "tool_artifact_parameters" | ||
| 1716 | }, | ||
| 1717 | { | ||
| 1718 | "type": "boolean", | ||
| 1719 | "typeSchema": null, | ||
| 1720 | "notNull": true, | ||
| 1721 | "dimensions": 0, | ||
| 1722 | "default": null, | ||
| 1723 | "generated": null, | ||
| 1724 | "identity": null, | ||
| 1725 | "name": "required", | ||
| 1726 | "entityType": "columns", | ||
| 1727 | "schema": "public", | ||
| 1728 | "table": "tool_artifact_parameters" | ||
| 1729 | }, | ||
| 1730 | { | ||
| 1731 | "type": "text", | ||
| 1732 | "typeSchema": null, | ||
| 1733 | "notNull": true, | ||
| 1734 | "dimensions": 0, | ||
| 1735 | "default": null, | ||
| 1736 | "generated": null, | ||
| 1737 | "identity": null, | ||
| 1738 | "name": "workspace_id", | ||
| 1739 | "entityType": "columns", | ||
| 1740 | "schema": "public", | ||
| 1741 | "table": "tool_artifact_ref_hint_keys" | ||
| 1742 | }, | ||
| 1743 | { | ||
| 1744 | "type": "text", | ||
| 1745 | "typeSchema": null, | ||
| 1746 | "notNull": true, | ||
| 1747 | "dimensions": 0, | ||
| 1748 | "default": null, | ||
| 1749 | "generated": null, | ||
| 1750 | "identity": null, | ||
| 1751 | "name": "path", | ||
| 1752 | "entityType": "columns", | ||
| 1753 | "schema": "public", | ||
| 1754 | "table": "tool_artifact_ref_hint_keys" | ||
| 1755 | }, | ||
| 1756 | { | ||
| 1757 | "type": "bigint", | ||
| 1758 | "typeSchema": null, | ||
| 1759 | "notNull": true, | ||
| 1760 | "dimensions": 0, | ||
| 1761 | "default": null, | ||
| 1762 | "generated": null, | ||
| 1763 | "identity": null, | ||
| 1764 | "name": "position", | ||
| 1765 | "entityType": "columns", | ||
| 1766 | "schema": "public", | ||
| 1767 | "table": "tool_artifact_ref_hint_keys" | ||
| 1768 | }, | ||
| 1769 | { | ||
| 1770 | "type": "text", | ||
| 1771 | "typeSchema": null, | ||
| 1772 | "notNull": true, | ||
| 1773 | "dimensions": 0, | ||
| 1774 | "default": null, | ||
| 1775 | "generated": null, | ||
| 1776 | "identity": null, | ||
| 1777 | "name": "ref_hint_key", | ||
| 1778 | "entityType": "columns", | ||
| 1779 | "schema": "public", | ||
| 1780 | "table": "tool_artifact_ref_hint_keys" | ||
| 1781 | }, | ||
| 1782 | { | ||
| 1783 | "type": "text", | ||
| 1784 | "typeSchema": null, | ||
| 1785 | "notNull": true, | ||
| 1786 | "dimensions": 0, | ||
| 1787 | "default": null, | ||
| 1788 | "generated": null, | ||
| 1789 | "identity": null, | ||
| 1790 | "name": "workspace_id", | ||
| 1791 | "entityType": "columns", | ||
| 1792 | "schema": "public", | ||
| 1793 | "table": "tool_artifact_request_body_content_types" | ||
| 1794 | }, | ||
| 1795 | { | ||
| 1796 | "type": "text", | ||
| 1797 | "typeSchema": null, | ||
| 1798 | "notNull": true, | ||
| 1799 | "dimensions": 0, | ||
| 1800 | "default": null, | ||
| 1801 | "generated": null, | ||
| 1802 | "identity": null, | ||
| 1803 | "name": "path", | ||
| 1804 | "entityType": "columns", | ||
| 1805 | "schema": "public", | ||
| 1806 | "table": "tool_artifact_request_body_content_types" | ||
| 1807 | }, | ||
| 1808 | { | ||
| 1809 | "type": "bigint", | ||
| 1810 | "typeSchema": null, | ||
| 1811 | "notNull": true, | ||
| 1812 | "dimensions": 0, | ||
| 1813 | "default": null, | ||
| 1814 | "generated": null, | ||
| 1815 | "identity": null, | ||
| 1816 | "name": "position", | ||
| 1817 | "entityType": "columns", | ||
| 1818 | "schema": "public", | ||
| 1819 | "table": "tool_artifact_request_body_content_types" | ||
| 1820 | }, | ||
| 1821 | { | ||
| 1822 | "type": "text", | ||
| 1823 | "typeSchema": null, | ||
| 1824 | "notNull": true, | ||
| 1825 | "dimensions": 0, | ||
| 1826 | "default": null, | ||
| 1827 | "generated": null, | ||
| 1828 | "identity": null, | ||
| 1829 | "name": "content_type", | ||
| 1830 | "entityType": "columns", | ||
| 1831 | "schema": "public", | ||
| 1832 | "table": "tool_artifact_request_body_content_types" | ||
| 1833 | }, | ||
| 1834 | { | ||
| 1835 | "type": "text", | ||
| 1836 | "typeSchema": null, | ||
| 1837 | "notNull": true, | ||
| 1838 | "dimensions": 0, | ||
| 1839 | "default": null, | ||
| 1840 | "generated": null, | ||
| 1841 | "identity": null, | ||
| 1842 | "name": "workspace_id", | ||
| 1843 | "entityType": "columns", | ||
| 1844 | "schema": "public", | ||
| 1845 | "table": "tool_artifacts" | ||
| 1846 | }, | ||
| 1847 | { | ||
| 1848 | "type": "text", | ||
| 1849 | "typeSchema": null, | ||
| 1850 | "notNull": true, | ||
| 1851 | "dimensions": 0, | ||
| 1852 | "default": null, | ||
| 1853 | "generated": null, | ||
| 1854 | "identity": null, | ||
| 1855 | "name": "path", | ||
| 1856 | "entityType": "columns", | ||
| 1857 | "schema": "public", | ||
| 1858 | "table": "tool_artifacts" | ||
| 1859 | }, | ||
| 1860 | { | ||
| 1861 | "type": "text", | ||
| 1862 | "typeSchema": null, | ||
| 1863 | "notNull": true, | ||
| 1864 | "dimensions": 0, | ||
| 1865 | "default": null, | ||
| 1866 | "generated": null, | ||
| 1867 | "identity": null, | ||
| 1868 | "name": "tool_id", | ||
| 1869 | "entityType": "columns", | ||
| 1870 | "schema": "public", | ||
| 1871 | "table": "tool_artifacts" | ||
| 1872 | }, | ||
| 1873 | { | ||
| 1874 | "type": "text", | ||
| 1875 | "typeSchema": null, | ||
| 1876 | "notNull": true, | ||
| 1877 | "dimensions": 0, | ||
| 1878 | "default": null, | ||
| 1879 | "generated": null, | ||
| 1880 | "identity": null, | ||
| 1881 | "name": "source_id", | ||
| 1882 | "entityType": "columns", | ||
| 1883 | "schema": "public", | ||
| 1884 | "table": "tool_artifacts" | ||
| 1885 | }, | ||
| 1886 | { | ||
| 1887 | "type": "text", | ||
| 1888 | "typeSchema": null, | ||
| 1889 | "notNull": false, | ||
| 1890 | "dimensions": 0, | ||
| 1891 | "default": null, | ||
| 1892 | "generated": null, | ||
| 1893 | "identity": null, | ||
| 1894 | "name": "title", | ||
| 1895 | "entityType": "columns", | ||
| 1896 | "schema": "public", | ||
| 1897 | "table": "tool_artifacts" | ||
| 1898 | }, | ||
| 1899 | { | ||
| 1900 | "type": "text", | ||
| 1901 | "typeSchema": null, | ||
| 1902 | "notNull": false, | ||
| 1903 | "dimensions": 0, | ||
| 1904 | "default": null, | ||
| 1905 | "generated": null, | ||
| 1906 | "identity": null, | ||
| 1907 | "name": "description", | ||
| 1908 | "entityType": "columns", | ||
| 1909 | "schema": "public", | ||
| 1910 | "table": "tool_artifacts" | ||
| 1911 | }, | ||
| 1912 | { | ||
| 1913 | "type": "text", | ||
| 1914 | "typeSchema": null, | ||
| 1915 | "notNull": true, | ||
| 1916 | "dimensions": 0, | ||
| 1917 | "default": null, | ||
| 1918 | "generated": null, | ||
| 1919 | "identity": null, | ||
| 1920 | "name": "search_namespace", | ||
| 1921 | "entityType": "columns", | ||
| 1922 | "schema": "public", | ||
| 1923 | "table": "tool_artifacts" | ||
| 1924 | }, | ||
| 1925 | { | ||
| 1926 | "type": "text", | ||
| 1927 | "typeSchema": null, | ||
| 1928 | "notNull": true, | ||
| 1929 | "dimensions": 0, | ||
| 1930 | "default": null, | ||
| 1931 | "generated": null, | ||
| 1932 | "identity": null, | ||
| 1933 | "name": "search_text", | ||
| 1934 | "entityType": "columns", | ||
| 1935 | "schema": "public", | ||
| 1936 | "table": "tool_artifacts" | ||
| 1937 | }, | ||
| 1938 | { | ||
| 1939 | "type": "text", | ||
| 1940 | "typeSchema": null, | ||
| 1941 | "notNull": false, | ||
| 1942 | "dimensions": 0, | ||
| 1943 | "default": null, | ||
| 1944 | "generated": null, | ||
| 1945 | "identity": null, | ||
| 1946 | "name": "input_schema_json", | ||
| 1947 | "entityType": "columns", | ||
| 1948 | "schema": "public", | ||
| 1949 | "table": "tool_artifacts" | ||
| 1950 | }, | ||
| 1951 | { | ||
| 1952 | "type": "text", | ||
| 1953 | "typeSchema": null, | ||
| 1954 | "notNull": false, | ||
| 1955 | "dimensions": 0, | ||
| 1956 | "default": null, | ||
| 1957 | "generated": null, | ||
| 1958 | "identity": null, | ||
| 1959 | "name": "output_schema_json", | ||
| 1960 | "entityType": "columns", | ||
| 1961 | "schema": "public", | ||
| 1962 | "table": "tool_artifacts" | ||
| 1963 | }, | ||
| 1964 | { | ||
| 1965 | "type": "text", | ||
| 1966 | "typeSchema": null, | ||
| 1967 | "notNull": true, | ||
| 1968 | "dimensions": 0, | ||
| 1969 | "default": null, | ||
| 1970 | "generated": null, | ||
| 1971 | "identity": null, | ||
| 1972 | "name": "provider_kind", | ||
| 1973 | "entityType": "columns", | ||
| 1974 | "schema": "public", | ||
| 1975 | "table": "tool_artifacts" | ||
| 1976 | }, | ||
| 1977 | { | ||
| 1978 | "type": "text", | ||
| 1979 | "typeSchema": null, | ||
| 1980 | "notNull": false, | ||
| 1981 | "dimensions": 0, | ||
| 1982 | "default": null, | ||
| 1983 | "generated": null, | ||
| 1984 | "identity": null, | ||
| 1985 | "name": "mcp_tool_name", | ||
| 1986 | "entityType": "columns", | ||
| 1987 | "schema": "public", | ||
| 1988 | "table": "tool_artifacts" | ||
| 1989 | }, | ||
| 1990 | { | ||
| 1991 | "type": "text", | ||
| 1992 | "typeSchema": null, | ||
| 1993 | "notNull": false, | ||
| 1994 | "dimensions": 0, | ||
| 1995 | "default": null, | ||
| 1996 | "generated": null, | ||
| 1997 | "identity": null, | ||
| 1998 | "name": "openapi_method", | ||
| 1999 | "entityType": "columns", | ||
| 2000 | "schema": "public", | ||
| 2001 | "table": "tool_artifacts" | ||
| 2002 | }, | ||
| 2003 | { | ||
| 2004 | "type": "text", | ||
| 2005 | "typeSchema": null, | ||
| 2006 | "notNull": false, | ||
| 2007 | "dimensions": 0, | ||
| 2008 | "default": null, | ||
| 2009 | "generated": null, | ||
| 2010 | "identity": null, | ||
| 2011 | "name": "openapi_path_template", | ||
| 2012 | "entityType": "columns", | ||
| 2013 | "schema": "public", | ||
| 2014 | "table": "tool_artifacts" | ||
| 2015 | }, | ||
| 2016 | { | ||
| 2017 | "type": "text", | ||
| 2018 | "typeSchema": null, | ||
| 2019 | "notNull": false, | ||
| 2020 | "dimensions": 0, | ||
| 2021 | "default": null, | ||
| 2022 | "generated": null, | ||
| 2023 | "identity": null, | ||
| 2024 | "name": "openapi_operation_hash", | ||
| 2025 | "entityType": "columns", | ||
| 2026 | "schema": "public", | ||
| 2027 | "table": "tool_artifacts" | ||
| 2028 | }, | ||
| 2029 | { | ||
| 2030 | "type": "boolean", | ||
| 2031 | "typeSchema": null, | ||
| 2032 | "notNull": false, | ||
| 2033 | "dimensions": 0, | ||
| 2034 | "default": null, | ||
| 2035 | "generated": null, | ||
| 2036 | "identity": null, | ||
| 2037 | "name": "openapi_request_body_required", | ||
| 2038 | "entityType": "columns", | ||
| 2039 | "schema": "public", | ||
| 2040 | "table": "tool_artifacts" | ||
| 2041 | }, | ||
| 2042 | { | ||
| 2043 | "type": "bigint", | ||
| 2044 | "typeSchema": null, | ||
| 2045 | "notNull": true, | ||
| 2046 | "dimensions": 0, | ||
| 2047 | "default": null, | ||
| 2048 | "generated": null, | ||
| 2049 | "identity": null, | ||
| 2050 | "name": "created_at", | ||
| 2051 | "entityType": "columns", | ||
| 2052 | "schema": "public", | ||
| 2053 | "table": "tool_artifacts" | ||
| 2054 | }, | ||
| 2055 | { | ||
| 2056 | "type": "bigint", | ||
| 2057 | "typeSchema": null, | ||
| 2058 | "notNull": true, | ||
| 2059 | "dimensions": 0, | ||
| 2060 | "default": null, | ||
| 2061 | "generated": null, | ||
| 2062 | "identity": null, | ||
| 2063 | "name": "updated_at", | ||
| 2064 | "entityType": "columns", | ||
| 2065 | "schema": "public", | ||
| 2066 | "table": "tool_artifacts" | ||
| 2067 | }, | ||
| 2068 | { | ||
| 2069 | "type": "text", | ||
| 2070 | "typeSchema": null, | ||
| 2071 | "notNull": true, | ||
| 2072 | "dimensions": 0, | ||
| 2073 | "default": null, | ||
| 2074 | "generated": null, | ||
| 2075 | "identity": null, | ||
| 2076 | "name": "id", | ||
| 2077 | "entityType": "columns", | ||
| 2078 | "schema": "public", | ||
| 2079 | "table": "workspaces" | ||
| 2080 | }, | ||
| 2081 | { | ||
| 2082 | "type": "text", | ||
| 2083 | "typeSchema": null, | ||
| 2084 | "notNull": true, | ||
| 2085 | "dimensions": 0, | ||
| 2086 | "default": null, | ||
| 2087 | "generated": null, | ||
| 2088 | "identity": null, | ||
| 2089 | "name": "organization_id", | ||
| 2090 | "entityType": "columns", | ||
| 2091 | "schema": "public", | ||
| 2092 | "table": "workspaces" | ||
| 2093 | }, | ||
| 2094 | { | ||
| 2095 | "type": "text", | ||
| 2096 | "typeSchema": null, | ||
| 2097 | "notNull": true, | ||
| 2098 | "dimensions": 0, | ||
| 2099 | "default": null, | ||
| 2100 | "generated": null, | ||
| 2101 | "identity": null, | ||
| 2102 | "name": "name", | ||
| 2103 | "entityType": "columns", | ||
| 2104 | "schema": "public", | ||
| 2105 | "table": "workspaces" | ||
| 2106 | }, | ||
| 2107 | { | ||
| 2108 | "type": "text", | ||
| 2109 | "typeSchema": null, | ||
| 2110 | "notNull": false, | ||
| 2111 | "dimensions": 0, | ||
| 2112 | "default": null, | ||
| 2113 | "generated": null, | ||
| 2114 | "identity": null, | ||
| 2115 | "name": "created_by_account_id", | ||
| 2116 | "entityType": "columns", | ||
| 2117 | "schema": "public", | ||
| 2118 | "table": "workspaces" | ||
| 2119 | }, | ||
| 2120 | { | ||
| 2121 | "type": "bigint", | ||
| 2122 | "typeSchema": null, | ||
| 2123 | "notNull": true, | ||
| 2124 | "dimensions": 0, | ||
| 2125 | "default": null, | ||
| 2126 | "generated": null, | ||
| 2127 | "identity": null, | ||
| 2128 | "name": "created_at", | ||
| 2129 | "entityType": "columns", | ||
| 2130 | "schema": "public", | ||
| 2131 | "table": "workspaces" | ||
| 2132 | }, | ||
| 2133 | { | ||
| 2134 | "type": "bigint", | ||
| 2135 | "typeSchema": null, | ||
| 2136 | "notNull": true, | ||
| 2137 | "dimensions": 0, | ||
| 2138 | "default": null, | ||
| 2139 | "generated": null, | ||
| 2140 | "identity": null, | ||
| 2141 | "name": "updated_at", | ||
| 2142 | "entityType": "columns", | ||
| 2143 | "schema": "public", | ||
| 2144 | "table": "workspaces" | ||
| 2145 | }, | ||
| 2146 | { | ||
| 2147 | "nameExplicit": true, | ||
| 2148 | "columns": [ | ||
| 2149 | { | ||
| 2150 | "value": "provider", | ||
| 2151 | "isExpression": false, | ||
| 2152 | "asc": true, | ||
| 2153 | "nullsFirst": false, | ||
| 2154 | "opclass": null | ||
| 2155 | }, | ||
| 2156 | { | ||
| 2157 | "value": "subject", | ||
| 2158 | "isExpression": false, | ||
| 2159 | "asc": true, | ||
| 2160 | "nullsFirst": false, | ||
| 2161 | "opclass": null | ||
| 2162 | } | ||
| 2163 | ], | ||
| 2164 | "isUnique": true, | ||
| 2165 | "where": null, | ||
| 2166 | "with": "", | ||
| 2167 | "method": "btree", | ||
| 2168 | "concurrently": false, | ||
| 2169 | "name": "accounts_provider_subject_idx", | ||
| 2170 | "entityType": "indexes", | ||
| 2171 | "schema": "public", | ||
| 2172 | "table": "accounts" | ||
| 2173 | }, | ||
| 2174 | { | ||
| 2175 | "nameExplicit": true, | ||
| 2176 | "columns": [ | ||
| 2177 | { | ||
| 2178 | "value": "updated_at", | ||
| 2179 | "isExpression": false, | ||
| 2180 | "asc": true, | ||
| 2181 | "nullsFirst": false, | ||
| 2182 | "opclass": null | ||
| 2183 | }, | ||
| 2184 | { | ||
| 2185 | "value": "id", | ||
| 2186 | "isExpression": false, | ||
| 2187 | "asc": true, | ||
| 2188 | "nullsFirst": false, | ||
| 2189 | "opclass": null | ||
| 2190 | } | ||
| 2191 | ], | ||
| 2192 | "isUnique": false, | ||
| 2193 | "where": null, | ||
| 2194 | "with": "", | ||
| 2195 | "method": "btree", | ||
| 2196 | "concurrently": false, | ||
| 2197 | "name": "accounts_updated_idx", | ||
| 2198 | "entityType": "indexes", | ||
| 2199 | "schema": "public", | ||
| 2200 | "table": "accounts" | ||
| 2201 | }, | ||
| 2202 | { | ||
| 2203 | "nameExplicit": true, | ||
| 2204 | "columns": [ | ||
| 2205 | { | ||
| 2206 | "value": "execution_id", | ||
| 2207 | "isExpression": false, | ||
| 2208 | "asc": true, | ||
| 2209 | "nullsFirst": false, | ||
| 2210 | "opclass": null | ||
| 2211 | }, | ||
| 2212 | { | ||
| 2213 | "value": "updated_at", | ||
| 2214 | "isExpression": false, | ||
| 2215 | "asc": true, | ||
| 2216 | "nullsFirst": false, | ||
| 2217 | "opclass": null | ||
| 2218 | }, | ||
| 2219 | { | ||
| 2220 | "value": "id", | ||
| 2221 | "isExpression": false, | ||
| 2222 | "asc": true, | ||
| 2223 | "nullsFirst": false, | ||
| 2224 | "opclass": null | ||
| 2225 | } | ||
| 2226 | ], | ||
| 2227 | "isUnique": false, | ||
| 2228 | "where": null, | ||
| 2229 | "with": "", | ||
| 2230 | "method": "btree", | ||
| 2231 | "concurrently": false, | ||
| 2232 | "name": "execution_interactions_execution_idx", | ||
| 2233 | "entityType": "indexes", | ||
| 2234 | "schema": "public", | ||
| 2235 | "table": "execution_interactions" | ||
| 2236 | }, | ||
| 2237 | { | ||
| 2238 | "nameExplicit": true, | ||
| 2239 | "columns": [ | ||
| 2240 | { | ||
| 2241 | "value": "workspace_id", | ||
| 2242 | "isExpression": false, | ||
| 2243 | "asc": true, | ||
| 2244 | "nullsFirst": false, | ||
| 2245 | "opclass": null | ||
| 2246 | }, | ||
| 2247 | { | ||
| 2248 | "value": "updated_at", | ||
| 2249 | "isExpression": false, | ||
| 2250 | "asc": true, | ||
| 2251 | "nullsFirst": false, | ||
| 2252 | "opclass": null | ||
| 2253 | }, | ||
| 2254 | { | ||
| 2255 | "value": "id", | ||
| 2256 | "isExpression": false, | ||
| 2257 | "asc": true, | ||
| 2258 | "nullsFirst": false, | ||
| 2259 | "opclass": null | ||
| 2260 | } | ||
| 2261 | ], | ||
| 2262 | "isUnique": false, | ||
| 2263 | "where": null, | ||
| 2264 | "with": "", | ||
| 2265 | "method": "btree", | ||
| 2266 | "concurrently": false, | ||
| 2267 | "name": "executions_workspace_idx", | ||
| 2268 | "entityType": "indexes", | ||
| 2269 | "schema": "public", | ||
| 2270 | "table": "executions" | ||
| 2271 | }, | ||
| 2272 | { | ||
| 2273 | "nameExplicit": true, | ||
| 2274 | "columns": [ | ||
| 2275 | { | ||
| 2276 | "value": "organization_id", | ||
| 2277 | "isExpression": false, | ||
| 2278 | "asc": true, | ||
| 2279 | "nullsFirst": false, | ||
| 2280 | "opclass": null | ||
| 2281 | } | ||
| 2282 | ], | ||
| 2283 | "isUnique": false, | ||
| 2284 | "where": null, | ||
| 2285 | "with": "", | ||
| 2286 | "method": "btree", | ||
| 2287 | "concurrently": false, | ||
| 2288 | "name": "organization_memberships_org_idx", | ||
| 2289 | "entityType": "indexes", | ||
| 2290 | "schema": "public", | ||
| 2291 | "table": "organization_memberships" | ||
| 2292 | }, | ||
| 2293 | { | ||
| 2294 | "nameExplicit": true, | ||
| 2295 | "columns": [ | ||
| 2296 | { | ||
| 2297 | "value": "account_id", | ||
| 2298 | "isExpression": false, | ||
| 2299 | "asc": true, | ||
| 2300 | "nullsFirst": false, | ||
| 2301 | "opclass": null | ||
| 2302 | } | ||
| 2303 | ], | ||
| 2304 | "isUnique": false, | ||
| 2305 | "where": null, | ||
| 2306 | "with": "", | ||
| 2307 | "method": "btree", | ||
| 2308 | "concurrently": false, | ||
| 2309 | "name": "organization_memberships_account_idx", | ||
| 2310 | "entityType": "indexes", | ||
| 2311 | "schema": "public", | ||
| 2312 | "table": "organization_memberships" | ||
| 2313 | }, | ||
| 2314 | { | ||
| 2315 | "nameExplicit": true, | ||
| 2316 | "columns": [ | ||
| 2317 | { | ||
| 2318 | "value": "organization_id", | ||
| 2319 | "isExpression": false, | ||
| 2320 | "asc": true, | ||
| 2321 | "nullsFirst": false, | ||
| 2322 | "opclass": null | ||
| 2323 | }, | ||
| 2324 | { | ||
| 2325 | "value": "account_id", | ||
| 2326 | "isExpression": false, | ||
| 2327 | "asc": true, | ||
| 2328 | "nullsFirst": false, | ||
| 2329 | "opclass": null | ||
| 2330 | } | ||
| 2331 | ], | ||
| 2332 | "isUnique": true, | ||
| 2333 | "where": null, | ||
| 2334 | "with": "", | ||
| 2335 | "method": "btree", | ||
| 2336 | "concurrently": false, | ||
| 2337 | "name": "organization_memberships_org_account_idx", | ||
| 2338 | "entityType": "indexes", | ||
| 2339 | "schema": "public", | ||
| 2340 | "table": "organization_memberships" | ||
| 2341 | }, | ||
| 2342 | { | ||
| 2343 | "nameExplicit": true, | ||
| 2344 | "columns": [ | ||
| 2345 | { | ||
| 2346 | "value": "slug", | ||
| 2347 | "isExpression": false, | ||
| 2348 | "asc": true, | ||
| 2349 | "nullsFirst": false, | ||
| 2350 | "opclass": null | ||
| 2351 | } | ||
| 2352 | ], | ||
| 2353 | "isUnique": true, | ||
| 2354 | "where": null, | ||
| 2355 | "with": "", | ||
| 2356 | "method": "btree", | ||
| 2357 | "concurrently": false, | ||
| 2358 | "name": "organizations_slug_idx", | ||
| 2359 | "entityType": "indexes", | ||
| 2360 | "schema": "public", | ||
| 2361 | "table": "organizations" | ||
| 2362 | }, | ||
| 2363 | { | ||
| 2364 | "nameExplicit": true, | ||
| 2365 | "columns": [ | ||
| 2366 | { | ||
| 2367 | "value": "updated_at", | ||
| 2368 | "isExpression": false, | ||
| 2369 | "asc": true, | ||
| 2370 | "nullsFirst": false, | ||
| 2371 | "opclass": null | ||
| 2372 | }, | ||
| 2373 | { | ||
| 2374 | "value": "id", | ||
| 2375 | "isExpression": false, | ||
| 2376 | "asc": true, | ||
| 2377 | "nullsFirst": false, | ||
| 2378 | "opclass": null | ||
| 2379 | } | ||
| 2380 | ], | ||
| 2381 | "isUnique": false, | ||
| 2382 | "where": null, | ||
| 2383 | "with": "", | ||
| 2384 | "method": "btree", | ||
| 2385 | "concurrently": false, | ||
| 2386 | "name": "organizations_updated_idx", | ||
| 2387 | "entityType": "indexes", | ||
| 2388 | "schema": "public", | ||
| 2389 | "table": "organizations" | ||
| 2390 | }, | ||
| 2391 | { | ||
| 2392 | "nameExplicit": true, | ||
| 2393 | "columns": [ | ||
| 2394 | { | ||
| 2395 | "value": "workspace_id", | ||
| 2396 | "isExpression": false, | ||
| 2397 | "asc": true, | ||
| 2398 | "nullsFirst": false, | ||
| 2399 | "opclass": null | ||
| 2400 | }, | ||
| 2401 | { | ||
| 2402 | "value": "updated_at", | ||
| 2403 | "isExpression": false, | ||
| 2404 | "asc": true, | ||
| 2405 | "nullsFirst": false, | ||
| 2406 | "opclass": null | ||
| 2407 | }, | ||
| 2408 | { | ||
| 2409 | "value": "id", | ||
| 2410 | "isExpression": false, | ||
| 2411 | "asc": true, | ||
| 2412 | "nullsFirst": false, | ||
| 2413 | "opclass": null | ||
| 2414 | } | ||
| 2415 | ], | ||
| 2416 | "isUnique": false, | ||
| 2417 | "where": null, | ||
| 2418 | "with": "", | ||
| 2419 | "method": "btree", | ||
| 2420 | "concurrently": false, | ||
| 2421 | "name": "policies_workspace_idx", | ||
| 2422 | "entityType": "indexes", | ||
| 2423 | "schema": "public", | ||
| 2424 | "table": "policies" | ||
| 2425 | }, | ||
| 2426 | { | ||
| 2427 | "nameExplicit": true, | ||
| 2428 | "columns": [ | ||
| 2429 | { | ||
| 2430 | "value": "updated_at", | ||
| 2431 | "isExpression": false, | ||
| 2432 | "asc": true, | ||
| 2433 | "nullsFirst": false, | ||
| 2434 | "opclass": null | ||
| 2435 | }, | ||
| 2436 | { | ||
| 2437 | "value": "id", | ||
| 2438 | "isExpression": false, | ||
| 2439 | "asc": true, | ||
| 2440 | "nullsFirst": false, | ||
| 2441 | "opclass": null | ||
| 2442 | } | ||
| 2443 | ], | ||
| 2444 | "isUnique": false, | ||
| 2445 | "where": null, | ||
| 2446 | "with": "", | ||
| 2447 | "method": "btree", | ||
| 2448 | "concurrently": false, | ||
| 2449 | "name": "secret_materials_updated_idx", | ||
| 2450 | "entityType": "indexes", | ||
| 2451 | "schema": "public", | ||
| 2452 | "table": "secret_materials" | ||
| 2453 | }, | ||
| 2454 | { | ||
| 2455 | "nameExplicit": true, | ||
| 2456 | "columns": [ | ||
| 2457 | { | ||
| 2458 | "value": "workspace_id", | ||
| 2459 | "isExpression": false, | ||
| 2460 | "asc": true, | ||
| 2461 | "nullsFirst": false, | ||
| 2462 | "opclass": null | ||
| 2463 | }, | ||
| 2464 | { | ||
| 2465 | "value": "updated_at", | ||
| 2466 | "isExpression": false, | ||
| 2467 | "asc": true, | ||
| 2468 | "nullsFirst": false, | ||
| 2469 | "opclass": null | ||
| 2470 | }, | ||
| 2471 | { | ||
| 2472 | "value": "id", | ||
| 2473 | "isExpression": false, | ||
| 2474 | "asc": true, | ||
| 2475 | "nullsFirst": false, | ||
| 2476 | "opclass": null | ||
| 2477 | } | ||
| 2478 | ], | ||
| 2479 | "isUnique": false, | ||
| 2480 | "where": null, | ||
| 2481 | "with": "", | ||
| 2482 | "method": "btree", | ||
| 2483 | "concurrently": false, | ||
| 2484 | "name": "source_auth_sessions_workspace_idx", | ||
| 2485 | "entityType": "indexes", | ||
| 2486 | "schema": "public", | ||
| 2487 | "table": "source_auth_sessions" | ||
| 2488 | }, | ||
| 2489 | { | ||
| 2490 | "nameExplicit": true, | ||
| 2491 | "columns": [ | ||
| 2492 | { | ||
| 2493 | "value": "state", | ||
| 2494 | "isExpression": false, | ||
| 2495 | "asc": true, | ||
| 2496 | "nullsFirst": false, | ||
| 2497 | "opclass": null | ||
| 2498 | } | ||
| 2499 | ], | ||
| 2500 | "isUnique": true, | ||
| 2501 | "where": null, | ||
| 2502 | "with": "", | ||
| 2503 | "method": "btree", | ||
| 2504 | "concurrently": false, | ||
| 2505 | "name": "source_auth_sessions_state_idx", | ||
| 2506 | "entityType": "indexes", | ||
| 2507 | "schema": "public", | ||
| 2508 | "table": "source_auth_sessions" | ||
| 2509 | }, | ||
| 2510 | { | ||
| 2511 | "nameExplicit": true, | ||
| 2512 | "columns": [ | ||
| 2513 | { | ||
| 2514 | "value": "workspace_id", | ||
| 2515 | "isExpression": false, | ||
| 2516 | "asc": true, | ||
| 2517 | "nullsFirst": false, | ||
| 2518 | "opclass": null | ||
| 2519 | }, | ||
| 2520 | { | ||
| 2521 | "value": "updated_at", | ||
| 2522 | "isExpression": false, | ||
| 2523 | "asc": true, | ||
| 2524 | "nullsFirst": false, | ||
| 2525 | "opclass": null | ||
| 2526 | }, | ||
| 2527 | { | ||
| 2528 | "value": "source_id", | ||
| 2529 | "isExpression": false, | ||
| 2530 | "asc": true, | ||
| 2531 | "nullsFirst": false, | ||
| 2532 | "opclass": null | ||
| 2533 | } | ||
| 2534 | ], | ||
| 2535 | "isUnique": false, | ||
| 2536 | "where": null, | ||
| 2537 | "with": "", | ||
| 2538 | "method": "btree", | ||
| 2539 | "concurrently": false, | ||
| 2540 | "name": "source_credential_bindings_workspace_idx", | ||
| 2541 | "entityType": "indexes", | ||
| 2542 | "schema": "public", | ||
| 2543 | "table": "source_credential_bindings" | ||
| 2544 | }, | ||
| 2545 | { | ||
| 2546 | "nameExplicit": true, | ||
| 2547 | "columns": [ | ||
| 2548 | { | ||
| 2549 | "value": "workspace_id", | ||
| 2550 | "isExpression": false, | ||
| 2551 | "asc": true, | ||
| 2552 | "nullsFirst": false, | ||
| 2553 | "opclass": null | ||
| 2554 | }, | ||
| 2555 | { | ||
| 2556 | "value": "name", | ||
| 2557 | "isExpression": false, | ||
| 2558 | "asc": true, | ||
| 2559 | "nullsFirst": false, | ||
| 2560 | "opclass": null | ||
| 2561 | } | ||
| 2562 | ], | ||
| 2563 | "isUnique": true, | ||
| 2564 | "where": null, | ||
| 2565 | "with": "", | ||
| 2566 | "method": "btree", | ||
| 2567 | "concurrently": false, | ||
| 2568 | "name": "sources_workspace_name_idx", | ||
| 2569 | "entityType": "indexes", | ||
| 2570 | "schema": "public", | ||
| 2571 | "table": "sources" | ||
| 2572 | }, | ||
| 2573 | { | ||
| 2574 | "nameExplicit": true, | ||
| 2575 | "columns": [ | ||
| 2576 | { | ||
| 2577 | "value": "workspace_id", | ||
| 2578 | "isExpression": false, | ||
| 2579 | "asc": true, | ||
| 2580 | "nullsFirst": false, | ||
| 2581 | "opclass": null | ||
| 2582 | }, | ||
| 2583 | { | ||
| 2584 | "value": "path", | ||
| 2585 | "isExpression": false, | ||
| 2586 | "asc": true, | ||
| 2587 | "nullsFirst": false, | ||
| 2588 | "opclass": null | ||
| 2589 | }, | ||
| 2590 | { | ||
| 2591 | "value": "position", | ||
| 2592 | "isExpression": false, | ||
| 2593 | "asc": true, | ||
| 2594 | "nullsFirst": false, | ||
| 2595 | "opclass": null | ||
| 2596 | } | ||
| 2597 | ], | ||
| 2598 | "isUnique": false, | ||
| 2599 | "where": null, | ||
| 2600 | "with": "", | ||
| 2601 | "method": "btree", | ||
| 2602 | "concurrently": false, | ||
| 2603 | "name": "tool_artifact_parameters_lookup_idx", | ||
| 2604 | "entityType": "indexes", | ||
| 2605 | "schema": "public", | ||
| 2606 | "table": "tool_artifact_parameters" | ||
| 2607 | }, | ||
| 2608 | { | ||
| 2609 | "nameExplicit": true, | ||
| 2610 | "columns": [ | ||
| 2611 | { | ||
| 2612 | "value": "workspace_id", | ||
| 2613 | "isExpression": false, | ||
| 2614 | "asc": true, | ||
| 2615 | "nullsFirst": false, | ||
| 2616 | "opclass": null | ||
| 2617 | }, | ||
| 2618 | { | ||
| 2619 | "value": "path", | ||
| 2620 | "isExpression": false, | ||
| 2621 | "asc": true, | ||
| 2622 | "nullsFirst": false, | ||
| 2623 | "opclass": null | ||
| 2624 | }, | ||
| 2625 | { | ||
| 2626 | "value": "position", | ||
| 2627 | "isExpression": false, | ||
| 2628 | "asc": true, | ||
| 2629 | "nullsFirst": false, | ||
| 2630 | "opclass": null | ||
| 2631 | } | ||
| 2632 | ], | ||
| 2633 | "isUnique": false, | ||
| 2634 | "where": null, | ||
| 2635 | "with": "", | ||
| 2636 | "method": "btree", | ||
| 2637 | "concurrently": false, | ||
| 2638 | "name": "tool_artifact_ref_hint_keys_lookup_idx", | ||
| 2639 | "entityType": "indexes", | ||
| 2640 | "schema": "public", | ||
| 2641 | "table": "tool_artifact_ref_hint_keys" | ||
| 2642 | }, | ||
| 2643 | { | ||
| 2644 | "nameExplicit": true, | ||
| 2645 | "columns": [ | ||
| 2646 | { | ||
| 2647 | "value": "workspace_id", | ||
| 2648 | "isExpression": false, | ||
| 2649 | "asc": true, | ||
| 2650 | "nullsFirst": false, | ||
| 2651 | "opclass": null | ||
| 2652 | }, | ||
| 2653 | { | ||
| 2654 | "value": "path", | ||
| 2655 | "isExpression": false, | ||
| 2656 | "asc": true, | ||
| 2657 | "nullsFirst": false, | ||
| 2658 | "opclass": null | ||
| 2659 | }, | ||
| 2660 | { | ||
| 2661 | "value": "position", | ||
| 2662 | "isExpression": false, | ||
| 2663 | "asc": true, | ||
| 2664 | "nullsFirst": false, | ||
| 2665 | "opclass": null | ||
| 2666 | } | ||
| 2667 | ], | ||
| 2668 | "isUnique": false, | ||
| 2669 | "where": null, | ||
| 2670 | "with": "", | ||
| 2671 | "method": "btree", | ||
| 2672 | "concurrently": false, | ||
| 2673 | "name": "tool_artifact_request_body_content_types_lookup_idx", | ||
| 2674 | "entityType": "indexes", | ||
| 2675 | "schema": "public", | ||
| 2676 | "table": "tool_artifact_request_body_content_types" | ||
| 2677 | }, | ||
| 2678 | { | ||
| 2679 | "nameExplicit": true, | ||
| 2680 | "columns": [ | ||
| 2681 | { | ||
| 2682 | "value": "workspace_id", | ||
| 2683 | "isExpression": false, | ||
| 2684 | "asc": true, | ||
| 2685 | "nullsFirst": false, | ||
| 2686 | "opclass": null | ||
| 2687 | }, | ||
| 2688 | { | ||
| 2689 | "value": "source_id", | ||
| 2690 | "isExpression": false, | ||
| 2691 | "asc": true, | ||
| 2692 | "nullsFirst": false, | ||
| 2693 | "opclass": null | ||
| 2694 | }, | ||
| 2695 | { | ||
| 2696 | "value": "updated_at", | ||
| 2697 | "isExpression": false, | ||
| 2698 | "asc": true, | ||
| 2699 | "nullsFirst": false, | ||
| 2700 | "opclass": null | ||
| 2701 | }, | ||
| 2702 | { | ||
| 2703 | "value": "path", | ||
| 2704 | "isExpression": false, | ||
| 2705 | "asc": true, | ||
| 2706 | "nullsFirst": false, | ||
| 2707 | "opclass": null | ||
| 2708 | } | ||
| 2709 | ], | ||
| 2710 | "isUnique": false, | ||
| 2711 | "where": null, | ||
| 2712 | "with": "", | ||
| 2713 | "method": "btree", | ||
| 2714 | "concurrently": false, | ||
| 2715 | "name": "tool_artifacts_workspace_source_idx", | ||
| 2716 | "entityType": "indexes", | ||
| 2717 | "schema": "public", | ||
| 2718 | "table": "tool_artifacts" | ||
| 2719 | }, | ||
| 2720 | { | ||
| 2721 | "nameExplicit": true, | ||
| 2722 | "columns": [ | ||
| 2723 | { | ||
| 2724 | "value": "workspace_id", | ||
| 2725 | "isExpression": false, | ||
| 2726 | "asc": true, | ||
| 2727 | "nullsFirst": false, | ||
| 2728 | "opclass": null | ||
| 2729 | }, | ||
| 2730 | { | ||
| 2731 | "value": "search_namespace", | ||
| 2732 | "isExpression": false, | ||
| 2733 | "asc": true, | ||
| 2734 | "nullsFirst": false, | ||
| 2735 | "opclass": null | ||
| 2736 | }, | ||
| 2737 | { | ||
| 2738 | "value": "path", | ||
| 2739 | "isExpression": false, | ||
| 2740 | "asc": true, | ||
| 2741 | "nullsFirst": false, | ||
| 2742 | "opclass": null | ||
| 2743 | } | ||
| 2744 | ], | ||
| 2745 | "isUnique": false, | ||
| 2746 | "where": null, | ||
| 2747 | "with": "", | ||
| 2748 | "method": "btree", | ||
| 2749 | "concurrently": false, | ||
| 2750 | "name": "tool_artifacts_workspace_namespace_idx", | ||
| 2751 | "entityType": "indexes", | ||
| 2752 | "schema": "public", | ||
| 2753 | "table": "tool_artifacts" | ||
| 2754 | }, | ||
| 2755 | { | ||
| 2756 | "nameExplicit": true, | ||
| 2757 | "columns": [ | ||
| 2758 | { | ||
| 2759 | "value": "organization_id", | ||
| 2760 | "isExpression": false, | ||
| 2761 | "asc": true, | ||
| 2762 | "nullsFirst": false, | ||
| 2763 | "opclass": null | ||
| 2764 | } | ||
| 2765 | ], | ||
| 2766 | "isUnique": false, | ||
| 2767 | "where": null, | ||
| 2768 | "with": "", | ||
| 2769 | "method": "btree", | ||
| 2770 | "concurrently": false, | ||
| 2771 | "name": "workspaces_org_idx", | ||
| 2772 | "entityType": "indexes", | ||
| 2773 | "schema": "public", | ||
| 2774 | "table": "workspaces" | ||
| 2775 | }, | ||
| 2776 | { | ||
| 2777 | "nameExplicit": true, | ||
| 2778 | "columns": [ | ||
| 2779 | { | ||
| 2780 | "value": "organization_id", | ||
| 2781 | "isExpression": false, | ||
| 2782 | "asc": true, | ||
| 2783 | "nullsFirst": false, | ||
| 2784 | "opclass": null | ||
| 2785 | }, | ||
| 2786 | { | ||
| 2787 | "value": "name", | ||
| 2788 | "isExpression": false, | ||
| 2789 | "asc": true, | ||
| 2790 | "nullsFirst": false, | ||
| 2791 | "opclass": null | ||
| 2792 | } | ||
| 2793 | ], | ||
| 2794 | "isUnique": true, | ||
| 2795 | "where": null, | ||
| 2796 | "with": "", | ||
| 2797 | "method": "btree", | ||
| 2798 | "concurrently": false, | ||
| 2799 | "name": "workspaces_org_name_idx", | ||
| 2800 | "entityType": "indexes", | ||
| 2801 | "schema": "public", | ||
| 2802 | "table": "workspaces" | ||
| 2803 | }, | ||
| 2804 | { | ||
| 2805 | "columns": [ | ||
| 2806 | "workspace_id", | ||
| 2807 | "source_id" | ||
| 2808 | ], | ||
| 2809 | "nameExplicit": false, | ||
| 2810 | "name": "source_credential_bindings_pkey", | ||
| 2811 | "entityType": "pks", | ||
| 2812 | "schema": "public", | ||
| 2813 | "table": "source_credential_bindings" | ||
| 2814 | }, | ||
| 2815 | { | ||
| 2816 | "columns": [ | ||
| 2817 | "workspace_id", | ||
| 2818 | "source_id" | ||
| 2819 | ], | ||
| 2820 | "nameExplicit": false, | ||
| 2821 | "name": "sources_pkey", | ||
| 2822 | "entityType": "pks", | ||
| 2823 | "schema": "public", | ||
| 2824 | "table": "sources" | ||
| 2825 | }, | ||
| 2826 | { | ||
| 2827 | "columns": [ | ||
| 2828 | "workspace_id", | ||
| 2829 | "path", | ||
| 2830 | "position" | ||
| 2831 | ], | ||
| 2832 | "nameExplicit": false, | ||
| 2833 | "name": "tool_artifact_parameters_pkey", | ||
| 2834 | "entityType": "pks", | ||
| 2835 | "schema": "public", | ||
| 2836 | "table": "tool_artifact_parameters" | ||
| 2837 | }, | ||
| 2838 | { | ||
| 2839 | "columns": [ | ||
| 2840 | "workspace_id", | ||
| 2841 | "path", | ||
| 2842 | "position" | ||
| 2843 | ], | ||
| 2844 | "nameExplicit": false, | ||
| 2845 | "name": "tool_artifact_ref_hint_keys_pkey", | ||
| 2846 | "entityType": "pks", | ||
| 2847 | "schema": "public", | ||
| 2848 | "table": "tool_artifact_ref_hint_keys" | ||
| 2849 | }, | ||
| 2850 | { | ||
| 2851 | "columns": [ | ||
| 2852 | "workspace_id", | ||
| 2853 | "path", | ||
| 2854 | "position" | ||
| 2855 | ], | ||
| 2856 | "nameExplicit": false, | ||
| 2857 | "name": "tool_artifact_request_body_content_types_pkey", | ||
| 2858 | "entityType": "pks", | ||
| 2859 | "schema": "public", | ||
| 2860 | "table": "tool_artifact_request_body_content_types" | ||
| 2861 | }, | ||
| 2862 | { | ||
| 2863 | "columns": [ | ||
| 2864 | "workspace_id", | ||
| 2865 | "path" | ||
| 2866 | ], | ||
| 2867 | "nameExplicit": false, | ||
| 2868 | "name": "tool_artifacts_pkey", | ||
| 2869 | "entityType": "pks", | ||
| 2870 | "schema": "public", | ||
| 2871 | "table": "tool_artifacts" | ||
| 2872 | }, | ||
| 2873 | { | ||
| 2874 | "columns": [ | ||
| 2875 | "id" | ||
| 2876 | ], | ||
| 2877 | "nameExplicit": false, | ||
| 2878 | "name": "accounts_pkey", | ||
| 2879 | "schema": "public", | ||
| 2880 | "table": "accounts", | ||
| 2881 | "entityType": "pks" | ||
| 2882 | }, | ||
| 2883 | { | ||
| 2884 | "columns": [ | ||
| 2885 | "id" | ||
| 2886 | ], | ||
| 2887 | "nameExplicit": false, | ||
| 2888 | "name": "execution_interactions_pkey", | ||
| 2889 | "schema": "public", | ||
| 2890 | "table": "execution_interactions", | ||
| 2891 | "entityType": "pks" | ||
| 2892 | }, | ||
| 2893 | { | ||
| 2894 | "columns": [ | ||
| 2895 | "id" | ||
| 2896 | ], | ||
| 2897 | "nameExplicit": false, | ||
| 2898 | "name": "executions_pkey", | ||
| 2899 | "schema": "public", | ||
| 2900 | "table": "executions", | ||
| 2901 | "entityType": "pks" | ||
| 2902 | }, | ||
| 2903 | { | ||
| 2904 | "columns": [ | ||
| 2905 | "id" | ||
| 2906 | ], | ||
| 2907 | "nameExplicit": false, | ||
| 2908 | "name": "local_installations_pkey", | ||
| 2909 | "schema": "public", | ||
| 2910 | "table": "local_installations", | ||
| 2911 | "entityType": "pks" | ||
| 2912 | }, | ||
| 2913 | { | ||
| 2914 | "columns": [ | ||
| 2915 | "id" | ||
| 2916 | ], | ||
| 2917 | "nameExplicit": false, | ||
| 2918 | "name": "organization_memberships_pkey", | ||
| 2919 | "schema": "public", | ||
| 2920 | "table": "organization_memberships", | ||
| 2921 | "entityType": "pks" | ||
| 2922 | }, | ||
| 2923 | { | ||
| 2924 | "columns": [ | ||
| 2925 | "id" | ||
| 2926 | ], | ||
| 2927 | "nameExplicit": false, | ||
| 2928 | "name": "organizations_pkey", | ||
| 2929 | "schema": "public", | ||
| 2930 | "table": "organizations", | ||
| 2931 | "entityType": "pks" | ||
| 2932 | }, | ||
| 2933 | { | ||
| 2934 | "columns": [ | ||
| 2935 | "id" | ||
| 2936 | ], | ||
| 2937 | "nameExplicit": false, | ||
| 2938 | "name": "policies_pkey", | ||
| 2939 | "schema": "public", | ||
| 2940 | "table": "policies", | ||
| 2941 | "entityType": "pks" | ||
| 2942 | }, | ||
| 2943 | { | ||
| 2944 | "columns": [ | ||
| 2945 | "id" | ||
| 2946 | ], | ||
| 2947 | "nameExplicit": false, | ||
| 2948 | "name": "secret_materials_pkey", | ||
| 2949 | "schema": "public", | ||
| 2950 | "table": "secret_materials", | ||
| 2951 | "entityType": "pks" | ||
| 2952 | }, | ||
| 2953 | { | ||
| 2954 | "columns": [ | ||
| 2955 | "id" | ||
| 2956 | ], | ||
| 2957 | "nameExplicit": false, | ||
| 2958 | "name": "source_auth_sessions_pkey", | ||
| 2959 | "schema": "public", | ||
| 2960 | "table": "source_auth_sessions", | ||
| 2961 | "entityType": "pks" | ||
| 2962 | }, | ||
| 2963 | { | ||
| 2964 | "columns": [ | ||
| 2965 | "id" | ||
| 2966 | ], | ||
| 2967 | "nameExplicit": false, | ||
| 2968 | "name": "workspaces_pkey", | ||
| 2969 | "schema": "public", | ||
| 2970 | "table": "workspaces", | ||
| 2971 | "entityType": "pks" | ||
| 2972 | }, | ||
| 2973 | { | ||
| 2974 | "value": "\"provider\" in ('local', 'workos', 'service')", | ||
| 2975 | "name": "accounts_provider_check", | ||
| 2976 | "entityType": "checks", | ||
| 2977 | "schema": "public", | ||
| 2978 | "table": "accounts" | ||
| 2979 | }, | ||
| 2980 | { | ||
| 2981 | "value": "\"status\" in ('pending', 'resolved', 'cancelled')", | ||
| 2982 | "name": "execution_interactions_status_check", | ||
| 2983 | "entityType": "checks", | ||
| 2984 | "schema": "public", | ||
| 2985 | "table": "execution_interactions" | ||
| 2986 | }, | ||
| 2987 | { | ||
| 2988 | "value": "\"status\" in ('pending', 'running', 'waiting_for_interaction', 'completed', 'failed', 'cancelled')", | ||
| 2989 | "name": "executions_status_check", | ||
| 2990 | "entityType": "checks", | ||
| 2991 | "schema": "public", | ||
| 2992 | "table": "executions" | ||
| 2993 | }, | ||
| 2994 | { | ||
| 2995 | "value": "\"role\" in ('viewer', 'editor', 'admin', 'owner')", | ||
| 2996 | "name": "organization_memberships_role_check", | ||
| 2997 | "entityType": "checks", | ||
| 2998 | "schema": "public", | ||
| 2999 | "table": "organization_memberships" | ||
| 3000 | }, | ||
| 3001 | { | ||
| 3002 | "value": "\"status\" in ('invited', 'active', 'suspended', 'removed')", | ||
| 3003 | "name": "organization_memberships_status_check", | ||
| 3004 | "entityType": "checks", | ||
| 3005 | "schema": "public", | ||
| 3006 | "table": "organization_memberships" | ||
| 3007 | }, | ||
| 3008 | { | ||
| 3009 | "value": "\"status\" in ('active', 'suspended', 'archived')", | ||
| 3010 | "name": "organizations_status_check", | ||
| 3011 | "entityType": "checks", | ||
| 3012 | "schema": "public", | ||
| 3013 | "table": "organizations" | ||
| 3014 | }, | ||
| 3015 | { | ||
| 3016 | "value": "\"resource_type\" in ('all_tools', 'source', 'namespace', 'tool_path')", | ||
| 3017 | "name": "policies_resource_type_check", | ||
| 3018 | "entityType": "checks", | ||
| 3019 | "schema": "public", | ||
| 3020 | "table": "policies" | ||
| 3021 | }, | ||
| 3022 | { | ||
| 3023 | "value": "\"match_type\" in ('glob', 'exact')", | ||
| 3024 | "name": "policies_match_type_check", | ||
| 3025 | "entityType": "checks", | ||
| 3026 | "schema": "public", | ||
| 3027 | "table": "policies" | ||
| 3028 | }, | ||
| 3029 | { | ||
| 3030 | "value": "\"effect\" in ('allow', 'deny')", | ||
| 3031 | "name": "policies_effect_check", | ||
| 3032 | "entityType": "checks", | ||
| 3033 | "schema": "public", | ||
| 3034 | "table": "policies" | ||
| 3035 | }, | ||
| 3036 | { | ||
| 3037 | "value": "\"approval_mode\" in ('auto', 'required')", | ||
| 3038 | "name": "policies_approval_mode_check", | ||
| 3039 | "entityType": "checks", | ||
| 3040 | "schema": "public", | ||
| 3041 | "table": "policies" | ||
| 3042 | }, | ||
| 3043 | { | ||
| 3044 | "value": "\"purpose\" in ('auth_material', 'oauth_access_token', 'oauth_refresh_token', 'oauth_client_info')", | ||
| 3045 | "name": "secret_materials_purpose_check", | ||
| 3046 | "entityType": "checks", | ||
| 3047 | "schema": "public", | ||
| 3048 | "table": "secret_materials" | ||
| 3049 | }, | ||
| 3050 | { | ||
| 3051 | "value": "\"strategy\" in ('oauth2_authorization_code')", | ||
| 3052 | "name": "source_auth_sessions_strategy_check", | ||
| 3053 | "entityType": "checks", | ||
| 3054 | "schema": "public", | ||
| 3055 | "table": "source_auth_sessions" | ||
| 3056 | }, | ||
| 3057 | { | ||
| 3058 | "value": "\"status\" in ('pending', 'completed', 'failed', 'cancelled')", | ||
| 3059 | "name": "source_auth_sessions_status_check", | ||
| 3060 | "entityType": "checks", | ||
| 3061 | "schema": "public", | ||
| 3062 | "table": "source_auth_sessions" | ||
| 3063 | }, | ||
| 3064 | { | ||
| 3065 | "value": "\"kind\" in ('mcp', 'openapi', 'graphql', 'internal')", | ||
| 3066 | "name": "sources_kind_check", | ||
| 3067 | "entityType": "checks", | ||
| 3068 | "schema": "public", | ||
| 3069 | "table": "sources" | ||
| 3070 | }, | ||
| 3071 | { | ||
| 3072 | "value": "\"status\" in ('draft', 'probing', 'auth_required', 'connected', 'error')", | ||
| 3073 | "name": "sources_status_check", | ||
| 3074 | "entityType": "checks", | ||
| 3075 | "schema": "public", | ||
| 3076 | "table": "sources" | ||
| 3077 | }, | ||
| 3078 | { | ||
| 3079 | "value": "\"transport\" is null or \"transport\" in ('auto', 'streamable-http', 'sse')", | ||
| 3080 | "name": "sources_transport_check", | ||
| 3081 | "entityType": "checks", | ||
| 3082 | "schema": "public", | ||
| 3083 | "table": "sources" | ||
| 3084 | }, | ||
| 3085 | { | ||
| 3086 | "value": "\"auth_kind\" in ('none', 'bearer', 'oauth2')", | ||
| 3087 | "name": "sources_auth_kind_check", | ||
| 3088 | "entityType": "checks", | ||
| 3089 | "schema": "public", | ||
| 3090 | "table": "sources" | ||
| 3091 | }, | ||
| 3092 | { | ||
| 3093 | "value": "\"location\" in ('path', 'query', 'header', 'cookie')", | ||
| 3094 | "name": "tool_artifact_parameters_location_check", | ||
| 3095 | "entityType": "checks", | ||
| 3096 | "schema": "public", | ||
| 3097 | "table": "tool_artifact_parameters" | ||
| 3098 | }, | ||
| 3099 | { | ||
| 3100 | "value": "\"provider_kind\" in ('mcp', 'openapi')", | ||
| 3101 | "name": "tool_artifacts_provider_kind_check", | ||
| 3102 | "entityType": "checks", | ||
| 3103 | "schema": "public", | ||
| 3104 | "table": "tool_artifacts" | ||
| 3105 | }, | ||
| 3106 | { | ||
| 3107 | "value": "\"provider_kind\" <> 'mcp'\n or (\n \"mcp_tool_name\" is not null\n and \"openapi_method\" is null\n and \"openapi_path_template\" is null\n and \"openapi_operation_hash\" is null\n and \"openapi_request_body_required\" is null\n )", | ||
| 3108 | "name": "tool_artifacts_mcp_shape_check", | ||
| 3109 | "entityType": "checks", | ||
| 3110 | "schema": "public", | ||
| 3111 | "table": "tool_artifacts" | ||
| 3112 | }, | ||
| 3113 | { | ||
| 3114 | "value": "\"provider_kind\" <> 'openapi'\n or (\n \"mcp_tool_name\" is null\n and \"openapi_method\" in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')\n and \"openapi_path_template\" is not null\n and \"openapi_operation_hash\" is not null\n )", | ||
| 3115 | "name": "tool_artifacts_openapi_shape_check", | ||
| 3116 | "entityType": "checks", | ||
| 3117 | "schema": "public", | ||
| 3118 | "table": "tool_artifacts" | ||
| 3119 | } | ||
| 3120 | ], | ||
| 3121 | "renames": [] | ||
| 3122 | } | ||
| @@ -1,37 +0,0 @@ | |||
| 1 | ALTER TABLE "tool_artifacts" ADD COLUMN "openapi_raw_tool_id" text;--> statement-breakpoint | ||
| 2 | ALTER TABLE "tool_artifacts" ADD COLUMN "openapi_operation_id" text;--> statement-breakpoint | ||
| 3 | ALTER TABLE "tool_artifacts" ADD COLUMN "openapi_tags_json" text;--> statement-breakpoint | ||
| 4 | UPDATE "tool_artifacts" | ||
| 5 | SET | ||
| 6 | "openapi_raw_tool_id" = "tool_id", | ||
| 7 | "openapi_operation_id" = "tool_id", | ||
| 8 | "openapi_tags_json" = '[]' | ||
| 9 | WHERE "provider_kind" = 'openapi';--> statement-breakpoint | ||
| 10 | ALTER TABLE "tool_artifacts" DROP CONSTRAINT "tool_artifacts_mcp_shape_check";--> statement-breakpoint | ||
| 11 | ALTER TABLE "tool_artifacts" DROP CONSTRAINT "tool_artifacts_openapi_shape_check";--> statement-breakpoint | ||
| 12 | ALTER TABLE "tool_artifacts" | ||
| 13 | ADD CONSTRAINT "tool_artifacts_mcp_shape_check" CHECK ( | ||
| 14 | "provider_kind" <> 'mcp' | ||
| 15 | OR ( | ||
| 16 | "mcp_tool_name" is not null | ||
| 17 | and "openapi_method" is null | ||
| 18 | and "openapi_path_template" is null | ||
| 19 | and "openapi_operation_hash" is null | ||
| 20 | and "openapi_raw_tool_id" is null | ||
| 21 | and "openapi_operation_id" is null | ||
| 22 | and "openapi_tags_json" is null | ||
| 23 | and "openapi_request_body_required" is null | ||
| 24 | ) | ||
| 25 | );--> statement-breakpoint | ||
| 26 | ALTER TABLE "tool_artifacts" | ||
| 27 | ADD CONSTRAINT "tool_artifacts_openapi_shape_check" CHECK ( | ||
| 28 | "provider_kind" <> 'openapi' | ||
| 29 | OR ( | ||
| 30 | "mcp_tool_name" is null | ||
| 31 | and "openapi_method" in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace') | ||
| 32 | and "openapi_path_template" is not null | ||
| 33 | and "openapi_operation_hash" is not null | ||
| 34 | and "openapi_raw_tool_id" is not null | ||
| 35 | and "openapi_tags_json" is not null | ||
| 36 | ) | ||
| 37 | ); | ||
| @@ -1 +0,0 @@ | |||
| 1 | ALTER TABLE "sources" ADD COLUMN "source_document_text" text; | ||
| @@ -1,41 +0,0 @@ | |||
| 1 | CREATE TABLE "credentials" ( | ||
| 2 | "id" text PRIMARY KEY NOT NULL, | ||
| 3 | "workspace_id" text NOT NULL, | ||
| 4 | "auth_kind" text NOT NULL, | ||
| 5 | "auth_header_name" text NOT NULL, | ||
| 6 | "auth_prefix" text NOT NULL, | ||
| 7 | "token_provider_id" text NOT NULL, | ||
| 8 | "token_handle" text NOT NULL, | ||
| 9 | "refresh_token_provider_id" text, | ||
| 10 | "refresh_token_handle" text, | ||
| 11 | "created_at" bigint NOT NULL, | ||
| 12 | "updated_at" bigint NOT NULL, | ||
| 13 | CONSTRAINT "credentials_auth_kind_check" CHECK ("auth_kind" in ('bearer', 'oauth2')) | ||
| 14 | ); | ||
| 15 | --> statement-breakpoint | ||
| 16 | CREATE INDEX "credentials_workspace_idx" ON "credentials" ("workspace_id","updated_at","id"); | ||
| 17 | --> statement-breakpoint | ||
| 18 | ALTER TABLE "sources" DROP CONSTRAINT "sources_auth_kind_check"; | ||
| 19 | --> statement-breakpoint | ||
| 20 | ALTER TABLE "sources" DROP COLUMN "auth_kind"; | ||
| 21 | --> statement-breakpoint | ||
| 22 | ALTER TABLE "sources" DROP COLUMN "auth_header_name"; | ||
| 23 | --> statement-breakpoint | ||
| 24 | ALTER TABLE "sources" DROP COLUMN "auth_prefix"; | ||
| 25 | --> statement-breakpoint | ||
| 26 | DROP INDEX IF EXISTS "source_credential_bindings_workspace_idx"; | ||
| 27 | --> statement-breakpoint | ||
| 28 | DROP TABLE "source_credential_bindings"; | ||
| 29 | --> statement-breakpoint | ||
| 30 | CREATE TABLE "source_credential_bindings" ( | ||
| 31 | "id" text PRIMARY KEY NOT NULL, | ||
| 32 | "workspace_id" text NOT NULL, | ||
| 33 | "source_id" text NOT NULL, | ||
| 34 | "credential_id" text NOT NULL, | ||
| 35 | "created_at" bigint NOT NULL, | ||
| 36 | "updated_at" bigint NOT NULL | ||
| 37 | ); | ||
| 38 | --> statement-breakpoint | ||
| 39 | CREATE UNIQUE INDEX "source_credential_bindings_workspace_source_idx" ON "source_credential_bindings" ("workspace_id","source_id"); | ||
| 40 | --> statement-breakpoint | ||
| 41 | CREATE INDEX "source_credential_bindings_workspace_idx" ON "source_credential_bindings" ("workspace_id","updated_at","source_id"); | ||
| @@ -1,16 +0,0 @@ | |||
| 1 | DROP INDEX IF EXISTS "organization_memberships_org_idx";--> statement-breakpoint | ||
| 2 | DROP INDEX IF EXISTS "organization_memberships_account_idx";--> statement-breakpoint | ||
| 3 | CREATE INDEX "organization_memberships_org_updated_idx" ON "organization_memberships" ("organization_id","updated_at","id");--> statement-breakpoint | ||
| 4 | CREATE INDEX "organization_memberships_account_updated_idx" ON "organization_memberships" ("account_id","updated_at","id");--> statement-breakpoint | ||
| 5 | DROP INDEX IF EXISTS "workspaces_org_idx";--> statement-breakpoint | ||
| 6 | CREATE INDEX "workspaces_org_updated_idx" ON "workspaces" ("organization_id","updated_at","id");--> statement-breakpoint | ||
| 7 | CREATE INDEX "sources_workspace_updated_idx" ON "sources" ("workspace_id","updated_at","source_id");--> statement-breakpoint | ||
| 8 | CREATE INDEX "tool_artifacts_search_text_idx" ON "tool_artifacts" USING gin (to_tsvector('simple', "search_text"));--> statement-breakpoint | ||
| 9 | DROP INDEX IF EXISTS "tool_artifact_parameters_lookup_idx";--> statement-breakpoint | ||
| 10 | DROP INDEX IF EXISTS "tool_artifact_request_body_content_types_lookup_idx";--> statement-breakpoint | ||
| 11 | DROP INDEX IF EXISTS "tool_artifact_ref_hint_keys_lookup_idx";--> statement-breakpoint | ||
| 12 | CREATE INDEX "source_auth_sessions_pending_idx" ON "source_auth_sessions" ("workspace_id","source_id","status","updated_at","id");--> statement-breakpoint | ||
| 13 | DROP INDEX IF EXISTS "policies_workspace_idx";--> statement-breakpoint | ||
| 14 | CREATE INDEX "policies_workspace_priority_idx" ON "policies" ("workspace_id","priority" DESC,"updated_at","id");--> statement-breakpoint | ||
| 15 | CREATE INDEX "local_installations_organization_idx" ON "local_installations" ("organization_id");--> statement-breakpoint | ||
| 16 | CREATE INDEX "local_installations_workspace_idx" ON "local_installations" ("workspace_id"); | ||
| @@ -1 +0,0 @@ | |||
| 1 | ALTER TABLE "secret_materials" ADD COLUMN "name" text; | ||
| @@ -1,17 +0,0 @@ | |||
| 1 | ALTER TABLE "policies" ADD COLUMN "scope_type" text;--> statement-breakpoint | ||
| 2 | ALTER TABLE "policies" ADD COLUMN "organization_id" text;--> statement-breakpoint | ||
| 3 | UPDATE "policies" AS "pol" | ||
| 4 | SET | ||
| 5 | "scope_type" = 'workspace', | ||
| 6 | "organization_id" = "ws"."organization_id" | ||
| 7 | FROM "workspaces" AS "ws" | ||
| 8 | WHERE "pol"."workspace_id" = "ws"."id";--> statement-breakpoint | ||
| 9 | ALTER TABLE "policies" ALTER COLUMN "scope_type" SET NOT NULL;--> statement-breakpoint | ||
| 10 | ALTER TABLE "policies" ALTER COLUMN "organization_id" SET NOT NULL;--> statement-breakpoint | ||
| 11 | ALTER TABLE "policies" ALTER COLUMN "workspace_id" DROP NOT NULL;--> statement-breakpoint | ||
| 12 | ALTER TABLE "policies" ADD CONSTRAINT "policies_scope_type_check" CHECK ("scope_type" in ('organization', 'workspace'));--> statement-breakpoint | ||
| 13 | ALTER TABLE "policies" ADD CONSTRAINT "policies_scope_consistency_check" CHECK (("scope_type" = 'organization' and "workspace_id" is null) or ("scope_type" = 'workspace' and "workspace_id" is not null));--> statement-breakpoint | ||
| 14 | DROP INDEX IF EXISTS "policies_workspace_priority_idx";--> statement-breakpoint | ||
| 15 | CREATE INDEX "policies_organization_priority_idx" ON "policies" ("organization_id","priority" DESC,"updated_at","id");--> statement-breakpoint | ||
| 16 | CREATE INDEX "policies_workspace_priority_idx" ON "policies" ("workspace_id","priority" DESC,"updated_at","id");--> statement-breakpoint | ||
| 17 | ALTER TABLE "execution_interactions" ADD COLUMN "purpose" text NOT NULL DEFAULT 'elicitation'; | ||
| @@ -1,491 +0,0 @@ | |||
| 1 | ALTER TABLE "sources" ADD COLUMN "recipe_id" text;--> statement-breakpoint | ||
| 2 | ALTER TABLE "sources" ADD COLUMN "recipe_revision_id" text;--> statement-breakpoint | ||
| 3 | ALTER TABLE "sources" ADD COLUMN "binding_config_json" text;--> statement-breakpoint | ||
| 4 | |||
| 5 | CREATE TABLE "source_recipes" ( | ||
| 6 | "id" text PRIMARY KEY NOT NULL, | ||
| 7 | "kind" text NOT NULL, | ||
| 8 | "importer_kind" text NOT NULL, | ||
| 9 | "provider_key" text NOT NULL, | ||
| 10 | "name" text NOT NULL, | ||
| 11 | "summary" text, | ||
| 12 | "visibility" text NOT NULL, | ||
| 13 | "latest_revision_id" text NOT NULL, | ||
| 14 | "created_at" bigint NOT NULL, | ||
| 15 | "updated_at" bigint NOT NULL, | ||
| 16 | CONSTRAINT "source_recipes_kind_check" CHECK ("kind" in ('http_recipe', 'graphql_recipe', 'mcp_recipe', 'internal_recipe')), | ||
| 17 | CONSTRAINT "source_recipes_importer_kind_check" CHECK ("importer_kind" in ( | ||
| 18 | 'openapi', | ||
| 19 | 'google_discovery', | ||
| 20 | 'postman_collection', | ||
| 21 | 'snippet_bundle', | ||
| 22 | 'graphql_introspection', | ||
| 23 | 'mcp_manifest', | ||
| 24 | 'internal_manifest' | ||
| 25 | )), | ||
| 26 | CONSTRAINT "source_recipes_visibility_check" CHECK ("visibility" in ('private', 'workspace', 'organization', 'public')) | ||
| 27 | );--> statement-breakpoint | ||
| 28 | |||
| 29 | CREATE TABLE "source_recipe_revisions" ( | ||
| 30 | "id" text PRIMARY KEY NOT NULL, | ||
| 31 | "recipe_id" text NOT NULL, | ||
| 32 | "revision_number" bigint NOT NULL, | ||
| 33 | "source_config_json" text NOT NULL, | ||
| 34 | "manifest_json" text, | ||
| 35 | "manifest_hash" text, | ||
| 36 | "created_at" bigint NOT NULL, | ||
| 37 | "updated_at" bigint NOT NULL | ||
| 38 | );--> statement-breakpoint | ||
| 39 | |||
| 40 | CREATE TABLE "source_recipe_documents" ( | ||
| 41 | "id" text PRIMARY KEY NOT NULL, | ||
| 42 | "recipe_revision_id" text NOT NULL, | ||
| 43 | "document_kind" text NOT NULL, | ||
| 44 | "document_key" text NOT NULL, | ||
| 45 | "content_text" text NOT NULL, | ||
| 46 | "content_hash" text NOT NULL, | ||
| 47 | "fetched_at" bigint, | ||
| 48 | "created_at" bigint NOT NULL, | ||
| 49 | "updated_at" bigint NOT NULL, | ||
| 50 | CONSTRAINT "source_recipe_documents_kind_check" CHECK ("document_kind" in ( | ||
| 51 | 'google_discovery', | ||
| 52 | 'openapi', | ||
| 53 | 'postman_collection', | ||
| 54 | 'postman_environment', | ||
| 55 | 'graphql_introspection', | ||
| 56 | 'mcp_manifest' | ||
| 57 | )) | ||
| 58 | );--> statement-breakpoint | ||
| 59 | |||
| 60 | CREATE TABLE "source_recipe_operations" ( | ||
| 61 | "id" text PRIMARY KEY NOT NULL, | ||
| 62 | "recipe_revision_id" text NOT NULL, | ||
| 63 | "operation_key" text NOT NULL, | ||
| 64 | "transport_kind" text NOT NULL, | ||
| 65 | "tool_id" text NOT NULL, | ||
| 66 | "title" text, | ||
| 67 | "description" text, | ||
| 68 | "operation_kind" text NOT NULL, | ||
| 69 | "search_text" text NOT NULL, | ||
| 70 | "input_schema_json" text, | ||
| 71 | "output_schema_json" text, | ||
| 72 | "provider_kind" text NOT NULL, | ||
| 73 | "provider_data_json" text, | ||
| 74 | "mcp_tool_name" text, | ||
| 75 | "openapi_method" text, | ||
| 76 | "openapi_path_template" text, | ||
| 77 | "openapi_operation_hash" text, | ||
| 78 | "openapi_raw_tool_id" text, | ||
| 79 | "openapi_operation_id" text, | ||
| 80 | "openapi_tags_json" text, | ||
| 81 | "openapi_request_body_required" boolean, | ||
| 82 | "graphql_operation_type" text, | ||
| 83 | "graphql_operation_name" text, | ||
| 84 | "created_at" bigint NOT NULL, | ||
| 85 | "updated_at" bigint NOT NULL, | ||
| 86 | CONSTRAINT "source_recipe_operations_transport_kind_check" CHECK ("transport_kind" in ('http', 'graphql', 'mcp', 'internal')), | ||
| 87 | CONSTRAINT "source_recipe_operations_kind_check" CHECK ("operation_kind" in ('read', 'write', 'delete', 'unknown')), | ||
| 88 | CONSTRAINT "source_recipe_operations_provider_kind_check" CHECK ("provider_kind" in ('mcp', 'openapi', 'graphql', 'internal')) | ||
| 89 | );--> statement-breakpoint | ||
| 90 | |||
| 91 | CREATE TABLE "workspace_source_oauth_clients" ( | ||
| 92 | "id" text PRIMARY KEY NOT NULL, | ||
| 93 | "workspace_id" text NOT NULL, | ||
| 94 | "source_id" text NOT NULL, | ||
| 95 | "provider_key" text NOT NULL, | ||
| 96 | "client_id" text NOT NULL, | ||
| 97 | "client_secret_provider_id" text, | ||
| 98 | "client_secret_handle" text, | ||
| 99 | "client_metadata_json" text, | ||
| 100 | "created_at" bigint NOT NULL, | ||
| 101 | "updated_at" bigint NOT NULL | ||
| 102 | );--> statement-breakpoint | ||
| 103 | |||
| 104 | UPDATE "sources" | ||
| 105 | SET | ||
| 106 | "recipe_id" = 'src_recipe_' || md5("workspace_id" || ':' || "source_id"), | ||
| 107 | "recipe_revision_id" = 'src_recipe_rev_' || md5("workspace_id" || ':' || "source_id"), | ||
| 108 | "binding_config_json" = null;--> statement-breakpoint | ||
| 109 | |||
| 110 | INSERT INTO "source_recipes" ( | ||
| 111 | "id", | ||
| 112 | "kind", | ||
| 113 | "importer_kind", | ||
| 114 | "provider_key", | ||
| 115 | "name", | ||
| 116 | "summary", | ||
| 117 | "visibility", | ||
| 118 | "latest_revision_id", | ||
| 119 | "created_at", | ||
| 120 | "updated_at" | ||
| 121 | ) | ||
| 122 | SELECT | ||
| 123 | "s"."recipe_id", | ||
| 124 | CASE | ||
| 125 | WHEN "s"."kind" = 'openapi' THEN 'http_recipe' | ||
| 126 | WHEN "s"."kind" = 'graphql' THEN 'graphql_recipe' | ||
| 127 | WHEN "s"."kind" = 'mcp' THEN 'mcp_recipe' | ||
| 128 | ELSE 'internal_recipe' | ||
| 129 | END, | ||
| 130 | CASE | ||
| 131 | WHEN "s"."kind" = 'openapi' THEN 'openapi' | ||
| 132 | WHEN "s"."kind" = 'graphql' THEN 'graphql_introspection' | ||
| 133 | WHEN "s"."kind" = 'mcp' THEN 'mcp_manifest' | ||
| 134 | ELSE 'internal_manifest' | ||
| 135 | END, | ||
| 136 | CASE | ||
| 137 | WHEN "s"."kind" = 'openapi' THEN 'generic_http' | ||
| 138 | WHEN "s"."kind" = 'graphql' THEN 'generic_graphql' | ||
| 139 | WHEN "s"."kind" = 'mcp' THEN 'generic_mcp' | ||
| 140 | ELSE 'generic_internal' | ||
| 141 | END, | ||
| 142 | "s"."name", | ||
| 143 | null, | ||
| 144 | 'workspace', | ||
| 145 | "s"."recipe_revision_id", | ||
| 146 | "s"."created_at", | ||
| 147 | "s"."updated_at" | ||
| 148 | FROM "sources" AS "s";--> statement-breakpoint | ||
| 149 | |||
| 150 | INSERT INTO "source_recipe_revisions" ( | ||
| 151 | "id", | ||
| 152 | "recipe_id", | ||
| 153 | "revision_number", | ||
| 154 | "source_config_json", | ||
| 155 | "manifest_json", | ||
| 156 | "manifest_hash", | ||
| 157 | "created_at", | ||
| 158 | "updated_at" | ||
| 159 | ) | ||
| 160 | SELECT | ||
| 161 | "s"."recipe_revision_id", | ||
| 162 | "s"."recipe_id", | ||
| 163 | 1, | ||
| 164 | CASE | ||
| 165 | WHEN "s"."kind" = 'mcp' THEN jsonb_build_object( | ||
| 166 | 'kind', 'mcp', | ||
| 167 | 'endpoint', "s"."endpoint", | ||
| 168 | 'transport', "s"."transport", | ||
| 169 | 'queryParams', COALESCE("s"."query_params_json"::jsonb, 'null'::jsonb), | ||
| 170 | 'headers', COALESCE("s"."headers_json"::jsonb, 'null'::jsonb) | ||
| 171 | )::text | ||
| 172 | WHEN "s"."kind" = 'openapi' THEN jsonb_build_object( | ||
| 173 | 'kind', 'openapi', | ||
| 174 | 'endpoint', "s"."endpoint", | ||
| 175 | 'specUrl', COALESCE("s"."spec_url", "s"."endpoint"), | ||
| 176 | 'defaultHeaders', COALESCE("s"."default_headers_json"::jsonb, 'null'::jsonb) | ||
| 177 | )::text | ||
| 178 | WHEN "s"."kind" = 'graphql' THEN jsonb_build_object( | ||
| 179 | 'kind', 'graphql', | ||
| 180 | 'endpoint', "s"."endpoint", | ||
| 181 | 'defaultHeaders', COALESCE("s"."default_headers_json"::jsonb, 'null'::jsonb) | ||
| 182 | )::text | ||
| 183 | ELSE jsonb_build_object( | ||
| 184 | 'kind', 'internal', | ||
| 185 | 'endpoint', "s"."endpoint" | ||
| 186 | )::text | ||
| 187 | END, | ||
| 188 | null, | ||
| 189 | "s"."source_hash", | ||
| 190 | "s"."created_at", | ||
| 191 | "s"."updated_at" | ||
| 192 | FROM "sources" AS "s";--> statement-breakpoint | ||
| 193 | |||
| 194 | INSERT INTO "source_recipe_documents" ( | ||
| 195 | "id", | ||
| 196 | "recipe_revision_id", | ||
| 197 | "document_kind", | ||
| 198 | "document_key", | ||
| 199 | "content_text", | ||
| 200 | "content_hash", | ||
| 201 | "fetched_at", | ||
| 202 | "created_at", | ||
| 203 | "updated_at" | ||
| 204 | ) | ||
| 205 | SELECT | ||
| 206 | 'src_recipe_doc_' || md5("s"."workspace_id" || ':' || "s"."source_id" || ':primary'), | ||
| 207 | "s"."recipe_revision_id", | ||
| 208 | CASE | ||
| 209 | WHEN "s"."kind" = 'openapi' THEN 'openapi' | ||
| 210 | WHEN "s"."kind" = 'graphql' THEN 'graphql_introspection' | ||
| 211 | ELSE 'mcp_manifest' | ||
| 212 | END, | ||
| 213 | CASE | ||
| 214 | WHEN "s"."kind" = 'openapi' THEN COALESCE("s"."spec_url", "s"."endpoint") | ||
| 215 | ELSE "s"."endpoint" | ||
| 216 | END, | ||
| 217 | "s"."source_document_text", | ||
| 218 | md5("s"."source_document_text"), | ||
| 219 | "s"."updated_at", | ||
| 220 | "s"."created_at", | ||
| 221 | "s"."updated_at" | ||
| 222 | FROM "sources" AS "s" | ||
| 223 | WHERE "s"."source_document_text" IS NOT NULL;--> statement-breakpoint | ||
| 224 | |||
| 225 | INSERT INTO "source_recipe_operations" ( | ||
| 226 | "id", | ||
| 227 | "recipe_revision_id", | ||
| 228 | "operation_key", | ||
| 229 | "transport_kind", | ||
| 230 | "tool_id", | ||
| 231 | "title", | ||
| 232 | "description", | ||
| 233 | "operation_kind", | ||
| 234 | "search_text", | ||
| 235 | "input_schema_json", | ||
| 236 | "output_schema_json", | ||
| 237 | "provider_kind", | ||
| 238 | "provider_data_json", | ||
| 239 | "mcp_tool_name", | ||
| 240 | "openapi_method", | ||
| 241 | "openapi_path_template", | ||
| 242 | "openapi_operation_hash", | ||
| 243 | "openapi_raw_tool_id", | ||
| 244 | "openapi_operation_id", | ||
| 245 | "openapi_tags_json", | ||
| 246 | "openapi_request_body_required", | ||
| 247 | "graphql_operation_type", | ||
| 248 | "graphql_operation_name", | ||
| 249 | "created_at", | ||
| 250 | "updated_at" | ||
| 251 | ) | ||
| 252 | SELECT | ||
| 253 | 'src_recipe_op_' || md5("ta"."workspace_id" || ':' || "ta"."source_id" || ':' || "ta"."path"), | ||
| 254 | "s"."recipe_revision_id", | ||
| 255 | "ta"."tool_id", | ||
| 256 | CASE | ||
| 257 | WHEN "ta"."provider_kind" = 'openapi' THEN 'http' | ||
| 258 | ELSE 'mcp' | ||
| 259 | END, | ||
| 260 | "ta"."tool_id", | ||
| 261 | "ta"."title", | ||
| 262 | "ta"."description", | ||
| 263 | CASE | ||
| 264 | WHEN "ta"."provider_kind" = 'openapi' AND upper(COALESCE("ta"."openapi_method", '')) IN ('GET', 'HEAD') THEN 'read' | ||
| 265 | WHEN "ta"."provider_kind" = 'openapi' AND upper(COALESCE("ta"."openapi_method", '')) = 'DELETE' THEN 'delete' | ||
| 266 | WHEN "ta"."provider_kind" = 'openapi' THEN 'write' | ||
| 267 | ELSE 'unknown' | ||
| 268 | END, | ||
| 269 | "ta"."search_text", | ||
| 270 | "ta"."input_schema_json", | ||
| 271 | "ta"."output_schema_json", | ||
| 272 | "ta"."provider_kind", | ||
| 273 | CASE | ||
| 274 | WHEN "ta"."provider_kind" = 'mcp' THEN jsonb_build_object( | ||
| 275 | 'kind', 'mcp', | ||
| 276 | 'toolId', "ta"."tool_id", | ||
| 277 | 'toolName', COALESCE("ta"."mcp_tool_name", "ta"."title", "ta"."path"), | ||
| 278 | 'description', "ta"."description" | ||
| 279 | )::text | ||
| 280 | ELSE jsonb_build_object( | ||
| 281 | 'kind', 'openapi', | ||
| 282 | 'toolId', "ta"."tool_id", | ||
| 283 | 'rawToolId', "ta"."openapi_raw_tool_id", | ||
| 284 | 'operationId', "ta"."openapi_operation_id", | ||
| 285 | 'tags', COALESCE("ta"."openapi_tags_json"::jsonb, '[]'::jsonb), | ||
| 286 | 'method', "ta"."openapi_method", | ||
| 287 | 'path', "ta"."openapi_path_template", | ||
| 288 | 'operationHash', "ta"."openapi_operation_hash" | ||
| 289 | )::text | ||
| 290 | END, | ||
| 291 | "ta"."mcp_tool_name", | ||
| 292 | "ta"."openapi_method", | ||
| 293 | "ta"."openapi_path_template", | ||
| 294 | "ta"."openapi_operation_hash", | ||
| 295 | "ta"."openapi_raw_tool_id", | ||
| 296 | "ta"."openapi_operation_id", | ||
| 297 | "ta"."openapi_tags_json", | ||
| 298 | "ta"."openapi_request_body_required", | ||
| 299 | null, | ||
| 300 | null, | ||
| 301 | "ta"."created_at", | ||
| 302 | "ta"."updated_at" | ||
| 303 | FROM "tool_artifacts" AS "ta" | ||
| 304 | JOIN "sources" AS "s" | ||
| 305 | ON "s"."workspace_id" = "ta"."workspace_id" | ||
| 306 | AND "s"."source_id" = "ta"."source_id";--> statement-breakpoint | ||
| 307 | |||
| 308 | WITH "mcp_manifests" AS ( | ||
| 309 | SELECT | ||
| 310 | "s"."workspace_id", | ||
| 311 | "s"."source_id", | ||
| 312 | "s"."recipe_revision_id", | ||
| 313 | jsonb_build_object( | ||
| 314 | 'version', 1, | ||
| 315 | 'tools', COALESCE( | ||
| 316 | jsonb_agg( | ||
| 317 | jsonb_build_object( | ||
| 318 | 'toolId', "ta"."tool_id", | ||
| 319 | 'toolName', COALESCE("ta"."mcp_tool_name", "ta"."title", "ta"."path"), | ||
| 320 | 'description', "ta"."description", | ||
| 321 | 'inputSchemaJson', "ta"."input_schema_json", | ||
| 322 | 'outputSchemaJson', "ta"."output_schema_json" | ||
| 323 | ) | ||
| 324 | ORDER BY "ta"."path" | ||
| 325 | ) FILTER (WHERE "ta"."provider_kind" = 'mcp'), | ||
| 326 | '[]'::jsonb | ||
| 327 | ) | ||
| 328 | )::text AS "manifest_json" | ||
| 329 | FROM "sources" AS "s" | ||
| 330 | LEFT JOIN "tool_artifacts" AS "ta" | ||
| 331 | ON "ta"."workspace_id" = "s"."workspace_id" | ||
| 332 | AND "ta"."source_id" = "s"."source_id" | ||
| 333 | WHERE "s"."kind" = 'mcp' | ||
| 334 | GROUP BY "s"."workspace_id", "s"."source_id", "s"."recipe_revision_id" | ||
| 335 | ) | ||
| 336 | UPDATE "source_recipe_revisions" AS "rev" | ||
| 337 | SET | ||
| 338 | "manifest_json" = "mcp"."manifest_json", | ||
| 339 | "manifest_hash" = md5("mcp"."manifest_json") | ||
| 340 | FROM "mcp_manifests" AS "mcp" | ||
| 341 | WHERE "rev"."id" = "mcp"."recipe_revision_id";--> statement-breakpoint | ||
| 342 | |||
| 343 | INSERT INTO "source_recipe_documents" ( | ||
| 344 | "id", | ||
| 345 | "recipe_revision_id", | ||
| 346 | "document_kind", | ||
| 347 | "document_key", | ||
| 348 | "content_text", | ||
| 349 | "content_hash", | ||
| 350 | "fetched_at", | ||
| 351 | "created_at", | ||
| 352 | "updated_at" | ||
| 353 | ) | ||
| 354 | SELECT | ||
| 355 | 'src_recipe_doc_' || md5("s"."workspace_id" || ':' || "s"."source_id" || ':mcp_manifest'), | ||
| 356 | "s"."recipe_revision_id", | ||
| 357 | 'mcp_manifest', | ||
| 358 | "s"."endpoint", | ||
| 359 | "rev"."manifest_json", | ||
| 360 | md5("rev"."manifest_json"), | ||
| 361 | "s"."updated_at", | ||
| 362 | "s"."created_at", | ||
| 363 | "s"."updated_at" | ||
| 364 | FROM "sources" AS "s" | ||
| 365 | JOIN "source_recipe_revisions" AS "rev" | ||
| 366 | ON "rev"."id" = "s"."recipe_revision_id" | ||
| 367 | WHERE "s"."kind" = 'mcp' | ||
| 368 | AND "rev"."manifest_json" IS NOT NULL | ||
| 369 | AND NOT EXISTS ( | ||
| 370 | SELECT 1 | ||
| 371 | FROM "source_recipe_documents" AS "doc" | ||
| 372 | WHERE "doc"."recipe_revision_id" = "s"."recipe_revision_id" | ||
| 373 | AND "doc"."document_kind" = 'mcp_manifest' | ||
| 374 | AND "doc"."document_key" = "s"."endpoint" | ||
| 375 | );--> statement-breakpoint | ||
| 376 | |||
| 377 | ALTER TABLE "sources" ALTER COLUMN "recipe_id" SET NOT NULL;--> statement-breakpoint | ||
| 378 | ALTER TABLE "sources" ALTER COLUMN "recipe_revision_id" SET NOT NULL;--> statement-breakpoint | ||
| 379 | CREATE INDEX "sources_workspace_recipe_idx" ON "sources" ("workspace_id","recipe_id","updated_at","source_id");--> statement-breakpoint | ||
| 380 | |||
| 381 | CREATE TABLE "workspace_source_credentials" ( | ||
| 382 | "id" text PRIMARY KEY NOT NULL, | ||
| 383 | "workspace_id" text NOT NULL, | ||
| 384 | "source_id" text NOT NULL, | ||
| 385 | "actor_account_id" text, | ||
| 386 | "auth_kind" text NOT NULL, | ||
| 387 | "auth_header_name" text NOT NULL, | ||
| 388 | "auth_prefix" text NOT NULL, | ||
| 389 | "token_provider_id" text NOT NULL, | ||
| 390 | "token_handle" text NOT NULL, | ||
| 391 | "refresh_token_provider_id" text, | ||
| 392 | "refresh_token_handle" text, | ||
| 393 | "created_at" bigint NOT NULL, | ||
| 394 | "updated_at" bigint NOT NULL, | ||
| 395 | CONSTRAINT "credentials_auth_kind_check" CHECK ("auth_kind" in ('bearer', 'oauth2')) | ||
| 396 | );--> statement-breakpoint | ||
| 397 | |||
| 398 | INSERT INTO "workspace_source_credentials" ( | ||
| 399 | "id", | ||
| 400 | "workspace_id", | ||
| 401 | "source_id", | ||
| 402 | "actor_account_id", | ||
| 403 | "auth_kind", | ||
| 404 | "auth_header_name", | ||
| 405 | "auth_prefix", | ||
| 406 | "token_provider_id", | ||
| 407 | "token_handle", | ||
| 408 | "refresh_token_provider_id", | ||
| 409 | "refresh_token_handle", | ||
| 410 | "created_at", | ||
| 411 | "updated_at" | ||
| 412 | ) | ||
| 413 | SELECT | ||
| 414 | CASE | ||
| 415 | WHEN count(*) OVER (PARTITION BY "c"."id") = 1 THEN "c"."id" | ||
| 416 | ELSE "c"."id" || ':' || "scb"."source_id" | ||
| 417 | END, | ||
| 418 | "c"."workspace_id", | ||
| 419 | "scb"."source_id", | ||
| 420 | null, | ||
| 421 | "c"."auth_kind", | ||
| 422 | "c"."auth_header_name", | ||
| 423 | "c"."auth_prefix", | ||
| 424 | "c"."token_provider_id", | ||
| 425 | "c"."token_handle", | ||
| 426 | "c"."refresh_token_provider_id", | ||
| 427 | "c"."refresh_token_handle", | ||
| 428 | "c"."created_at", | ||
| 429 | "c"."updated_at" | ||
| 430 | FROM "credentials" AS "c" | ||
| 431 | JOIN "source_credential_bindings" AS "scb" | ||
| 432 | ON "scb"."workspace_id" = "c"."workspace_id" | ||
| 433 | AND "scb"."credential_id" = "c"."id";--> statement-breakpoint | ||
| 434 | |||
| 435 | DROP TABLE "credentials";--> statement-breakpoint | ||
| 436 | DROP TABLE "source_credential_bindings";--> statement-breakpoint | ||
| 437 | CREATE INDEX "credentials_workspace_idx" ON "workspace_source_credentials" ("workspace_id","updated_at","id");--> statement-breakpoint | ||
| 438 | CREATE UNIQUE INDEX "credentials_workspace_source_actor_idx" ON "workspace_source_credentials" ("workspace_id","source_id","actor_account_id");--> statement-breakpoint | ||
| 439 | CREATE INDEX "credentials_workspace_source_idx" ON "workspace_source_credentials" ("workspace_id","source_id","updated_at","id");--> statement-breakpoint | ||
| 440 | |||
| 441 | ALTER TABLE "source_auth_sessions" ADD COLUMN "actor_account_id" text;--> statement-breakpoint | ||
| 442 | ALTER TABLE "source_auth_sessions" ADD COLUMN "provider_kind" text;--> statement-breakpoint | ||
| 443 | ALTER TABLE "source_auth_sessions" ADD COLUMN "session_data_json" text;--> statement-breakpoint | ||
| 444 | |||
| 445 | UPDATE "source_auth_sessions" | ||
| 446 | SET | ||
| 447 | "provider_kind" = 'mcp_oauth', | ||
| 448 | "session_data_json" = jsonb_build_object( | ||
| 449 | 'kind', 'mcp_oauth', | ||
| 450 | 'endpoint', "endpoint", | ||
| 451 | 'redirectUri', "redirect_uri", | ||
| 452 | 'scope', "scope", | ||
| 453 | 'resourceMetadataUrl', "resource_metadata_url", | ||
| 454 | 'authorizationServerUrl', "authorization_server_url", | ||
| 455 | 'resourceMetadataJson', "resource_metadata_json", | ||
| 456 | 'authorizationServerMetadataJson', "authorization_server_metadata_json", | ||
| 457 | 'clientInformationJson', "client_information_json", | ||
| 458 | 'codeVerifier', "code_verifier", | ||
| 459 | 'authorizationUrl', "authorization_url" | ||
| 460 | )::text;--> statement-breakpoint | ||
| 461 | |||
| 462 | ALTER TABLE "source_auth_sessions" ALTER COLUMN "provider_kind" SET NOT NULL;--> statement-breakpoint | ||
| 463 | ALTER TABLE "source_auth_sessions" ALTER COLUMN "session_data_json" SET NOT NULL;--> statement-breakpoint | ||
| 464 | DROP INDEX IF EXISTS "source_auth_sessions_pending_idx";--> statement-breakpoint | ||
| 465 | ALTER TABLE "source_auth_sessions" DROP CONSTRAINT "source_auth_sessions_strategy_check";--> statement-breakpoint | ||
| 466 | ALTER TABLE "source_auth_sessions" DROP COLUMN "strategy";--> statement-breakpoint | ||
| 467 | ALTER TABLE "source_auth_sessions" DROP COLUMN "endpoint";--> statement-breakpoint | ||
| 468 | ALTER TABLE "source_auth_sessions" DROP COLUMN "redirect_uri";--> statement-breakpoint | ||
| 469 | ALTER TABLE "source_auth_sessions" DROP COLUMN "scope";--> statement-breakpoint | ||
| 470 | ALTER TABLE "source_auth_sessions" DROP COLUMN "resource_metadata_url";--> statement-breakpoint | ||
| 471 | ALTER TABLE "source_auth_sessions" DROP COLUMN "authorization_server_url";--> statement-breakpoint | ||
| 472 | ALTER TABLE "source_auth_sessions" DROP COLUMN "resource_metadata_json";--> statement-breakpoint | ||
| 473 | ALTER TABLE "source_auth_sessions" DROP COLUMN "authorization_server_metadata_json";--> statement-breakpoint | ||
| 474 | ALTER TABLE "source_auth_sessions" DROP COLUMN "client_information_json";--> statement-breakpoint | ||
| 475 | ALTER TABLE "source_auth_sessions" DROP COLUMN "code_verifier";--> statement-breakpoint | ||
| 476 | ALTER TABLE "source_auth_sessions" DROP COLUMN "authorization_url";--> statement-breakpoint | ||
| 477 | ALTER TABLE "source_auth_sessions" ADD CONSTRAINT "source_auth_sessions_provider_kind_check" CHECK ("provider_kind" in ('mcp_oauth', 'oauth2_pkce'));--> statement-breakpoint | ||
| 478 | CREATE INDEX "source_auth_sessions_pending_idx" ON "source_auth_sessions" ("workspace_id","source_id","actor_account_id","status","updated_at","id");--> statement-breakpoint | ||
| 479 | |||
| 480 | CREATE INDEX "source_recipes_provider_updated_idx" ON "source_recipes" ("provider_key","updated_at","id");--> statement-breakpoint | ||
| 481 | CREATE INDEX "source_recipes_visibility_updated_idx" ON "source_recipes" ("visibility","updated_at","id");--> statement-breakpoint | ||
| 482 | CREATE UNIQUE INDEX "source_recipe_revisions_recipe_revision_idx" ON "source_recipe_revisions" ("recipe_id","revision_number");--> statement-breakpoint | ||
| 483 | CREATE UNIQUE INDEX "source_recipe_revisions_recipe_manifest_idx" ON "source_recipe_revisions" ("recipe_id","manifest_hash");--> statement-breakpoint | ||
| 484 | CREATE INDEX "source_recipe_revisions_recipe_created_idx" ON "source_recipe_revisions" ("recipe_id","created_at","id");--> statement-breakpoint | ||
| 485 | CREATE UNIQUE INDEX "source_recipe_documents_revision_kind_key_idx" ON "source_recipe_documents" ("recipe_revision_id","document_kind","document_key");--> statement-breakpoint | ||
| 486 | CREATE INDEX "source_recipe_documents_revision_created_idx" ON "source_recipe_documents" ("recipe_revision_id","created_at","id");--> statement-breakpoint | ||
| 487 | CREATE UNIQUE INDEX "source_recipe_operations_revision_operation_key_idx" ON "source_recipe_operations" ("recipe_revision_id","operation_key");--> statement-breakpoint | ||
| 488 | CREATE INDEX "source_recipe_operations_revision_tool_idx" ON "source_recipe_operations" ("recipe_revision_id","tool_id","updated_at","id");--> statement-breakpoint | ||
| 489 | CREATE INDEX "source_recipe_operations_search_text_idx" ON "source_recipe_operations" USING gin (to_tsvector('simple', "search_text"));--> statement-breakpoint | ||
| 490 | CREATE UNIQUE INDEX "workspace_source_oauth_clients_workspace_source_provider_idx" ON "workspace_source_oauth_clients" ("workspace_id","source_id","provider_key");--> statement-breakpoint | ||
| 491 | CREATE INDEX "workspace_source_oauth_clients_workspace_idx" ON "workspace_source_oauth_clients" ("workspace_id","updated_at","source_id"); | ||
| @@ -1,14 +0,0 @@ | |||
| 1 | CREATE TABLE "source_recipe_schema_bundles" ( | ||
| 2 | "id" text PRIMARY KEY NOT NULL, | ||
| 3 | "recipe_revision_id" text NOT NULL, | ||
| 4 | "bundle_kind" text NOT NULL, | ||
| 5 | "refs_json" text NOT NULL, | ||
| 6 | "content_hash" text NOT NULL, | ||
| 7 | "created_at" bigint NOT NULL, | ||
| 8 | "updated_at" bigint NOT NULL, | ||
| 9 | CONSTRAINT "source_recipe_schema_bundles_kind_check" CHECK ("source_recipe_schema_bundles"."bundle_kind" in ('json_schema_ref_map')) | ||
| 10 | ); | ||
| 11 | --> statement-breakpoint | ||
| 12 | CREATE UNIQUE INDEX "source_recipe_schema_bundles_revision_kind_idx" ON "source_recipe_schema_bundles" USING btree ("recipe_revision_id","bundle_kind"); | ||
| 13 | --> statement-breakpoint | ||
| 14 | CREATE INDEX "source_recipe_schema_bundles_revision_created_idx" ON "source_recipe_schema_bundles" USING btree ("recipe_revision_id","created_at","id"); | ||
| @@ -1,9 +0,0 @@ | |||
| 1 | ALTER TABLE "sources" DROP CONSTRAINT IF EXISTS "sources_kind_check"; | ||
| 2 | --> statement-breakpoint | ||
| 3 | ALTER TABLE "source_recipes" DROP CONSTRAINT IF EXISTS "source_recipes_importer_kind_check"; | ||
| 4 | --> statement-breakpoint | ||
| 5 | ALTER TABLE "source_recipe_documents" DROP CONSTRAINT IF EXISTS "source_recipe_documents_kind_check"; | ||
| 6 | --> statement-breakpoint | ||
| 7 | ALTER TABLE "source_recipe_schema_bundles" DROP CONSTRAINT IF EXISTS "source_recipe_schema_bundles_kind_check"; | ||
| 8 | --> statement-breakpoint | ||
| 9 | ALTER TABLE "source_recipe_operations" DROP CONSTRAINT IF EXISTS "source_recipe_operations_provider_kind_check"; | ||
| @@ -1,23 +0,0 @@ | |||
| 1 | ALTER TABLE "source_recipes" | ||
| 2 | DROP CONSTRAINT IF EXISTS "source_recipes_kind_check";--> statement-breakpoint | ||
| 3 | |||
| 4 | UPDATE "source_recipes" | ||
| 5 | SET "kind" = 'http_api' | ||
| 6 | WHERE "kind" IN ('http_recipe', 'graphql_recipe');--> statement-breakpoint | ||
| 7 | |||
| 8 | UPDATE "source_recipes" | ||
| 9 | SET "kind" = 'mcp' | ||
| 10 | WHERE "kind" = 'mcp_recipe';--> statement-breakpoint | ||
| 11 | |||
| 12 | UPDATE "source_recipes" | ||
| 13 | SET "kind" = 'internal' | ||
| 14 | WHERE "kind" = 'internal_recipe';--> statement-breakpoint | ||
| 15 | |||
| 16 | ALTER TABLE "source_recipes" | ||
| 17 | ADD CONSTRAINT "source_recipes_kind_check" | ||
| 18 | CHECK ("kind" in ('http_api', 'mcp', 'internal'));--> statement-breakpoint | ||
| 19 | |||
| 20 | CREATE TABLE IF NOT EXISTS "control_plane_data_migrations" ( | ||
| 21 | "id" text PRIMARY KEY NOT NULL, | ||
| 22 | "applied_at" bigint NOT NULL | ||
| 23 | ); | ||
| @@ -1,62 +0,0 @@ | |||
| 1 | UPDATE "sources" | ||
| 2 | SET "binding_config_json" = CASE | ||
| 3 | WHEN "binding_config_json" IS NOT NULL AND btrim("binding_config_json") <> '' THEN "binding_config_json" | ||
| 4 | WHEN "kind" = 'openapi' THEN jsonb_build_object( | ||
| 5 | 'adapterKey', 'openapi', | ||
| 6 | 'specUrl', COALESCE("spec_url", "endpoint"), | ||
| 7 | 'defaultHeaders', | ||
| 8 | CASE | ||
| 9 | WHEN "default_headers_json" IS NULL OR btrim("default_headers_json") = '' THEN NULL | ||
| 10 | ELSE "default_headers_json"::jsonb | ||
| 11 | END | ||
| 12 | )::text | ||
| 13 | WHEN "kind" = 'graphql' THEN jsonb_build_object( | ||
| 14 | 'adapterKey', 'graphql', | ||
| 15 | 'defaultHeaders', | ||
| 16 | CASE | ||
| 17 | WHEN "default_headers_json" IS NULL OR btrim("default_headers_json") = '' THEN NULL | ||
| 18 | ELSE "default_headers_json"::jsonb | ||
| 19 | END | ||
| 20 | )::text | ||
| 21 | WHEN "kind" = 'mcp' THEN jsonb_build_object( | ||
| 22 | 'adapterKey', 'mcp', | ||
| 23 | 'transport', "transport", | ||
| 24 | 'queryParams', | ||
| 25 | CASE | ||
| 26 | WHEN "query_params_json" IS NULL OR btrim("query_params_json") = '' THEN NULL | ||
| 27 | ELSE "query_params_json"::jsonb | ||
| 28 | END, | ||
| 29 | 'headers', | ||
| 30 | CASE | ||
| 31 | WHEN "headers_json" IS NULL OR btrim("headers_json") = '' THEN NULL | ||
| 32 | ELSE "headers_json"::jsonb | ||
| 33 | END | ||
| 34 | )::text | ||
| 35 | ELSE jsonb_build_object( | ||
| 36 | 'adapterKey', "kind" | ||
| 37 | )::text | ||
| 38 | END;--> statement-breakpoint | ||
| 39 | |||
| 40 | ALTER TABLE "sources" | ||
| 41 | ALTER COLUMN "binding_config_json" SET NOT NULL;--> statement-breakpoint | ||
| 42 | |||
| 43 | ALTER TABLE "sources" | ||
| 44 | DROP CONSTRAINT IF EXISTS "sources_transport_check";--> statement-breakpoint | ||
| 45 | |||
| 46 | ALTER TABLE "sources" | ||
| 47 | DROP COLUMN IF EXISTS "transport";--> statement-breakpoint | ||
| 48 | |||
| 49 | ALTER TABLE "sources" | ||
| 50 | DROP COLUMN IF EXISTS "query_params_json";--> statement-breakpoint | ||
| 51 | |||
| 52 | ALTER TABLE "sources" | ||
| 53 | DROP COLUMN IF EXISTS "headers_json";--> statement-breakpoint | ||
| 54 | |||
| 55 | ALTER TABLE "sources" | ||
| 56 | DROP COLUMN IF EXISTS "spec_url";--> statement-breakpoint | ||
| 57 | |||
| 58 | ALTER TABLE "sources" | ||
| 59 | DROP COLUMN IF EXISTS "default_headers_json";--> statement-breakpoint | ||
| 60 | |||
| 61 | ALTER TABLE "sources" | ||
| 62 | DROP COLUMN IF EXISTS "source_document_text"; | ||
| @@ -1,82 +0,0 @@ | |||
| 1 | ALTER TABLE "sources" ADD COLUMN "import_auth_policy" text;--> statement-breakpoint | ||
| 2 | |||
| 3 | UPDATE "sources" | ||
| 4 | SET "import_auth_policy" = CASE | ||
| 5 | WHEN "kind" = 'internal' THEN 'none' | ||
| 6 | ELSE 'reuse_runtime' | ||
| 7 | END | ||
| 8 | WHERE "import_auth_policy" IS NULL;--> statement-breakpoint | ||
| 9 | |||
| 10 | ALTER TABLE "sources" | ||
| 11 | ALTER COLUMN "import_auth_policy" SET NOT NULL;--> statement-breakpoint | ||
| 12 | |||
| 13 | ALTER TABLE "sources" | ||
| 14 | DROP CONSTRAINT IF EXISTS "sources_import_auth_policy_check";--> statement-breakpoint | ||
| 15 | |||
| 16 | ALTER TABLE "sources" | ||
| 17 | ADD CONSTRAINT "sources_import_auth_policy_check" | ||
| 18 | CHECK ("import_auth_policy" in ('none', 'reuse_runtime', 'separate'));--> statement-breakpoint | ||
| 19 | |||
| 20 | ALTER TABLE "source_recipe_revisions" ADD COLUMN "materialization_hash" text;--> statement-breakpoint | ||
| 21 | |||
| 22 | UPDATE "source_recipe_revisions" | ||
| 23 | SET "materialization_hash" = "manifest_hash" | ||
| 24 | WHERE "materialization_hash" IS NULL;--> statement-breakpoint | ||
| 25 | |||
| 26 | DROP INDEX IF EXISTS "source_recipe_revisions_recipe_manifest_idx";--> statement-breakpoint | ||
| 27 | |||
| 28 | CREATE UNIQUE INDEX "source_recipe_revisions_recipe_materialization_idx" | ||
| 29 | ON "source_recipe_revisions" ("recipe_id","materialization_hash");--> statement-breakpoint | ||
| 30 | |||
| 31 | CREATE INDEX "source_recipe_revisions_recipe_manifest_idx" | ||
| 32 | ON "source_recipe_revisions" ("recipe_id","manifest_hash");--> statement-breakpoint | ||
| 33 | |||
| 34 | ALTER TABLE "workspace_source_credentials" ADD COLUMN "slot" text;--> statement-breakpoint | ||
| 35 | |||
| 36 | UPDATE "workspace_source_credentials" | ||
| 37 | SET "slot" = 'runtime' | ||
| 38 | WHERE "slot" IS NULL;--> statement-breakpoint | ||
| 39 | |||
| 40 | ALTER TABLE "workspace_source_credentials" | ||
| 41 | ALTER COLUMN "slot" SET NOT NULL;--> statement-breakpoint | ||
| 42 | |||
| 43 | DROP INDEX IF EXISTS "credentials_workspace_source_actor_idx";--> statement-breakpoint | ||
| 44 | |||
| 45 | ALTER TABLE "workspace_source_credentials" | ||
| 46 | DROP CONSTRAINT IF EXISTS "credentials_slot_check";--> statement-breakpoint | ||
| 47 | |||
| 48 | ALTER TABLE "workspace_source_credentials" | ||
| 49 | ADD CONSTRAINT "credentials_slot_check" | ||
| 50 | CHECK ("slot" in ('runtime', 'import'));--> statement-breakpoint | ||
| 51 | |||
| 52 | CREATE UNIQUE INDEX "credentials_workspace_source_actor_idx" | ||
| 53 | ON "workspace_source_credentials" ("workspace_id","source_id","actor_account_id","slot");--> statement-breakpoint | ||
| 54 | |||
| 55 | ALTER TABLE "source_auth_sessions" ADD COLUMN "credential_slot" text;--> statement-breakpoint | ||
| 56 | |||
| 57 | UPDATE "source_auth_sessions" | ||
| 58 | SET "credential_slot" = 'runtime' | ||
| 59 | WHERE "credential_slot" IS NULL;--> statement-breakpoint | ||
| 60 | |||
| 61 | ALTER TABLE "source_auth_sessions" | ||
| 62 | ALTER COLUMN "credential_slot" SET NOT NULL;--> statement-breakpoint | ||
| 63 | |||
| 64 | DROP INDEX IF EXISTS "source_auth_sessions_pending_idx";--> statement-breakpoint | ||
| 65 | |||
| 66 | ALTER TABLE "source_auth_sessions" | ||
| 67 | DROP CONSTRAINT IF EXISTS "source_auth_sessions_credential_slot_check";--> statement-breakpoint | ||
| 68 | |||
| 69 | ALTER TABLE "source_auth_sessions" | ||
| 70 | ADD CONSTRAINT "source_auth_sessions_credential_slot_check" | ||
| 71 | CHECK ("credential_slot" in ('runtime', 'import'));--> statement-breakpoint | ||
| 72 | |||
| 73 | CREATE INDEX "source_auth_sessions_pending_idx" | ||
| 74 | ON "source_auth_sessions" ( | ||
| 75 | "workspace_id", | ||
| 76 | "source_id", | ||
| 77 | "actor_account_id", | ||
| 78 | "credential_slot", | ||
| 79 | "status", | ||
| 80 | "updated_at", | ||
| 81 | "id" | ||
| 82 | );--> statement-breakpoint | ||
| @@ -1,73 +0,0 @@ | |||
| 1 | UPDATE "source_recipe_operations" | ||
| 2 | SET "provider_data_json" = CASE | ||
| 3 | WHEN "provider_kind" = 'openapi' THEN jsonb_strip_nulls(jsonb_build_object( | ||
| 4 | 'kind', 'openapi', | ||
| 5 | 'toolId', "tool_id", | ||
| 6 | 'rawToolId', COALESCE("openapi_raw_tool_id", "tool_id"), | ||
| 7 | 'operationId', "openapi_operation_id", | ||
| 8 | 'group', CASE | ||
| 9 | WHEN position('.' in "tool_id") > 0 THEN split_part("tool_id", '.', 1) | ||
| 10 | ELSE "tool_id" | ||
| 11 | END, | ||
| 12 | 'leaf', CASE | ||
| 13 | WHEN position('.' in "tool_id") > 0 THEN substring("tool_id" from position('.' in "tool_id") + 1) | ||
| 14 | ELSE "tool_id" | ||
| 15 | END, | ||
| 16 | 'tags', CASE | ||
| 17 | WHEN "openapi_tags_json" IS NOT NULL THEN "openapi_tags_json"::jsonb | ||
| 18 | ELSE '[]'::jsonb | ||
| 19 | END, | ||
| 20 | 'method', COALESCE("openapi_method", 'get'), | ||
| 21 | 'path', COALESCE("openapi_path_template", '/'), | ||
| 22 | 'operationHash', COALESCE( | ||
| 23 | "openapi_operation_hash", | ||
| 24 | md5(COALESCE("tool_id", '') || ':' || COALESCE("openapi_path_template", '/')) | ||
| 25 | ), | ||
| 26 | 'invocation', jsonb_build_object( | ||
| 27 | 'method', COALESCE("openapi_method", 'get'), | ||
| 28 | 'pathTemplate', COALESCE("openapi_path_template", '/'), | ||
| 29 | 'parameters', '[]'::jsonb, | ||
| 30 | 'requestBody', CASE | ||
| 31 | WHEN "openapi_request_body_required" IS NULL THEN null | ||
| 32 | ELSE jsonb_build_object( | ||
| 33 | 'required', "openapi_request_body_required", | ||
| 34 | 'contentTypes', '[]'::jsonb | ||
| 35 | ) | ||
| 36 | END | ||
| 37 | ) | ||
| 38 | ))::text | ||
| 39 | WHEN "provider_kind" = 'graphql' THEN jsonb_build_object( | ||
| 40 | 'kind', 'graphql', | ||
| 41 | 'toolKind', 'field', | ||
| 42 | 'toolId', "tool_id", | ||
| 43 | 'rawToolId', null, | ||
| 44 | 'group', null, | ||
| 45 | 'leaf', null, | ||
| 46 | 'fieldName', null, | ||
| 47 | 'operationType', "graphql_operation_type", | ||
| 48 | 'operationName', "graphql_operation_name", | ||
| 49 | 'operationDocument', null, | ||
| 50 | 'queryTypeName', null, | ||
| 51 | 'mutationTypeName', null, | ||
| 52 | 'subscriptionTypeName', null | ||
| 53 | )::text | ||
| 54 | WHEN "provider_kind" = 'mcp' THEN jsonb_build_object( | ||
| 55 | 'kind', 'mcp', | ||
| 56 | 'toolId', "tool_id", | ||
| 57 | 'toolName', COALESCE("mcp_tool_name", "title", "tool_id"), | ||
| 58 | 'description', "description" | ||
| 59 | )::text | ||
| 60 | ELSE "provider_data_json" | ||
| 61 | END | ||
| 62 | WHERE "provider_data_json" IS NULL;--> statement-breakpoint | ||
| 63 | |||
| 64 | ALTER TABLE "source_recipe_operations" DROP COLUMN "mcp_tool_name";--> statement-breakpoint | ||
| 65 | ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_method";--> statement-breakpoint | ||
| 66 | ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_path_template";--> statement-breakpoint | ||
| 67 | ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_operation_hash";--> statement-breakpoint | ||
| 68 | ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_raw_tool_id";--> statement-breakpoint | ||
| 69 | ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_operation_id";--> statement-breakpoint | ||
| 70 | ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_tags_json";--> statement-breakpoint | ||
| 71 | ALTER TABLE "source_recipe_operations" DROP COLUMN "openapi_request_body_required";--> statement-breakpoint | ||
| 72 | ALTER TABLE "source_recipe_operations" DROP COLUMN "graphql_operation_type";--> statement-breakpoint | ||
| 73 | ALTER TABLE "source_recipe_operations" DROP COLUMN "graphql_operation_name";--> statement-breakpoint | ||
| @@ -1,7 +0,0 @@ | |||
| 1 | DROP TABLE IF EXISTS "tool_artifact_parameters"; | ||
| 2 | --> statement-breakpoint | ||
| 3 | DROP TABLE IF EXISTS "tool_artifact_request_body_content_types"; | ||
| 4 | --> statement-breakpoint | ||
| 5 | DROP TABLE IF EXISTS "tool_artifact_ref_hint_keys"; | ||
| 6 | --> statement-breakpoint | ||
| 7 | DROP TABLE IF EXISTS "tool_artifacts"; | ||
| @@ -1,55 +0,0 @@ | |||
| 1 | ALTER TABLE "source_recipes" | ||
| 2 | RENAME COLUMN "importer_kind" TO "adapter_key";--> statement-breakpoint | ||
| 3 | |||
| 4 | ALTER TABLE "control_plane_data_migrations" | ||
| 5 | RENAME TO "control_plane_code_migrations";--> statement-breakpoint | ||
| 6 | |||
| 7 | UPDATE "source_recipes" | ||
| 8 | SET "adapter_key" = CASE | ||
| 9 | WHEN "adapter_key" = 'graphql_introspection' THEN 'graphql' | ||
| 10 | WHEN "adapter_key" = 'mcp_manifest' THEN 'mcp' | ||
| 11 | ELSE "adapter_key" | ||
| 12 | END | ||
| 13 | WHERE "adapter_key" IN ('graphql_introspection', 'mcp_manifest');--> statement-breakpoint | ||
| 14 | |||
| 15 | UPDATE "sources" | ||
| 16 | SET "binding_config_json" = jsonb_build_object( | ||
| 17 | 'adapterKey', "kind", | ||
| 18 | 'version', 1, | ||
| 19 | 'payload', | ||
| 20 | CASE | ||
| 21 | WHEN "kind" = 'openapi' THEN jsonb_build_object( | ||
| 22 | 'specUrl', COALESCE(("binding_config_json"::jsonb ->> 'specUrl'), "endpoint"), | ||
| 23 | 'defaultHeaders', COALESCE(("binding_config_json"::jsonb -> 'defaultHeaders'), 'null'::jsonb) | ||
| 24 | ) | ||
| 25 | WHEN "kind" = 'graphql' THEN jsonb_build_object( | ||
| 26 | 'defaultHeaders', COALESCE(("binding_config_json"::jsonb -> 'defaultHeaders'), 'null'::jsonb) | ||
| 27 | ) | ||
| 28 | WHEN "kind" = 'mcp' THEN jsonb_build_object( | ||
| 29 | 'transport', COALESCE(("binding_config_json"::jsonb -> 'transport'), 'null'::jsonb), | ||
| 30 | 'queryParams', COALESCE(("binding_config_json"::jsonb -> 'queryParams'), 'null'::jsonb), | ||
| 31 | 'headers', COALESCE(("binding_config_json"::jsonb -> 'headers'), 'null'::jsonb) | ||
| 32 | ) | ||
| 33 | ELSE '{}'::jsonb | ||
| 34 | END | ||
| 35 | )::text | ||
| 36 | WHERE "binding_config_json" IS NOT NULL | ||
| 37 | AND ( | ||
| 38 | ("binding_config_json"::jsonb -> 'version') IS NULL | ||
| 39 | OR ("binding_config_json"::jsonb -> 'payload') IS NULL | ||
| 40 | );--> statement-breakpoint | ||
| 41 | |||
| 42 | UPDATE "sources" | ||
| 43 | SET "binding_config_json" = jsonb_set( | ||
| 44 | "binding_config_json"::jsonb, | ||
| 45 | '{adapterKey}', | ||
| 46 | to_jsonb( | ||
| 47 | CASE | ||
| 48 | WHEN "binding_config_json"::jsonb ->> 'adapterKey' = 'graphql_introspection' THEN 'graphql' | ||
| 49 | WHEN "binding_config_json"::jsonb ->> 'adapterKey' = 'mcp_manifest' THEN 'mcp' | ||
| 50 | ELSE "binding_config_json"::jsonb ->> 'adapterKey' | ||
| 51 | END | ||
| 52 | ) | ||
| 53 | )::text | ||
| 54 | WHERE "binding_config_json" IS NOT NULL | ||
| 55 | AND ("binding_config_json"::jsonb ->> 'adapterKey') IN ('graphql_introspection', 'mcp_manifest'); | ||
| @@ -1,132 +0,0 @@ | |||
| 1 | ALTER TABLE "workspace_source_credentials" | ||
| 2 | RENAME TO "workspace_source_auth_artifacts";--> statement-breakpoint | ||
| 3 | |||
| 4 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 5 | ADD COLUMN "artifact_kind" text;--> statement-breakpoint | ||
| 6 | |||
| 7 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 8 | ADD COLUMN "config_json" text;--> statement-breakpoint | ||
| 9 | |||
| 10 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 11 | ADD COLUMN "grant_set_json" text;--> statement-breakpoint | ||
| 12 | |||
| 13 | UPDATE "workspace_source_auth_artifacts" | ||
| 14 | SET | ||
| 15 | "artifact_kind" = CASE | ||
| 16 | WHEN "auth_kind" = 'bearer' THEN 'static_bearer' | ||
| 17 | WHEN "auth_kind" = 'oauth2' THEN 'static_oauth2' | ||
| 18 | ELSE 'static_placements' | ||
| 19 | END, | ||
| 20 | "config_json" = CASE | ||
| 21 | WHEN "auth_kind" = 'bearer' THEN jsonb_build_object( | ||
| 22 | 'headerName', "auth_header_name", | ||
| 23 | 'prefix', "auth_prefix", | ||
| 24 | 'token', jsonb_build_object( | ||
| 25 | 'providerId', "token_provider_id", | ||
| 26 | 'handle', "token_handle" | ||
| 27 | ) | ||
| 28 | )::text | ||
| 29 | WHEN "auth_kind" = 'oauth2' THEN jsonb_build_object( | ||
| 30 | 'headerName', "auth_header_name", | ||
| 31 | 'prefix', "auth_prefix", | ||
| 32 | 'accessToken', jsonb_build_object( | ||
| 33 | 'providerId', "token_provider_id", | ||
| 34 | 'handle', "token_handle" | ||
| 35 | ), | ||
| 36 | 'refreshToken', | ||
| 37 | CASE | ||
| 38 | WHEN "refresh_token_provider_id" IS NOT NULL AND "refresh_token_handle" IS NOT NULL | ||
| 39 | THEN jsonb_build_object( | ||
| 40 | 'providerId', "refresh_token_provider_id", | ||
| 41 | 'handle', "refresh_token_handle" | ||
| 42 | ) | ||
| 43 | ELSE 'null'::jsonb | ||
| 44 | END | ||
| 45 | )::text | ||
| 46 | ELSE jsonb_build_object('placements', '[]'::jsonb)::text | ||
| 47 | END | ||
| 48 | WHERE "artifact_kind" IS NULL OR "config_json" IS NULL;--> statement-breakpoint | ||
| 49 | |||
| 50 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 51 | ALTER COLUMN "artifact_kind" SET NOT NULL;--> statement-breakpoint | ||
| 52 | |||
| 53 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 54 | ALTER COLUMN "config_json" SET NOT NULL;--> statement-breakpoint | ||
| 55 | |||
| 56 | DROP INDEX IF EXISTS "credentials_workspace_idx";--> statement-breakpoint | ||
| 57 | DROP INDEX IF EXISTS "credentials_workspace_source_actor_idx";--> statement-breakpoint | ||
| 58 | DROP INDEX IF EXISTS "credentials_workspace_source_idx";--> statement-breakpoint | ||
| 59 | |||
| 60 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 61 | DROP CONSTRAINT IF EXISTS "credentials_auth_kind_check";--> statement-breakpoint | ||
| 62 | |||
| 63 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 64 | DROP CONSTRAINT IF EXISTS "credentials_slot_check";--> statement-breakpoint | ||
| 65 | |||
| 66 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 67 | DROP COLUMN "auth_kind";--> statement-breakpoint | ||
| 68 | |||
| 69 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 70 | DROP COLUMN "auth_header_name";--> statement-breakpoint | ||
| 71 | |||
| 72 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 73 | DROP COLUMN "auth_prefix";--> statement-breakpoint | ||
| 74 | |||
| 75 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 76 | DROP COLUMN "token_provider_id";--> statement-breakpoint | ||
| 77 | |||
| 78 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 79 | DROP COLUMN "token_handle";--> statement-breakpoint | ||
| 80 | |||
| 81 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 82 | DROP COLUMN "refresh_token_provider_id";--> statement-breakpoint | ||
| 83 | |||
| 84 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 85 | DROP COLUMN "refresh_token_handle";--> statement-breakpoint | ||
| 86 | |||
| 87 | ALTER TABLE "workspace_source_auth_artifacts" | ||
| 88 | ADD CONSTRAINT "auth_artifacts_slot_check" | ||
| 89 | CHECK ("slot" in ('runtime', 'import'));--> statement-breakpoint | ||
| 90 | |||
| 91 | CREATE INDEX "auth_artifacts_workspace_idx" | ||
| 92 | ON "workspace_source_auth_artifacts" ("workspace_id","updated_at","id");--> statement-breakpoint | ||
| 93 | |||
| 94 | CREATE UNIQUE INDEX "auth_artifacts_workspace_source_actor_idx" | ||
| 95 | ON "workspace_source_auth_artifacts" ("workspace_id","source_id","actor_account_id","slot");--> statement-breakpoint | ||
| 96 | |||
| 97 | CREATE INDEX "auth_artifacts_workspace_source_idx" | ||
| 98 | ON "workspace_source_auth_artifacts" ("workspace_id","source_id","updated_at","id");--> statement-breakpoint | ||
| 99 | |||
| 100 | CREATE TABLE "workspace_source_auth_leases" ( | ||
| 101 | "id" text PRIMARY KEY NOT NULL, | ||
| 102 | "auth_artifact_id" text NOT NULL, | ||
| 103 | "workspace_id" text NOT NULL, | ||
| 104 | "source_id" text NOT NULL, | ||
| 105 | "actor_account_id" text, | ||
| 106 | "slot" text NOT NULL, | ||
| 107 | "placements_json" text NOT NULL, | ||
| 108 | "expires_at" bigint, | ||
| 109 | "refresh_after" bigint, | ||
| 110 | "created_at" bigint NOT NULL, | ||
| 111 | "updated_at" bigint NOT NULL | ||
| 112 | );--> statement-breakpoint | ||
| 113 | |||
| 114 | ALTER TABLE "workspace_source_auth_leases" | ||
| 115 | ADD CONSTRAINT "auth_leases_slot_check" | ||
| 116 | CHECK ("slot" in ('runtime', 'import'));--> statement-breakpoint | ||
| 117 | |||
| 118 | CREATE UNIQUE INDEX "auth_leases_auth_artifact_idx" | ||
| 119 | ON "workspace_source_auth_leases" ("auth_artifact_id");--> statement-breakpoint | ||
| 120 | |||
| 121 | CREATE INDEX "auth_leases_workspace_source_idx" | ||
| 122 | ON "workspace_source_auth_leases" ( | ||
| 123 | "workspace_id", | ||
| 124 | "source_id", | ||
| 125 | "actor_account_id", | ||
| 126 | "slot", | ||
| 127 | "updated_at", | ||
| 128 | "id" | ||
| 129 | );--> statement-breakpoint | ||
| 130 | |||
| 131 | ALTER TABLE "source_auth_sessions" | ||
| 132 | DROP CONSTRAINT IF EXISTS "source_auth_sessions_provider_kind_check";--> statement-breakpoint | ||
| @@ -1,40 +0,0 @@ | |||
| 1 | ALTER TABLE "workspace_source_auth_leases" | ||
| 2 | ADD COLUMN "placements_template_json" text;--> statement-breakpoint | ||
| 3 | |||
| 4 | UPDATE "workspace_source_auth_leases" | ||
| 5 | SET "placements_template_json" = COALESCE( | ||
| 6 | ( | ||
| 7 | SELECT jsonb_agg( | ||
| 8 | CASE | ||
| 9 | WHEN placement ->> 'location' = 'body' THEN jsonb_build_object( | ||
| 10 | 'location', 'body', | ||
| 11 | 'path', placement ->> 'path', | ||
| 12 | 'parts', jsonb_build_array( | ||
| 13 | jsonb_build_object( | ||
| 14 | 'kind', 'literal', | ||
| 15 | 'value', COALESCE(placement ->> 'value', '') | ||
| 16 | ) | ||
| 17 | ) | ||
| 18 | ) | ||
| 19 | ELSE jsonb_build_object( | ||
| 20 | 'location', placement ->> 'location', | ||
| 21 | 'name', placement ->> 'name', | ||
| 22 | 'parts', jsonb_build_array( | ||
| 23 | jsonb_build_object( | ||
| 24 | 'kind', 'literal', | ||
| 25 | 'value', COALESCE(placement ->> 'value', '') | ||
| 26 | ) | ||
| 27 | ) | ||
| 28 | ) | ||
| 29 | END | ||
| 30 | )::text | ||
| 31 | FROM jsonb_array_elements(COALESCE("placements_json"::jsonb, '[]'::jsonb)) AS placement | ||
| 32 | ), | ||
| 33 | '[]' | ||
| 34 | );--> statement-breakpoint | ||
| 35 | |||
| 36 | ALTER TABLE "workspace_source_auth_leases" | ||
| 37 | ALTER COLUMN "placements_template_json" SET NOT NULL;--> statement-breakpoint | ||
| 38 | |||
| 39 | ALTER TABLE "workspace_source_auth_leases" | ||
| 40 | DROP COLUMN "placements_json";--> statement-breakpoint | ||
| @@ -1,25 +0,0 @@ | |||
| 1 | ALTER TABLE "execution_interactions" | ||
| 2 | ADD COLUMN "response_private_json" text;--> statement-breakpoint | ||
| 3 | |||
| 4 | CREATE TABLE "execution_steps" ( | ||
| 5 | "id" text PRIMARY KEY NOT NULL, | ||
| 6 | "execution_id" text NOT NULL, | ||
| 7 | "sequence" bigint NOT NULL, | ||
| 8 | "kind" text NOT NULL, | ||
| 9 | "status" text NOT NULL, | ||
| 10 | "path" text NOT NULL, | ||
| 11 | "args_json" text NOT NULL, | ||
| 12 | "result_json" text, | ||
| 13 | "error_text" text, | ||
| 14 | "interaction_id" text, | ||
| 15 | "created_at" bigint NOT NULL, | ||
| 16 | "updated_at" bigint NOT NULL, | ||
| 17 | CONSTRAINT "execution_steps_kind_check" CHECK ("execution_steps"."kind" in ('tool_call')), | ||
| 18 | CONSTRAINT "execution_steps_status_check" CHECK ("execution_steps"."status" in ('pending', 'waiting', 'completed', 'failed')) | ||
| 19 | );--> statement-breakpoint | ||
| 20 | |||
| 21 | CREATE UNIQUE INDEX "execution_steps_execution_sequence_idx" | ||
| 22 | ON "execution_steps" ("execution_id", "sequence");--> statement-breakpoint | ||
| 23 | |||
| 24 | CREATE INDEX "execution_steps_execution_updated_idx" | ||
| 25 | ON "execution_steps" ("execution_id", "updated_at", "id"); | ||
| @@ -12,7 +12,7 @@ import { · 11 unmodified lines | |||
| 12 | import { | 12 | import { |
| 13 | ENV_SECRET_PROVIDER_ID, | 13 | ENV_SECRET_PROVIDER_ID, |
| 14 | KEYCHAIN_SECRET_PROVIDER_ID, | 14 | KEYCHAIN_SECRET_PROVIDER_ID, |
| 15 | POSTGRES_SECRET_PROVIDER_ID, | 15 | LOCAL_SECRET_PROVIDER_ID, |
| 16 | parseSecretStoreProviderId, | 16 | parseSecretStoreProviderId, |
| 17 | } from "../../runtime/secret-material-providers"; | 17 | } from "../../runtime/secret-material-providers"; |
| 18 | import { ControlPlaneStore } from "../../runtime/store"; | 18 | import { ControlPlaneStore } from "../../runtime/store"; |
| @@ -35,8 +35,8 @@ const SECRET_STORE_PROVIDER_ENV = "EXECUTOR_SECRET_STORE_PROVIDER"; · 16 unmodified lines | |||
| 35 | const getInstanceConfig = (): Effect.Effect<InstanceConfig> => { | 35 | const getInstanceConfig = (): Effect.Effect<InstanceConfig> => { |
| 36 | const providers: SecretProvider[] = [ | 36 | const providers: SecretProvider[] = [ |
| 37 | { | 37 | { |
| 38 | id: POSTGRES_SECRET_PROVIDER_ID, | 38 | id: LOCAL_SECRET_PROVIDER_ID, |
| 39 | name: "Database", | 39 | name: "Local store", |
| 40 | canStore: true, | 40 | canStore: true, |
| 41 | }, | 41 | }, |
| 42 | ]; | 42 | ]; |
| @@ -57,7 +57,7 @@ const getInstanceConfig = (): Effect.Effect<InstanceConfig> => { · 14 unmodified lines | |||
| 57 | 57 | ||
| 58 | const defaultStoreProvider = | 58 | const defaultStoreProvider = |
| 59 | parseSecretStoreProviderId(process.env[SECRET_STORE_PROVIDER_ENV]) | 59 | parseSecretStoreProviderId(process.env[SECRET_STORE_PROVIDER_ENV]) |
| 60 | ?? POSTGRES_SECRET_PROVIDER_ID; | 60 | ?? LOCAL_SECRET_PROVIDER_ID; |
| 61 | 61 | ||
| 62 | return Effect.succeed({ | 62 | return Effect.succeed({ |
| 63 | platform: process.platform, | 63 | platform: process.platform, |
| @@ -133,7 +133,7 @@ export const ControlPlaneLocalLive = HttpApiBuilder.group( · 69 unmodified lines | |||
| 133 | return { | 133 | return { |
| 134 | id, | 134 | id, |
| 135 | name, | 135 | name, |
| 136 | providerId: POSTGRES_SECRET_PROVIDER_ID, | 136 | providerId: LOCAL_SECRET_PROVIDER_ID, |
| 137 | purpose, | 137 | purpose, |
| 138 | createdAt: now, | 138 | createdAt: now, |
| 139 | updatedAt: now, | 139 | updatedAt: now, |
| @@ -1,5 +1,4 @@ | |||
| 1 | export * from "./schema/index"; | 1 | export * from "./schema/index"; |
| 2 | export * from "./persistence/index"; | ||
| 3 | export * from "./api/index"; | 2 | export * from "./api/index"; |
| 4 | export * from "./runtime/index"; | 3 | export * from "./runtime/index"; |
| 5 | export * from "./client"; | 4 | export * from "./client"; |
| @@ -1,70 +0,0 @@ | |||
| 1 | import * as Effect from "effect/Effect"; | ||
| 2 | |||
| 3 | import { | ||
| 4 | type ControlPlanePersistenceError, | ||
| 5 | toPersistenceError, | ||
| 6 | } from "./persistence-errors"; | ||
| 7 | import type { DrizzleDb, SqlBackend } from "./sql-runtime"; | ||
| 8 | |||
| 9 | export type DrizzleSession = Pick<DrizzleDb, "select" | "insert" | "update" | "delete">; | ||
| 10 | |||
| 11 | export type DrizzleClient = { | ||
| 12 | backend: SqlBackend; | ||
| 13 | db: DrizzleDb; | ||
| 14 | use: <A>( | ||
| 15 | operation: string, | ||
| 16 | run: (db: DrizzleSession) => Promise<A>, | ||
| 17 | ) => Effect.Effect<A, ControlPlanePersistenceError>; | ||
| 18 | useTx: <A>( | ||
| 19 | operation: string, | ||
| 20 | run: (tx: DrizzleSession) => Promise<A>, | ||
| 21 | ) => Effect.Effect<A, ControlPlanePersistenceError>; | ||
| 22 | }; | ||
| 23 | |||
| 24 | const createSerializer = (backend: SqlBackend) => { | ||
| 25 | let queue = Promise.resolve<void>(undefined); | ||
| 26 | |||
| 27 | return <A>(run: () => Promise<A>): Promise<A> => { | ||
| 28 | if (backend !== "pglite") { | ||
| 29 | return run(); | ||
| 30 | } | ||
| 31 | |||
| 32 | const next: Promise<A> = queue.then(run, run); | ||
| 33 | queue = next.then( | ||
| 34 | () => undefined, | ||
| 35 | () => undefined, | ||
| 36 | ); | ||
| 37 | return next; | ||
| 38 | }; | ||
| 39 | }; | ||
| 40 | |||
| 41 | export const createDrizzleClient = (input: { | ||
| 42 | backend: SqlBackend; | ||
| 43 | db: DrizzleDb; | ||
| 44 | }): DrizzleClient => { | ||
| 45 | const serialize = createSerializer(input.backend); | ||
| 46 | |||
| 47 | const use = <A>( | ||
| 48 | operation: string, | ||
| 49 | run: (db: DrizzleSession) => Promise<A>, | ||
| 50 | ): Effect.Effect<A, ControlPlanePersistenceError> => | ||
| 51 | Effect.tryPromise({ | ||
| 52 | try: () => serialize(() => run(input.db)), | ||
| 53 | catch: (cause) => toPersistenceError(operation, cause), | ||
| 54 | }); | ||
| 55 | |||
| 56 | const useTx = <A>( | ||
| 57 | operation: string, | ||
| 58 | run: (tx: DrizzleSession) => Promise<A>, | ||
| 59 | ): Effect.Effect<A, ControlPlanePersistenceError> => | ||
| 60 | use(operation, () => | ||
| 61 | input.db.transaction(async (tx) => run(tx)), | ||
| 62 | ); | ||
| 63 | |||
| 64 | return { | ||
| 65 | backend: input.backend, | ||
| 66 | db: input.db, | ||
| 67 | use, | ||
| 68 | useTx, | ||
| 69 | }; | ||
| 70 | }; | ||
| @@ -1,234 +0,0 @@ | |||
| 1 | import { | ||
| 2 | type McpSourceAuthSessionData, | ||
| 3 | type SourceAuthSession, | ||
| 4 | type StoredSourceRecipeDocumentRecord, | ||
| 5 | type StoredSourceRecord, | ||
| 6 | } from "#schema"; | ||
| 7 | import { | ||
| 8 | JsonObjectSchema, | ||
| 9 | McpSourceAuthSessionDataJsonSchema, | ||
| 10 | } from "#schema"; | ||
| 11 | import * as Either from "effect/Either"; | ||
| 12 | import * as Effect from "effect/Effect"; | ||
| 13 | import * as ParseResult from "effect/ParseResult"; | ||
| 14 | import * as Schema from "effect/Schema"; | ||
| 15 | |||
| 16 | import { rebuildGraphqlRecipeRevisionContent } from "../runtime/source-adapters/graphql"; | ||
| 17 | import { rebuildOpenApiRecipeRevisionContent } from "../runtime/source-adapters/openapi"; | ||
| 18 | import type { SqlControlPlaneRows } from "./index"; | ||
| 19 | |||
| 20 | type DataMigration = { | ||
| 21 | id: string; | ||
| 22 | run: (rows: SqlControlPlaneRows) => Effect.Effect<void, Error, never>; | ||
| 23 | }; | ||
| 24 | |||
| 25 | const LegacyMcpSourceAuthSessionDataSchema = Schema.Struct({ | ||
| 26 | kind: Schema.Literal("mcp_oauth"), | ||
| 27 | endpoint: Schema.String, | ||
| 28 | redirectUri: Schema.String, | ||
| 29 | scope: Schema.NullOr(Schema.String), | ||
| 30 | resourceMetadataUrl: Schema.NullOr(Schema.String), | ||
| 31 | authorizationServerUrl: Schema.NullOr(Schema.String), | ||
| 32 | resourceMetadataJson: Schema.NullOr(Schema.String), | ||
| 33 | authorizationServerMetadataJson: Schema.NullOr(Schema.String), | ||
| 34 | clientInformationJson: Schema.NullOr(Schema.String), | ||
| 35 | codeVerifier: Schema.NullOr(Schema.String), | ||
| 36 | authorizationUrl: Schema.NullOr(Schema.String), | ||
| 37 | }); | ||
| 38 | |||
| 39 | const decodeLegacyMcpSourceAuthSessionDataJson = Schema.decodeUnknownEither( | ||
| 40 | Schema.parseJson(LegacyMcpSourceAuthSessionDataSchema), | ||
| 41 | ); | ||
| 42 | |||
| 43 | const decodeMcpSourceAuthSessionDataJson = Schema.decodeUnknownEither( | ||
| 44 | McpSourceAuthSessionDataJsonSchema, | ||
| 45 | ); | ||
| 46 | |||
| 47 | const encodeMcpSourceAuthSessionDataJson = Schema.encodeSync( | ||
| 48 | McpSourceAuthSessionDataJsonSchema, | ||
| 49 | ); | ||
| 50 | |||
| 51 | const decodeJsonObjectFromJson = Schema.decodeUnknownEither( | ||
| 52 | Schema.parseJson(JsonObjectSchema), | ||
| 53 | ); | ||
| 54 | |||
| 55 | const decodeLegacyJsonObject = (value: string | null) => { | ||
| 56 | if (value === null) { | ||
| 57 | return null; | ||
| 58 | } | ||
| 59 | |||
| 60 | const decoded = decodeJsonObjectFromJson(value); | ||
| 61 | return Either.isRight(decoded) ? decoded.right : null; | ||
| 62 | }; | ||
| 63 | |||
| 64 | const repairLegacyMcpSourceAuthSession = (input: { | ||
| 65 | rows: SqlControlPlaneRows; | ||
| 66 | session: SourceAuthSession; | ||
| 67 | }): Effect.Effect<void, never, never> => { | ||
| 68 | if (input.session.providerKind !== "mcp_oauth") { | ||
| 69 | return Effect.void; | ||
| 70 | } | ||
| 71 | |||
| 72 | if ( | ||
| 73 | Either.isRight( | ||
| 74 | decodeMcpSourceAuthSessionDataJson(input.session.sessionDataJson), | ||
| 75 | ) | ||
| 76 | ) { | ||
| 77 | return Effect.void; | ||
| 78 | } | ||
| 79 | |||
| 80 | const legacy = decodeLegacyMcpSourceAuthSessionDataJson( | ||
| 81 | input.session.sessionDataJson, | ||
| 82 | ); | ||
| 83 | if (Either.isLeft(legacy)) { | ||
| 84 | console.warn( | ||
| 85 | `Skipping source auth session data migration for ${input.session.id}: ${ParseResult.TreeFormatter.formatErrorSync(legacy.left)}`, | ||
| 86 | ); | ||
| 87 | return Effect.void; | ||
| 88 | } | ||
| 89 | |||
| 90 | const repaired: McpSourceAuthSessionData = { | ||
| 91 | kind: "mcp_oauth", | ||
| 92 | endpoint: legacy.right.endpoint, | ||
| 93 | redirectUri: legacy.right.redirectUri, | ||
| 94 | scope: legacy.right.scope, | ||
| 95 | resourceMetadataUrl: legacy.right.resourceMetadataUrl, | ||
| 96 | authorizationServerUrl: legacy.right.authorizationServerUrl, | ||
| 97 | resourceMetadata: decodeLegacyJsonObject(legacy.right.resourceMetadataJson), | ||
| 98 | authorizationServerMetadata: decodeLegacyJsonObject( | ||
| 99 | legacy.right.authorizationServerMetadataJson, | ||
| 100 | ), | ||
| 101 | clientInformation: decodeLegacyJsonObject( | ||
| 102 | legacy.right.clientInformationJson, | ||
| 103 | ), | ||
| 104 | codeVerifier: legacy.right.codeVerifier, | ||
| 105 | authorizationUrl: legacy.right.authorizationUrl, | ||
| 106 | }; | ||
| 107 | |||
| 108 | return input.rows.sourceAuthSessions | ||
| 109 | .update(input.session.id, { | ||
| 110 | sessionDataJson: encodeMcpSourceAuthSessionDataJson(repaired), | ||
| 111 | }) | ||
| 112 | .pipe(Effect.asVoid, Effect.orDie); | ||
| 113 | }; | ||
| 114 | |||
| 115 | const migrateLegacySourceAuthSessions: DataMigration = { | ||
| 116 | id: "20260310_repair_legacy_mcp_oauth_sessions", | ||
| 117 | run: (rows) => | ||
| 118 | Effect.gen(function* () { | ||
| 119 | const sourceAuthSessions = yield* rows.sourceAuthSessions.listAll(); | ||
| 120 | yield* Effect.forEach( | ||
| 121 | sourceAuthSessions, | ||
| 122 | (session) => | ||
| 123 | repairLegacyMcpSourceAuthSession({ | ||
| 124 | rows, | ||
| 125 | session, | ||
| 126 | }), | ||
| 127 | { discard: true }, | ||
| 128 | ); | ||
| 129 | }), | ||
| 130 | }; | ||
| 131 | |||
| 132 | const rebuildPersistedSourceRecipes: DataMigration = { | ||
| 133 | id: "20260310_rebuild_persisted_source_recipes", | ||
| 134 | run: (rows) => | ||
| 135 | Effect.gen(function* () { | ||
| 136 | const sourceRecords = yield* rows.sources.listAll(); | ||
| 137 | if (sourceRecords.length === 0) { | ||
| 138 | return; | ||
| 139 | } | ||
| 140 | |||
| 141 | const revisionIds = [ | ||
| 142 | ...new Set(sourceRecords.map((sourceRecord) => sourceRecord.recipeRevisionId)), | ||
| 143 | ]; | ||
| 144 | const [revisions, documents] = yield* Effect.all([ | ||
| 145 | rows.sourceRecipeRevisions.listByIds(revisionIds), | ||
| 146 | rows.sourceRecipeDocuments.listByRevisionIds(revisionIds), | ||
| 147 | ]); | ||
| 148 | |||
| 149 | const revisionById = new Map( | ||
| 150 | revisions.map((revision) => [revision.id, revision]), | ||
| 151 | ); | ||
| 152 | const sourceRecordByRevisionId = new Map<string, StoredSourceRecord>(); | ||
| 153 | for (const sourceRecord of sourceRecords) { | ||
| 154 | if (!sourceRecordByRevisionId.has(sourceRecord.recipeRevisionId)) { | ||
| 155 | sourceRecordByRevisionId.set(sourceRecord.recipeRevisionId, sourceRecord); | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | const documentsByRevisionId = new Map<string, StoredSourceRecipeDocumentRecord[]>(); | ||
| 160 | for (const document of documents) { | ||
| 161 | const existing = documentsByRevisionId.get(document.recipeRevisionId) ?? []; | ||
| 162 | existing.push(document); | ||
| 163 | documentsByRevisionId.set(document.recipeRevisionId, existing); | ||
| 164 | } | ||
| 165 | |||
| 166 | yield* Effect.forEach( | ||
| 167 | revisionIds, | ||
| 168 | (revisionId) => | ||
| 169 | Effect.gen(function* () { | ||
| 170 | const sourceRecord = sourceRecordByRevisionId.get(revisionId); | ||
| 171 | const revision = revisionById.get(revisionId); | ||
| 172 | const documentsForRevision = documentsByRevisionId.get(revisionId) ?? []; | ||
| 173 | if (!sourceRecord || !revision) { | ||
| 174 | return; | ||
| 175 | } | ||
| 176 | |||
| 177 | if (sourceRecord.kind === "openapi") { | ||
| 178 | if (documentsForRevision.length === 0) { | ||
| 179 | return; | ||
| 180 | } | ||
| 181 | yield* rebuildOpenApiRecipeRevisionContent({ | ||
| 182 | rows, | ||
| 183 | sourceRecord, | ||
| 184 | revision, | ||
| 185 | documents: documentsForRevision, | ||
| 186 | }); | ||
| 187 | return; | ||
| 188 | } | ||
| 189 | |||
| 190 | if (sourceRecord.kind !== "graphql") { | ||
| 191 | return; | ||
| 192 | } | ||
| 193 | |||
| 194 | if (documentsForRevision.length === 0) { | ||
| 195 | return; | ||
| 196 | } | ||
| 197 | |||
| 198 | yield* rebuildGraphqlRecipeRevisionContent({ | ||
| 199 | rows, | ||
| 200 | sourceRecord, | ||
| 201 | revision, | ||
| 202 | documents: documentsForRevision, | ||
| 203 | }); | ||
| 204 | }), | ||
| 205 | { discard: true }, | ||
| 206 | ); | ||
| 207 | }), | ||
| 208 | }; | ||
| 209 | |||
| 210 | const codeMigrations = [ | ||
| 211 | rebuildPersistedSourceRecipes, | ||
| 212 | migrateLegacySourceAuthSessions, | ||
| 213 | ] as const satisfies readonly DataMigration[]; | ||
| 214 | |||
| 215 | export const runCodeMigrations = ( | ||
| 216 | rows: SqlControlPlaneRows, | ||
| 217 | ): Effect.Effect<void, Error, never> => | ||
| 218 | Effect.gen(function* () { | ||
| 219 | const applied = new Set( | ||
| 220 | (yield* rows.codeMigrations.listAll()).map((migration) => migration.id), | ||
| 221 | ); | ||
| 222 | |||
| 223 | for (const migration of codeMigrations) { | ||
| 224 | if (applied.has(migration.id)) { | ||
| 225 | continue; | ||
| 226 | } | ||
| 227 | |||
| 228 | yield* migration.run(rows); | ||
| 229 | yield* rows.codeMigrations.upsert({ | ||
| 230 | id: migration.id, | ||
| 231 | appliedAt: Date.now(), | ||
| 232 | }); | ||
| 233 | } | ||
| 234 | }); | ||
| @@ -1,182 +0,0 @@ | |||
| 1 | import * as Context from "effect/Context"; | ||
| 2 | import * as Data from "effect/Data"; | ||
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | |||
| 6 | import { createDrizzleClient, type DrizzleClient } from "./client"; | ||
| 7 | import { | ||
| 8 | createAccountsRepo, | ||
| 9 | createAuthArtifactsRepo, | ||
| 10 | createAuthLeasesRepo, | ||
| 11 | createCodeMigrationsRepo, | ||
| 12 | createExecutionInteractionsRepo, | ||
| 13 | createExecutionStepsRepo, | ||
| 14 | createExecutionsRepo, | ||
| 15 | createSecretMaterialsRepo, | ||
| 16 | createSourceAuthSessionsRepo, | ||
| 17 | createSourceOauthClientsRepo, | ||
| 18 | createSourceRecipeDocumentsRepo, | ||
| 19 | createSourceRecipeSchemaBundlesRepo, | ||
| 20 | createSourceRecipeOperationsRepo, | ||
| 21 | createSourceRecipeRevisionsRepo, | ||
| 22 | createSourceRecipesRepo, | ||
| 23 | createSourcesRepo, | ||
| 24 | } from "./repos"; | ||
| 25 | import { drizzleSchema, tableNames, type DrizzleTables } from "./schema"; | ||
| 26 | import { | ||
| 27 | createSqlRuntime, | ||
| 28 | runMigrations, | ||
| 29 | type CreateSqlRuntimeOptions, | ||
| 30 | type DrizzleDb, | ||
| 31 | type SqlBackend, | ||
| 32 | type SqlRuntime, | ||
| 33 | } from "./sql-runtime"; | ||
| 34 | import { runCodeMigrations } from "./code-migrations"; | ||
| 35 | |||
| 36 | export { tableNames, type DrizzleTables } from "./schema"; | ||
| 37 | export { | ||
| 38 | ControlPlanePersistenceError, | ||
| 39 | toPersistenceError, | ||
| 40 | type PersistenceErrorKind, | ||
| 41 | } from "./persistence-errors"; | ||
| 42 | export { createDrizzleClient, type DrizzleClient } from "./client"; | ||
| 43 | export { | ||
| 44 | createSqlRuntime, | ||
| 45 | runMigrations, | ||
| 46 | type SqlRuntime, | ||
| 47 | type SqlBackend, | ||
| 48 | type DrizzleDb, | ||
| 49 | type CreateSqlRuntimeOptions, | ||
| 50 | } from "./sql-runtime"; | ||
| 51 | |||
| 52 | const createRows = (client: DrizzleClient, tables: DrizzleTables = drizzleSchema) => ({ | ||
| 53 | accounts: createAccountsRepo(client, tables), | ||
| 54 | sources: createSourcesRepo(client, tables), | ||
| 55 | sourceRecipes: createSourceRecipesRepo(client, tables), | ||
| 56 | sourceRecipeRevisions: createSourceRecipeRevisionsRepo(client, tables), | ||
| 57 | sourceRecipeDocuments: createSourceRecipeDocumentsRepo(client, tables), | ||
| 58 | sourceRecipeSchemaBundles: createSourceRecipeSchemaBundlesRepo(client, tables), | ||
| 59 | sourceRecipeOperations: createSourceRecipeOperationsRepo(client, tables), | ||
| 60 | codeMigrations: createCodeMigrationsRepo(client, tables), | ||
| 61 | authArtifacts: createAuthArtifactsRepo(client, tables), | ||
| 62 | authLeases: createAuthLeasesRepo(client, tables), | ||
| 63 | sourceOauthClients: createSourceOauthClientsRepo(client, tables), | ||
| 64 | secretMaterials: createSecretMaterialsRepo(client, tables), | ||
| 65 | sourceAuthSessions: createSourceAuthSessionsRepo(client, tables), | ||
| 66 | executions: createExecutionsRepo(client, tables), | ||
| 67 | executionInteractions: createExecutionInteractionsRepo(client, tables), | ||
| 68 | executionSteps: createExecutionStepsRepo(client, tables), | ||
| 69 | }); | ||
| 70 | |||
| 71 | export type SqlControlPlaneRows = ReturnType<typeof createRows>; | ||
| 72 | |||
| 73 | export type SqlControlPlanePersistence = { | ||
| 74 | backend: SqlBackend; | ||
| 75 | db: DrizzleDb; | ||
| 76 | rows: SqlControlPlaneRows; | ||
| 77 | close: () => Promise<void>; | ||
| 78 | }; | ||
| 79 | |||
| 80 | export class SqlControlPlanePersistenceService extends Context.Tag( | ||
| 81 | "#persistence/SqlControlPlanePersistenceService", | ||
| 82 | )<SqlControlPlanePersistenceService, SqlControlPlanePersistence>() {} | ||
| 83 | |||
| 84 | export class SqlControlPlaneRowsService extends Context.Tag( | ||
| 85 | "#persistence/SqlControlPlaneRowsService", | ||
| 86 | )<SqlControlPlaneRowsService, SqlControlPlaneRows>() {} | ||
| 87 | |||
| 88 | export class SqlPersistenceBootstrapError extends Data.TaggedError( | ||
| 89 | "SqlPersistenceBootstrapError", | ||
| 90 | )<{ | ||
| 91 | message: string; | ||
| 92 | details: string | null; | ||
| 93 | }> {} | ||
| 94 | |||
| 95 | const toBootstrapError = (cause: unknown): SqlPersistenceBootstrapError => { | ||
| 96 | const details = cause instanceof Error ? cause.message : String(cause); | ||
| 97 | return new SqlPersistenceBootstrapError({ | ||
| 98 | message: `Failed initializing SQL control-plane persistence: ${details}`, | ||
| 99 | details, | ||
| 100 | }); | ||
| 101 | }; | ||
| 102 | |||
| 103 | const createRuntimeEffect = (options: CreateSqlRuntimeOptions) => | ||
| 104 | Effect.tryPromise({ | ||
| 105 | try: () => createSqlRuntime(options), | ||
| 106 | catch: toBootstrapError, | ||
| 107 | }); | ||
| 108 | |||
| 109 | const runMigrationsEffect = ( | ||
| 110 | runtime: SqlRuntime, | ||
| 111 | migrationsFolder: string | undefined, | ||
| 112 | ) => | ||
| 113 | Effect.tryPromise({ | ||
| 114 | try: () => runMigrations(runtime, { migrationsFolder }), | ||
| 115 | catch: toBootstrapError, | ||
| 116 | }); | ||
| 117 | |||
| 118 | const closeRuntimeEffect = (runtime: SqlRuntime) => | ||
| 119 | Effect.tryPromise({ | ||
| 120 | try: () => runtime.close(), | ||
| 121 | catch: (cause) => | ||
| 122 | cause instanceof Error ? cause : new Error(String(cause ?? "unknown close error")), | ||
| 123 | }).pipe(Effect.orDie); | ||
| 124 | |||
| 125 | export type CreateSqlControlPlanePersistenceOptions = | ||
| 126 | & CreateSqlRuntimeOptions | ||
| 127 | & { | ||
| 128 | runCodeMigrations?: boolean; | ||
| 129 | }; | ||
| 130 | |||
| 131 | export const createSqlControlPlanePersistence = ( | ||
| 132 | options: CreateSqlControlPlanePersistenceOptions, | ||
| 133 | ): Effect.Effect<SqlControlPlanePersistence, SqlPersistenceBootstrapError> => | ||
| 134 | Effect.flatMap(createRuntimeEffect(options), (runtime) => | ||
| 135 | runMigrationsEffect(runtime, options.migrationsFolder).pipe( | ||
| 136 | Effect.map(() => { | ||
| 137 | const client = createDrizzleClient({ | ||
| 138 | backend: runtime.backend, | ||
| 139 | db: runtime.db, | ||
| 140 | }); | ||
| 141 | const rows = createRows(client); | ||
| 142 | |||
| 143 | return { | ||
| 144 | backend: runtime.backend, | ||
| 145 | db: runtime.db, | ||
| 146 | rows, | ||
| 147 | close: () => runtime.close(), | ||
| 148 | } satisfies SqlControlPlanePersistence; | ||
| 149 | }), | ||
| 150 | Effect.flatMap((persistence) => | ||
| 151 | options.runCodeMigrations === false | ||
| 152 | ? Effect.succeed(persistence) | ||
| 153 | : runCodeMigrations(persistence.rows).pipe( | ||
| 154 | Effect.mapError(toBootstrapError), | ||
| 155 | Effect.map(() => persistence), | ||
| 156 | )), | ||
| 157 | Effect.catchAll((error) => | ||
| 158 | closeRuntimeEffect(runtime).pipe( | ||
| 159 | Effect.zipRight(Effect.fail(error)), | ||
| 160 | )), | ||
| 161 | )); | ||
| 162 | |||
| 163 | export const SqlControlPlanePersistenceLive = ( | ||
| 164 | options: CreateSqlControlPlanePersistenceOptions, | ||
| 165 | ) => | ||
| 166 | Layer.scoped( | ||
| 167 | SqlControlPlanePersistenceService, | ||
| 168 | Effect.acquireRelease( | ||
| 169 | createSqlControlPlanePersistence(options), | ||
| 170 | (persistence) => | ||
| 171 | Effect.tryPromise({ | ||
| 172 | try: () => persistence.close(), | ||
| 173 | catch: (cause) => | ||
| 174 | cause instanceof Error ? cause : new Error(String(cause ?? "unknown close error")), | ||
| 175 | }).pipe(Effect.orDie), | ||
| 176 | ), | ||
| 177 | ); | ||
| 178 | |||
| 179 | export const SqlControlPlaneRowsLive = Layer.effect( | ||
| 180 | SqlControlPlaneRowsService, | ||
| 181 | Effect.map(SqlControlPlanePersistenceService, (persistence) => persistence.rows), | ||
| 182 | ); | ||
| @@ -1,92 +0,0 @@ | |||
| 1 | import * as Data from "effect/Data"; | ||
| 2 | |||
| 3 | export type PersistenceErrorKind = | ||
| 4 | | "unique_violation" | ||
| 5 | | "not_null_violation" | ||
| 6 | | "check_violation" | ||
| 7 | | "foreign_key_violation" | ||
| 8 | | "serialization_failure" | ||
| 9 | | "unknown"; | ||
| 10 | |||
| 11 | type ErrorMetadata = { | ||
| 12 | code: string | null; | ||
| 13 | constraint: string | null; | ||
| 14 | table: string | null; | ||
| 15 | details: string | null; | ||
| 16 | message: string; | ||
| 17 | kind: PersistenceErrorKind; | ||
| 18 | }; | ||
| 19 | |||
| 20 | export class ControlPlanePersistenceError extends Data.TaggedError( | ||
| 21 | "ControlPlanePersistenceError", | ||
| 22 | )<{ | ||
| 23 | operation: string; | ||
| 24 | message: string; | ||
| 25 | details: string | null; | ||
| 26 | code: string | null; | ||
| 27 | constraint: string | null; | ||
| 28 | table: string | null; | ||
| 29 | kind: PersistenceErrorKind; | ||
| 30 | cause: unknown; | ||
| 31 | }> {} | ||
| 32 | |||
| 33 | const asRecord = (value: unknown): Record<string, unknown> | null => | ||
| 34 | typeof value === "object" && value !== null ? (value as Record<string, unknown>) : null; | ||
| 35 | |||
| 36 | const readString = (value: unknown): string | null => | ||
| 37 | typeof value === "string" && value.length > 0 ? value : null; | ||
| 38 | |||
| 39 | const classifyKind = (code: string | null): PersistenceErrorKind => { | ||
| 40 | switch (code) { | ||
| 41 | case "23505": | ||
| 42 | return "unique_violation"; | ||
| 43 | case "23502": | ||
| 44 | return "not_null_violation"; | ||
| 45 | case "23514": | ||
| 46 | return "check_violation"; | ||
| 47 | case "23503": | ||
| 48 | return "foreign_key_violation"; | ||
| 49 | case "40001": | ||
| 50 | return "serialization_failure"; | ||
| 51 | default: | ||
| 52 | return "unknown"; | ||
| 53 | } | ||
| 54 | }; | ||
| 55 | |||
| 56 | const toErrorMetadata = (cause: unknown): ErrorMetadata => { | ||
| 57 | const record = asRecord(cause); | ||
| 58 | const code = readString(record?.code); | ||
| 59 | const constraint = | ||
| 60 | readString(record?.constraint) | ||
| 61 | ?? readString(record?.constraint_name) | ||
| 62 | ?? readString(record?.constraintName); | ||
| 63 | const table = readString(record?.table) ?? readString(record?.table_name); | ||
| 64 | const details = readString(record?.detail) ?? readString(record?.details); | ||
| 65 | const message = cause instanceof Error ? cause.message : String(cause); | ||
| 66 | |||
| 67 | return { | ||
| 68 | code, | ||
| 69 | constraint, | ||
| 70 | table, | ||
| 71 | details, | ||
| 72 | message, | ||
| 73 | kind: classifyKind(code), | ||
| 74 | }; | ||
| 75 | }; | ||
| 76 | |||
| 77 | export const toPersistenceError = ( | ||
| 78 | operation: string, | ||
| 79 | cause: unknown, | ||
| 80 | ): ControlPlanePersistenceError => { | ||
| 81 | const metadata = toErrorMetadata(cause); | ||
| 82 | return new ControlPlanePersistenceError({ | ||
| 83 | operation, | ||
| 84 | message: `Control-plane persistence failed during ${operation}`, | ||
| 85 | details: metadata.details ?? metadata.message, | ||
| 86 | code: metadata.code, | ||
| 87 | constraint: metadata.constraint, | ||
| 88 | table: metadata.table, | ||
| 89 | kind: metadata.kind, | ||
| 90 | cause, | ||
| 91 | }); | ||
| 92 | }; | ||
| @@ -1,915 +0,0 @@ | |||
| 1 | import { cpSync, mkdtempSync, readdirSync, rmSync } from "node:fs"; | ||
| 2 | import { tmpdir } from "node:os"; | ||
| 3 | import path from "node:path"; | ||
| 4 | import { fileURLToPath } from "node:url"; | ||
| 5 | |||
| 6 | import { describe, expect, it } from "@effect/vitest"; | ||
| 7 | import { | ||
| 8 | AccountIdSchema, | ||
| 9 | McpSourceAuthSessionDataJsonSchema, | ||
| 10 | SourceAuthSessionIdSchema, | ||
| 11 | decodeAuthLeasePlacementTemplates, | ||
| 12 | SourceIdSchema, | ||
| 13 | SourceRecipeIdSchema, | ||
| 14 | SourceRecipeRevisionIdSchema, | ||
| 15 | WorkspaceIdSchema, | ||
| 16 | } from "#schema"; | ||
| 17 | import * as Effect from "effect/Effect"; | ||
| 18 | import * as Option from "effect/Option"; | ||
| 19 | import * as Schema from "effect/Schema"; | ||
| 20 | import * as Scope from "effect/Scope"; | ||
| 21 | import { buildSchema, getIntrospectionQuery, graphqlSync } from "graphql"; | ||
| 22 | import { sql } from "drizzle-orm"; | ||
| 23 | |||
| 24 | import { | ||
| 25 | createSqlControlPlanePersistence, | ||
| 26 | type SqlControlPlanePersistence, | ||
| 27 | } from "./index"; | ||
| 28 | import { runCodeMigrations } from "./code-migrations"; | ||
| 29 | |||
| 30 | const openApiBindingConfigJson = (specUrl: string): string => | ||
| 31 | JSON.stringify({ | ||
| 32 | adapterKey: "openapi", | ||
| 33 | version: 1, | ||
| 34 | payload: { | ||
| 35 | specUrl, | ||
| 36 | defaultHeaders: null, | ||
| 37 | }, | ||
| 38 | }); | ||
| 39 | |||
| 40 | const graphqlBindingConfigJson = (): string => | ||
| 41 | JSON.stringify({ | ||
| 42 | adapterKey: "graphql", | ||
| 43 | version: 1, | ||
| 44 | payload: { | ||
| 45 | defaultHeaders: null, | ||
| 46 | }, | ||
| 47 | }); | ||
| 48 | |||
| 49 | const baseRevisionRecord = (input: { | ||
| 50 | id: ReturnType<typeof SourceRecipeRevisionIdSchema.make>; | ||
| 51 | recipeId: ReturnType<typeof SourceRecipeIdSchema.make>; | ||
| 52 | revisionNumber: number; | ||
| 53 | sourceConfigJson: string; | ||
| 54 | manifestJson?: string | null; | ||
| 55 | manifestHash?: string | null; | ||
| 56 | materializationHash?: string | null; | ||
| 57 | createdAt: number; | ||
| 58 | updatedAt: number; | ||
| 59 | }) => ({ | ||
| 60 | id: input.id, | ||
| 61 | recipeId: input.recipeId, | ||
| 62 | revisionNumber: input.revisionNumber, | ||
| 63 | sourceConfigJson: input.sourceConfigJson, | ||
| 64 | manifestJson: input.manifestJson ?? null, | ||
| 65 | manifestHash: input.manifestHash ?? null, | ||
| 66 | materializationHash: input.materializationHash ?? null, | ||
| 67 | createdAt: input.createdAt, | ||
| 68 | updatedAt: input.updatedAt, | ||
| 69 | }); | ||
| 70 | |||
| 71 | const drizzleDir = path.resolve( | ||
| 72 | path.dirname(fileURLToPath(import.meta.url)), | ||
| 73 | "../../drizzle", | ||
| 74 | ); | ||
| 75 | |||
| 76 | const copyMigrationsBefore = (cutoffDirectoryName: string): string => { | ||
| 77 | const targetDir = mkdtempSync(path.join(tmpdir(), "executor-cp-migrations-")); | ||
| 78 | for (const directoryName of readdirSync(drizzleDir).sort()) { | ||
| 79 | if (directoryName >= cutoffDirectoryName) { | ||
| 80 | continue; | ||
| 81 | } | ||
| 82 | |||
| 83 | cpSync( | ||
| 84 | path.join(drizzleDir, directoryName), | ||
| 85 | path.join(targetDir, directoryName), | ||
| 86 | { recursive: true }, | ||
| 87 | ); | ||
| 88 | } | ||
| 89 | |||
| 90 | return targetDir; | ||
| 91 | }; | ||
| 92 | |||
| 93 | const makePersistence: Effect.Effect< | ||
| 94 | SqlControlPlanePersistence, | ||
| 95 | unknown, | ||
| 96 | Scope.Scope | ||
| 97 | > = Effect.acquireRelease( | ||
| 98 | createSqlControlPlanePersistence({ | ||
| 99 | localDataDir: ":memory:", | ||
| 100 | }), | ||
| 101 | (persistence) => | ||
| 102 | Effect.tryPromise({ | ||
| 103 | try: () => persistence.close(), | ||
| 104 | catch: (cause) => | ||
| 105 | cause instanceof Error ? cause : new Error(String(cause)), | ||
| 106 | }).pipe(Effect.orDie), | ||
| 107 | ); | ||
| 108 | |||
| 109 | const seedMigratedSourceRecipe = (input: { | ||
| 110 | persistence: SqlControlPlanePersistence; | ||
| 111 | kind: "openapi" | "graphql"; | ||
| 112 | workspaceId: ReturnType<typeof WorkspaceIdSchema.make>; | ||
| 113 | sourceId: ReturnType<typeof SourceIdSchema.make>; | ||
| 114 | documentText: string; | ||
| 115 | }): Effect.Effect< | ||
| 116 | { | ||
| 117 | recipeRevisionId: ReturnType<typeof SourceRecipeRevisionIdSchema.make>; | ||
| 118 | }, | ||
| 119 | unknown, | ||
| 120 | never | ||
| 121 | > => | ||
| 122 | Effect.gen(function* () { | ||
| 123 | const now = Date.now(); | ||
| 124 | const accountId = AccountIdSchema.make(`acc_${input.sourceId}`); | ||
| 125 | const recipeId = SourceRecipeIdSchema.make(`src_recipe_${input.sourceId}`); | ||
| 126 | const recipeRevisionId = SourceRecipeRevisionIdSchema.make( | ||
| 127 | `src_recipe_rev_${input.sourceId}`, | ||
| 128 | ); | ||
| 129 | |||
| 130 | yield* input.persistence.rows.sourceRecipes.upsert({ | ||
| 131 | id: recipeId, | ||
| 132 | kind: "http_api", | ||
| 133 | adapterKey: | ||
| 134 | input.kind === "openapi" ? "openapi" : "graphql", | ||
| 135 | providerKey: | ||
| 136 | input.kind === "openapi" ? "generic_http" : "generic_graphql", | ||
| 137 | name: input.kind === "openapi" ? "GitHub" : "GraphQL Demo", | ||
| 138 | summary: null, | ||
| 139 | visibility: "workspace", | ||
| 140 | latestRevisionId: recipeRevisionId, | ||
| 141 | createdAt: now, | ||
| 142 | updatedAt: now, | ||
| 143 | }); | ||
| 144 | yield* input.persistence.rows.sourceRecipeRevisions.upsert(baseRevisionRecord({ | ||
| 145 | id: recipeRevisionId, | ||
| 146 | recipeId, | ||
| 147 | revisionNumber: 1, | ||
| 148 | sourceConfigJson: JSON.stringify( | ||
| 149 | input.kind === "openapi" | ||
| 150 | ? { | ||
| 151 | kind: "openapi", | ||
| 152 | endpoint: "https://api.example.com", | ||
| 153 | specUrl: "https://api.example.com/openapi.json", | ||
| 154 | defaultHeaders: null, | ||
| 155 | } | ||
| 156 | : { | ||
| 157 | kind: "graphql", | ||
| 158 | endpoint: "https://api.example.com/graphql", | ||
| 159 | defaultHeaders: null, | ||
| 160 | }, | ||
| 161 | ), | ||
| 162 | manifestJson: null, | ||
| 163 | manifestHash: null, | ||
| 164 | materializationHash: null, | ||
| 165 | createdAt: now, | ||
| 166 | updatedAt: now, | ||
| 167 | })); | ||
| 168 | yield* input.persistence.rows.sources.insert({ | ||
| 169 | id: input.sourceId, | ||
| 170 | workspaceId: input.workspaceId, | ||
| 171 | recipeId, | ||
| 172 | recipeRevisionId, | ||
| 173 | name: input.kind === "openapi" ? "GitHub" : "GraphQL Demo", | ||
| 174 | kind: input.kind, | ||
| 175 | endpoint: | ||
| 176 | input.kind === "openapi" | ||
| 177 | ? "https://api.example.com" | ||
| 178 | : "https://api.example.com/graphql", | ||
| 179 | status: "connected", | ||
| 180 | enabled: true, | ||
| 181 | namespace: input.kind === "openapi" ? "github" : "graphql", | ||
| 182 | importAuthPolicy: "reuse_runtime", | ||
| 183 | bindingConfigJson: | ||
| 184 | input.kind === "openapi" | ||
| 185 | ? openApiBindingConfigJson("https://api.example.com/openapi.json") | ||
| 186 | : graphqlBindingConfigJson(), | ||
| 187 | sourceHash: null, | ||
| 188 | lastError: null, | ||
| 189 | createdAt: now, | ||
| 190 | updatedAt: now, | ||
| 191 | }); | ||
| 192 | yield* input.persistence.rows.sourceRecipeDocuments.replaceForRevision({ | ||
| 193 | recipeRevisionId, | ||
| 194 | documents: [ | ||
| 195 | { | ||
| 196 | id: `src_recipe_doc_${input.sourceId}`, | ||
| 197 | recipeRevisionId, | ||
| 198 | documentKind: | ||
| 199 | input.kind === "openapi" ? "openapi" : "graphql_introspection", | ||
| 200 | documentKey: | ||
| 201 | input.kind === "openapi" | ||
| 202 | ? "https://api.example.com/openapi.json" | ||
| 203 | : "https://api.example.com/graphql", | ||
| 204 | contentText: input.documentText, | ||
| 205 | contentHash: `hash_${input.sourceId}`, | ||
| 206 | fetchedAt: now, | ||
| 207 | createdAt: now, | ||
| 208 | updatedAt: now, | ||
| 209 | }, | ||
| 210 | ], | ||
| 211 | }); | ||
| 212 | |||
| 213 | return { recipeRevisionId }; | ||
| 214 | }); | ||
| 215 | |||
| 216 | describe("code-migrations", () => { | ||
| 217 | it.scoped("repairs migrated OpenAPI recipes from stored documents", () => | ||
| 218 | Effect.gen(function* () { | ||
| 219 | const persistence = yield* makePersistence; | ||
| 220 | const workspaceId = WorkspaceIdSchema.make("ws_post_migration_openapi"); | ||
| 221 | const sourceId = SourceIdSchema.make("src_post_migration_openapi"); | ||
| 222 | yield* persistence.rows.codeMigrations.clearAll(); | ||
| 223 | |||
| 224 | const openApiDocument = JSON.stringify({ | ||
| 225 | openapi: "3.0.3", | ||
| 226 | info: { | ||
| 227 | title: "GitHub", | ||
| 228 | version: "1.0.0", | ||
| 229 | }, | ||
| 230 | paths: { | ||
| 231 | "/repos/{owner}/{repo}": { | ||
| 232 | get: { | ||
| 233 | operationId: "repos.getRepo", | ||
| 234 | parameters: [ | ||
| 235 | { | ||
| 236 | name: "owner", | ||
| 237 | in: "path", | ||
| 238 | required: true, | ||
| 239 | schema: { type: "string" }, | ||
| 240 | }, | ||
| 241 | { | ||
| 242 | name: "repo", | ||
| 243 | in: "path", | ||
| 244 | required: true, | ||
| 245 | schema: { type: "string" }, | ||
| 246 | }, | ||
| 247 | ], | ||
| 248 | responses: { | ||
| 249 | 200: { | ||
| 250 | description: "ok", | ||
| 251 | content: { | ||
| 252 | "application/json": { | ||
| 253 | schema: { | ||
| 254 | $ref: "#/components/schemas/Repository", | ||
| 255 | }, | ||
| 256 | }, | ||
| 257 | }, | ||
| 258 | }, | ||
| 259 | }, | ||
| 260 | }, | ||
| 261 | }, | ||
| 262 | }, | ||
| 263 | components: { | ||
| 264 | schemas: { | ||
| 265 | Repository: { | ||
| 266 | type: "object", | ||
| 267 | properties: { | ||
| 268 | full_name: { type: "string" }, | ||
| 269 | }, | ||
| 270 | required: ["full_name"], | ||
| 271 | }, | ||
| 272 | }, | ||
| 273 | }, | ||
| 274 | }); | ||
| 275 | |||
| 276 | const { recipeRevisionId } = yield* seedMigratedSourceRecipe({ | ||
| 277 | persistence, | ||
| 278 | kind: "openapi", | ||
| 279 | workspaceId, | ||
| 280 | sourceId, | ||
| 281 | documentText: openApiDocument, | ||
| 282 | }); | ||
| 283 | |||
| 284 | yield* runCodeMigrations(persistence.rows); | ||
| 285 | |||
| 286 | const revision = | ||
| 287 | yield* persistence.rows.sourceRecipeRevisions.getById(recipeRevisionId); | ||
| 288 | expect(Option.isSome(revision)).toBe(true); | ||
| 289 | expect(revision.pipe(Option.getOrNull)?.manifestJson).not.toBeNull(); | ||
| 290 | expect( | ||
| 291 | (yield* persistence.rows.sourceRecipeSchemaBundles.listByRevisionId( | ||
| 292 | recipeRevisionId, | ||
| 293 | )).length, | ||
| 294 | ).toBe(1); | ||
| 295 | expect( | ||
| 296 | (yield* persistence.rows.sourceRecipeOperations.listByRevisionId( | ||
| 297 | recipeRevisionId, | ||
| 298 | )).length, | ||
| 299 | ).toBeGreaterThan(0); | ||
| 300 | }), | ||
| 301 | 60_000, | ||
| 302 | ); | ||
| 303 | |||
| 304 | it.scoped("repairs migrated GraphQL recipes from stored documents", () => | ||
| 305 | Effect.gen(function* () { | ||
| 306 | const persistence = yield* makePersistence; | ||
| 307 | const workspaceId = WorkspaceIdSchema.make("ws_post_migration_graphql"); | ||
| 308 | const sourceId = SourceIdSchema.make("src_post_migration_graphql"); | ||
| 309 | yield* persistence.rows.codeMigrations.clearAll(); | ||
| 310 | const schema = buildSchema(` | ||
| 311 | type Query { | ||
| 312 | viewer: User! | ||
| 313 | } | ||
| 314 | |||
| 315 | type Mutation { | ||
| 316 | createIssue(title: String!): Issue! | ||
| 317 | } | ||
| 318 | |||
| 319 | type User { | ||
| 320 | login: String! | ||
| 321 | } | ||
| 322 | |||
| 323 | type Issue { | ||
| 324 | id: ID! | ||
| 325 | title: String! | ||
| 326 | } | ||
| 327 | `); | ||
| 328 | const graphqlDocument = JSON.stringify( | ||
| 329 | graphqlSync({ | ||
| 330 | schema, | ||
| 331 | source: getIntrospectionQuery(), | ||
| 332 | }), | ||
| 333 | ); | ||
| 334 | |||
| 335 | const { recipeRevisionId } = yield* seedMigratedSourceRecipe({ | ||
| 336 | persistence, | ||
| 337 | kind: "graphql", | ||
| 338 | workspaceId, | ||
| 339 | sourceId, | ||
| 340 | documentText: graphqlDocument, | ||
| 341 | }); | ||
| 342 | |||
| 343 | yield* runCodeMigrations(persistence.rows); | ||
| 344 | |||
| 345 | const revision = | ||
| 346 | yield* persistence.rows.sourceRecipeRevisions.getById(recipeRevisionId); | ||
| 347 | expect(Option.isSome(revision)).toBe(true); | ||
| 348 | expect(revision.pipe(Option.getOrNull)?.manifestJson).not.toBeNull(); | ||
| 349 | expect( | ||
| 350 | (yield* persistence.rows.sourceRecipeSchemaBundles.listByRevisionId( | ||
| 351 | recipeRevisionId, | ||
| 352 | )).length, | ||
| 353 | ).toBe(1); | ||
| 354 | expect( | ||
| 355 | (yield* persistence.rows.sourceRecipeOperations.listByRevisionId( | ||
| 356 | recipeRevisionId, | ||
| 357 | )).length, | ||
| 358 | ).toBeGreaterThan(0); | ||
| 359 | }), | ||
| 360 | 60_000, | ||
| 361 | ); | ||
| 362 | |||
| 363 | it.scoped("repairs legacy MCP source auth session payloads", () => | ||
| 364 | Effect.gen(function* () { | ||
| 365 | const persistence = yield* makePersistence; | ||
| 366 | const workspaceId = WorkspaceIdSchema.make("ws_post_migration_session"); | ||
| 367 | const sourceId = SourceIdSchema.make("src_post_migration_session"); | ||
| 368 | yield* persistence.rows.codeMigrations.clearAll(); | ||
| 369 | const sessionId = SourceAuthSessionIdSchema.make( | ||
| 370 | "src_auth_post_migration_session", | ||
| 371 | ); | ||
| 372 | const now = Date.now(); | ||
| 373 | |||
| 374 | yield* persistence.rows.sourceAuthSessions.insert({ | ||
| 375 | id: sessionId, | ||
| 376 | workspaceId, | ||
| 377 | sourceId, | ||
| 378 | actorAccountId: null, | ||
| 379 | executionId: null, | ||
| 380 | interactionId: null, | ||
| 381 | providerKind: "mcp_oauth", | ||
| 382 | credentialSlot: "runtime", | ||
| 383 | status: "pending", | ||
| 384 | state: "state_post_migration_session", | ||
| 385 | sessionDataJson: JSON.stringify({ | ||
| 386 | kind: "mcp_oauth", | ||
| 387 | endpoint: "https://api.github.com", | ||
| 388 | redirectUri: "http://127.0.0.1/callback", | ||
| 389 | scope: null, | ||
| 390 | resourceMetadataUrl: | ||
| 391 | "https://api.github.com/.well-known/oauth-protected-resource", | ||
| 392 | authorizationServerUrl: "https://github.com/login/oauth", | ||
| 393 | resourceMetadataJson: '{"issuer":"https://api.github.com"}', | ||
| 394 | authorizationServerMetadataJson: | ||
| 395 | '{"token_endpoint":"https://github.com/login/oauth/access_token"}', | ||
| 396 | clientInformationJson: '{"client_id":"abc123"}', | ||
| 397 | codeVerifier: "verifier", | ||
| 398 | authorizationUrl: "https://github.com/login/oauth/authorize", | ||
| 399 | }), | ||
| 400 | errorText: null, | ||
| 401 | completedAt: null, | ||
| 402 | createdAt: now, | ||
| 403 | updatedAt: now, | ||
| 404 | }); | ||
| 405 | |||
| 406 | yield* runCodeMigrations(persistence.rows); | ||
| 407 | |||
| 408 | const session = | ||
| 409 | yield* persistence.rows.sourceAuthSessions.getById(sessionId); | ||
| 410 | expect(Option.isSome(session)).toBe(true); | ||
| 411 | const repairedSession = session.pipe(Option.getOrNull); | ||
| 412 | expect(repairedSession).not.toBeNull(); | ||
| 413 | const repaired = Schema.decodeUnknownSync( | ||
| 414 | McpSourceAuthSessionDataJsonSchema, | ||
| 415 | )(repairedSession!.sessionDataJson); | ||
| 416 | expect(repaired.resourceMetadata).toEqual({ | ||
| 417 | issuer: "https://api.github.com", | ||
| 418 | }); | ||
| 419 | expect(repaired.authorizationServerMetadata).toEqual({ | ||
| 420 | token_endpoint: "https://github.com/login/oauth/access_token", | ||
| 421 | }); | ||
| 422 | expect(repaired.clientInformation).toEqual({ | ||
| 423 | client_id: "abc123", | ||
| 424 | }); | ||
| 425 | }), | ||
| 426 | ); | ||
| 427 | |||
| 428 | it("upgrades legacy source binding columns into adapter-owned binding config", async () => { | ||
| 429 | const localDataDir = mkdtempSync(path.join(tmpdir(), "executor-cp-db-")); | ||
| 430 | const legacyMigrationsDir = copyMigrationsBefore( | ||
| 431 | "20260311010000_source_binding_configs", | ||
| 432 | ); | ||
| 433 | const workspaceId = WorkspaceIdSchema.make("ws_legacy_source_bindings"); | ||
| 434 | const accountId = AccountIdSchema.make("acc_legacy_source_bindings"); | ||
| 435 | const now = Date.now(); | ||
| 436 | let legacyPersistence: SqlControlPlanePersistence | null = null; | ||
| 437 | let upgradedPersistence: SqlControlPlanePersistence | null = null; | ||
| 438 | |||
| 439 | try { | ||
| 440 | legacyPersistence = await Effect.runPromise( | ||
| 441 | createSqlControlPlanePersistence({ | ||
| 442 | localDataDir, | ||
| 443 | migrationsFolder: legacyMigrationsDir, | ||
| 444 | runCodeMigrations: false, | ||
| 445 | }), | ||
| 446 | ); | ||
| 447 | |||
| 448 | const seedRecipe = async (input: { | ||
| 449 | sourceId: string; | ||
| 450 | kind: "openapi" | "graphql" | "mcp"; | ||
| 451 | adapterKey: string; | ||
| 452 | providerKey: string; | ||
| 453 | name: string; | ||
| 454 | endpoint: string; | ||
| 455 | sourceConfigJson: string; | ||
| 456 | }) => { | ||
| 457 | const recipeId = SourceRecipeIdSchema.make(`src_recipe_${input.sourceId}`); | ||
| 458 | const recipeRevisionId = SourceRecipeRevisionIdSchema.make( | ||
| 459 | `src_recipe_rev_${input.sourceId}`, | ||
| 460 | ); | ||
| 461 | |||
| 462 | await legacyPersistence!.db.execute(sql` | ||
| 463 | INSERT INTO "source_recipes" ( | ||
| 464 | "id", | ||
| 465 | "kind", | ||
| 466 | "importer_kind", | ||
| 467 | "provider_key", | ||
| 468 | "name", | ||
| 469 | "summary", | ||
| 470 | "visibility", | ||
| 471 | "latest_revision_id", | ||
| 472 | "created_at", | ||
| 473 | "updated_at" | ||
| 474 | ) VALUES ( | ||
| 475 | ${recipeId}, | ||
| 476 | ${input.kind === "mcp" ? "mcp" : "http_api"}, | ||
| 477 | ${input.adapterKey}, | ||
| 478 | ${input.providerKey}, | ||
| 479 | ${input.name}, | ||
| 480 | NULL, | ||
| 481 | 'workspace', | ||
| 482 | ${recipeRevisionId}, | ||
| 483 | ${now}, | ||
| 484 | ${now} | ||
| 485 | ) | ||
| 486 | `); | ||
| 487 | await legacyPersistence!.db.execute(sql` | ||
| 488 | INSERT INTO "source_recipe_revisions" ( | ||
| 489 | "id", | ||
| 490 | "recipe_id", | ||
| 491 | "revision_number", | ||
| 492 | "source_config_json", | ||
| 493 | "manifest_json", | ||
| 494 | "manifest_hash", | ||
| 495 | "created_at", | ||
| 496 | "updated_at" | ||
| 497 | ) VALUES ( | ||
| 498 | ${recipeRevisionId}, | ||
| 499 | ${recipeId}, | ||
| 500 | 1, | ||
| 501 | ${input.sourceConfigJson}, | ||
| 502 | NULL, | ||
| 503 | NULL, | ||
| 504 | ${now}, | ||
| 505 | ${now} | ||
| 506 | ) | ||
| 507 | `); | ||
| 508 | |||
| 509 | return { recipeId, recipeRevisionId }; | ||
| 510 | }; | ||
| 511 | |||
| 512 | const openapi = await seedRecipe({ | ||
| 513 | sourceId: "src_legacy_openapi", | ||
| 514 | kind: "openapi", | ||
| 515 | adapterKey: "openapi", | ||
| 516 | providerKey: "generic_http", | ||
| 517 | name: "Legacy OpenAPI", | ||
| 518 | endpoint: "https://api.example.com", | ||
| 519 | sourceConfigJson: JSON.stringify({ | ||
| 520 | kind: "openapi", | ||
| 521 | endpoint: "https://api.example.com", | ||
| 522 | specUrl: "https://api.example.com/openapi.json", | ||
| 523 | defaultHeaders: { accept: "application/json" }, | ||
| 524 | }), | ||
| 525 | }); | ||
| 526 | const graphql = await seedRecipe({ | ||
| 527 | sourceId: "src_legacy_graphql", | ||
| 528 | kind: "graphql", | ||
| 529 | adapterKey: "graphql_introspection", | ||
| 530 | providerKey: "generic_graphql", | ||
| 531 | name: "Legacy GraphQL", | ||
| 532 | endpoint: "https://api.example.com/graphql", | ||
| 533 | sourceConfigJson: JSON.stringify({ | ||
| 534 | kind: "graphql", | ||
| 535 | endpoint: "https://api.example.com/graphql", | ||
| 536 | defaultHeaders: { accept: "application/json" }, | ||
| 537 | }), | ||
| 538 | }); | ||
| 539 | const mcp = await seedRecipe({ | ||
| 540 | sourceId: "src_legacy_mcp", | ||
| 541 | kind: "mcp", | ||
| 542 | adapterKey: "mcp_manifest", | ||
| 543 | providerKey: "generic_mcp", | ||
| 544 | name: "Legacy MCP", | ||
| 545 | endpoint: "https://api.example.com/mcp", | ||
| 546 | sourceConfigJson: JSON.stringify({ | ||
| 547 | kind: "mcp", | ||
| 548 | endpoint: "https://api.example.com/mcp", | ||
| 549 | transport: "streamable-http", | ||
| 550 | queryParams: { tenant: "acme" }, | ||
| 551 | headers: { "x-tenant": "acme" }, | ||
| 552 | }), | ||
| 553 | }); | ||
| 554 | |||
| 555 | await legacyPersistence.db.execute(sql` | ||
| 556 | INSERT INTO "sources" ( | ||
| 557 | "workspace_id", | ||
| 558 | "source_id", | ||
| 559 | "recipe_id", | ||
| 560 | "recipe_revision_id", | ||
| 561 | "name", | ||
| 562 | "kind", | ||
| 563 | "endpoint", | ||
| 564 | "status", | ||
| 565 | "enabled", | ||
| 566 | "namespace", | ||
| 567 | "binding_config_json", | ||
| 568 | "transport", | ||
| 569 | "query_params_json", | ||
| 570 | "headers_json", | ||
| 571 | "spec_url", | ||
| 572 | "default_headers_json", | ||
| 573 | "source_hash", | ||
| 574 | "source_document_text", | ||
| 575 | "last_error", | ||
| 576 | "created_at", | ||
| 577 | "updated_at" | ||
| 578 | ) VALUES | ||
| 579 | ( | ||
| 580 | ${workspaceId}, | ||
| 581 | ${SourceIdSchema.make("src_legacy_openapi")}, | ||
| 582 | ${openapi.recipeId}, | ||
| 583 | ${openapi.recipeRevisionId}, | ||
| 584 | 'Legacy OpenAPI', | ||
| 585 | 'openapi', | ||
| 586 | 'https://api.example.com', | ||
| 587 | 'connected', | ||
| 588 | true, | ||
| 589 | 'legacy.openapi', | ||
| 590 | NULL, | ||
| 591 | NULL, | ||
| 592 | NULL, | ||
| 593 | NULL, | ||
| 594 | 'https://api.example.com/openapi.json', | ||
| 595 | '{"accept":"application/json"}', | ||
| 596 | NULL, | ||
| 597 | NULL, | ||
| 598 | NULL, | ||
| 599 | ${now}, | ||
| 600 | ${now} | ||
| 601 | ), | ||
| 602 | ( | ||
| 603 | ${workspaceId}, | ||
| 604 | ${SourceIdSchema.make("src_legacy_graphql")}, | ||
| 605 | ${graphql.recipeId}, | ||
| 606 | ${graphql.recipeRevisionId}, | ||
| 607 | 'Legacy GraphQL', | ||
| 608 | 'graphql', | ||
| 609 | 'https://api.example.com/graphql', | ||
| 610 | 'connected', | ||
| 611 | true, | ||
| 612 | 'legacy.graphql', | ||
| 613 | NULL, | ||
| 614 | NULL, | ||
| 615 | NULL, | ||
| 616 | NULL, | ||
| 617 | NULL, | ||
| 618 | '{"accept":"application/json"}', | ||
| 619 | NULL, | ||
| 620 | NULL, | ||
| 621 | NULL, | ||
| 622 | ${now}, | ||
| 623 | ${now} | ||
| 624 | ), | ||
| 625 | ( | ||
| 626 | ${workspaceId}, | ||
| 627 | ${SourceIdSchema.make("src_legacy_mcp")}, | ||
| 628 | ${mcp.recipeId}, | ||
| 629 | ${mcp.recipeRevisionId}, | ||
| 630 | 'Legacy MCP', | ||
| 631 | 'mcp', | ||
| 632 | 'https://api.example.com/mcp', | ||
| 633 | 'connected', | ||
| 634 | true, | ||
| 635 | 'legacy.mcp', | ||
| 636 | NULL, | ||
| 637 | 'streamable-http', | ||
| 638 | '{"tenant":"acme"}', | ||
| 639 | '{"x-tenant":"acme"}', | ||
| 640 | NULL, | ||
| 641 | NULL, | ||
| 642 | NULL, | ||
| 643 | NULL, | ||
| 644 | NULL, | ||
| 645 | ${now}, | ||
| 646 | ${now} | ||
| 647 | ); | ||
| 648 | `); | ||
| 649 | |||
| 650 | await legacyPersistence.close(); | ||
| 651 | legacyPersistence = null; | ||
| 652 | |||
| 653 | upgradedPersistence = await Effect.runPromise( | ||
| 654 | createSqlControlPlanePersistence({ | ||
| 655 | localDataDir, | ||
| 656 | }), | ||
| 657 | ); | ||
| 658 | |||
| 659 | const openApiSource = await Effect.runPromise( | ||
| 660 | upgradedPersistence.rows.sources.getByWorkspaceAndId( | ||
| 661 | workspaceId, | ||
| 662 | SourceIdSchema.make("src_legacy_openapi"), | ||
| 663 | ), | ||
| 664 | ); | ||
| 665 | expect( | ||
| 666 | JSON.parse(Option.getOrThrow(openApiSource).bindingConfigJson), | ||
| 667 | ).toEqual({ | ||
| 668 | adapterKey: "openapi", | ||
| 669 | version: 1, | ||
| 670 | payload: { | ||
| 671 | specUrl: "https://api.example.com/openapi.json", | ||
| 672 | defaultHeaders: { | ||
| 673 | accept: "application/json", | ||
| 674 | }, | ||
| 675 | }, | ||
| 676 | }); | ||
| 677 | |||
| 678 | const graphqlSource = await Effect.runPromise( | ||
| 679 | upgradedPersistence.rows.sources.getByWorkspaceAndId( | ||
| 680 | workspaceId, | ||
| 681 | SourceIdSchema.make("src_legacy_graphql"), | ||
| 682 | ), | ||
| 683 | ); | ||
| 684 | expect( | ||
| 685 | JSON.parse(Option.getOrThrow(graphqlSource).bindingConfigJson), | ||
| 686 | ).toEqual({ | ||
| 687 | adapterKey: "graphql", | ||
| 688 | version: 1, | ||
| 689 | payload: { | ||
| 690 | defaultHeaders: { | ||
| 691 | accept: "application/json", | ||
| 692 | }, | ||
| 693 | }, | ||
| 694 | }); | ||
| 695 | |||
| 696 | const graphqlRecipe = await Effect.runPromise( | ||
| 697 | upgradedPersistence.rows.sourceRecipes.getById(graphql.recipeId), | ||
| 698 | ); | ||
| 699 | expect(Option.getOrNull(graphqlRecipe)?.adapterKey).toBe("graphql"); | ||
| 700 | |||
| 701 | const mcpSource = await Effect.runPromise( | ||
| 702 | upgradedPersistence.rows.sources.getByWorkspaceAndId( | ||
| 703 | workspaceId, | ||
| 704 | SourceIdSchema.make("src_legacy_mcp"), | ||
| 705 | ), | ||
| 706 | ); | ||
| 707 | expect( | ||
| 708 | JSON.parse(Option.getOrThrow(mcpSource).bindingConfigJson), | ||
| 709 | ).toEqual({ | ||
| 710 | adapterKey: "mcp", | ||
| 711 | version: 1, | ||
| 712 | payload: { | ||
| 713 | transport: "streamable-http", | ||
| 714 | queryParams: { | ||
| 715 | tenant: "acme", | ||
| 716 | }, | ||
| 717 | headers: { | ||
| 718 | "x-tenant": "acme", | ||
| 719 | }, | ||
| 720 | }, | ||
| 721 | }); | ||
| 722 | |||
| 723 | const mcpRecipe = await Effect.runPromise( | ||
| 724 | upgradedPersistence.rows.sourceRecipes.getById(mcp.recipeId), | ||
| 725 | ); | ||
| 726 | expect(Option.getOrNull(mcpRecipe)?.adapterKey).toBe("mcp"); | ||
| 727 | } finally { | ||
| 728 | await legacyPersistence?.close().catch(() => undefined); | ||
| 729 | await upgradedPersistence?.close().catch(() => undefined); | ||
| 730 | rmSync(localDataDir, { recursive: true, force: true }); | ||
| 731 | rmSync(legacyMigrationsDir, { recursive: true, force: true }); | ||
| 732 | } | ||
| 733 | }); | ||
| 734 | |||
| 735 | it("upgrades auth leases into template-based placement storage", async () => { | ||
| 736 | const localDataDir = mkdtempSync(path.join(tmpdir(), "executor-cp-db-")); | ||
| 737 | const legacyMigrationsDir = copyMigrationsBefore( | ||
| 738 | "20260312143000_auth_lease_template_placements", | ||
| 739 | ); | ||
| 740 | const workspaceId = WorkspaceIdSchema.make("ws_legacy_auth_lease"); | ||
| 741 | const accountId = AccountIdSchema.make("acc_legacy_auth_lease"); | ||
| 742 | const sourceId = SourceIdSchema.make("src_legacy_auth_lease"); | ||
| 743 | const recipeId = SourceRecipeIdSchema.make("src_recipe_legacy_auth_lease"); | ||
| 744 | const recipeRevisionId = SourceRecipeRevisionIdSchema.make( | ||
| 745 | "src_recipe_rev_legacy_auth_lease", | ||
| 746 | ); | ||
| 747 | const authArtifactId = "auth_art_legacy_auth_lease"; | ||
| 748 | const now = Date.now(); | ||
| 749 | let legacyPersistence: SqlControlPlanePersistence | null = null; | ||
| 750 | let upgradedPersistence: SqlControlPlanePersistence | null = null; | ||
| 751 | |||
| 752 | try { | ||
| 753 | legacyPersistence = await Effect.runPromise( | ||
| 754 | createSqlControlPlanePersistence({ | ||
| 755 | localDataDir, | ||
| 756 | migrationsFolder: legacyMigrationsDir, | ||
| 757 | runCodeMigrations: false, | ||
| 758 | }), | ||
| 759 | ); | ||
| 760 | |||
| 761 | await Effect.runPromise(legacyPersistence.rows.sourceRecipes.upsert({ | ||
| 762 | id: recipeId, | ||
| 763 | kind: "http_api", | ||
| 764 | adapterKey: "openapi", | ||
| 765 | providerKey: "generic_http", | ||
| 766 | name: "Legacy OpenAPI", | ||
| 767 | summary: null, | ||
| 768 | visibility: "workspace", | ||
| 769 | latestRevisionId: recipeRevisionId, | ||
| 770 | createdAt: now, | ||
| 771 | updatedAt: now, | ||
| 772 | })); | ||
| 773 | await Effect.runPromise(legacyPersistence.rows.sourceRecipeRevisions.upsert(baseRevisionRecord({ | ||
| 774 | id: recipeRevisionId, | ||
| 775 | recipeId, | ||
| 776 | revisionNumber: 1, | ||
| 777 | sourceConfigJson: JSON.stringify({ | ||
| 778 | kind: "openapi", | ||
| 779 | endpoint: "https://api.example.com", | ||
| 780 | specUrl: "https://api.example.com/openapi.json", | ||
| 781 | }), | ||
| 782 | createdAt: now, | ||
| 783 | updatedAt: now, | ||
| 784 | }))); | ||
| 785 | await legacyPersistence.db.execute(sql` | ||
| 786 | INSERT INTO "sources" ( | ||
| 787 | "source_id", | ||
| 788 | "workspace_id", | ||
| 789 | "recipe_id", | ||
| 790 | "recipe_revision_id", | ||
| 791 | "name", | ||
| 792 | "kind", | ||
| 793 | "endpoint", | ||
| 794 | "status", | ||
| 795 | "enabled", | ||
| 796 | "namespace", | ||
| 797 | "import_auth_policy", | ||
| 798 | "binding_config_json", | ||
| 799 | "source_hash", | ||
| 800 | "last_error", | ||
| 801 | "created_at", | ||
| 802 | "updated_at" | ||
| 803 | ) VALUES ( | ||
| 804 | ${sourceId}, | ||
| 805 | ${workspaceId}, | ||
| 806 | ${recipeId}, | ||
| 807 | ${recipeRevisionId}, | ||
| 808 | ${"Legacy OpenAPI"}, | ||
| 809 | ${"openapi"}, | ||
| 810 | ${"https://api.example.com"}, | ||
| 811 | ${"connected"}, | ||
| 812 | ${true}, | ||
| 813 | ${"legacy.openapi"}, | ||
| 814 | ${"reuse_runtime"}, | ||
| 815 | ${openApiBindingConfigJson("https://api.example.com/openapi.json")}, | ||
| 816 | NULL, | ||
| 817 | NULL, | ||
| 818 | ${now}, | ||
| 819 | ${now} | ||
| 820 | ) | ||
| 821 | `); | ||
| 822 | await Effect.runPromise(legacyPersistence.rows.authArtifacts.upsert({ | ||
| 823 | id: authArtifactId as any, | ||
| 824 | workspaceId, | ||
| 825 | sourceId, | ||
| 826 | actorAccountId: accountId, | ||
| 827 | slot: "runtime", | ||
| 828 | artifactKind: "static_bearer", | ||
| 829 | configJson: JSON.stringify({ | ||
| 830 | headerName: "Authorization", | ||
| 831 | prefix: "Bearer ", | ||
| 832 | token: { | ||
| 833 | providerId: "postgres", | ||
| 834 | handle: "sec_legacy_auth_lease_token", | ||
| 835 | }, | ||
| 836 | }), | ||
| 837 | grantSetJson: null, | ||
| 838 | createdAt: now, | ||
| 839 | updatedAt: now, | ||
| 840 | })); | ||
| 841 | |||
| 842 | await legacyPersistence.db.execute(sql` | ||
| 843 | INSERT INTO "workspace_source_auth_leases" ( | ||
| 844 | "id", | ||
| 845 | "auth_artifact_id", | ||
| 846 | "workspace_id", | ||
| 847 | "source_id", | ||
| 848 | "actor_account_id", | ||
| 849 | "slot", | ||
| 850 | "placements_json", | ||
| 851 | "expires_at", | ||
| 852 | "refresh_after", | ||
| 853 | "created_at", | ||
| 854 | "updated_at" | ||
| 855 | ) VALUES ( | ||
| 856 | 'auth_lease_legacy_auth_lease', | ||
| 857 | ${authArtifactId}, | ||
| 858 | ${workspaceId}, | ||
| 859 | ${sourceId}, | ||
| 860 | ${accountId}, | ||
| 861 | 'runtime', | ||
| 862 | '[{"location":"header","name":"Authorization","value":"Bearer abc"},{"location":"query","name":"api_key","value":"q123"},{"location":"cookie","name":"sid","value":"cookie123"},{"location":"body","path":"auth.token","value":"body123"}]', | ||
| 863 | NULL, | ||
| 864 | NULL, | ||
| 865 | ${now}, | ||
| 866 | ${now} | ||
| 867 | ); | ||
| 868 | `); | ||
| 869 | |||
| 870 | await legacyPersistence.close(); | ||
| 871 | legacyPersistence = null; | ||
| 872 | |||
| 873 | upgradedPersistence = await Effect.runPromise( | ||
| 874 | createSqlControlPlanePersistence({ | ||
| 875 | localDataDir, | ||
| 876 | }), | ||
| 877 | ); | ||
| 878 | |||
| 879 | const leaseOption = await Effect.runPromise( | ||
| 880 | upgradedPersistence.rows.authLeases.getByAuthArtifactId(authArtifactId as any), | ||
| 881 | ); | ||
| 882 | expect(Option.isSome(leaseOption)).toBe(true); | ||
| 883 | const lease = Option.getOrNull(leaseOption); | ||
| 884 | expect(lease).not.toBeNull(); | ||
| 885 | expect(lease?.placementsTemplateJson).not.toBeNull(); | ||
| 886 | expect(decodeAuthLeasePlacementTemplates(lease!)).toEqual([ | ||
| 887 | { | ||
| 888 | location: "header", | ||
| 889 | name: "Authorization", | ||
| 890 | parts: [{ kind: "literal", value: "Bearer abc" }], | ||
| 891 | }, | ||
| 892 | { | ||
| 893 | location: "query", | ||
| 894 | name: "api_key", | ||
| 895 | parts: [{ kind: "literal", value: "q123" }], | ||
| 896 | }, | ||
| 897 | { | ||
| 898 | location: "cookie", | ||
| 899 | name: "sid", | ||
| 900 | parts: [{ kind: "literal", value: "cookie123" }], | ||
| 901 | }, | ||
| 902 | { | ||
| 903 | location: "body", | ||
| 904 | path: "auth.token", | ||
| 905 | parts: [{ kind: "literal", value: "body123" }], | ||
| 906 | }, | ||
| 907 | ]); | ||
| 908 | } finally { | ||
| 909 | await legacyPersistence?.close().catch(() => undefined); | ||
| 910 | await upgradedPersistence?.close().catch(() => undefined); | ||
| 911 | rmSync(localDataDir, { recursive: true, force: true }); | ||
| 912 | rmSync(legacyMigrationsDir, { recursive: true, force: true }); | ||
| 913 | } | ||
| 914 | }); | ||
| 915 | }); | ||
| @@ -1,71 +0,0 @@ | |||
| 1 | import { type Account, AccountSchema } from "#schema"; | ||
| 2 | import * as Option from "effect/Option"; | ||
| 3 | import { Schema } from "effect"; | ||
| 4 | import { and, eq } from "drizzle-orm"; | ||
| 5 | |||
| 6 | import type { DrizzleClient } from "../client"; | ||
| 7 | import type { DrizzleTables } from "../schema"; | ||
| 8 | import { firstOption } from "./shared"; | ||
| 9 | |||
| 10 | const decodeAccount = Schema.decodeUnknownSync(AccountSchema); | ||
| 11 | |||
| 12 | export const createAccountsRepo = ( | ||
| 13 | client: DrizzleClient, | ||
| 14 | tables: DrizzleTables, | ||
| 15 | ) => ({ | ||
| 16 | getById: (accountId: Account["id"]) => | ||
| 17 | client.use("rows.accounts.get_by_id", async (db) => { | ||
| 18 | const rows = await db | ||
| 19 | .select() | ||
| 20 | .from(tables.accountsTable) | ||
| 21 | .where(eq(tables.accountsTable.id, accountId)) | ||
| 22 | .limit(1); | ||
| 23 | |||
| 24 | const row = firstOption(rows); | ||
| 25 | return Option.isSome(row) | ||
| 26 | ? Option.some(decodeAccount(row.value)) | ||
| 27 | : Option.none<Account>(); | ||
| 28 | }), | ||
| 29 | |||
| 30 | getByProviderAndSubject: ( | ||
| 31 | provider: Account["provider"], | ||
| 32 | subject: Account["subject"], | ||
| 33 | ) => | ||
| 34 | client.use("rows.accounts.get_by_provider_and_subject", async (db) => { | ||
| 35 | const rows = await db | ||
| 36 | .select() | ||
| 37 | .from(tables.accountsTable) | ||
| 38 | .where( | ||
| 39 | and( | ||
| 40 | eq(tables.accountsTable.provider, provider), | ||
| 41 | eq(tables.accountsTable.subject, subject), | ||
| 42 | ), | ||
| 43 | ) | ||
| 44 | .limit(1); | ||
| 45 | |||
| 46 | const row = firstOption(rows); | ||
| 47 | return Option.isSome(row) | ||
| 48 | ? Option.some(decodeAccount(row.value)) | ||
| 49 | : Option.none<Account>(); | ||
| 50 | }), | ||
| 51 | |||
| 52 | insert: (account: Account) => | ||
| 53 | client.use("rows.accounts.insert", async (db) => { | ||
| 54 | await db.insert(tables.accountsTable).values(account); | ||
| 55 | }), | ||
| 56 | |||
| 57 | upsert: (account: Account) => | ||
| 58 | client.use("rows.accounts.upsert", async (db) => { | ||
| 59 | await db | ||
| 60 | .insert(tables.accountsTable) | ||
| 61 | .values(account) | ||
| 62 | .onConflictDoUpdate({ | ||
| 63 | target: [tables.accountsTable.provider, tables.accountsTable.subject], | ||
| 64 | set: { | ||
| 65 | email: account.email, | ||
| 66 | displayName: account.displayName, | ||
| 67 | updatedAt: account.updatedAt, | ||
| 68 | }, | ||
| 69 | }); | ||
| 70 | }), | ||
| 71 | }); | ||
| @@ -1,226 +0,0 @@ | |||
| 1 | import { | ||
| 2 | AuthArtifactSchema, | ||
| 3 | type AuthArtifact, | ||
| 4 | } from "#schema"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import { Schema } from "effect"; | ||
| 7 | import { and, asc, eq, inArray, isNull, or } from "drizzle-orm"; | ||
| 8 | |||
| 9 | import type { DrizzleClient } from "../client"; | ||
| 10 | import type { DrizzleTables } from "../schema"; | ||
| 11 | import { firstOption } from "./shared"; | ||
| 12 | |||
| 13 | const decodeAuthArtifact = Schema.decodeUnknownSync(AuthArtifactSchema); | ||
| 14 | |||
| 15 | const authArtifactUpdateSet = (artifact: AuthArtifact) => { | ||
| 16 | const { | ||
| 17 | id: _id, | ||
| 18 | workspaceId: _workspaceId, | ||
| 19 | sourceId: _sourceId, | ||
| 20 | actorAccountId: _actorAccountId, | ||
| 21 | createdAt: _createdAt, | ||
| 22 | ...patch | ||
| 23 | } = artifact; | ||
| 24 | return patch; | ||
| 25 | }; | ||
| 26 | |||
| 27 | export const createAuthArtifactsRepo = ( | ||
| 28 | client: DrizzleClient, | ||
| 29 | tables: DrizzleTables, | ||
| 30 | ) => ({ | ||
| 31 | listByWorkspaceId: (workspaceId: AuthArtifact["workspaceId"]) => | ||
| 32 | client.use("rows.auth_artifacts.list_by_workspace", async (db) => { | ||
| 33 | const rows = await db | ||
| 34 | .select() | ||
| 35 | .from(tables.authArtifactsTable) | ||
| 36 | .where(eq(tables.authArtifactsTable.workspaceId, workspaceId)) | ||
| 37 | .orderBy( | ||
| 38 | asc(tables.authArtifactsTable.updatedAt), | ||
| 39 | asc(tables.authArtifactsTable.id), | ||
| 40 | ); | ||
| 41 | |||
| 42 | return rows.map((row) => decodeAuthArtifact(row)); | ||
| 43 | }), | ||
| 44 | |||
| 45 | listByWorkspaceAndSourceId: (input: { | ||
| 46 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 47 | sourceId: AuthArtifact["sourceId"]; | ||
| 48 | }) => | ||
| 49 | client.use("rows.auth_artifacts.list_by_workspace_source", async (db) => { | ||
| 50 | const rows = await db | ||
| 51 | .select() | ||
| 52 | .from(tables.authArtifactsTable) | ||
| 53 | .where( | ||
| 54 | and( | ||
| 55 | eq(tables.authArtifactsTable.workspaceId, input.workspaceId), | ||
| 56 | eq(tables.authArtifactsTable.sourceId, input.sourceId), | ||
| 57 | ), | ||
| 58 | ) | ||
| 59 | .orderBy( | ||
| 60 | asc(tables.authArtifactsTable.updatedAt), | ||
| 61 | asc(tables.authArtifactsTable.id), | ||
| 62 | ); | ||
| 63 | |||
| 64 | return rows.map((row) => decodeAuthArtifact(row)); | ||
| 65 | }), | ||
| 66 | |||
| 67 | listByWorkspaceSourceAndActor: (input: { | ||
| 68 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 69 | sourceId: AuthArtifact["sourceId"]; | ||
| 70 | actorAccountId: AuthArtifact["actorAccountId"]; | ||
| 71 | }) => | ||
| 72 | client.use("rows.auth_artifacts.list_by_workspace_source_actor", async (db) => { | ||
| 73 | const rows = await db | ||
| 74 | .select() | ||
| 75 | .from(tables.authArtifactsTable) | ||
| 76 | .where( | ||
| 77 | and( | ||
| 78 | eq(tables.authArtifactsTable.workspaceId, input.workspaceId), | ||
| 79 | eq(tables.authArtifactsTable.sourceId, input.sourceId), | ||
| 80 | or( | ||
| 81 | input.actorAccountId === null | ||
| 82 | ? isNull(tables.authArtifactsTable.actorAccountId) | ||
| 83 | : eq(tables.authArtifactsTable.actorAccountId, input.actorAccountId), | ||
| 84 | input.actorAccountId === null | ||
| 85 | ? undefined | ||
| 86 | : isNull(tables.authArtifactsTable.actorAccountId), | ||
| 87 | ), | ||
| 88 | ), | ||
| 89 | ) | ||
| 90 | .orderBy( | ||
| 91 | asc(tables.authArtifactsTable.slot), | ||
| 92 | asc(tables.authArtifactsTable.actorAccountId), | ||
| 93 | asc(tables.authArtifactsTable.updatedAt), | ||
| 94 | asc(tables.authArtifactsTable.id), | ||
| 95 | ); | ||
| 96 | |||
| 97 | return rows.map((row) => decodeAuthArtifact(row)); | ||
| 98 | }), | ||
| 99 | |||
| 100 | getByWorkspaceSourceAndActor: (input: { | ||
| 101 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 102 | sourceId: AuthArtifact["sourceId"]; | ||
| 103 | actorAccountId: AuthArtifact["actorAccountId"]; | ||
| 104 | slot: AuthArtifact["slot"]; | ||
| 105 | }) => | ||
| 106 | client.use("rows.auth_artifacts.get_by_workspace_source_actor", async (db) => { | ||
| 107 | const rows = await db | ||
| 108 | .select() | ||
| 109 | .from(tables.authArtifactsTable) | ||
| 110 | .where( | ||
| 111 | and( | ||
| 112 | eq(tables.authArtifactsTable.workspaceId, input.workspaceId), | ||
| 113 | eq(tables.authArtifactsTable.sourceId, input.sourceId), | ||
| 114 | eq(tables.authArtifactsTable.slot, input.slot), | ||
| 115 | input.actorAccountId === null | ||
| 116 | ? isNull(tables.authArtifactsTable.actorAccountId) | ||
| 117 | : eq(tables.authArtifactsTable.actorAccountId, input.actorAccountId), | ||
| 118 | ), | ||
| 119 | ) | ||
| 120 | .limit(1); | ||
| 121 | |||
| 122 | const row = firstOption(rows); | ||
| 123 | return Option.isSome(row) | ||
| 124 | ? Option.some(decodeAuthArtifact(row.value)) | ||
| 125 | : Option.none<AuthArtifact>(); | ||
| 126 | }), | ||
| 127 | |||
| 128 | upsert: (artifact: AuthArtifact) => | ||
| 129 | client.use("rows.auth_artifacts.upsert", async (db) => { | ||
| 130 | if (artifact.actorAccountId === null) { | ||
| 131 | const existingRows = await db | ||
| 132 | .select({ | ||
| 133 | id: tables.authArtifactsTable.id, | ||
| 134 | }) | ||
| 135 | .from(tables.authArtifactsTable) | ||
| 136 | .where( | ||
| 137 | and( | ||
| 138 | eq(tables.authArtifactsTable.workspaceId, artifact.workspaceId), | ||
| 139 | eq(tables.authArtifactsTable.sourceId, artifact.sourceId), | ||
| 140 | eq(tables.authArtifactsTable.slot, artifact.slot), | ||
| 141 | isNull(tables.authArtifactsTable.actorAccountId), | ||
| 142 | ), | ||
| 143 | ) | ||
| 144 | .orderBy( | ||
| 145 | asc(tables.authArtifactsTable.updatedAt), | ||
| 146 | asc(tables.authArtifactsTable.id), | ||
| 147 | ); | ||
| 148 | |||
| 149 | const existing = firstOption(existingRows); | ||
| 150 | if (Option.isSome(existing)) { | ||
| 151 | await db | ||
| 152 | .update(tables.authArtifactsTable) | ||
| 153 | .set(authArtifactUpdateSet(artifact)) | ||
| 154 | .where(eq(tables.authArtifactsTable.id, existing.value.id)); | ||
| 155 | |||
| 156 | const duplicateIds = existingRows.slice(1).map((row) => row.id); | ||
| 157 | if (duplicateIds.length > 0) { | ||
| 158 | await db | ||
| 159 | .delete(tables.authArtifactsTable) | ||
| 160 | .where(inArray(tables.authArtifactsTable.id, duplicateIds)); | ||
| 161 | } | ||
| 162 | |||
| 163 | return; | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | await db | ||
| 168 | .insert(tables.authArtifactsTable) | ||
| 169 | .values(artifact) | ||
| 170 | .onConflictDoUpdate({ | ||
| 171 | target: [ | ||
| 172 | tables.authArtifactsTable.workspaceId, | ||
| 173 | tables.authArtifactsTable.sourceId, | ||
| 174 | tables.authArtifactsTable.actorAccountId, | ||
| 175 | tables.authArtifactsTable.slot, | ||
| 176 | ], | ||
| 177 | set: { | ||
| 178 | ...authArtifactUpdateSet(artifact), | ||
| 179 | }, | ||
| 180 | }); | ||
| 181 | }), | ||
| 182 | |||
| 183 | removeByWorkspaceSourceAndActor: (input: { | ||
| 184 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 185 | sourceId: AuthArtifact["sourceId"]; | ||
| 186 | actorAccountId: AuthArtifact["actorAccountId"]; | ||
| 187 | slot?: AuthArtifact["slot"]; | ||
| 188 | }) => | ||
| 189 | client.use("rows.auth_artifacts.remove_by_workspace_source_actor", async (db) => { | ||
| 190 | const deleted = await db | ||
| 191 | .delete(tables.authArtifactsTable) | ||
| 192 | .where( | ||
| 193 | and( | ||
| 194 | eq(tables.authArtifactsTable.workspaceId, input.workspaceId), | ||
| 195 | eq(tables.authArtifactsTable.sourceId, input.sourceId), | ||
| 196 | input.slot === undefined | ||
| 197 | ? undefined | ||
| 198 | : eq(tables.authArtifactsTable.slot, input.slot), | ||
| 199 | input.actorAccountId === null | ||
| 200 | ? isNull(tables.authArtifactsTable.actorAccountId) | ||
| 201 | : eq(tables.authArtifactsTable.actorAccountId, input.actorAccountId), | ||
| 202 | ), | ||
| 203 | ) | ||
| 204 | .returning(); | ||
| 205 | |||
| 206 | return deleted.length > 0; | ||
| 207 | }), | ||
| 208 | |||
| 209 | removeByWorkspaceAndSourceId: (input: { | ||
| 210 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 211 | sourceId: AuthArtifact["sourceId"]; | ||
| 212 | }) => | ||
| 213 | client.use("rows.auth_artifacts.remove_by_workspace_source", async (db) => { | ||
| 214 | const deleted = await db | ||
| 215 | .delete(tables.authArtifactsTable) | ||
| 216 | .where( | ||
| 217 | and( | ||
| 218 | eq(tables.authArtifactsTable.workspaceId, input.workspaceId), | ||
| 219 | eq(tables.authArtifactsTable.sourceId, input.sourceId), | ||
| 220 | ), | ||
| 221 | ) | ||
| 222 | .returning(); | ||
| 223 | |||
| 224 | return deleted.length; | ||
| 225 | }), | ||
| 226 | }); | ||
| @@ -1,145 +0,0 @@ | |||
| 1 | import { | ||
| 2 | AuthLeaseSchema, | ||
| 3 | type AuthLease, | ||
| 4 | } from "#schema"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import { Schema } from "effect"; | ||
| 7 | import { and, asc, eq, isNull } from "drizzle-orm"; | ||
| 8 | |||
| 9 | import type { DrizzleClient } from "../client"; | ||
| 10 | import type { DrizzleTables } from "../schema"; | ||
| 11 | import { firstOption, withoutCreatedAt } from "./shared"; | ||
| 12 | |||
| 13 | const decodeAuthLease = Schema.decodeUnknownSync(AuthLeaseSchema); | ||
| 14 | |||
| 15 | export const createAuthLeasesRepo = ( | ||
| 16 | client: DrizzleClient, | ||
| 17 | tables: DrizzleTables, | ||
| 18 | ) => ({ | ||
| 19 | getByAuthArtifactId: (authArtifactId: AuthLease["authArtifactId"]) => | ||
| 20 | client.use("rows.auth_leases.get_by_auth_artifact_id", async (db) => { | ||
| 21 | const rows = await db | ||
| 22 | .select() | ||
| 23 | .from(tables.authLeasesTable) | ||
| 24 | .where(eq(tables.authLeasesTable.authArtifactId, authArtifactId)) | ||
| 25 | .limit(1); | ||
| 26 | |||
| 27 | const row = firstOption(rows); | ||
| 28 | return Option.isSome(row) | ||
| 29 | ? Option.some(decodeAuthLease(row.value)) | ||
| 30 | : Option.none<AuthLease>(); | ||
| 31 | }), | ||
| 32 | |||
| 33 | listByWorkspaceId: (workspaceId: AuthLease["workspaceId"]) => | ||
| 34 | client.use("rows.auth_leases.list_by_workspace", async (db) => { | ||
| 35 | const rows = await db | ||
| 36 | .select() | ||
| 37 | .from(tables.authLeasesTable) | ||
| 38 | .where(eq(tables.authLeasesTable.workspaceId, workspaceId)) | ||
| 39 | .orderBy( | ||
| 40 | asc(tables.authLeasesTable.updatedAt), | ||
| 41 | asc(tables.authLeasesTable.id), | ||
| 42 | ); | ||
| 43 | |||
| 44 | return rows.map((row) => decodeAuthLease(row)); | ||
| 45 | }), | ||
| 46 | |||
| 47 | getByWorkspaceSourceAndActor: (input: { | ||
| 48 | workspaceId: AuthLease["workspaceId"]; | ||
| 49 | sourceId: AuthLease["sourceId"]; | ||
| 50 | actorAccountId: AuthLease["actorAccountId"]; | ||
| 51 | slot: AuthLease["slot"]; | ||
| 52 | }) => | ||
| 53 | client.use("rows.auth_leases.get_by_workspace_source_actor", async (db) => { | ||
| 54 | const rows = await db | ||
| 55 | .select() | ||
| 56 | .from(tables.authLeasesTable) | ||
| 57 | .where( | ||
| 58 | and( | ||
| 59 | eq(tables.authLeasesTable.workspaceId, input.workspaceId), | ||
| 60 | eq(tables.authLeasesTable.sourceId, input.sourceId), | ||
| 61 | eq(tables.authLeasesTable.slot, input.slot), | ||
| 62 | input.actorAccountId === null | ||
| 63 | ? isNull(tables.authLeasesTable.actorAccountId) | ||
| 64 | : eq(tables.authLeasesTable.actorAccountId, input.actorAccountId), | ||
| 65 | ), | ||
| 66 | ) | ||
| 67 | .orderBy( | ||
| 68 | asc(tables.authLeasesTable.updatedAt), | ||
| 69 | asc(tables.authLeasesTable.id), | ||
| 70 | ) | ||
| 71 | .limit(1); | ||
| 72 | |||
| 73 | const row = firstOption(rows); | ||
| 74 | return Option.isSome(row) | ||
| 75 | ? Option.some(decodeAuthLease(row.value)) | ||
| 76 | : Option.none<AuthLease>(); | ||
| 77 | }), | ||
| 78 | |||
| 79 | upsert: (lease: AuthLease) => | ||
| 80 | client.use("rows.auth_leases.upsert", async (db) => { | ||
| 81 | await db | ||
| 82 | .insert(tables.authLeasesTable) | ||
| 83 | .values(lease) | ||
| 84 | .onConflictDoUpdate({ | ||
| 85 | target: [tables.authLeasesTable.authArtifactId], | ||
| 86 | set: { | ||
| 87 | ...withoutCreatedAt(lease), | ||
| 88 | }, | ||
| 89 | }); | ||
| 90 | }), | ||
| 91 | |||
| 92 | removeByAuthArtifactId: (authArtifactId: AuthLease["authArtifactId"]) => | ||
| 93 | client.use("rows.auth_leases.remove_by_auth_artifact_id", async (db) => { | ||
| 94 | const deleted = await db | ||
| 95 | .delete(tables.authLeasesTable) | ||
| 96 | .where(eq(tables.authLeasesTable.authArtifactId, authArtifactId)) | ||
| 97 | .returning(); | ||
| 98 | |||
| 99 | return deleted.length > 0; | ||
| 100 | }), | ||
| 101 | |||
| 102 | removeByWorkspaceSourceAndActor: (input: { | ||
| 103 | workspaceId: AuthLease["workspaceId"]; | ||
| 104 | sourceId: AuthLease["sourceId"]; | ||
| 105 | actorAccountId: AuthLease["actorAccountId"]; | ||
| 106 | slot?: AuthLease["slot"]; | ||
| 107 | }) => | ||
| 108 | client.use("rows.auth_leases.remove_by_workspace_source_actor", async (db) => { | ||
| 109 | const deleted = await db | ||
| 110 | .delete(tables.authLeasesTable) | ||
| 111 | .where( | ||
| 112 | and( | ||
| 113 | eq(tables.authLeasesTable.workspaceId, input.workspaceId), | ||
| 114 | eq(tables.authLeasesTable.sourceId, input.sourceId), | ||
| 115 | input.slot === undefined | ||
| 116 | ? undefined | ||
| 117 | : eq(tables.authLeasesTable.slot, input.slot), | ||
| 118 | input.actorAccountId === null | ||
| 119 | ? isNull(tables.authLeasesTable.actorAccountId) | ||
| 120 | : eq(tables.authLeasesTable.actorAccountId, input.actorAccountId), | ||
| 121 | ), | ||
| 122 | ) | ||
| 123 | .returning(); | ||
| 124 | |||
| 125 | return deleted.length > 0; | ||
| 126 | }), | ||
| 127 | |||
| 128 | removeByWorkspaceAndSourceId: (input: { | ||
| 129 | workspaceId: AuthLease["workspaceId"]; | ||
| 130 | sourceId: AuthLease["sourceId"]; | ||
| 131 | }) => | ||
| 132 | client.use("rows.auth_leases.remove_by_workspace_source", async (db) => { | ||
| 133 | const deleted = await db | ||
| 134 | .delete(tables.authLeasesTable) | ||
| 135 | .where( | ||
| 136 | and( | ||
| 137 | eq(tables.authLeasesTable.workspaceId, input.workspaceId), | ||
| 138 | eq(tables.authLeasesTable.sourceId, input.sourceId), | ||
| 139 | ), | ||
| 140 | ) | ||
| 141 | .returning(); | ||
| 142 | |||
| 143 | return deleted.length; | ||
| 144 | }), | ||
| 145 | }); | ||
| @@ -1,41 +0,0 @@ | |||
| 1 | import type { | ||
| 2 | StoredCodeMigrationRecord, | ||
| 3 | } from "#schema"; | ||
| 4 | import { StoredCodeMigrationRecordSchema } from "#schema"; | ||
| 5 | import { Schema } from "effect"; | ||
| 6 | import { asc } from "drizzle-orm"; | ||
| 7 | |||
| 8 | import type { DrizzleClient } from "../client"; | ||
| 9 | import type { DrizzleTables } from "../schema"; | ||
| 10 | |||
| 11 | const decodeStoredCodeMigrationRecord = Schema.decodeUnknownSync( | ||
| 12 | StoredCodeMigrationRecordSchema, | ||
| 13 | ); | ||
| 14 | |||
| 15 | export const createCodeMigrationsRepo = ( | ||
| 16 | client: DrizzleClient, | ||
| 17 | tables: DrizzleTables, | ||
| 18 | ) => ({ | ||
| 19 | listAll: () => | ||
| 20 | client.use("rows.code_migrations.list_all", async (db) => { | ||
| 21 | const rows = await db | ||
| 22 | .select() | ||
| 23 | .from(tables.codeMigrationsTable) | ||
| 24 | .orderBy(asc(tables.codeMigrationsTable.appliedAt)); | ||
| 25 | |||
| 26 | return rows.map((row) => decodeStoredCodeMigrationRecord(row)); | ||
| 27 | }), | ||
| 28 | |||
| 29 | upsert: (record: StoredCodeMigrationRecord) => | ||
| 30 | client.use("rows.code_migrations.upsert", async (db) => { | ||
| 31 | await db | ||
| 32 | .insert(tables.codeMigrationsTable) | ||
| 33 | .values(record) | ||
| 34 | .onConflictDoNothing(); | ||
| 35 | }), | ||
| 36 | |||
| 37 | clearAll: () => | ||
| 38 | client.use("rows.code_migrations.clear_all", async (db) => { | ||
| 39 | await db.delete(tables.codeMigrationsTable); | ||
| 40 | }), | ||
| 41 | }); | ||
| @@ -1,96 +0,0 @@ | |||
| 1 | import { | ||
| 2 | type ExecutionInteraction, | ||
| 3 | ExecutionInteractionSchema, | ||
| 4 | } from "#schema"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import { Schema } from "effect"; | ||
| 7 | import { and, desc, eq } from "drizzle-orm"; | ||
| 8 | |||
| 9 | import type { DrizzleClient } from "../client"; | ||
| 10 | import type { DrizzleTables } from "../schema"; | ||
| 11 | import { firstOption } from "./shared"; | ||
| 12 | |||
| 13 | const decodeExecutionInteraction = Schema.decodeUnknownSync( | ||
| 14 | ExecutionInteractionSchema, | ||
| 15 | ); | ||
| 16 | |||
| 17 | export const createExecutionInteractionsRepo = ( | ||
| 18 | client: DrizzleClient, | ||
| 19 | tables: DrizzleTables, | ||
| 20 | ) => ({ | ||
| 21 | getById: (interactionId: ExecutionInteraction["id"]) => | ||
| 22 | client.use("rows.execution_interactions.get_by_id", async (db) => { | ||
| 23 | const rows = await db | ||
| 24 | .select() | ||
| 25 | .from(tables.executionInteractionsTable) | ||
| 26 | .where(eq(tables.executionInteractionsTable.id, interactionId)) | ||
| 27 | .limit(1); | ||
| 28 | |||
| 29 | const row = firstOption(rows); | ||
| 30 | return Option.isSome(row) | ||
| 31 | ? Option.some(decodeExecutionInteraction(row.value)) | ||
| 32 | : Option.none<ExecutionInteraction>(); | ||
| 33 | }), | ||
| 34 | |||
| 35 | listByExecutionId: (executionId: ExecutionInteraction["executionId"]) => | ||
| 36 | client.use("rows.execution_interactions.list_by_execution_id", async (db) => { | ||
| 37 | const rows = await db | ||
| 38 | .select() | ||
| 39 | .from(tables.executionInteractionsTable) | ||
| 40 | .where(eq(tables.executionInteractionsTable.executionId, executionId)) | ||
| 41 | .orderBy( | ||
| 42 | desc(tables.executionInteractionsTable.updatedAt), | ||
| 43 | desc(tables.executionInteractionsTable.id), | ||
| 44 | ); | ||
| 45 | |||
| 46 | return rows.map((row) => decodeExecutionInteraction(row)); | ||
| 47 | }), | ||
| 48 | |||
| 49 | getPendingByExecutionId: (executionId: ExecutionInteraction["executionId"]) => | ||
| 50 | client.use( | ||
| 51 | "rows.execution_interactions.get_pending_by_execution_id", | ||
| 52 | async (db) => { | ||
| 53 | const rows = await db | ||
| 54 | .select() | ||
| 55 | .from(tables.executionInteractionsTable) | ||
| 56 | .where( | ||
| 57 | and( | ||
| 58 | eq(tables.executionInteractionsTable.executionId, executionId), | ||
| 59 | eq(tables.executionInteractionsTable.status, "pending"), | ||
| 60 | ), | ||
| 61 | ) | ||
| 62 | .orderBy( | ||
| 63 | desc(tables.executionInteractionsTable.updatedAt), | ||
| 64 | desc(tables.executionInteractionsTable.id), | ||
| 65 | ) | ||
| 66 | .limit(1); | ||
| 67 | |||
| 68 | const row = firstOption(rows); | ||
| 69 | return Option.isSome(row) | ||
| 70 | ? Option.some(decodeExecutionInteraction(row.value)) | ||
| 71 | : Option.none<ExecutionInteraction>(); | ||
| 72 | }, | ||
| 73 | ), | ||
| 74 | |||
| 75 | insert: (interaction: ExecutionInteraction) => | ||
| 76 | client.use("rows.execution_interactions.insert", async (db) => { | ||
| 77 | await db.insert(tables.executionInteractionsTable).values(interaction); | ||
| 78 | }), | ||
| 79 | |||
| 80 | update: ( | ||
| 81 | interactionId: ExecutionInteraction["id"], | ||
| 82 | patch: Partial<Omit<ExecutionInteraction, "id" | "executionId" | "createdAt">>, | ||
| 83 | ) => | ||
| 84 | client.use("rows.execution_interactions.update", async (db) => { | ||
| 85 | const rows = await db | ||
| 86 | .update(tables.executionInteractionsTable) | ||
| 87 | .set(patch) | ||
| 88 | .where(eq(tables.executionInteractionsTable.id, interactionId)) | ||
| 89 | .returning(); | ||
| 90 | |||
| 91 | const row = firstOption(rows); | ||
| 92 | return Option.isSome(row) | ||
| 93 | ? Option.some(decodeExecutionInteraction(row.value)) | ||
| 94 | : Option.none<ExecutionInteraction>(); | ||
| 95 | }), | ||
| 96 | }); | ||
| @@ -1,89 +0,0 @@ | |||
| 1 | import { | ||
| 2 | type ExecutionStep, | ||
| 3 | ExecutionStepSchema, | ||
| 4 | } from "#schema"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import { Schema } from "effect"; | ||
| 7 | import { and, desc, eq } from "drizzle-orm"; | ||
| 8 | |||
| 9 | import type { DrizzleClient } from "../client"; | ||
| 10 | import type { DrizzleTables } from "../schema"; | ||
| 11 | import { firstOption } from "./shared"; | ||
| 12 | |||
| 13 | const decodeExecutionStep = Schema.decodeUnknownSync(ExecutionStepSchema); | ||
| 14 | |||
| 15 | export const createExecutionStepsRepo = ( | ||
| 16 | client: DrizzleClient, | ||
| 17 | tables: DrizzleTables, | ||
| 18 | ) => ({ | ||
| 19 | getByExecutionAndSequence: ( | ||
| 20 | executionId: ExecutionStep["executionId"], | ||
| 21 | sequence: ExecutionStep["sequence"], | ||
| 22 | ) => | ||
| 23 | client.use("rows.execution_steps.get_by_execution_and_sequence", async (db) => { | ||
| 24 | const rows = await db | ||
| 25 | .select() | ||
| 26 | .from(tables.executionStepsTable) | ||
| 27 | .where( | ||
| 28 | and( | ||
| 29 | eq(tables.executionStepsTable.executionId, executionId), | ||
| 30 | eq(tables.executionStepsTable.sequence, sequence), | ||
| 31 | ), | ||
| 32 | ) | ||
| 33 | .limit(1); | ||
| 34 | |||
| 35 | const row = firstOption(rows); | ||
| 36 | return Option.isSome(row) | ||
| 37 | ? Option.some(decodeExecutionStep(row.value)) | ||
| 38 | : Option.none<ExecutionStep>(); | ||
| 39 | }), | ||
| 40 | |||
| 41 | listByExecutionId: (executionId: ExecutionStep["executionId"]) => | ||
| 42 | client.use("rows.execution_steps.list_by_execution_id", async (db) => { | ||
| 43 | const rows = await db | ||
| 44 | .select() | ||
| 45 | .from(tables.executionStepsTable) | ||
| 46 | .where(eq(tables.executionStepsTable.executionId, executionId)) | ||
| 47 | .orderBy( | ||
| 48 | tables.executionStepsTable.sequence, | ||
| 49 | desc(tables.executionStepsTable.updatedAt), | ||
| 50 | ); | ||
| 51 | |||
| 52 | return rows.map((row) => decodeExecutionStep(row)); | ||
| 53 | }), | ||
| 54 | |||
| 55 | insert: (step: ExecutionStep) => | ||
| 56 | client.use("rows.execution_steps.insert", async (db) => { | ||
| 57 | await db.insert(tables.executionStepsTable).values(step); | ||
| 58 | }), | ||
| 59 | |||
| 60 | deleteByExecutionId: (executionId: ExecutionStep["executionId"]) => | ||
| 61 | client.use("rows.execution_steps.delete_by_execution_id", async (db) => { | ||
| 62 | await db | ||
| 63 | .delete(tables.executionStepsTable) | ||
| 64 | .where(eq(tables.executionStepsTable.executionId, executionId)); | ||
| 65 | }), | ||
| 66 | |||
| 67 | updateByExecutionAndSequence: ( | ||
| 68 | executionId: ExecutionStep["executionId"], | ||
| 69 | sequence: ExecutionStep["sequence"], | ||
| 70 | patch: Partial<Omit<ExecutionStep, "id" | "executionId" | "sequence" | "createdAt">>, | ||
| 71 | ) => | ||
| 72 | client.use("rows.execution_steps.update_by_execution_and_sequence", async (db) => { | ||
| 73 | const rows = await db | ||
| 74 | .update(tables.executionStepsTable) | ||
| 75 | .set(patch) | ||
| 76 | .where( | ||
| 77 | and( | ||
| 78 | eq(tables.executionStepsTable.executionId, executionId), | ||
| 79 | eq(tables.executionStepsTable.sequence, sequence), | ||
| 80 | ), | ||
| 81 | ) | ||
| 82 | .returning(); | ||
| 83 | |||
| 84 | const row = firstOption(rows); | ||
| 85 | return Option.isSome(row) | ||
| 86 | ? Option.some(decodeExecutionStep(row.value)) | ||
| 87 | : Option.none<ExecutionStep>(); | ||
| 88 | }), | ||
| 89 | }); | ||
| @@ -1,75 +0,0 @@ | |||
| 1 | import { type Execution, ExecutionSchema } from "#schema"; | ||
| 2 | import * as Option from "effect/Option"; | ||
| 3 | import { Schema } from "effect"; | ||
| 4 | import { and, eq } from "drizzle-orm"; | ||
| 5 | |||
| 6 | import type { DrizzleClient } from "../client"; | ||
| 7 | import type { DrizzleTables } from "../schema"; | ||
| 8 | import { firstOption } from "./shared"; | ||
| 9 | |||
| 10 | const decodeExecution = Schema.decodeUnknownSync(ExecutionSchema); | ||
| 11 | |||
| 12 | export const createExecutionsRepo = ( | ||
| 13 | client: DrizzleClient, | ||
| 14 | tables: DrizzleTables, | ||
| 15 | ) => ({ | ||
| 16 | getById: (executionId: Execution["id"]) => | ||
| 17 | client.use("rows.executions.get_by_id", async (db) => { | ||
| 18 | const rows = await db | ||
| 19 | .select() | ||
| 20 | .from(tables.executionsTable) | ||
| 21 | .where(eq(tables.executionsTable.id, executionId)) | ||
| 22 | .limit(1); | ||
| 23 | |||
| 24 | const row = firstOption(rows); | ||
| 25 | return Option.isSome(row) | ||
| 26 | ? Option.some(decodeExecution(row.value)) | ||
| 27 | : Option.none<Execution>(); | ||
| 28 | }), | ||
| 29 | |||
| 30 | getByWorkspaceAndId: ( | ||
| 31 | workspaceId: Execution["workspaceId"], | ||
| 32 | executionId: Execution["id"], | ||
| 33 | ) => | ||
| 34 | client.use("rows.executions.get_by_workspace_and_id", async (db) => { | ||
| 35 | const rows = await db | ||
| 36 | .select() | ||
| 37 | .from(tables.executionsTable) | ||
| 38 | .where( | ||
| 39 | and( | ||
| 40 | eq(tables.executionsTable.workspaceId, workspaceId), | ||
| 41 | eq(tables.executionsTable.id, executionId), | ||
| 42 | ), | ||
| 43 | ) | ||
| 44 | .limit(1); | ||
| 45 | |||
| 46 | const row = firstOption(rows); | ||
| 47 | return Option.isSome(row) | ||
| 48 | ? Option.some(decodeExecution(row.value)) | ||
| 49 | : Option.none<Execution>(); | ||
| 50 | }), | ||
| 51 | |||
| 52 | insert: (execution: Execution) => | ||
| 53 | client.use("rows.executions.insert", async (db) => { | ||
| 54 | await db.insert(tables.executionsTable).values(execution); | ||
| 55 | }), | ||
| 56 | |||
| 57 | update: ( | ||
| 58 | executionId: Execution["id"], | ||
| 59 | patch: Partial< | ||
| 60 | Omit<Execution, "id" | "workspaceId" | "createdByAccountId" | "createdAt"> | ||
| 61 | >, | ||
| 62 | ) => | ||
| 63 | client.use("rows.executions.update", async (db) => { | ||
| 64 | const rows = await db | ||
| 65 | .update(tables.executionsTable) | ||
| 66 | .set(patch) | ||
| 67 | .where(eq(tables.executionsTable.id, executionId)) | ||
| 68 | .returning(); | ||
| 69 | |||
| 70 | const row = firstOption(rows); | ||
| 71 | return Option.isSome(row) | ||
| 72 | ? Option.some(decodeExecution(row.value)) | ||
| 73 | : Option.none<Execution>(); | ||
| 74 | }), | ||
| 75 | }); | ||
| @@ -1,16 +0,0 @@ | |||
| 1 | export { createAccountsRepo } from "./accounts-repo"; | ||
| 2 | export { createSourcesRepo } from "./sources-repo"; | ||
| 3 | export { createSourceRecipesRepo } from "./source-recipes-repo"; | ||
| 4 | export { createSourceRecipeRevisionsRepo } from "./source-recipe-revisions-repo"; | ||
| 5 | export { createSourceRecipeDocumentsRepo } from "./source-recipe-documents-repo"; | ||
| 6 | export { createSourceRecipeSchemaBundlesRepo } from "./source-recipe-schema-bundles-repo"; | ||
| 7 | export { createSourceRecipeOperationsRepo } from "./source-recipe-operations-repo"; | ||
| 8 | export { createCodeMigrationsRepo } from "./code-migrations-repo"; | ||
| 9 | export { createAuthArtifactsRepo } from "./auth-artifacts-repo"; | ||
| 10 | export { createAuthLeasesRepo } from "./auth-leases-repo"; | ||
| 11 | export { createSourceOauthClientsRepo } from "./source-oauth-clients-repo"; | ||
| 12 | export { createSecretMaterialsRepo } from "./secret-materials-repo"; | ||
| 13 | export { createSourceAuthSessionsRepo } from "./source-auth-sessions-repo"; | ||
| 14 | export { createExecutionsRepo } from "./executions-repo"; | ||
| 15 | export { createExecutionInteractionsRepo } from "./execution-interactions-repo"; | ||
| 16 | export { createExecutionStepsRepo } from "./execution-steps-repo"; | ||
| @@ -1,144 +0,0 @@ | |||
| 1 | import { | ||
| 2 | authArtifactSecretRefs, | ||
| 3 | type SecretMaterial, | ||
| 4 | SecretMaterialSchema, | ||
| 5 | } from "#schema"; | ||
| 6 | import * as Option from "effect/Option"; | ||
| 7 | import { Schema } from "effect"; | ||
| 8 | import { eq, desc, and } from "drizzle-orm"; | ||
| 9 | |||
| 10 | import type { DrizzleClient } from "../client"; | ||
| 11 | import type { DrizzleTables } from "../schema"; | ||
| 12 | import { firstOption, withoutCreatedAt } from "./shared"; | ||
| 13 | |||
| 14 | const decodeSecretMaterial = Schema.decodeUnknownSync(SecretMaterialSchema); | ||
| 15 | |||
| 16 | export const createSecretMaterialsRepo = ( | ||
| 17 | client: DrizzleClient, | ||
| 18 | tables: DrizzleTables, | ||
| 19 | ) => ({ | ||
| 20 | getById: (id: SecretMaterial["id"]) => | ||
| 21 | client.use("rows.secret_materials.get_by_id", async (db) => { | ||
| 22 | const rows = await db | ||
| 23 | .select() | ||
| 24 | .from(tables.secretMaterialsTable) | ||
| 25 | .where(eq(tables.secretMaterialsTable.id, id)) | ||
| 26 | .limit(1); | ||
| 27 | |||
| 28 | const row = firstOption(rows); | ||
| 29 | return Option.isSome(row) | ||
| 30 | ? Option.some(decodeSecretMaterial(row.value)) | ||
| 31 | : Option.none<SecretMaterial>(); | ||
| 32 | }), | ||
| 33 | |||
| 34 | listAll: () => | ||
| 35 | client.use("rows.secret_materials.list_all", async (db) => { | ||
| 36 | const rows = await db | ||
| 37 | .select({ | ||
| 38 | id: tables.secretMaterialsTable.id, | ||
| 39 | name: tables.secretMaterialsTable.name, | ||
| 40 | purpose: tables.secretMaterialsTable.purpose, | ||
| 41 | createdAt: tables.secretMaterialsTable.createdAt, | ||
| 42 | updatedAt: tables.secretMaterialsTable.updatedAt, | ||
| 43 | }) | ||
| 44 | .from(tables.secretMaterialsTable) | ||
| 45 | .orderBy(desc(tables.secretMaterialsTable.updatedAt)); | ||
| 46 | |||
| 47 | return rows; | ||
| 48 | }), | ||
| 49 | |||
| 50 | upsert: (material: SecretMaterial) => | ||
| 51 | client.use("rows.secret_materials.upsert", async (db) => { | ||
| 52 | await db | ||
| 53 | .insert(tables.secretMaterialsTable) | ||
| 54 | .values(material) | ||
| 55 | .onConflictDoUpdate({ | ||
| 56 | target: [tables.secretMaterialsTable.id], | ||
| 57 | set: { | ||
| 58 | ...withoutCreatedAt(material), | ||
| 59 | }, | ||
| 60 | }); | ||
| 61 | }), | ||
| 62 | |||
| 63 | updateById: (id: SecretMaterial["id"], update: { name?: string | null; value?: string }) => | ||
| 64 | client.use("rows.secret_materials.update_by_id", async (db) => { | ||
| 65 | const set: Record<string, unknown> = { updatedAt: Date.now() }; | ||
| 66 | if (update.name !== undefined) set.name = update.name; | ||
| 67 | if (update.value !== undefined) set.value = update.value; | ||
| 68 | |||
| 69 | const updated = await db | ||
| 70 | .update(tables.secretMaterialsTable) | ||
| 71 | .set(set) | ||
| 72 | .where(eq(tables.secretMaterialsTable.id, id)) | ||
| 73 | .returning({ | ||
| 74 | id: tables.secretMaterialsTable.id, | ||
| 75 | name: tables.secretMaterialsTable.name, | ||
| 76 | purpose: tables.secretMaterialsTable.purpose, | ||
| 77 | createdAt: tables.secretMaterialsTable.createdAt, | ||
| 78 | updatedAt: tables.secretMaterialsTable.updatedAt, | ||
| 79 | }); | ||
| 80 | |||
| 81 | const row = firstOption(updated); | ||
| 82 | return Option.isSome(row) ? Option.some(row.value) : Option.none(); | ||
| 83 | }), | ||
| 84 | |||
| 85 | removeById: (id: SecretMaterial["id"]) => | ||
| 86 | client.use("rows.secret_materials.remove", async (db) => { | ||
| 87 | const deleted = await db | ||
| 88 | .delete(tables.secretMaterialsTable) | ||
| 89 | .where(eq(tables.secretMaterialsTable.id, id)) | ||
| 90 | .returning(); | ||
| 91 | |||
| 92 | return deleted.length > 0; | ||
| 93 | }), | ||
| 94 | |||
| 95 | /** | ||
| 96 | * For each secret, find the sources that reference it via auth artifacts. | ||
| 97 | * Returns a map of secretId -> Array<{ sourceId, sourceName }>. | ||
| 98 | */ | ||
| 99 | listLinkedSources: () => | ||
| 100 | client.use("rows.secret_materials.list_linked_sources", async (db) => { | ||
| 101 | const rows = await db | ||
| 102 | .select({ | ||
| 103 | artifactKind: tables.authArtifactsTable.artifactKind, | ||
| 104 | configJson: tables.authArtifactsTable.configJson, | ||
| 105 | sourceId: tables.authArtifactsTable.sourceId, | ||
| 106 | sourceName: tables.sourcesTable.name, | ||
| 107 | }) | ||
| 108 | .from(tables.authArtifactsTable) | ||
| 109 | .innerJoin( | ||
| 110 | tables.sourcesTable, | ||
| 111 | and( | ||
| 112 | eq(tables.authArtifactsTable.workspaceId, tables.sourcesTable.workspaceId), | ||
| 113 | eq(tables.authArtifactsTable.sourceId, tables.sourcesTable.sourceId), | ||
| 114 | ), | ||
| 115 | ); | ||
| 116 | |||
| 117 | const result = new Map<string, Array<{ sourceId: string; sourceName: string }>>(); | ||
| 118 | |||
| 119 | for (const row of rows) { | ||
| 120 | const addLink = (secretId: string) => { | ||
| 121 | let links = result.get(secretId); | ||
| 122 | if (!links) { | ||
| 123 | links = []; | ||
| 124 | result.set(secretId, links); | ||
| 125 | } | ||
| 126 | // Avoid duplicate entries | ||
| 127 | if (!links.some((l) => l.sourceId === row.sourceId)) { | ||
| 128 | links.push({ sourceId: row.sourceId, sourceName: row.sourceName }); | ||
| 129 | } | ||
| 130 | }; | ||
| 131 | |||
| 132 | for (const ref of authArtifactSecretRefs({ | ||
| 133 | artifactKind: row.artifactKind, | ||
| 134 | configJson: row.configJson, | ||
| 135 | })) { | ||
| 136 | if (ref.providerId === "postgres") { | ||
| 137 | addLink(ref.handle); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | return result; | ||
| 143 | }), | ||
| 144 | }); | ||
| @@ -1,126 +0,0 @@ | |||
| 1 | import { authArtifactSecretRefs, type AuthArtifact } from "#schema"; | ||
| 2 | import { inArray } from "drizzle-orm"; | ||
| 3 | import * as Option from "effect/Option"; | ||
| 4 | |||
| 5 | import type { DrizzleClient } from "../client"; | ||
| 6 | import type { DrizzleTables } from "../schema"; | ||
| 7 | |||
| 8 | export const firstOption = <A>(rows: ReadonlyArray<A>): Option.Option<A> => | ||
| 9 | rows.length > 0 ? Option.some(rows[0] as A) : Option.none<A>(); | ||
| 10 | |||
| 11 | export const withoutCreatedAt = <A extends { createdAt: unknown }>( | ||
| 12 | value: A, | ||
| 13 | ): Omit<A, "createdAt"> => { | ||
| 14 | const { createdAt: _createdAt, ...rest } = value; | ||
| 15 | return rest; | ||
| 16 | }; | ||
| 17 | |||
| 18 | const POSTGRES_SECRET_PROVIDER_ID = "postgres"; | ||
| 19 | |||
| 20 | export const postgresSecretHandlesFromAuthArtifacts = ( | ||
| 21 | artifacts: ReadonlyArray<Pick<AuthArtifact, "artifactKind" | "configJson">>, | ||
| 22 | ): ReadonlyArray<string> => { | ||
| 23 | const handles = new Set<string>(); | ||
| 24 | |||
| 25 | for (const artifact of artifacts) { | ||
| 26 | for (const ref of authArtifactSecretRefs(artifact)) { | ||
| 27 | if (ref.providerId === POSTGRES_SECRET_PROVIDER_ID) { | ||
| 28 | handles.add(ref.handle); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | return [...handles]; | ||
| 34 | }; | ||
| 35 | |||
| 36 | export const chunkArray = <A>( | ||
| 37 | values: ReadonlyArray<A>, | ||
| 38 | chunkSize: number, | ||
| 39 | ): ReadonlyArray<ReadonlyArray<A>> => { | ||
| 40 | if (chunkSize <= 0) { | ||
| 41 | throw new Error(`chunkSize must be positive, received ${chunkSize}`); | ||
| 42 | } | ||
| 43 | |||
| 44 | const chunks: Array<ReadonlyArray<A>> = []; | ||
| 45 | for (let index = 0; index < values.length; index += chunkSize) { | ||
| 46 | chunks.push(values.slice(index, index + chunkSize)); | ||
| 47 | } | ||
| 48 | |||
| 49 | return chunks; | ||
| 50 | }; | ||
| 51 | |||
| 52 | export const cleanupOrphanedSourceRecipes = async (input: { | ||
| 53 | tx: Parameters<Parameters<DrizzleClient["useTx"]>[1]>[0]; | ||
| 54 | tables: DrizzleTables; | ||
| 55 | candidateRecipeIds: ReadonlyArray<string>; | ||
| 56 | candidateRecipeRevisionIds: ReadonlyArray<string>; | ||
| 57 | }) => { | ||
| 58 | const recipeIds = [...new Set(input.candidateRecipeIds)]; | ||
| 59 | const recipeRevisionIds = [...new Set(input.candidateRecipeRevisionIds)]; | ||
| 60 | |||
| 61 | const remainingRevisionIds = recipeRevisionIds.length > 0 | ||
| 62 | ? ( | ||
| 63 | await input.tx | ||
| 64 | .select({ recipeRevisionId: input.tables.sourcesTable.recipeRevisionId }) | ||
| 65 | .from(input.tables.sourcesTable) | ||
| 66 | .where(inArray(input.tables.sourcesTable.recipeRevisionId, recipeRevisionIds)) | ||
| 67 | ).map((row) => row.recipeRevisionId) | ||
| 68 | : []; | ||
| 69 | const orphanRevisionIds = recipeRevisionIds.filter( | ||
| 70 | (recipeRevisionId) => !remainingRevisionIds.includes(recipeRevisionId), | ||
| 71 | ); | ||
| 72 | |||
| 73 | if (orphanRevisionIds.length > 0) { | ||
| 74 | await input.tx | ||
| 75 | .delete(input.tables.sourceRecipeDocumentsTable) | ||
| 76 | .where(inArray(input.tables.sourceRecipeDocumentsTable.recipeRevisionId, orphanRevisionIds)); | ||
| 77 | await input.tx | ||
| 78 | .delete(input.tables.sourceRecipeSchemaBundlesTable) | ||
| 79 | .where(inArray(input.tables.sourceRecipeSchemaBundlesTable.recipeRevisionId, orphanRevisionIds)); | ||
| 80 | await input.tx | ||
| 81 | .delete(input.tables.sourceRecipeOperationsTable) | ||
| 82 | .where(inArray(input.tables.sourceRecipeOperationsTable.recipeRevisionId, orphanRevisionIds)); | ||
| 83 | } | ||
| 84 | |||
| 85 | const remainingRecipeIds = recipeIds.length > 0 | ||
| 86 | ? ( | ||
| 87 | await input.tx | ||
| 88 | .select({ recipeId: input.tables.sourcesTable.recipeId }) | ||
| 89 | .from(input.tables.sourcesTable) | ||
| 90 | .where(inArray(input.tables.sourcesTable.recipeId, recipeIds)) | ||
| 91 | ).map((row) => row.recipeId) | ||
| 92 | : []; | ||
| 93 | const orphanRecipeIds = recipeIds.filter( | ||
| 94 | (recipeId) => !remainingRecipeIds.includes(recipeId), | ||
| 95 | ); | ||
| 96 | |||
| 97 | if (orphanRecipeIds.length === 0) { | ||
| 98 | return; | ||
| 99 | } | ||
| 100 | |||
| 101 | const orphanRecipeRevisionIds = ( | ||
| 102 | await input.tx | ||
| 103 | .select({ id: input.tables.sourceRecipeRevisionsTable.id }) | ||
| 104 | .from(input.tables.sourceRecipeRevisionsTable) | ||
| 105 | .where(inArray(input.tables.sourceRecipeRevisionsTable.recipeId, orphanRecipeIds)) | ||
| 106 | ).map((revision) => revision.id); | ||
| 107 | |||
| 108 | if (orphanRecipeRevisionIds.length > 0) { | ||
| 109 | await input.tx | ||
| 110 | .delete(input.tables.sourceRecipeDocumentsTable) | ||
| 111 | .where(inArray(input.tables.sourceRecipeDocumentsTable.recipeRevisionId, orphanRecipeRevisionIds)); | ||
| 112 | await input.tx | ||
| 113 | .delete(input.tables.sourceRecipeSchemaBundlesTable) | ||
| 114 | .where(inArray(input.tables.sourceRecipeSchemaBundlesTable.recipeRevisionId, orphanRecipeRevisionIds)); | ||
| 115 | await input.tx | ||
| 116 | .delete(input.tables.sourceRecipeOperationsTable) | ||
| 117 | .where(inArray(input.tables.sourceRecipeOperationsTable.recipeRevisionId, orphanRecipeRevisionIds)); | ||
| 118 | } | ||
| 119 | |||
| 120 | await input.tx | ||
| 121 | .delete(input.tables.sourceRecipeRevisionsTable) | ||
| 122 | .where(inArray(input.tables.sourceRecipeRevisionsTable.recipeId, orphanRecipeIds)); | ||
| 123 | await input.tx | ||
| 124 | .delete(input.tables.sourceRecipesTable) | ||
| 125 | .where(inArray(input.tables.sourceRecipesTable.id, orphanRecipeIds)); | ||
| 126 | }; | ||
| @@ -1,163 +0,0 @@ | |||
| 1 | import { | ||
| 2 | type SourceAuthSession, | ||
| 3 | SourceAuthSessionSchema, | ||
| 4 | } from "#schema"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import { Schema } from "effect"; | ||
| 7 | import { and, asc, eq, isNull } from "drizzle-orm"; | ||
| 8 | |||
| 9 | import type { DrizzleClient } from "../client"; | ||
| 10 | import type { DrizzleTables } from "../schema"; | ||
| 11 | import { firstOption, withoutCreatedAt } from "./shared"; | ||
| 12 | |||
| 13 | const decodeSourceAuthSession = Schema.decodeUnknownSync(SourceAuthSessionSchema); | ||
| 14 | |||
| 15 | type Mutable<T> = { -readonly [K in keyof T]: T[K] }; | ||
| 16 | type SourceAuthSessionPatch = Partial< | ||
| 17 | Omit<Mutable<SourceAuthSession>, "id" | "workspaceId" | "sourceId" | "createdAt"> | ||
| 18 | >; | ||
| 19 | |||
| 20 | export const createSourceAuthSessionsRepo = ( | ||
| 21 | client: DrizzleClient, | ||
| 22 | tables: DrizzleTables, | ||
| 23 | ) => ({ | ||
| 24 | listAll: () => | ||
| 25 | client.use("rows.source_auth_sessions.list_all", async (db) => { | ||
| 26 | const rows = await db | ||
| 27 | .select() | ||
| 28 | .from(tables.sourceAuthSessionsTable) | ||
| 29 | .orderBy(asc(tables.sourceAuthSessionsTable.updatedAt), asc(tables.sourceAuthSessionsTable.id)); | ||
| 30 | |||
| 31 | return rows.map((row) => decodeSourceAuthSession(row)); | ||
| 32 | }), | ||
| 33 | |||
| 34 | listByWorkspaceId: (workspaceId: SourceAuthSession["workspaceId"]) => | ||
| 35 | client.use("rows.source_auth_sessions.list_by_workspace", async (db) => { | ||
| 36 | const rows = await db | ||
| 37 | .select() | ||
| 38 | .from(tables.sourceAuthSessionsTable) | ||
| 39 | .where(eq(tables.sourceAuthSessionsTable.workspaceId, workspaceId)) | ||
| 40 | .orderBy(asc(tables.sourceAuthSessionsTable.updatedAt), asc(tables.sourceAuthSessionsTable.id)); | ||
| 41 | |||
| 42 | return rows.map((row) => decodeSourceAuthSession(row)); | ||
| 43 | }), | ||
| 44 | |||
| 45 | getById: (id: SourceAuthSession["id"]) => | ||
| 46 | client.use("rows.source_auth_sessions.get_by_id", async (db) => { | ||
| 47 | const rows = await db | ||
| 48 | .select() | ||
| 49 | .from(tables.sourceAuthSessionsTable) | ||
| 50 | .where(eq(tables.sourceAuthSessionsTable.id, id)) | ||
| 51 | .limit(1); | ||
| 52 | |||
| 53 | const row = firstOption(rows); | ||
| 54 | return Option.isSome(row) | ||
| 55 | ? Option.some(decodeSourceAuthSession(row.value)) | ||
| 56 | : Option.none<SourceAuthSession>(); | ||
| 57 | }), | ||
| 58 | |||
| 59 | getByState: (state: SourceAuthSession["state"]) => | ||
| 60 | client.use("rows.source_auth_sessions.get_by_state", async (db) => { | ||
| 61 | const rows = await db | ||
| 62 | .select() | ||
| 63 | .from(tables.sourceAuthSessionsTable) | ||
| 64 | .where(eq(tables.sourceAuthSessionsTable.state, state)) | ||
| 65 | .limit(1); | ||
| 66 | |||
| 67 | const row = firstOption(rows); | ||
| 68 | return Option.isSome(row) | ||
| 69 | ? Option.some(decodeSourceAuthSession(row.value)) | ||
| 70 | : Option.none<SourceAuthSession>(); | ||
| 71 | }), | ||
| 72 | |||
| 73 | getPendingByWorkspaceSourceAndActor: (input: { | ||
| 74 | workspaceId: SourceAuthSession["workspaceId"]; | ||
| 75 | sourceId: SourceAuthSession["sourceId"]; | ||
| 76 | actorAccountId: SourceAuthSession["actorAccountId"]; | ||
| 77 | credentialSlot?: SourceAuthSession["credentialSlot"]; | ||
| 78 | }) => | ||
| 79 | client.use( | ||
| 80 | "rows.source_auth_sessions.get_pending_by_workspace_source_actor", | ||
| 81 | async (db) => { | ||
| 82 | const rows = await db | ||
| 83 | .select() | ||
| 84 | .from(tables.sourceAuthSessionsTable) | ||
| 85 | .where( | ||
| 86 | and( | ||
| 87 | eq(tables.sourceAuthSessionsTable.workspaceId, input.workspaceId), | ||
| 88 | eq(tables.sourceAuthSessionsTable.sourceId, input.sourceId), | ||
| 89 | input.credentialSlot === undefined | ||
| 90 | ? undefined | ||
| 91 | : eq(tables.sourceAuthSessionsTable.credentialSlot, input.credentialSlot), | ||
| 92 | input.actorAccountId === null | ||
| 93 | ? isNull(tables.sourceAuthSessionsTable.actorAccountId) | ||
| 94 | : eq(tables.sourceAuthSessionsTable.actorAccountId, input.actorAccountId), | ||
| 95 | eq(tables.sourceAuthSessionsTable.status, "pending"), | ||
| 96 | ), | ||
| 97 | ) | ||
| 98 | .orderBy( | ||
| 99 | asc(tables.sourceAuthSessionsTable.updatedAt), | ||
| 100 | asc(tables.sourceAuthSessionsTable.id), | ||
| 101 | ) | ||
| 102 | .limit(1); | ||
| 103 | |||
| 104 | const row = firstOption(rows); | ||
| 105 | return Option.isSome(row) | ||
| 106 | ? Option.some(decodeSourceAuthSession(row.value)) | ||
| 107 | : Option.none<SourceAuthSession>(); | ||
| 108 | }, | ||
| 109 | ), | ||
| 110 | |||
| 111 | insert: (session: SourceAuthSession) => | ||
| 112 | client.use("rows.source_auth_sessions.insert", async (db) => { | ||
| 113 | await db.insert(tables.sourceAuthSessionsTable).values(session); | ||
| 114 | }), | ||
| 115 | |||
| 116 | update: ( | ||
| 117 | id: SourceAuthSession["id"], | ||
| 118 | patch: SourceAuthSessionPatch, | ||
| 119 | ) => | ||
| 120 | client.use("rows.source_auth_sessions.update", async (db) => { | ||
| 121 | const rows = await db | ||
| 122 | .update(tables.sourceAuthSessionsTable) | ||
| 123 | .set(patch) | ||
| 124 | .where(eq(tables.sourceAuthSessionsTable.id, id)) | ||
| 125 | .returning(); | ||
| 126 | |||
| 127 | const row = firstOption(rows); | ||
| 128 | return Option.isSome(row) | ||
| 129 | ? Option.some(decodeSourceAuthSession(row.value)) | ||
| 130 | : Option.none<SourceAuthSession>(); | ||
| 131 | }), | ||
| 132 | |||
| 133 | upsert: (session: SourceAuthSession) => | ||
| 134 | client.use("rows.source_auth_sessions.upsert", async (db) => { | ||
| 135 | await db | ||
| 136 | .insert(tables.sourceAuthSessionsTable) | ||
| 137 | .values(session) | ||
| 138 | .onConflictDoUpdate({ | ||
| 139 | target: [tables.sourceAuthSessionsTable.id], | ||
| 140 | set: { | ||
| 141 | ...withoutCreatedAt(session), | ||
| 142 | }, | ||
| 143 | }); | ||
| 144 | }), | ||
| 145 | |||
| 146 | removeByWorkspaceAndSourceId: ( | ||
| 147 | workspaceId: SourceAuthSession["workspaceId"], | ||
| 148 | sourceId: SourceAuthSession["sourceId"], | ||
| 149 | ) => | ||
| 150 | client.use("rows.source_auth_sessions.remove_by_workspace_source", async (db) => { | ||
| 151 | const deleted = await db | ||
| 152 | .delete(tables.sourceAuthSessionsTable) | ||
| 153 | .where( | ||
| 154 | and( | ||
| 155 | eq(tables.sourceAuthSessionsTable.workspaceId, workspaceId), | ||
| 156 | eq(tables.sourceAuthSessionsTable.sourceId, sourceId), | ||
| 157 | ), | ||
| 158 | ) | ||
| 159 | .returning(); | ||
| 160 | |||
| 161 | return deleted.length > 0; | ||
| 162 | }), | ||
| 163 | }); | ||
| @@ -1,79 +0,0 @@ | |||
| 1 | import { | ||
| 2 | type WorkspaceSourceOauthClient, | ||
| 3 | WorkspaceSourceOauthClientSchema, | ||
| 4 | } from "#schema"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import { Schema } from "effect"; | ||
| 7 | import { and, eq } from "drizzle-orm"; | ||
| 8 | |||
| 9 | import type { DrizzleClient } from "../client"; | ||
| 10 | import type { DrizzleTables } from "../schema"; | ||
| 11 | import { firstOption, withoutCreatedAt } from "./shared"; | ||
| 12 | |||
| 13 | const decodeWorkspaceSourceOauthClient = Schema.decodeUnknownSync( | ||
| 14 | WorkspaceSourceOauthClientSchema, | ||
| 15 | ); | ||
| 16 | |||
| 17 | export const createSourceOauthClientsRepo = ( | ||
| 18 | client: DrizzleClient, | ||
| 19 | tables: DrizzleTables, | ||
| 20 | ) => ({ | ||
| 21 | getByWorkspaceSourceAndProvider: (input: { | ||
| 22 | workspaceId: WorkspaceSourceOauthClient["workspaceId"]; | ||
| 23 | sourceId: WorkspaceSourceOauthClient["sourceId"]; | ||
| 24 | providerKey: string; | ||
| 25 | }) => | ||
| 26 | client.use("rows.source_oauth_clients.get_by_workspace_source_provider", async (db) => { | ||
| 27 | const rows = await db | ||
| 28 | .select() | ||
| 29 | .from(tables.workspaceSourceOauthClientsTable) | ||
| 30 | .where( | ||
| 31 | and( | ||
| 32 | eq(tables.workspaceSourceOauthClientsTable.workspaceId, input.workspaceId), | ||
| 33 | eq(tables.workspaceSourceOauthClientsTable.sourceId, input.sourceId), | ||
| 34 | eq(tables.workspaceSourceOauthClientsTable.providerKey, input.providerKey), | ||
| 35 | ), | ||
| 36 | ) | ||
| 37 | .limit(1); | ||
| 38 | |||
| 39 | const row = firstOption(rows); | ||
| 40 | return Option.isSome(row) | ||
| 41 | ? Option.some(decodeWorkspaceSourceOauthClient(row.value)) | ||
| 42 | : Option.none<WorkspaceSourceOauthClient>(); | ||
| 43 | }), | ||
| 44 | |||
| 45 | upsert: (oauthClient: WorkspaceSourceOauthClient) => | ||
| 46 | client.use("rows.source_oauth_clients.upsert", async (db) => { | ||
| 47 | await db | ||
| 48 | .insert(tables.workspaceSourceOauthClientsTable) | ||
| 49 | .values(oauthClient) | ||
| 50 | .onConflictDoUpdate({ | ||
| 51 | target: [ | ||
| 52 | tables.workspaceSourceOauthClientsTable.workspaceId, | ||
| 53 | tables.workspaceSourceOauthClientsTable.sourceId, | ||
| 54 | tables.workspaceSourceOauthClientsTable.providerKey, | ||
| 55 | ], | ||
| 56 | set: { | ||
| 57 | ...withoutCreatedAt(oauthClient), | ||
| 58 | }, | ||
| 59 | }); | ||
| 60 | }), | ||
| 61 | |||
| 62 | removeByWorkspaceAndSourceId: (input: { | ||
| 63 | workspaceId: WorkspaceSourceOauthClient["workspaceId"]; | ||
| 64 | sourceId: WorkspaceSourceOauthClient["sourceId"]; | ||
| 65 | }) => | ||
| 66 | client.use("rows.source_oauth_clients.remove_by_workspace_source", async (db) => { | ||
| 67 | const deleted = await db | ||
| 68 | .delete(tables.workspaceSourceOauthClientsTable) | ||
| 69 | .where( | ||
| 70 | and( | ||
| 71 | eq(tables.workspaceSourceOauthClientsTable.workspaceId, input.workspaceId), | ||
| 72 | eq(tables.workspaceSourceOauthClientsTable.sourceId, input.sourceId), | ||
| 73 | ), | ||
| 74 | ) | ||
| 75 | .returning(); | ||
| 76 | |||
| 77 | return deleted.length; | ||
| 78 | }), | ||
| 79 | }); | ||
| @@ -1,85 +0,0 @@ | |||
| 1 | import { | ||
| 2 | type StoredSourceRecipeDocumentRecord, | ||
| 3 | StoredSourceRecipeDocumentRecordSchema, | ||
| 4 | } from "#schema"; | ||
| 5 | import { Schema } from "effect"; | ||
| 6 | import { asc, eq, inArray } from "drizzle-orm"; | ||
| 7 | |||
| 8 | import type { DrizzleClient } from "../client"; | ||
| 9 | import type { DrizzleTables } from "../schema"; | ||
| 10 | import { chunkArray } from "./shared"; | ||
| 11 | |||
| 12 | const decodeStoredSourceRecipeDocumentRecord = Schema.decodeUnknownSync( | ||
| 13 | StoredSourceRecipeDocumentRecordSchema, | ||
| 14 | ); | ||
| 15 | |||
| 16 | const RECIPE_DOCUMENT_INSERT_BATCH_SIZE = 500; | ||
| 17 | |||
| 18 | export const createSourceRecipeDocumentsRepo = ( | ||
| 19 | client: DrizzleClient, | ||
| 20 | tables: DrizzleTables, | ||
| 21 | ) => ({ | ||
| 22 | listByRevisionId: (recipeRevisionId: StoredSourceRecipeDocumentRecord["recipeRevisionId"]) => | ||
| 23 | client.use("rows.source_recipe_documents.list_by_revision", async (db) => { | ||
| 24 | const rows = await db | ||
| 25 | .select() | ||
| 26 | .from(tables.sourceRecipeDocumentsTable) | ||
| 27 | .where(eq(tables.sourceRecipeDocumentsTable.recipeRevisionId, recipeRevisionId)) | ||
| 28 | .orderBy( | ||
| 29 | asc(tables.sourceRecipeDocumentsTable.documentKind), | ||
| 30 | asc(tables.sourceRecipeDocumentsTable.documentKey), | ||
| 31 | ); | ||
| 32 | |||
| 33 | return rows.map((row) => decodeStoredSourceRecipeDocumentRecord(row)); | ||
| 34 | }), | ||
| 35 | |||
| 36 | listByRevisionIds: ( | ||
| 37 | recipeRevisionIds: readonly StoredSourceRecipeDocumentRecord["recipeRevisionId"][], | ||
| 38 | ) => | ||
| 39 | client.use("rows.source_recipe_documents.list_by_revisions", async (db) => { | ||
| 40 | if (recipeRevisionIds.length === 0) { | ||
| 41 | return [] as StoredSourceRecipeDocumentRecord[]; | ||
| 42 | } | ||
| 43 | |||
| 44 | const rows = await db | ||
| 45 | .select() | ||
| 46 | .from(tables.sourceRecipeDocumentsTable) | ||
| 47 | .where(inArray(tables.sourceRecipeDocumentsTable.recipeRevisionId, [...recipeRevisionIds])) | ||
| 48 | .orderBy( | ||
| 49 | asc(tables.sourceRecipeDocumentsTable.recipeRevisionId), | ||
| 50 | asc(tables.sourceRecipeDocumentsTable.documentKind), | ||
| 51 | asc(tables.sourceRecipeDocumentsTable.documentKey), | ||
| 52 | ); | ||
| 53 | |||
| 54 | return rows.map((row) => decodeStoredSourceRecipeDocumentRecord(row)); | ||
| 55 | }), | ||
| 56 | |||
| 57 | replaceForRevision: (input: { | ||
| 58 | recipeRevisionId: StoredSourceRecipeDocumentRecord["recipeRevisionId"]; | ||
| 59 | documents: readonly StoredSourceRecipeDocumentRecord[]; | ||
| 60 | }) => | ||
| 61 | client.useTx("rows.source_recipe_documents.replace_for_revision", async (tx) => { | ||
| 62 | await tx | ||
| 63 | .delete(tables.sourceRecipeDocumentsTable) | ||
| 64 | .where(eq(tables.sourceRecipeDocumentsTable.recipeRevisionId, input.recipeRevisionId)); | ||
| 65 | |||
| 66 | if (input.documents.length > 0) { | ||
| 67 | for (const batch of chunkArray( | ||
| 68 | input.documents, | ||
| 69 | RECIPE_DOCUMENT_INSERT_BATCH_SIZE, | ||
| 70 | )) { | ||
| 71 | await tx.insert(tables.sourceRecipeDocumentsTable).values([...batch]); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | }), | ||
| 75 | |||
| 76 | removeByRevisionId: (recipeRevisionId: StoredSourceRecipeDocumentRecord["recipeRevisionId"]) => | ||
| 77 | client.use("rows.source_recipe_documents.remove_by_revision", async (db) => { | ||
| 78 | const deleted = await db | ||
| 79 | .delete(tables.sourceRecipeDocumentsTable) | ||
| 80 | .where(eq(tables.sourceRecipeDocumentsTable.recipeRevisionId, recipeRevisionId)) | ||
| 81 | .returning(); | ||
| 82 | |||
| 83 | return deleted.length; | ||
| 84 | }), | ||
| 85 | }); | ||
| @@ -1,86 +0,0 @@ | |||
| 1 | import { | ||
| 2 | type StoredSourceRecipeOperationRecord, | ||
| 3 | StoredSourceRecipeOperationRecordSchema, | ||
| 4 | } from "#schema"; | ||
| 5 | import { Schema } from "effect"; | ||
| 6 | import { asc, eq, inArray } from "drizzle-orm"; | ||
| 7 | |||
| 8 | import type { DrizzleClient } from "../client"; | ||
| 9 | import type { DrizzleTables } from "../schema"; | ||
| 10 | import { chunkArray } from "./shared"; | ||
| 11 | |||
| 12 | const decodeStoredSourceRecipeOperationRecord = Schema.decodeUnknownSync( | ||
| 13 | StoredSourceRecipeOperationRecordSchema, | ||
| 14 | ); | ||
| 15 | |||
| 16 | // Keep wide recipe-operation inserts well below Postgres/PGlite parameter limits. | ||
| 17 | const RECIPE_OPERATION_INSERT_BATCH_SIZE = 200; | ||
| 18 | |||
| 19 | export const createSourceRecipeOperationsRepo = ( | ||
| 20 | client: DrizzleClient, | ||
| 21 | tables: DrizzleTables, | ||
| 22 | ) => ({ | ||
| 23 | listByRevisionId: (recipeRevisionId: StoredSourceRecipeOperationRecord["recipeRevisionId"]) => | ||
| 24 | client.use("rows.source_recipe_operations.list_by_revision", async (db) => { | ||
| 25 | const rows = await db | ||
| 26 | .select() | ||
| 27 | .from(tables.sourceRecipeOperationsTable) | ||
| 28 | .where(eq(tables.sourceRecipeOperationsTable.recipeRevisionId, recipeRevisionId)) | ||
| 29 | .orderBy( | ||
| 30 | asc(tables.sourceRecipeOperationsTable.toolId), | ||
| 31 | asc(tables.sourceRecipeOperationsTable.id), | ||
| 32 | ); | ||
| 33 | |||
| 34 | return rows.map((row) => decodeStoredSourceRecipeOperationRecord(row)); | ||
| 35 | }), | ||
| 36 | |||
| 37 | listByRevisionIds: ( | ||
| 38 | recipeRevisionIds: readonly StoredSourceRecipeOperationRecord["recipeRevisionId"][], | ||
| 39 | ) => | ||
| 40 | client.use("rows.source_recipe_operations.list_by_revisions", async (db) => { | ||
| 41 | if (recipeRevisionIds.length === 0) { | ||
| 42 | return [] as StoredSourceRecipeOperationRecord[]; | ||
| 43 | } | ||
| 44 | |||
| 45 | const rows = await db | ||
| 46 | .select() | ||
| 47 | .from(tables.sourceRecipeOperationsTable) | ||
| 48 | .where(inArray(tables.sourceRecipeOperationsTable.recipeRevisionId, [...recipeRevisionIds])) | ||
| 49 | .orderBy( | ||
| 50 | asc(tables.sourceRecipeOperationsTable.recipeRevisionId), | ||
| 51 | asc(tables.sourceRecipeOperationsTable.toolId), | ||
| 52 | asc(tables.sourceRecipeOperationsTable.id), | ||
| 53 | ); | ||
| 54 | |||
| 55 | return rows.map((row) => decodeStoredSourceRecipeOperationRecord(row)); | ||
| 56 | }), | ||
| 57 | |||
| 58 | replaceForRevision: (input: { | ||
| 59 | recipeRevisionId: StoredSourceRecipeOperationRecord["recipeRevisionId"]; | ||
| 60 | operations: readonly StoredSourceRecipeOperationRecord[]; | ||
| 61 | }) => | ||
| 62 | client.useTx("rows.source_recipe_operations.replace_for_revision", async (tx) => { | ||
| 63 | await tx | ||
| 64 | .delete(tables.sourceRecipeOperationsTable) | ||
| 65 | .where(eq(tables.sourceRecipeOperationsTable.recipeRevisionId, input.recipeRevisionId)); | ||
| 66 | |||
| 67 | if (input.operations.length > 0) { | ||
| 68 | for (const batch of chunkArray( | ||
| 69 | input.operations, | ||
| 70 | RECIPE_OPERATION_INSERT_BATCH_SIZE, | ||
| 71 | )) { | ||
| 72 | await tx.insert(tables.sourceRecipeOperationsTable).values([...batch]); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | }), | ||
| 76 | |||
| 77 | removeByRevisionId: (recipeRevisionId: StoredSourceRecipeOperationRecord["recipeRevisionId"]) => | ||
| 78 | client.use("rows.source_recipe_operations.remove_by_revision", async (db) => { | ||
| 79 | const deleted = await db | ||
| 80 | .delete(tables.sourceRecipeOperationsTable) | ||
| 81 | .where(eq(tables.sourceRecipeOperationsTable.recipeRevisionId, recipeRevisionId)) | ||
| 82 | .returning(); | ||
| 83 | |||
| 84 | return deleted.length; | ||
| 85 | }), | ||
| 86 | }); | ||
| @@ -1,157 +0,0 @@ | |||
| 1 | import { | ||
| 2 | type StoredSourceRecipeRevisionRecord, | ||
| 3 | StoredSourceRecipeRevisionRecordSchema, | ||
| 4 | } from "#schema"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import { Schema } from "effect"; | ||
| 7 | import { asc, desc, eq, inArray, and } from "drizzle-orm"; | ||
| 8 | |||
| 9 | import type { DrizzleClient } from "../client"; | ||
| 10 | import type { DrizzleTables } from "../schema"; | ||
| 11 | import { firstOption, withoutCreatedAt } from "./shared"; | ||
| 12 | |||
| 13 | const decodeStoredSourceRecipeRevisionRecord = Schema.decodeUnknownSync( | ||
| 14 | StoredSourceRecipeRevisionRecordSchema, | ||
| 15 | ); | ||
| 16 | |||
| 17 | type Mutable<T> = { -readonly [K in keyof T]: T[K] }; | ||
| 18 | type RecipeRevisionPatch = Partial< | ||
| 19 | Omit<Mutable<StoredSourceRecipeRevisionRecord>, "id" | "recipeId" | "revisionNumber" | "createdAt"> | ||
| 20 | >; | ||
| 21 | |||
| 22 | export const createSourceRecipeRevisionsRepo = ( | ||
| 23 | client: DrizzleClient, | ||
| 24 | tables: DrizzleTables, | ||
| 25 | ) => ({ | ||
| 26 | listByRecipeId: (recipeId: StoredSourceRecipeRevisionRecord["recipeId"]) => | ||
| 27 | client.use("rows.source_recipe_revisions.list_by_recipe", async (db) => { | ||
| 28 | const rows = await db | ||
| 29 | .select() | ||
| 30 | .from(tables.sourceRecipeRevisionsTable) | ||
| 31 | .where(eq(tables.sourceRecipeRevisionsTable.recipeId, recipeId)) | ||
| 32 | .orderBy( | ||
| 33 | asc(tables.sourceRecipeRevisionsTable.revisionNumber), | ||
| 34 | asc(tables.sourceRecipeRevisionsTable.createdAt), | ||
| 35 | ); | ||
| 36 | |||
| 37 | return rows.map((row) => decodeStoredSourceRecipeRevisionRecord(row)); | ||
| 38 | }), | ||
| 39 | |||
| 40 | listByIds: (ids: readonly StoredSourceRecipeRevisionRecord["id"][]) => | ||
| 41 | client.use("rows.source_recipe_revisions.list_by_ids", async (db) => { | ||
| 42 | if (ids.length === 0) { | ||
| 43 | return [] as StoredSourceRecipeRevisionRecord[]; | ||
| 44 | } | ||
| 45 | |||
| 46 | const rows = await db | ||
| 47 | .select() | ||
| 48 | .from(tables.sourceRecipeRevisionsTable) | ||
| 49 | .where(inArray(tables.sourceRecipeRevisionsTable.id, [...ids])) | ||
| 50 | .orderBy( | ||
| 51 | asc(tables.sourceRecipeRevisionsTable.recipeId), | ||
| 52 | asc(tables.sourceRecipeRevisionsTable.revisionNumber), | ||
| 53 | ); | ||
| 54 | |||
| 55 | return rows.map((row) => decodeStoredSourceRecipeRevisionRecord(row)); | ||
| 56 | }), | ||
| 57 | |||
| 58 | getById: (id: StoredSourceRecipeRevisionRecord["id"]) => | ||
| 59 | client.use("rows.source_recipe_revisions.get_by_id", async (db) => { | ||
| 60 | const rows = await db | ||
| 61 | .select() | ||
| 62 | .from(tables.sourceRecipeRevisionsTable) | ||
| 63 | .where(eq(tables.sourceRecipeRevisionsTable.id, id)) | ||
| 64 | .limit(1); | ||
| 65 | |||
| 66 | const row = firstOption(rows); | ||
| 67 | return Option.isSome(row) | ||
| 68 | ? Option.some(decodeStoredSourceRecipeRevisionRecord(row.value)) | ||
| 69 | : Option.none<StoredSourceRecipeRevisionRecord>(); | ||
| 70 | }), | ||
| 71 | |||
| 72 | getByRecipeAndMaterializationHash: (input: { | ||
| 73 | recipeId: StoredSourceRecipeRevisionRecord["recipeId"]; | ||
| 74 | materializationHash: string; | ||
| 75 | }) => | ||
| 76 | client.use("rows.source_recipe_revisions.get_by_recipe_materialization", async (db) => { | ||
| 77 | const rows = await db | ||
| 78 | .select() | ||
| 79 | .from(tables.sourceRecipeRevisionsTable) | ||
| 80 | .where( | ||
| 81 | and( | ||
| 82 | eq(tables.sourceRecipeRevisionsTable.recipeId, input.recipeId), | ||
| 83 | eq(tables.sourceRecipeRevisionsTable.materializationHash, input.materializationHash), | ||
| 84 | ), | ||
| 85 | ) | ||
| 86 | .limit(1); | ||
| 87 | |||
| 88 | const row = firstOption(rows); | ||
| 89 | return Option.isSome(row) | ||
| 90 | ? Option.some(decodeStoredSourceRecipeRevisionRecord(row.value)) | ||
| 91 | : Option.none<StoredSourceRecipeRevisionRecord>(); | ||
| 92 | }), | ||
| 93 | |||
| 94 | nextRevisionNumber: (recipeId: StoredSourceRecipeRevisionRecord["recipeId"]) => | ||
| 95 | client.use("rows.source_recipe_revisions.next_revision_number", async (db) => { | ||
| 96 | const rows = await db | ||
| 97 | .select({ | ||
| 98 | revisionNumber: tables.sourceRecipeRevisionsTable.revisionNumber, | ||
| 99 | }) | ||
| 100 | .from(tables.sourceRecipeRevisionsTable) | ||
| 101 | .where(eq(tables.sourceRecipeRevisionsTable.recipeId, recipeId)) | ||
| 102 | .orderBy(desc(tables.sourceRecipeRevisionsTable.revisionNumber)) | ||
| 103 | .limit(1); | ||
| 104 | |||
| 105 | return Number(rows[0]?.revisionNumber ?? 0) + 1; | ||
| 106 | }), | ||
| 107 | |||
| 108 | upsert: (revision: StoredSourceRecipeRevisionRecord) => | ||
| 109 | client.use("rows.source_recipe_revisions.upsert", async (db) => { | ||
| 110 | await db | ||
| 111 | .insert(tables.sourceRecipeRevisionsTable) | ||
| 112 | .values(revision) | ||
| 113 | .onConflictDoUpdate({ | ||
| 114 | target: [tables.sourceRecipeRevisionsTable.id], | ||
| 115 | set: { | ||
| 116 | ...withoutCreatedAt(revision), | ||
| 117 | }, | ||
| 118 | }); | ||
| 119 | }), | ||
| 120 | |||
| 121 | update: ( | ||
| 122 | id: StoredSourceRecipeRevisionRecord["id"], | ||
| 123 | patch: RecipeRevisionPatch, | ||
| 124 | ) => | ||
| 125 | client.use("rows.source_recipe_revisions.update", async (db) => { | ||
| 126 | const rows = await db | ||
| 127 | .update(tables.sourceRecipeRevisionsTable) | ||
| 128 | .set(patch) | ||
| 129 | .where(eq(tables.sourceRecipeRevisionsTable.id, id)) | ||
| 130 | .returning(); | ||
| 131 | |||
| 132 | const row = firstOption(rows); | ||
| 133 | return Option.isSome(row) | ||
| 134 | ? Option.some(decodeStoredSourceRecipeRevisionRecord(row.value)) | ||
| 135 | : Option.none<StoredSourceRecipeRevisionRecord>(); | ||
| 136 | }), | ||
| 137 | |||
| 138 | removeByRecipeId: (recipeId: StoredSourceRecipeRevisionRecord["recipeId"]) => | ||
| 139 | client.use("rows.source_recipe_revisions.remove_by_recipe", async (db) => { | ||
| 140 | const deleted = await db | ||
| 141 | .delete(tables.sourceRecipeRevisionsTable) | ||
| 142 | .where(eq(tables.sourceRecipeRevisionsTable.recipeId, recipeId)) | ||
| 143 | .returning(); | ||
| 144 | |||
| 145 | return deleted.length; | ||
| 146 | }), | ||
| 147 | |||
| 148 | removeById: (id: StoredSourceRecipeRevisionRecord["id"]) => | ||
| 149 | client.use("rows.source_recipe_revisions.remove_by_id", async (db) => { | ||
| 150 | const deleted = await db | ||
| 151 | .delete(tables.sourceRecipeRevisionsTable) | ||
| 152 | .where(eq(tables.sourceRecipeRevisionsTable.id, id)) | ||
| 153 | .returning(); | ||
| 154 | |||
| 155 | return deleted.length > 0; | ||
| 156 | }), | ||
| 157 | }); | ||
| @@ -1,106 +0,0 @@ | |||
| 1 | import { | ||
| 2 | type StoredSourceRecipeSchemaBundleRecord, | ||
| 3 | StoredSourceRecipeSchemaBundleRecordSchema, | ||
| 4 | } from "#schema"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import { Schema } from "effect"; | ||
| 7 | import { asc, eq, inArray } from "drizzle-orm"; | ||
| 8 | |||
| 9 | import type { DrizzleClient } from "../client"; | ||
| 10 | import type { DrizzleTables } from "../schema"; | ||
| 11 | import { firstOption, withoutCreatedAt } from "./shared"; | ||
| 12 | |||
| 13 | const decodeStoredSourceRecipeSchemaBundleRecord = Schema.decodeUnknownSync( | ||
| 14 | StoredSourceRecipeSchemaBundleRecordSchema, | ||
| 15 | ); | ||
| 16 | |||
| 17 | export const createSourceRecipeSchemaBundlesRepo = ( | ||
| 18 | client: DrizzleClient, | ||
| 19 | tables: DrizzleTables, | ||
| 20 | ) => ({ | ||
| 21 | getById: (id: StoredSourceRecipeSchemaBundleRecord["id"]) => | ||
| 22 | client.use("rows.source_recipe_schema_bundles.get_by_id", async (db) => { | ||
| 23 | const rows = await db | ||
| 24 | .select() | ||
| 25 | .from(tables.sourceRecipeSchemaBundlesTable) | ||
| 26 | .where(eq(tables.sourceRecipeSchemaBundlesTable.id, id)) | ||
| 27 | .limit(1); | ||
| 28 | |||
| 29 | const row = firstOption(rows); | ||
| 30 | return Option.isSome(row) | ||
| 31 | ? Option.some(decodeStoredSourceRecipeSchemaBundleRecord(row.value)) | ||
| 32 | : Option.none<StoredSourceRecipeSchemaBundleRecord>(); | ||
| 33 | }), | ||
| 34 | |||
| 35 | listByRevisionId: (recipeRevisionId: StoredSourceRecipeSchemaBundleRecord["recipeRevisionId"]) => | ||
| 36 | client.use("rows.source_recipe_schema_bundles.list_by_revision", async (db) => { | ||
| 37 | const rows = await db | ||
| 38 | .select() | ||
| 39 | .from(tables.sourceRecipeSchemaBundlesTable) | ||
| 40 | .where(eq(tables.sourceRecipeSchemaBundlesTable.recipeRevisionId, recipeRevisionId)) | ||
| 41 | .orderBy( | ||
| 42 | asc(tables.sourceRecipeSchemaBundlesTable.bundleKind), | ||
| 43 | asc(tables.sourceRecipeSchemaBundlesTable.createdAt), | ||
| 44 | ); | ||
| 45 | |||
| 46 | return rows.map((row) => decodeStoredSourceRecipeSchemaBundleRecord(row)); | ||
| 47 | }), | ||
| 48 | |||
| 49 | listByRevisionIds: ( | ||
| 50 | recipeRevisionIds: readonly StoredSourceRecipeSchemaBundleRecord["recipeRevisionId"][], | ||
| 51 | ) => | ||
| 52 | client.use("rows.source_recipe_schema_bundles.list_by_revisions", async (db) => { | ||
| 53 | if (recipeRevisionIds.length === 0) { | ||
| 54 | return [] as StoredSourceRecipeSchemaBundleRecord[]; | ||
| 55 | } | ||
| 56 | |||
| 57 | const rows = await db | ||
| 58 | .select() | ||
| 59 | .from(tables.sourceRecipeSchemaBundlesTable) | ||
| 60 | .where(inArray(tables.sourceRecipeSchemaBundlesTable.recipeRevisionId, [...recipeRevisionIds])) | ||
| 61 | .orderBy( | ||
| 62 | asc(tables.sourceRecipeSchemaBundlesTable.recipeRevisionId), | ||
| 63 | asc(tables.sourceRecipeSchemaBundlesTable.bundleKind), | ||
| 64 | asc(tables.sourceRecipeSchemaBundlesTable.createdAt), | ||
| 65 | ); | ||
| 66 | |||
| 67 | return rows.map((row) => decodeStoredSourceRecipeSchemaBundleRecord(row)); | ||
| 68 | }), | ||
| 69 | |||
| 70 | replaceForRevision: (input: { | ||
| 71 | recipeRevisionId: StoredSourceRecipeSchemaBundleRecord["recipeRevisionId"]; | ||
| 72 | bundles: readonly StoredSourceRecipeSchemaBundleRecord[]; | ||
| 73 | }) => | ||
| 74 | client.useTx("rows.source_recipe_schema_bundles.replace_for_revision", async (tx) => { | ||
| 75 | await tx | ||
| 76 | .delete(tables.sourceRecipeSchemaBundlesTable) | ||
| 77 | .where(eq(tables.sourceRecipeSchemaBundlesTable.recipeRevisionId, input.recipeRevisionId)); | ||
| 78 | |||
| 79 | if (input.bundles.length > 0) { | ||
| 80 | await tx.insert(tables.sourceRecipeSchemaBundlesTable).values([...input.bundles]); | ||
| 81 | } | ||
| 82 | }), | ||
| 83 | |||
| 84 | upsert: (bundle: StoredSourceRecipeSchemaBundleRecord) => | ||
| 85 | client.use("rows.source_recipe_schema_bundles.upsert", async (db) => { | ||
| 86 | await db | ||
| 87 | .insert(tables.sourceRecipeSchemaBundlesTable) | ||
| 88 | .values(bundle) | ||
| 89 | .onConflictDoUpdate({ | ||
| 90 | target: [tables.sourceRecipeSchemaBundlesTable.id], | ||
| 91 | set: { | ||
| 92 | ...withoutCreatedAt(bundle), | ||
| 93 | }, | ||
| 94 | }); | ||
| 95 | }), | ||
| 96 | |||
| 97 | removeByRevisionId: (recipeRevisionId: StoredSourceRecipeSchemaBundleRecord["recipeRevisionId"]) => | ||
| 98 | client.use("rows.source_recipe_schema_bundles.remove_by_revision", async (db) => { | ||
| 99 | const deleted = await db | ||
| 100 | .delete(tables.sourceRecipeSchemaBundlesTable) | ||
| 101 | .where(eq(tables.sourceRecipeSchemaBundlesTable.recipeRevisionId, recipeRevisionId)) | ||
| 102 | .returning(); | ||
| 103 | |||
| 104 | return deleted.length; | ||
| 105 | }), | ||
| 106 | }); | ||
| @@ -1,67 +0,0 @@ | |||
| 1 | import { | ||
| 2 | type StoredSourceRecipeRecord, | ||
| 3 | StoredSourceRecipeRecordSchema, | ||
| 4 | } from "#schema"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import { Schema } from "effect"; | ||
| 7 | import { asc, eq } from "drizzle-orm"; | ||
| 8 | |||
| 9 | import type { DrizzleClient } from "../client"; | ||
| 10 | import type { DrizzleTables } from "../schema"; | ||
| 11 | import { firstOption, withoutCreatedAt } from "./shared"; | ||
| 12 | |||
| 13 | const decodeStoredSourceRecipeRecord = Schema.decodeUnknownSync( | ||
| 14 | StoredSourceRecipeRecordSchema, | ||
| 15 | ); | ||
| 16 | |||
| 17 | export const createSourceRecipesRepo = ( | ||
| 18 | client: DrizzleClient, | ||
| 19 | tables: DrizzleTables, | ||
| 20 | ) => ({ | ||
| 21 | listByUpdatedAt: () => | ||
| 22 | client.use("rows.source_recipes.list", async (db) => { | ||
| 23 | const rows = await db | ||
| 24 | .select() | ||
| 25 | .from(tables.sourceRecipesTable) | ||
| 26 | .orderBy(asc(tables.sourceRecipesTable.updatedAt), asc(tables.sourceRecipesTable.id)); | ||
| 27 | |||
| 28 | return rows.map((row) => decodeStoredSourceRecipeRecord(row)); | ||
| 29 | }), | ||
| 30 | |||
| 31 | getById: (id: StoredSourceRecipeRecord["id"]) => | ||
| 32 | client.use("rows.source_recipes.get_by_id", async (db) => { | ||
| 33 | const rows = await db | ||
| 34 | .select() | ||
| 35 | .from(tables.sourceRecipesTable) | ||
| 36 | .where(eq(tables.sourceRecipesTable.id, id)) | ||
| 37 | .limit(1); | ||
| 38 | |||
| 39 | const row = firstOption(rows); | ||
| 40 | return Option.isSome(row) | ||
| 41 | ? Option.some(decodeStoredSourceRecipeRecord(row.value)) | ||
| 42 | : Option.none<StoredSourceRecipeRecord>(); | ||
| 43 | }), | ||
| 44 | |||
| 45 | upsert: (recipe: StoredSourceRecipeRecord) => | ||
| 46 | client.use("rows.source_recipes.upsert", async (db) => { | ||
| 47 | await db | ||
| 48 | .insert(tables.sourceRecipesTable) | ||
| 49 | .values(recipe) | ||
| 50 | .onConflictDoUpdate({ | ||
| 51 | target: [tables.sourceRecipesTable.id], | ||
| 52 | set: { | ||
| 53 | ...withoutCreatedAt(recipe), | ||
| 54 | }, | ||
| 55 | }); | ||
| 56 | }), | ||
| 57 | |||
| 58 | removeById: (id: StoredSourceRecipeRecord["id"]) => | ||
| 59 | client.use("rows.source_recipes.remove", async (db) => { | ||
| 60 | const deleted = await db | ||
| 61 | .delete(tables.sourceRecipesTable) | ||
| 62 | .where(eq(tables.sourceRecipesTable.id, id)) | ||
| 63 | .returning(); | ||
| 64 | |||
| 65 | return deleted.length > 0; | ||
| 66 | }), | ||
| 67 | }); | ||
| @@ -1,154 +0,0 @@ | |||
| 1 | import { | ||
| 2 | type StoredSourceRecord, | ||
| 3 | StoredSourceRecordSchema, | ||
| 4 | } from "#schema"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import { Schema } from "effect"; | ||
| 7 | import { and, asc, count, eq } from "drizzle-orm"; | ||
| 8 | |||
| 9 | import type { DrizzleClient } from "../client"; | ||
| 10 | import type { DrizzleTables } from "../schema"; | ||
| 11 | import { firstOption } from "./shared"; | ||
| 12 | |||
| 13 | const decodeStoredSourceRecord = Schema.decodeUnknownSync(StoredSourceRecordSchema); | ||
| 14 | const encodeStoredSourceRecord = Schema.encodeSync(StoredSourceRecordSchema); | ||
| 15 | |||
| 16 | const toSourceUpdateSet = ( | ||
| 17 | patch: Partial<Omit<StoredSourceRecord, "id" | "workspaceId" | "createdAt">>, | ||
| 18 | ): Partial<DrizzleTables["sourcesTable"]["$inferInsert"]> => { | ||
| 19 | const set: Partial<DrizzleTables["sourcesTable"]["$inferInsert"]> = {}; | ||
| 20 | |||
| 21 | if (patch.recipeId !== undefined) set.recipeId = patch.recipeId; | ||
| 22 | if (patch.recipeRevisionId !== undefined) set.recipeRevisionId = patch.recipeRevisionId; | ||
| 23 | if (patch.name !== undefined) set.name = patch.name; | ||
| 24 | if (patch.kind !== undefined) set.kind = patch.kind; | ||
| 25 | if (patch.endpoint !== undefined) set.endpoint = patch.endpoint; | ||
| 26 | if (patch.status !== undefined) set.status = patch.status; | ||
| 27 | if (patch.enabled !== undefined) set.enabled = patch.enabled; | ||
| 28 | if (patch.namespace !== undefined) set.namespace = patch.namespace; | ||
| 29 | if (patch.importAuthPolicy !== undefined) set.importAuthPolicy = patch.importAuthPolicy; | ||
| 30 | if (patch.bindingConfigJson !== undefined) set.bindingConfigJson = patch.bindingConfigJson; | ||
| 31 | if (patch.sourceHash !== undefined) set.sourceHash = patch.sourceHash; | ||
| 32 | if (patch.lastError !== undefined) set.lastError = patch.lastError; | ||
| 33 | if (patch.updatedAt !== undefined) set.updatedAt = patch.updatedAt; | ||
| 34 | |||
| 35 | return set; | ||
| 36 | }; | ||
| 37 | |||
| 38 | export const createSourcesRepo = ( | ||
| 39 | client: DrizzleClient, | ||
| 40 | tables: DrizzleTables, | ||
| 41 | ) => ({ | ||
| 42 | listAll: () => | ||
| 43 | client.use("rows.sources.list_all", async (db) => { | ||
| 44 | const rows = await db | ||
| 45 | .select() | ||
| 46 | .from(tables.sourcesTable) | ||
| 47 | .orderBy( | ||
| 48 | asc(tables.sourcesTable.workspaceId), | ||
| 49 | asc(tables.sourcesTable.updatedAt), | ||
| 50 | asc(tables.sourcesTable.sourceId), | ||
| 51 | ); | ||
| 52 | |||
| 53 | return rows.map((row) => decodeStoredSourceRecord(row)); | ||
| 54 | }), | ||
| 55 | |||
| 56 | listByWorkspaceId: (workspaceId: StoredSourceRecord["workspaceId"]) => | ||
| 57 | client.use("rows.sources.list_by_workspace", async (db) => { | ||
| 58 | const rows = await db | ||
| 59 | .select() | ||
| 60 | .from(tables.sourcesTable) | ||
| 61 | .where(eq(tables.sourcesTable.workspaceId, workspaceId)) | ||
| 62 | .orderBy(asc(tables.sourcesTable.updatedAt), asc(tables.sourcesTable.sourceId)); | ||
| 63 | |||
| 64 | return rows.map((row) => decodeStoredSourceRecord(row)); | ||
| 65 | }), | ||
| 66 | |||
| 67 | getByWorkspaceAndId: ( | ||
| 68 | workspaceId: StoredSourceRecord["workspaceId"], | ||
| 69 | sourceId: StoredSourceRecord["id"], | ||
| 70 | ) => | ||
| 71 | client.use("rows.sources.get_by_workspace_and_id", async (db) => { | ||
| 72 | const rows = await db | ||
| 73 | .select() | ||
| 74 | .from(tables.sourcesTable) | ||
| 75 | .where( | ||
| 76 | and( | ||
| 77 | eq(tables.sourcesTable.workspaceId, workspaceId), | ||
| 78 | eq(tables.sourcesTable.sourceId, sourceId), | ||
| 79 | ), | ||
| 80 | ) | ||
| 81 | .limit(1); | ||
| 82 | |||
| 83 | const row = firstOption(rows); | ||
| 84 | return Option.isSome(row) | ||
| 85 | ? Option.some(decodeStoredSourceRecord(row.value)) | ||
| 86 | : Option.none<StoredSourceRecord>(); | ||
| 87 | }), | ||
| 88 | |||
| 89 | countByRecipeId: (recipeId: StoredSourceRecord["recipeId"]) => | ||
| 90 | client.use("rows.sources.count_by_recipe", async (db) => { | ||
| 91 | const rows = await db | ||
| 92 | .select({ value: count() }) | ||
| 93 | .from(tables.sourcesTable) | ||
| 94 | .where(eq(tables.sourcesTable.recipeId, recipeId)); | ||
| 95 | |||
| 96 | return Number(rows[0]?.value ?? 0); | ||
| 97 | }), | ||
| 98 | |||
| 99 | countByRecipeRevisionId: (recipeRevisionId: StoredSourceRecord["recipeRevisionId"]) => | ||
| 100 | client.use("rows.sources.count_by_recipe_revision", async (db) => { | ||
| 101 | const rows = await db | ||
| 102 | .select({ value: count() }) | ||
| 103 | .from(tables.sourcesTable) | ||
| 104 | .where(eq(tables.sourcesTable.recipeRevisionId, recipeRevisionId)); | ||
| 105 | |||
| 106 | return Number(rows[0]?.value ?? 0); | ||
| 107 | }), | ||
| 108 | |||
| 109 | insert: (source: StoredSourceRecord) => | ||
| 110 | client.use("rows.sources.insert", async (db) => { | ||
| 111 | await db.insert(tables.sourcesTable).values(encodeStoredSourceRecord(source)); | ||
| 112 | }), | ||
| 113 | |||
| 114 | update: ( | ||
| 115 | workspaceId: StoredSourceRecord["workspaceId"], | ||
| 116 | sourceId: StoredSourceRecord["id"], | ||
| 117 | patch: Partial<Omit<StoredSourceRecord, "id" | "workspaceId" | "createdAt">>, | ||
| 118 | ) => | ||
| 119 | client.use("rows.sources.update", async (db) => { | ||
| 120 | const rows = await db | ||
| 121 | .update(tables.sourcesTable) | ||
| 122 | .set(toSourceUpdateSet(patch)) | ||
| 123 | .where( | ||
| 124 | and( | ||
| 125 | eq(tables.sourcesTable.workspaceId, workspaceId), | ||
| 126 | eq(tables.sourcesTable.sourceId, sourceId), | ||
| 127 | ), | ||
| 128 | ) | ||
| 129 | .returning(); | ||
| 130 | |||
| 131 | const row = firstOption(rows); | ||
| 132 | return Option.isSome(row) | ||
| 133 | ? Option.some(decodeStoredSourceRecord(row.value)) | ||
| 134 | : Option.none<StoredSourceRecord>(); | ||
| 135 | }), | ||
| 136 | |||
| 137 | removeByWorkspaceAndId: ( | ||
| 138 | workspaceId: StoredSourceRecord["workspaceId"], | ||
| 139 | sourceId: StoredSourceRecord["id"], | ||
| 140 | ) => | ||
| 141 | client.useTx("rows.sources.remove", async (tx) => { | ||
| 142 | const deleted = await tx | ||
| 143 | .delete(tables.sourcesTable) | ||
| 144 | .where( | ||
| 145 | and( | ||
| 146 | eq(tables.sourcesTable.workspaceId, workspaceId), | ||
| 147 | eq(tables.sourcesTable.sourceId, sourceId), | ||
| 148 | ), | ||
| 149 | ) | ||
| 150 | .returning(); | ||
| 151 | |||
| 152 | return deleted.length > 0; | ||
| 153 | }), | ||
| 154 | }); | ||
| @@ -1,550 +0,0 @@ | |||
| 1 | import { | ||
| 2 | bigint, | ||
| 3 | boolean, | ||
| 4 | check, | ||
| 5 | index, | ||
| 6 | pgTable, | ||
| 7 | primaryKey, | ||
| 8 | text, | ||
| 9 | uniqueIndex, | ||
| 10 | } from "drizzle-orm/pg-core"; | ||
| 11 | import { sql } from "drizzle-orm"; | ||
| 12 | |||
| 13 | export const tableNames = { | ||
| 14 | accounts: "accounts", | ||
| 15 | sources: "sources", | ||
| 16 | sourceRecipes: "source_recipes", | ||
| 17 | sourceRecipeRevisions: "source_recipe_revisions", | ||
| 18 | sourceRecipeDocuments: "source_recipe_documents", | ||
| 19 | sourceRecipeSchemaBundles: "source_recipe_schema_bundles", | ||
| 20 | sourceRecipeOperations: "source_recipe_operations", | ||
| 21 | codeMigrations: "control_plane_code_migrations", | ||
| 22 | authArtifacts: "workspace_source_auth_artifacts", | ||
| 23 | authLeases: "workspace_source_auth_leases", | ||
| 24 | workspaceSourceOauthClients: "workspace_source_oauth_clients", | ||
| 25 | secretMaterials: "secret_materials", | ||
| 26 | sourceAuthSessions: "source_auth_sessions", | ||
| 27 | executions: "executions", | ||
| 28 | executionInteractions: "execution_interactions", | ||
| 29 | executionSteps: "execution_steps", | ||
| 30 | } as const; | ||
| 31 | |||
| 32 | export const accountsTable = pgTable( | ||
| 33 | tableNames.accounts, | ||
| 34 | { | ||
| 35 | id: text("id").notNull().primaryKey(), | ||
| 36 | provider: text("provider").notNull(), | ||
| 37 | subject: text("subject").notNull(), | ||
| 38 | email: text("email"), | ||
| 39 | displayName: text("display_name"), | ||
| 40 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 41 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 42 | }, | ||
| 43 | (table) => [ | ||
| 44 | uniqueIndex("accounts_provider_subject_idx").on(table.provider, table.subject), | ||
| 45 | index("accounts_updated_idx").on(table.updatedAt, table.id), | ||
| 46 | check( | ||
| 47 | "accounts_provider_check", | ||
| 48 | sql`${table.provider} in ('local', 'workos', 'service')`, | ||
| 49 | ), | ||
| 50 | ], | ||
| 51 | ); | ||
| 52 | |||
| 53 | export const sourcesTable = pgTable( | ||
| 54 | tableNames.sources, | ||
| 55 | { | ||
| 56 | workspaceId: text("workspace_id").notNull(), | ||
| 57 | sourceId: text("source_id").notNull(), | ||
| 58 | recipeId: text("recipe_id").notNull(), | ||
| 59 | recipeRevisionId: text("recipe_revision_id").notNull(), | ||
| 60 | name: text("name").notNull(), | ||
| 61 | kind: text("kind").notNull(), | ||
| 62 | endpoint: text("endpoint").notNull(), | ||
| 63 | status: text("status").notNull(), | ||
| 64 | enabled: boolean("enabled").notNull(), | ||
| 65 | namespace: text("namespace"), | ||
| 66 | importAuthPolicy: text("import_auth_policy").notNull(), | ||
| 67 | bindingConfigJson: text("binding_config_json").notNull(), | ||
| 68 | sourceHash: text("source_hash"), | ||
| 69 | lastError: text("last_error"), | ||
| 70 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 71 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 72 | }, | ||
| 73 | (table) => [ | ||
| 74 | primaryKey({ | ||
| 75 | columns: [table.workspaceId, table.sourceId], | ||
| 76 | }), | ||
| 77 | index("sources_workspace_updated_idx").on( | ||
| 78 | table.workspaceId, | ||
| 79 | table.updatedAt, | ||
| 80 | table.sourceId, | ||
| 81 | ), | ||
| 82 | index("sources_workspace_recipe_idx").on( | ||
| 83 | table.workspaceId, | ||
| 84 | table.recipeId, | ||
| 85 | table.updatedAt, | ||
| 86 | table.sourceId, | ||
| 87 | ), | ||
| 88 | uniqueIndex("sources_workspace_name_idx").on(table.workspaceId, table.name), | ||
| 89 | check( | ||
| 90 | "sources_status_check", | ||
| 91 | sql`${table.status} in ('draft', 'probing', 'auth_required', 'connected', 'error')`, | ||
| 92 | ), | ||
| 93 | check( | ||
| 94 | "sources_import_auth_policy_check", | ||
| 95 | sql`${table.importAuthPolicy} in ('none', 'reuse_runtime', 'separate')`, | ||
| 96 | ), | ||
| 97 | ], | ||
| 98 | ); | ||
| 99 | |||
| 100 | export const sourceRecipesTable = pgTable( | ||
| 101 | tableNames.sourceRecipes, | ||
| 102 | { | ||
| 103 | id: text("id").notNull().primaryKey(), | ||
| 104 | kind: text("kind").notNull(), | ||
| 105 | adapterKey: text("adapter_key").notNull(), | ||
| 106 | providerKey: text("provider_key").notNull(), | ||
| 107 | name: text("name").notNull(), | ||
| 108 | summary: text("summary"), | ||
| 109 | visibility: text("visibility").notNull(), | ||
| 110 | latestRevisionId: text("latest_revision_id").notNull(), | ||
| 111 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 112 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 113 | }, | ||
| 114 | (table) => [ | ||
| 115 | index("source_recipes_provider_updated_idx").on( | ||
| 116 | table.providerKey, | ||
| 117 | table.updatedAt, | ||
| 118 | table.id, | ||
| 119 | ), | ||
| 120 | index("source_recipes_visibility_updated_idx").on( | ||
| 121 | table.visibility, | ||
| 122 | table.updatedAt, | ||
| 123 | table.id, | ||
| 124 | ), | ||
| 125 | check( | ||
| 126 | "source_recipes_kind_check", | ||
| 127 | sql`${table.kind} in ('http_api', 'mcp', 'internal')`, | ||
| 128 | ), | ||
| 129 | check( | ||
| 130 | "source_recipes_visibility_check", | ||
| 131 | sql`${table.visibility} in ('private', 'workspace', 'organization', 'public')`, | ||
| 132 | ), | ||
| 133 | ], | ||
| 134 | ); | ||
| 135 | |||
| 136 | export const sourceRecipeRevisionsTable = pgTable( | ||
| 137 | tableNames.sourceRecipeRevisions, | ||
| 138 | { | ||
| 139 | id: text("id").notNull().primaryKey(), | ||
| 140 | recipeId: text("recipe_id").notNull(), | ||
| 141 | revisionNumber: bigint("revision_number", { mode: "number" }).notNull(), | ||
| 142 | sourceConfigJson: text("source_config_json").notNull(), | ||
| 143 | manifestJson: text("manifest_json"), | ||
| 144 | manifestHash: text("manifest_hash"), | ||
| 145 | materializationHash: text("materialization_hash"), | ||
| 146 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 147 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 148 | }, | ||
| 149 | (table) => [ | ||
| 150 | uniqueIndex("source_recipe_revisions_recipe_revision_idx").on( | ||
| 151 | table.recipeId, | ||
| 152 | table.revisionNumber, | ||
| 153 | ), | ||
| 154 | uniqueIndex("source_recipe_revisions_recipe_materialization_idx").on( | ||
| 155 | table.recipeId, | ||
| 156 | table.materializationHash, | ||
| 157 | ), | ||
| 158 | index("source_recipe_revisions_recipe_manifest_idx").on( | ||
| 159 | table.recipeId, | ||
| 160 | table.manifestHash, | ||
| 161 | ), | ||
| 162 | index("source_recipe_revisions_recipe_created_idx").on( | ||
| 163 | table.recipeId, | ||
| 164 | table.createdAt, | ||
| 165 | table.id, | ||
| 166 | ), | ||
| 167 | ], | ||
| 168 | ); | ||
| 169 | |||
| 170 | export const sourceRecipeDocumentsTable = pgTable( | ||
| 171 | tableNames.sourceRecipeDocuments, | ||
| 172 | { | ||
| 173 | id: text("id").notNull().primaryKey(), | ||
| 174 | recipeRevisionId: text("recipe_revision_id").notNull(), | ||
| 175 | documentKind: text("document_kind").notNull(), | ||
| 176 | documentKey: text("document_key").notNull(), | ||
| 177 | contentText: text("content_text").notNull(), | ||
| 178 | contentHash: text("content_hash").notNull(), | ||
| 179 | fetchedAt: bigint("fetched_at", { mode: "number" }), | ||
| 180 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 181 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 182 | }, | ||
| 183 | (table) => [ | ||
| 184 | uniqueIndex("source_recipe_documents_revision_kind_key_idx").on( | ||
| 185 | table.recipeRevisionId, | ||
| 186 | table.documentKind, | ||
| 187 | table.documentKey, | ||
| 188 | ), | ||
| 189 | index("source_recipe_documents_revision_created_idx").on( | ||
| 190 | table.recipeRevisionId, | ||
| 191 | table.createdAt, | ||
| 192 | table.id, | ||
| 193 | ), | ||
| 194 | ], | ||
| 195 | ); | ||
| 196 | |||
| 197 | export const sourceRecipeSchemaBundlesTable = pgTable( | ||
| 198 | tableNames.sourceRecipeSchemaBundles, | ||
| 199 | { | ||
| 200 | id: text("id").notNull().primaryKey(), | ||
| 201 | recipeRevisionId: text("recipe_revision_id").notNull(), | ||
| 202 | bundleKind: text("bundle_kind").notNull(), | ||
| 203 | refsJson: text("refs_json").notNull(), | ||
| 204 | contentHash: text("content_hash").notNull(), | ||
| 205 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 206 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 207 | }, | ||
| 208 | (table) => [ | ||
| 209 | uniqueIndex("source_recipe_schema_bundles_revision_kind_idx").on( | ||
| 210 | table.recipeRevisionId, | ||
| 211 | table.bundleKind, | ||
| 212 | ), | ||
| 213 | index("source_recipe_schema_bundles_revision_created_idx").on( | ||
| 214 | table.recipeRevisionId, | ||
| 215 | table.createdAt, | ||
| 216 | table.id, | ||
| 217 | ), | ||
| 218 | ], | ||
| 219 | ); | ||
| 220 | |||
| 221 | export const sourceRecipeOperationsTable = pgTable( | ||
| 222 | tableNames.sourceRecipeOperations, | ||
| 223 | { | ||
| 224 | id: text("id").notNull().primaryKey(), | ||
| 225 | recipeRevisionId: text("recipe_revision_id").notNull(), | ||
| 226 | operationKey: text("operation_key").notNull(), | ||
| 227 | transportKind: text("transport_kind").notNull(), | ||
| 228 | toolId: text("tool_id").notNull(), | ||
| 229 | title: text("title"), | ||
| 230 | description: text("description"), | ||
| 231 | operationKind: text("operation_kind").notNull(), | ||
| 232 | searchText: text("search_text").notNull(), | ||
| 233 | inputSchemaJson: text("input_schema_json"), | ||
| 234 | outputSchemaJson: text("output_schema_json"), | ||
| 235 | providerKind: text("provider_kind").notNull(), | ||
| 236 | providerDataJson: text("provider_data_json"), | ||
| 237 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 238 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 239 | }, | ||
| 240 | (table) => [ | ||
| 241 | uniqueIndex("source_recipe_operations_revision_operation_key_idx").on( | ||
| 242 | table.recipeRevisionId, | ||
| 243 | table.operationKey, | ||
| 244 | ), | ||
| 245 | index("source_recipe_operations_revision_tool_idx").on( | ||
| 246 | table.recipeRevisionId, | ||
| 247 | table.toolId, | ||
| 248 | table.updatedAt, | ||
| 249 | table.id, | ||
| 250 | ), | ||
| 251 | index("source_recipe_operations_search_text_idx").using( | ||
| 252 | "gin", | ||
| 253 | sql`to_tsvector('simple', ${table.searchText})`, | ||
| 254 | ), | ||
| 255 | check( | ||
| 256 | "source_recipe_operations_transport_kind_check", | ||
| 257 | sql`${table.transportKind} in ('http', 'graphql', 'mcp', 'internal')`, | ||
| 258 | ), | ||
| 259 | check( | ||
| 260 | "source_recipe_operations_kind_check", | ||
| 261 | sql`${table.operationKind} in ('read', 'write', 'delete', 'unknown')`, | ||
| 262 | ), | ||
| 263 | ], | ||
| 264 | ); | ||
| 265 | |||
| 266 | export const codeMigrationsTable = pgTable( | ||
| 267 | tableNames.codeMigrations, | ||
| 268 | { | ||
| 269 | id: text("id").notNull().primaryKey(), | ||
| 270 | appliedAt: bigint("applied_at", { mode: "number" }).notNull(), | ||
| 271 | }, | ||
| 272 | ); | ||
| 273 | |||
| 274 | export const authArtifactsTable = pgTable( | ||
| 275 | tableNames.authArtifacts, | ||
| 276 | { | ||
| 277 | id: text("id").notNull().primaryKey(), | ||
| 278 | workspaceId: text("workspace_id").notNull(), | ||
| 279 | sourceId: text("source_id").notNull(), | ||
| 280 | actorAccountId: text("actor_account_id"), | ||
| 281 | slot: text("slot").notNull(), | ||
| 282 | artifactKind: text("artifact_kind").notNull(), | ||
| 283 | configJson: text("config_json").notNull(), | ||
| 284 | grantSetJson: text("grant_set_json"), | ||
| 285 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 286 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 287 | }, | ||
| 288 | (table) => [ | ||
| 289 | index("auth_artifacts_workspace_idx").on( | ||
| 290 | table.workspaceId, | ||
| 291 | table.updatedAt, | ||
| 292 | table.id, | ||
| 293 | ), | ||
| 294 | uniqueIndex("auth_artifacts_workspace_source_actor_idx").on( | ||
| 295 | table.workspaceId, | ||
| 296 | table.sourceId, | ||
| 297 | table.actorAccountId, | ||
| 298 | table.slot, | ||
| 299 | ), | ||
| 300 | index("auth_artifacts_workspace_source_idx").on( | ||
| 301 | table.workspaceId, | ||
| 302 | table.sourceId, | ||
| 303 | table.updatedAt, | ||
| 304 | table.id, | ||
| 305 | ), | ||
| 306 | check( | ||
| 307 | "auth_artifacts_slot_check", | ||
| 308 | sql`${table.slot} in ('runtime', 'import')`, | ||
| 309 | ), | ||
| 310 | ], | ||
| 311 | ); | ||
| 312 | |||
| 313 | export const authLeasesTable = pgTable( | ||
| 314 | tableNames.authLeases, | ||
| 315 | { | ||
| 316 | id: text("id").notNull().primaryKey(), | ||
| 317 | authArtifactId: text("auth_artifact_id").notNull(), | ||
| 318 | workspaceId: text("workspace_id").notNull(), | ||
| 319 | sourceId: text("source_id").notNull(), | ||
| 320 | actorAccountId: text("actor_account_id"), | ||
| 321 | slot: text("slot").notNull(), | ||
| 322 | placementsTemplateJson: text("placements_template_json").notNull(), | ||
| 323 | expiresAt: bigint("expires_at", { mode: "number" }), | ||
| 324 | refreshAfter: bigint("refresh_after", { mode: "number" }), | ||
| 325 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 326 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 327 | }, | ||
| 328 | (table) => [ | ||
| 329 | uniqueIndex("auth_leases_auth_artifact_idx").on(table.authArtifactId), | ||
| 330 | index("auth_leases_workspace_source_idx").on( | ||
| 331 | table.workspaceId, | ||
| 332 | table.sourceId, | ||
| 333 | table.actorAccountId, | ||
| 334 | table.slot, | ||
| 335 | table.updatedAt, | ||
| 336 | table.id, | ||
| 337 | ), | ||
| 338 | check( | ||
| 339 | "auth_leases_slot_check", | ||
| 340 | sql`${table.slot} in ('runtime', 'import')`, | ||
| 341 | ), | ||
| 342 | ], | ||
| 343 | ); | ||
| 344 | |||
| 345 | export const workspaceSourceOauthClientsTable = pgTable( | ||
| 346 | tableNames.workspaceSourceOauthClients, | ||
| 347 | { | ||
| 348 | id: text("id").notNull().primaryKey(), | ||
| 349 | workspaceId: text("workspace_id").notNull(), | ||
| 350 | sourceId: text("source_id").notNull(), | ||
| 351 | providerKey: text("provider_key").notNull(), | ||
| 352 | clientId: text("client_id").notNull(), | ||
| 353 | clientSecretProviderId: text("client_secret_provider_id"), | ||
| 354 | clientSecretHandle: text("client_secret_handle"), | ||
| 355 | clientMetadataJson: text("client_metadata_json"), | ||
| 356 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 357 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 358 | }, | ||
| 359 | (table) => [ | ||
| 360 | uniqueIndex("workspace_source_oauth_clients_workspace_source_provider_idx").on( | ||
| 361 | table.workspaceId, | ||
| 362 | table.sourceId, | ||
| 363 | table.providerKey, | ||
| 364 | ), | ||
| 365 | index("workspace_source_oauth_clients_workspace_idx").on( | ||
| 366 | table.workspaceId, | ||
| 367 | table.updatedAt, | ||
| 368 | table.sourceId, | ||
| 369 | ), | ||
| 370 | ], | ||
| 371 | ); | ||
| 372 | |||
| 373 | export const secretMaterialsTable = pgTable( | ||
| 374 | tableNames.secretMaterials, | ||
| 375 | { | ||
| 376 | id: text("id").notNull().primaryKey(), | ||
| 377 | name: text("name"), | ||
| 378 | purpose: text("purpose").notNull(), | ||
| 379 | value: text("value").notNull(), | ||
| 380 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 381 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 382 | }, | ||
| 383 | (table) => [ | ||
| 384 | index("secret_materials_updated_idx").on(table.updatedAt, table.id), | ||
| 385 | check( | ||
| 386 | "secret_materials_purpose_check", | ||
| 387 | sql`${table.purpose} in ('auth_material', 'oauth_access_token', 'oauth_refresh_token', 'oauth_client_info')`, | ||
| 388 | ), | ||
| 389 | ], | ||
| 390 | ); | ||
| 391 | |||
| 392 | export const sourceAuthSessionsTable = pgTable( | ||
| 393 | tableNames.sourceAuthSessions, | ||
| 394 | { | ||
| 395 | id: text("id").notNull().primaryKey(), | ||
| 396 | workspaceId: text("workspace_id").notNull(), | ||
| 397 | sourceId: text("source_id").notNull(), | ||
| 398 | actorAccountId: text("actor_account_id"), | ||
| 399 | credentialSlot: text("credential_slot").notNull(), | ||
| 400 | executionId: text("execution_id"), | ||
| 401 | interactionId: text("interaction_id"), | ||
| 402 | providerKind: text("provider_kind").notNull(), | ||
| 403 | status: text("status").notNull(), | ||
| 404 | state: text("state").notNull(), | ||
| 405 | sessionDataJson: text("session_data_json").notNull(), | ||
| 406 | errorText: text("error_text"), | ||
| 407 | completedAt: bigint("completed_at", { mode: "number" }), | ||
| 408 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 409 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 410 | }, | ||
| 411 | (table) => [ | ||
| 412 | index("source_auth_sessions_workspace_idx").on( | ||
| 413 | table.workspaceId, | ||
| 414 | table.updatedAt, | ||
| 415 | table.id, | ||
| 416 | ), | ||
| 417 | index("source_auth_sessions_pending_idx").on( | ||
| 418 | table.workspaceId, | ||
| 419 | table.sourceId, | ||
| 420 | table.actorAccountId, | ||
| 421 | table.credentialSlot, | ||
| 422 | table.status, | ||
| 423 | table.updatedAt, | ||
| 424 | table.id, | ||
| 425 | ), | ||
| 426 | uniqueIndex("source_auth_sessions_state_idx").on(table.state), | ||
| 427 | check( | ||
| 428 | "source_auth_sessions_provider_kind_check", | ||
| 429 | sql`${table.providerKind} in ('mcp_oauth', 'oauth2_pkce')`, | ||
| 430 | ), | ||
| 431 | check( | ||
| 432 | "source_auth_sessions_status_check", | ||
| 433 | sql`${table.status} in ('pending', 'completed', 'failed', 'cancelled')`, | ||
| 434 | ), | ||
| 435 | check( | ||
| 436 | "source_auth_sessions_credential_slot_check", | ||
| 437 | sql`${table.credentialSlot} in ('runtime', 'import')`, | ||
| 438 | ), | ||
| 439 | ], | ||
| 440 | ); | ||
| 441 | |||
| 442 | export const executionsTable = pgTable( | ||
| 443 | tableNames.executions, | ||
| 444 | { | ||
| 445 | id: text("id").notNull().primaryKey(), | ||
| 446 | workspaceId: text("workspace_id").notNull(), | ||
| 447 | createdByAccountId: text("created_by_account_id").notNull(), | ||
| 448 | status: text("status").notNull(), | ||
| 449 | code: text("code").notNull(), | ||
| 450 | resultJson: text("result_json"), | ||
| 451 | errorText: text("error_text"), | ||
| 452 | logsJson: text("logs_json"), | ||
| 453 | startedAt: bigint("started_at", { mode: "number" }), | ||
| 454 | completedAt: bigint("completed_at", { mode: "number" }), | ||
| 455 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 456 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 457 | }, | ||
| 458 | (table) => [ | ||
| 459 | index("executions_workspace_idx").on(table.workspaceId, table.updatedAt, table.id), | ||
| 460 | check( | ||
| 461 | "executions_status_check", | ||
| 462 | sql`${table.status} in ('pending', 'running', 'waiting_for_interaction', 'completed', 'failed', 'cancelled')`, | ||
| 463 | ), | ||
| 464 | ], | ||
| 465 | ); | ||
| 466 | |||
| 467 | export const executionInteractionsTable = pgTable( | ||
| 468 | tableNames.executionInteractions, | ||
| 469 | { | ||
| 470 | id: text("id").notNull().primaryKey(), | ||
| 471 | executionId: text("execution_id").notNull(), | ||
| 472 | status: text("status").notNull(), | ||
| 473 | kind: text("kind").notNull(), | ||
| 474 | purpose: text("purpose").notNull(), | ||
| 475 | payloadJson: text("payload_json").notNull(), | ||
| 476 | responseJson: text("response_json"), | ||
| 477 | responsePrivateJson: text("response_private_json"), | ||
| 478 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 479 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 480 | }, | ||
| 481 | (table) => [ | ||
| 482 | index("execution_interactions_execution_idx").on( | ||
| 483 | table.executionId, | ||
| 484 | table.updatedAt, | ||
| 485 | table.id, | ||
| 486 | ), | ||
| 487 | check( | ||
| 488 | "execution_interactions_status_check", | ||
| 489 | sql`${table.status} in ('pending', 'resolved', 'cancelled')`, | ||
| 490 | ), | ||
| 491 | ], | ||
| 492 | ); | ||
| 493 | |||
| 494 | export const executionStepsTable = pgTable( | ||
| 495 | tableNames.executionSteps, | ||
| 496 | { | ||
| 497 | id: text("id").notNull().primaryKey(), | ||
| 498 | executionId: text("execution_id").notNull(), | ||
| 499 | sequence: bigint("sequence", { mode: "number" }).notNull(), | ||
| 500 | kind: text("kind").notNull(), | ||
| 501 | status: text("status").notNull(), | ||
| 502 | path: text("path").notNull(), | ||
| 503 | argsJson: text("args_json").notNull(), | ||
| 504 | resultJson: text("result_json"), | ||
| 505 | errorText: text("error_text"), | ||
| 506 | interactionId: text("interaction_id"), | ||
| 507 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | ||
| 508 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | ||
| 509 | }, | ||
| 510 | (table) => [ | ||
| 511 | uniqueIndex("execution_steps_execution_sequence_idx").on( | ||
| 512 | table.executionId, | ||
| 513 | table.sequence, | ||
| 514 | ), | ||
| 515 | index("execution_steps_execution_updated_idx").on( | ||
| 516 | table.executionId, | ||
| 517 | table.updatedAt, | ||
| 518 | table.id, | ||
| 519 | ), | ||
| 520 | check( | ||
| 521 | "execution_steps_kind_check", | ||
| 522 | sql`${table.kind} in ('tool_call')`, | ||
| 523 | ), | ||
| 524 | check( | ||
| 525 | "execution_steps_status_check", | ||
| 526 | sql`${table.status} in ('pending', 'waiting', 'completed', 'failed')`, | ||
| 527 | ), | ||
| 528 | ], | ||
| 529 | ); | ||
| 530 | |||
| 531 | export const drizzleSchema = { | ||
| 532 | accountsTable, | ||
| 533 | sourcesTable, | ||
| 534 | sourceRecipesTable, | ||
| 535 | sourceRecipeRevisionsTable, | ||
| 536 | sourceRecipeDocumentsTable, | ||
| 537 | sourceRecipeSchemaBundlesTable, | ||
| 538 | sourceRecipeOperationsTable, | ||
| 539 | codeMigrationsTable, | ||
| 540 | authArtifactsTable, | ||
| 541 | authLeasesTable, | ||
| 542 | workspaceSourceOauthClientsTable, | ||
| 543 | secretMaterialsTable, | ||
| 544 | sourceAuthSessionsTable, | ||
| 545 | executionsTable, | ||
| 546 | executionInteractionsTable, | ||
| 547 | executionStepsTable, | ||
| 548 | }; | ||
| 549 | |||
| 550 | export type DrizzleTables = typeof drizzleSchema; | ||
| @@ -1,206 +0,0 @@ | |||
| 1 | import { PGlite } from "@electric-sql/pglite"; | ||
| 2 | import { drizzle as drizzlePGlite } from "drizzle-orm/pglite"; | ||
| 3 | import { migrate as migratePGlite } from "drizzle-orm/pglite/migrator"; | ||
| 4 | import { drizzle as drizzlePostgres } from "drizzle-orm/postgres-js"; | ||
| 5 | import { migrate as migratePostgres } from "drizzle-orm/postgres-js/migrator"; | ||
| 6 | import { existsSync, readdirSync, readFileSync } from "node:fs"; | ||
| 7 | import { mkdir, unlink } from "node:fs/promises"; | ||
| 8 | import * as path from "node:path"; | ||
| 9 | import { fileURLToPath } from "node:url"; | ||
| 10 | import postgres from "postgres"; | ||
| 11 | |||
| 12 | import { drizzleSchema } from "./schema"; | ||
| 13 | |||
| 14 | export type SqlBackend = "pglite" | "postgres"; | ||
| 15 | |||
| 16 | export type CreateSqlRuntimeOptions = { | ||
| 17 | databaseUrl?: string; | ||
| 18 | localDataDir?: string; | ||
| 19 | postgresApplicationName?: string; | ||
| 20 | migrationsFolder?: string; | ||
| 21 | }; | ||
| 22 | |||
| 23 | const createPGliteDb = (client: PGlite) => | ||
| 24 | drizzlePGlite({ client, schema: drizzleSchema }); | ||
| 25 | |||
| 26 | const createPostgresDb = (client: postgres.Sql) => | ||
| 27 | drizzlePostgres({ client, schema: drizzleSchema }); | ||
| 28 | |||
| 29 | export type PGliteDb = ReturnType<typeof createPGliteDb>; | ||
| 30 | export type PostgresDb = ReturnType<typeof createPostgresDb>; | ||
| 31 | export type DrizzleDb = PGliteDb | PostgresDb; | ||
| 32 | |||
| 33 | export type SqlRuntime = { | ||
| 34 | backend: SqlBackend; | ||
| 35 | db: DrizzleDb; | ||
| 36 | close: () => Promise<void>; | ||
| 37 | }; | ||
| 38 | |||
| 39 | const trim = (value: string | undefined): string | undefined => { | ||
| 40 | const candidate = value?.trim(); | ||
| 41 | return candidate && candidate.length > 0 ? candidate : undefined; | ||
| 42 | }; | ||
| 43 | |||
| 44 | const isPostgresUrl = (value: string): boolean => | ||
| 45 | value.startsWith("postgres://") || value.startsWith("postgresql://"); | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Remove a stale PGlite `postmaster.pid` lock file if present. | ||
| 49 | * | ||
| 50 | * PGlite writes this file on open and removes it on close. When a process | ||
| 51 | * exits without calling `PGlite.close()` (e.g. the Vite dev server is | ||
| 52 | * killed with SIGKILL, or the terminal is closed), the lock file is left | ||
| 53 | * behind and prevents subsequent PGlite instances from opening the | ||
| 54 | * database. PGlite uses a synthetic PID (e.g. `-42`) that is never a real | ||
| 55 | * OS process, so we cannot use it to detect a live owner. Instead we rely | ||
| 56 | * on the fact that in the local single-user context only one process | ||
| 57 | * should ever own this database at a time. | ||
| 58 | */ | ||
| 59 | const cleanupStalePGliteLock = async (dataDir: string): Promise<void> => { | ||
| 60 | const lockPath = path.join(dataDir, "postmaster.pid"); | ||
| 61 | if (!existsSync(lockPath)) { | ||
| 62 | return; | ||
| 63 | } | ||
| 64 | |||
| 65 | // PGlite's synthetic PID is always negative (e.g. -42). A real Postgres | ||
| 66 | // postmaster would write a positive PID. If we ever see a positive PID | ||
| 67 | // we leave the file alone. | ||
| 68 | try { | ||
| 69 | const content = readFileSync(lockPath, "utf-8"); | ||
| 70 | const firstLine = content.split("\n")[0]?.trim(); | ||
| 71 | const pid = Number(firstLine); | ||
| 72 | if (!Number.isNaN(pid) && pid > 0) { | ||
| 73 | // Looks like a real Postgres PID — don't touch it. | ||
| 74 | return; | ||
| 75 | } | ||
| 76 | } catch { | ||
| 77 | // If we can't read the file, try to remove it anyway. | ||
| 78 | } | ||
| 79 | |||
| 80 | try { | ||
| 81 | await unlink(lockPath); | ||
| 82 | } catch { | ||
| 83 | // Best-effort cleanup; PGlite will report the real error if this fails. | ||
| 84 | } | ||
| 85 | }; | ||
| 86 | |||
| 87 | const createPGliteRuntime = async (localDataDir: string): Promise<SqlRuntime> => { | ||
| 88 | const normalized = trim(localDataDir) ?? ".executor/control-plane-pgdata"; | ||
| 89 | |||
| 90 | let client: PGlite; | ||
| 91 | if (normalized === ":memory:") { | ||
| 92 | client = new PGlite(); | ||
| 93 | } else { | ||
| 94 | const resolvedDataDir = path.resolve(normalized); | ||
| 95 | if (!existsSync(resolvedDataDir)) { | ||
| 96 | await mkdir(resolvedDataDir, { recursive: true }); | ||
| 97 | } | ||
| 98 | await cleanupStalePGliteLock(resolvedDataDir); | ||
| 99 | client = new PGlite(resolvedDataDir); | ||
| 100 | } | ||
| 101 | |||
| 102 | const db = createPGliteDb(client); | ||
| 103 | |||
| 104 | return { | ||
| 105 | backend: "pglite", | ||
| 106 | db, | ||
| 107 | close: async () => { | ||
| 108 | await client.close(); | ||
| 109 | }, | ||
| 110 | }; | ||
| 111 | }; | ||
| 112 | |||
| 113 | const createPostgresRuntime = async ( | ||
| 114 | databaseUrl: string, | ||
| 115 | applicationName: string | undefined, | ||
| 116 | ): Promise<SqlRuntime> => { | ||
| 117 | const client = postgres(databaseUrl, { | ||
| 118 | prepare: false, | ||
| 119 | max: 10, | ||
| 120 | ...(applicationName | ||
| 121 | ? { connection: { application_name: applicationName } } | ||
| 122 | : {}), | ||
| 123 | }); | ||
| 124 | const db = createPostgresDb(client); | ||
| 125 | |||
| 126 | return { | ||
| 127 | backend: "postgres", | ||
| 128 | db, | ||
| 129 | close: async () => { | ||
| 130 | await client.end({ timeout: 5 }); | ||
| 131 | }, | ||
| 132 | }; | ||
| 133 | }; | ||
| 134 | |||
| 135 | const resolveMigrationsFolder = (explicit: string | undefined): string => { | ||
| 136 | const explicitTrimmed = trim(explicit); | ||
| 137 | if (explicitTrimmed) { | ||
| 138 | return path.resolve(explicitTrimmed); | ||
| 139 | } | ||
| 140 | |||
| 141 | const moduleDir = path.dirname(fileURLToPath(import.meta.url)); | ||
| 142 | const packageRoot = path.resolve(moduleDir, "../.."); | ||
| 143 | const cwd = process.cwd(); | ||
| 144 | const candidates = [ | ||
| 145 | path.resolve(packageRoot, "drizzle"), | ||
| 146 | path.resolve(cwd, "packages/control-plane/drizzle"), | ||
| 147 | path.resolve(cwd, "drizzle"), | ||
| 148 | ]; | ||
| 149 | |||
| 150 | const hasModernMigrations = (candidate: string): boolean => { | ||
| 151 | if (!existsSync(candidate)) { | ||
| 152 | return false; | ||
| 153 | } | ||
| 154 | |||
| 155 | const entries = readdirSync(candidate, { withFileTypes: true }); | ||
| 156 | return entries.some( | ||
| 157 | (entry) => | ||
| 158 | entry.isDirectory() | ||
| 159 | && existsSync(path.join(candidate, entry.name, "migration.sql")), | ||
| 160 | ); | ||
| 161 | }; | ||
| 162 | |||
| 163 | for (const candidate of candidates) { | ||
| 164 | if ( | ||
| 165 | existsSync(path.join(candidate, "meta", "_journal.json")) | ||
| 166 | || hasModernMigrations(candidate) | ||
| 167 | ) { | ||
| 168 | return candidate; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | throw new Error( | ||
| 173 | "Unable to resolve Drizzle migrations folder for control-plane persistence", | ||
| 174 | ); | ||
| 175 | }; | ||
| 176 | |||
| 177 | export const runMigrations = async ( | ||
| 178 | runtime: SqlRuntime, | ||
| 179 | options?: { migrationsFolder?: string }, | ||
| 180 | ): Promise<void> => { | ||
| 181 | const migrationsFolder = resolveMigrationsFolder(options?.migrationsFolder); | ||
| 182 | |||
| 183 | if (runtime.backend === "pglite") { | ||
| 184 | await migratePGlite(runtime.db as PGliteDb, { migrationsFolder }); | ||
| 185 | return; | ||
| 186 | } | ||
| 187 | |||
| 188 | await migratePostgres(runtime.db as PostgresDb, { migrationsFolder }); | ||
| 189 | }; | ||
| 190 | |||
| 191 | export const createSqlRuntime = async ( | ||
| 192 | options: CreateSqlRuntimeOptions, | ||
| 193 | ): Promise<SqlRuntime> => { | ||
| 194 | const databaseUrl = trim(options.databaseUrl); | ||
| 195 | const runtime = | ||
| 196 | databaseUrl && isPostgresUrl(databaseUrl) | ||
| 197 | ? await createPostgresRuntime( | ||
| 198 | databaseUrl, | ||
| 199 | trim(options.postgresApplicationName), | ||
| 200 | ) | ||
| 201 | : await createPGliteRuntime( | ||
| 202 | options.localDataDir ?? ".executor/control-plane-pgdata", | ||
| 203 | ); | ||
| 204 | |||
| 205 | return runtime; | ||
| 206 | }; | ||
| @@ -1,4 +1,3 @@ | |||
| 1 | import type { SqlControlPlaneRows } from "#persistence"; | ||
| 2 | import type { | 1 | import type { |
| 3 | AuthArtifact, | 2 | AuthArtifact, |
| 4 | AuthLease, | 3 | AuthLease, |
| @@ -14,6 +13,7 @@ import { · 9 unmodified lines | |||
| 14 | import * as Effect from "effect/Effect"; | 13 | import * as Effect from "effect/Effect"; |
| 15 | import * as Option from "effect/Option"; | 14 | import * as Option from "effect/Option"; |
| 16 | 15 | ||
| 16 | import type { ControlPlaneStoreShape } from "./store"; | ||
| 17 | import { resolveAuthArtifactMaterial, type ResolvedSourceAuthMaterial } from "./auth-artifacts"; | 17 | import { resolveAuthArtifactMaterial, type ResolvedSourceAuthMaterial } from "./auth-artifacts"; |
| 18 | import { | 18 | import { |
| 19 | createDefaultSecretMaterialDeleter, | 19 | createDefaultSecretMaterialDeleter, |
| @@ -33,7 +33,7 @@ const secretRefKey = (ref: SecretRef): string => · 13 unmodified lines | |||
| 33 | `${ref.providerId}:${ref.handle}`; | 33 | `${ref.providerId}:${ref.handle}`; |
| 34 | 34 | ||
| 35 | const cleanupAuthLeaseSecretRefs = ( | 35 | const cleanupAuthLeaseSecretRefs = ( |
| 36 | rows: SqlControlPlaneRows, | 36 | rows: ControlPlaneStoreShape, |
| 37 | input: { | 37 | input: { |
| 38 | previous: Pick<AuthLease, "placementsTemplateJson"> | null; | 38 | previous: Pick<AuthLease, "placementsTemplateJson"> | null; |
| 39 | next: Pick<AuthLease, "placementsTemplateJson"> | null; | 39 | next: Pick<AuthLease, "placementsTemplateJson"> | null; |
| @@ -76,7 +76,7 @@ const leaseIsFresh = (lease: AuthLease | null, now: number): boolean => { · 36 unmodified lines | |||
| 76 | }; | 76 | }; |
| 77 | 77 | ||
| 78 | const refreshRefreshableOauth2AuthorizedUserArtifact = (input: { | 78 | const refreshRefreshableOauth2AuthorizedUserArtifact = (input: { |
| 79 | rows: SqlControlPlaneRows; | 79 | rows: ControlPlaneStoreShape; |
| 80 | artifact: AuthArtifact; | 80 | artifact: AuthArtifact; |
| 81 | lease: AuthLease | null; | 81 | lease: AuthLease | null; |
| 82 | resolveSecretMaterial: ResolveSecretMaterial; | 82 | resolveSecretMaterial: ResolveSecretMaterial; |
| @@ -168,7 +168,7 @@ const refreshRefreshableOauth2AuthorizedUserArtifact = (input: { · 85 unmodified lines | |||
| 168 | return nextLease; | 168 | return nextLease; |
| 169 | }); | 169 | }); |
| 170 | 170 | ||
| 171 | export const removeAuthLeaseAndSecrets = (rows: SqlControlPlaneRows, input: { | 171 | export const removeAuthLeaseAndSecrets = (rows: ControlPlaneStoreShape, input: { |
| 172 | authArtifactId: AuthArtifact["id"]; | 172 | authArtifactId: AuthArtifact["id"]; |
| 173 | }): Effect.Effect<void, Error, never> => | 173 | }): Effect.Effect<void, Error, never> => |
| 174 | Effect.gen(function* () { | 174 | Effect.gen(function* () { |
| @@ -185,7 +185,7 @@ export const removeAuthLeaseAndSecrets = (rows: SqlControlPlaneRows, input: { · 10 unmodified lines | |||
| 185 | }); | 185 | }); |
| 186 | 186 | ||
| 187 | export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: { | 187 | export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: { |
| 188 | rows: SqlControlPlaneRows; | 188 | rows: ControlPlaneStoreShape; |
| 189 | artifact: AuthArtifact; | 189 | artifact: AuthArtifact; |
| 190 | tokenResponse: OAuth2TokenResponse; | 190 | tokenResponse: OAuth2TokenResponse; |
| 191 | }): Effect.Effect<void, Error, never> => | 191 | }): Effect.Effect<void, Error, never> => |
| @@ -256,7 +256,7 @@ export const upsertOauth2AuthorizedUserLeaseFromTokenResponse = (input: { · 64 unmodified lines | |||
| 256 | }); | 256 | }); |
| 257 | 257 | ||
| 258 | export const resolveAuthArtifactMaterialWithLeases = (input: { | 258 | export const resolveAuthArtifactMaterialWithLeases = (input: { |
| 259 | rows: SqlControlPlaneRows; | 259 | rows: ControlPlaneStoreShape; |
| 260 | artifact: AuthArtifact | null; | 260 | artifact: AuthArtifact | null; |
| 261 | resolveSecretMaterial: ResolveSecretMaterial; | 261 | resolveSecretMaterial: ResolveSecretMaterial; |
| 262 | context?: SecretMaterialResolveContext; | 262 | context?: SecretMaterialResolveContext; |
| @@ -319,7 +319,7 @@ describe("control-plane-runtime", () => { · 318 unmodified lines | |||
| 319 | decodedCredentialSelection.authKind === "bearer" | 319 | decodedCredentialSelection.authKind === "bearer" |
| 320 | ? decodedCredentialSelection.tokenRef.providerId | 320 | ? decodedCredentialSelection.tokenRef.providerId |
| 321 | : null, | 321 | : null, |
| 322 | ).toBe("postgres"); | 322 | ).toBe("local"); |
| 323 | 323 | ||
| 324 | const tokenSecretMaterialId = SecretMaterialIdSchema.make( | 324 | const tokenSecretMaterialId = SecretMaterialIdSchema.make( |
| 325 | decodedCredentialSelection.authKind === "bearer" | 325 | decodedCredentialSelection.authKind === "bearer" |
| @@ -1,271 +0,0 @@ | |||
| 1 | import { describe, expect, it } from "@effect/vitest"; | ||
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | import * as Schema from "effect/Schema"; | ||
| 4 | |||
| 5 | import { makeToolInvokerFromTools } from "@executor/codemode-core"; | ||
| 6 | import { makeDenoSubprocessExecutor } from "@executor/runtime-deno-subprocess"; | ||
| 7 | |||
| 8 | import { | ||
| 9 | createSqlControlPlaneRuntime, | ||
| 10 | type ResolveExecutionEnvironment, | ||
| 11 | } from "./index"; | ||
| 12 | import { withControlPlaneClient } from "./test-http-client"; | ||
| 13 | |||
| 14 | const gatedEchoElicitation = { | ||
| 15 | message: "Approve gated echo?", | ||
| 16 | requestedSchema: { | ||
| 17 | type: "object", | ||
| 18 | properties: { | ||
| 19 | approve: { | ||
| 20 | type: "boolean", | ||
| 21 | title: "Approve", | ||
| 22 | }, | ||
| 23 | }, | ||
| 24 | required: ["approve"], | ||
| 25 | }, | ||
| 26 | } as const; | ||
| 27 | |||
| 28 | const makeReplayResolver = (countedCalls: { value: number }): ResolveExecutionEnvironment => | ||
| 29 | ({ onElicitation }) => | ||
| 30 | Effect.succeed({ | ||
| 31 | executor: makeDenoSubprocessExecutor(), | ||
| 32 | toolInvoker: makeToolInvokerFromTools({ | ||
| 33 | onElicitation, | ||
| 34 | tools: { | ||
| 35 | "counted.add": { | ||
| 36 | description: "Adds numbers and counts calls", | ||
| 37 | inputSchema: Schema.standardSchemaV1( | ||
| 38 | Schema.Struct({ | ||
| 39 | a: Schema.optional(Schema.Number), | ||
| 40 | b: Schema.optional(Schema.Number), | ||
| 41 | }), | ||
| 42 | ), | ||
| 43 | execute: ({ a, b }: { a?: number; b?: number }) => { | ||
| 44 | countedCalls.value += 1; | ||
| 45 | return { sum: (a ?? 0) + (b ?? 0) }; | ||
| 46 | }, | ||
| 47 | }, | ||
| 48 | "gated.echo": { | ||
| 49 | tool: { | ||
| 50 | description: "Echoes only after approval", | ||
| 51 | inputSchema: Schema.standardSchemaV1( | ||
| 52 | Schema.Struct({ | ||
| 53 | value: Schema.String, | ||
| 54 | approve: Schema.optional(Schema.Boolean), | ||
| 55 | }), | ||
| 56 | ), | ||
| 57 | execute: ({ value, approve }: { value: string; approve?: boolean }) => ({ | ||
| 58 | text: approve === true ? `approved:${value}` : `denied:${value}`, | ||
| 59 | }), | ||
| 60 | }, | ||
| 61 | metadata: { | ||
| 62 | interaction: "required", | ||
| 63 | elicitation: gatedEchoElicitation, | ||
| 64 | }, | ||
| 65 | }, | ||
| 66 | }, | ||
| 67 | }), | ||
| 68 | }); | ||
| 69 | |||
| 70 | const createRuntime = (countedCalls: { value: number }) => | ||
| 71 | Effect.acquireRelease( | ||
| 72 | createSqlControlPlaneRuntime({ | ||
| 73 | localDataDir: ":memory:", | ||
| 74 | executionResolver: makeReplayResolver(countedCalls), | ||
| 75 | }), | ||
| 76 | (runtime) => Effect.promise(() => runtime.close()).pipe(Effect.orDie), | ||
| 77 | ); | ||
| 78 | |||
| 79 | describe("execution replay", () => { | ||
| 80 | it.scoped("replays completed proxy calls instead of re-executing them", () => | ||
| 81 | Effect.gen(function* () { | ||
| 82 | const countedCalls = { value: 0 }; | ||
| 83 | const runtime = yield* createRuntime(countedCalls); | ||
| 84 | const installation = runtime.localInstallation; | ||
| 85 | |||
| 86 | const created = yield* withControlPlaneClient( | ||
| 87 | { | ||
| 88 | runtime, | ||
| 89 | accountId: installation.accountId, | ||
| 90 | }, | ||
| 91 | (client) => | ||
| 92 | client.executions.create({ | ||
| 93 | path: { | ||
| 94 | workspaceId: installation.workspaceId, | ||
| 95 | }, | ||
| 96 | payload: { | ||
| 97 | code: [ | ||
| 98 | "const sum = await tools.counted.add({ a: 20, b: 22 });", | ||
| 99 | 'const echo = await tools.gated.echo({ value: "from-replay" });', | ||
| 100 | "return { sum, echo };", | ||
| 101 | ].join("\n"), | ||
| 102 | }, | ||
| 103 | }), | ||
| 104 | ); | ||
| 105 | |||
| 106 | expect(created.execution.status).toBe("waiting_for_interaction"); | ||
| 107 | expect(countedCalls.value).toBe(1); | ||
| 108 | |||
| 109 | const resumed = yield* withControlPlaneClient( | ||
| 110 | { | ||
| 111 | runtime, | ||
| 112 | accountId: installation.accountId, | ||
| 113 | }, | ||
| 114 | (client) => | ||
| 115 | client.executions.resume({ | ||
| 116 | path: { | ||
| 117 | workspaceId: installation.workspaceId, | ||
| 118 | executionId: created.execution.id, | ||
| 119 | }, | ||
| 120 | payload: { | ||
| 121 | responseJson: JSON.stringify({ | ||
| 122 | action: "accept", | ||
| 123 | content: { | ||
| 124 | approve: true, | ||
| 125 | }, | ||
| 126 | }), | ||
| 127 | }, | ||
| 128 | }), | ||
| 129 | ); | ||
| 130 | |||
| 131 | expect(resumed.execution.status).toBe("completed"); | ||
| 132 | expect(countedCalls.value).toBe(1); | ||
| 133 | expect(resumed.execution.resultJson).toBe( | ||
| 134 | JSON.stringify({ | ||
| 135 | sum: { sum: 42 }, | ||
| 136 | echo: { text: "approved:from-replay" }, | ||
| 137 | }), | ||
| 138 | ); | ||
| 139 | |||
| 140 | const completedSteps = yield* runtime.persistence.rows.executionSteps.listByExecutionId( | ||
| 141 | created.execution.id, | ||
| 142 | ); | ||
| 143 | expect(completedSteps).toHaveLength(0); | ||
| 144 | }), | ||
| 145 | ); | ||
| 146 | |||
| 147 | it.scoped("keeps live form interactions in the same run when requested", () => | ||
| 148 | Effect.gen(function* () { | ||
| 149 | const countedCalls = { value: 0 }; | ||
| 150 | const runtime = yield* createRuntime(countedCalls); | ||
| 151 | const installation = runtime.localInstallation; | ||
| 152 | |||
| 153 | const created = yield* withControlPlaneClient( | ||
| 154 | { | ||
| 155 | runtime, | ||
| 156 | accountId: installation.accountId, | ||
| 157 | }, | ||
| 158 | (client) => | ||
| 159 | client.executions.create({ | ||
| 160 | path: { | ||
| 161 | workspaceId: installation.workspaceId, | ||
| 162 | }, | ||
| 163 | payload: { | ||
| 164 | interactionMode: "live_form", | ||
| 165 | code: [ | ||
| 166 | "const nonce = String(Math.random());", | ||
| 167 | "return await tools.gated.echo({ value: nonce });", | ||
| 168 | ].join("\n"), | ||
| 169 | }, | ||
| 170 | }), | ||
| 171 | ); | ||
| 172 | |||
| 173 | expect(created.execution.status).toBe("waiting_for_interaction"); | ||
| 174 | |||
| 175 | const resumed = yield* withControlPlaneClient( | ||
| 176 | { | ||
| 177 | runtime, | ||
| 178 | accountId: installation.accountId, | ||
| 179 | }, | ||
| 180 | (client) => | ||
| 181 | client.executions.resume({ | ||
| 182 | path: { | ||
| 183 | workspaceId: installation.workspaceId, | ||
| 184 | executionId: created.execution.id, | ||
| 185 | }, | ||
| 186 | payload: { | ||
| 187 | interactionMode: "live_form", | ||
| 188 | responseJson: JSON.stringify({ | ||
| 189 | action: "accept", | ||
| 190 | content: { | ||
| 191 | approve: true, | ||
| 192 | }, | ||
| 193 | }), | ||
| 194 | }, | ||
| 195 | }), | ||
| 196 | ); | ||
| 197 | |||
| 198 | expect(resumed.execution.status).toBe("completed"); | ||
| 199 | expect(resumed.execution.resultJson).toContain("approved:"); | ||
| 200 | }), | ||
| 201 | ); | ||
| 202 | |||
| 203 | it.scoped("fails loudly when replayed code reaches a different proxy call", () => | ||
| 204 | Effect.gen(function* () { | ||
| 205 | const countedCalls = { value: 0 }; | ||
| 206 | const runtime = yield* createRuntime(countedCalls); | ||
| 207 | const installation = runtime.localInstallation; | ||
| 208 | |||
| 209 | const created = yield* withControlPlaneClient( | ||
| 210 | { | ||
| 211 | runtime, | ||
| 212 | accountId: installation.accountId, | ||
| 213 | }, | ||
| 214 | (client) => | ||
| 215 | client.executions.create({ | ||
| 216 | path: { | ||
| 217 | workspaceId: installation.workspaceId, | ||
| 218 | }, | ||
| 219 | payload: { | ||
| 220 | code: [ | ||
| 221 | "await tools.counted.add({ a: 1, b: 2 });", | ||
| 222 | 'return await tools.gated.echo({ value: "mismatch" });', | ||
| 223 | ].join("\n"), | ||
| 224 | }, | ||
| 225 | }), | ||
| 226 | ); | ||
| 227 | |||
| 228 | expect(created.execution.status).toBe("waiting_for_interaction"); | ||
| 229 | expect(countedCalls.value).toBe(1); | ||
| 230 | |||
| 231 | yield* runtime.persistence.rows.executions.update(created.execution.id, { | ||
| 232 | code: [ | ||
| 233 | "await tools.counted.add({ a: 1, b: 999 });", | ||
| 234 | 'return await tools.gated.echo({ value: "mismatch" });', | ||
| 235 | ].join("\n"), | ||
| 236 | updatedAt: Date.now(), | ||
| 237 | }); | ||
| 238 | |||
| 239 | const resumed = yield* withControlPlaneClient( | ||
| 240 | { | ||
| 241 | runtime, | ||
| 242 | accountId: installation.accountId, | ||
| 243 | }, | ||
| 244 | (client) => | ||
| 245 | client.executions.resume({ | ||
| 246 | path: { | ||
| 247 | workspaceId: installation.workspaceId, | ||
| 248 | executionId: created.execution.id, | ||
| 249 | }, | ||
| 250 | payload: { | ||
| 251 | responseJson: JSON.stringify({ | ||
| 252 | action: "accept", | ||
| 253 | content: { | ||
| 254 | approve: true, | ||
| 255 | }, | ||
| 256 | }), | ||
| 257 | }, | ||
| 258 | }), | ||
| 259 | ); | ||
| 260 | |||
| 261 | expect(resumed.execution.status).toBe("failed"); | ||
| 262 | expect(resumed.execution.errorText).toContain("Durable execution mismatch"); | ||
| 263 | expect(countedCalls.value).toBe(1); | ||
| 264 | |||
| 265 | const failedSteps = yield* runtime.persistence.rows.executionSteps.listByExecutionId( | ||
| 266 | created.execution.id, | ||
| 267 | ); | ||
| 268 | expect(failedSteps).toHaveLength(0); | ||
| 269 | }), | ||
| 270 | ); | ||
| 271 | }); | ||
| @@ -18,25 +18,33 @@ import { · 17 unmodified lines | |||
| 18 | import * as Effect from "effect/Effect"; | 18 | import * as Effect from "effect/Effect"; |
| 19 | import * as Cause from "effect/Cause"; | 19 | import * as Cause from "effect/Cause"; |
| 20 | import * as Exit from "effect/Exit"; | 20 | import * as Exit from "effect/Exit"; |
| 21 | import * as Layer from "effect/Layer"; | ||
| 21 | import * as Schema from "effect/Schema"; | 22 | import * as Schema from "effect/Schema"; |
| 22 | import type { RuntimeLocalWorkspaceState } from "./local-runtime-context"; | 23 | import type { RuntimeLocalWorkspaceState } from "./local-runtime-context"; |
| 23 | import { RuntimeLocalWorkspaceService } from "./local-runtime-context"; | 24 | import { |
| 25 | InstallationStore, | ||
| 26 | type LocalStorageServices, | ||
| 27 | LocalInstallationStore, | ||
| 28 | LocalSourceArtifactStore, | ||
| 29 | LocalWorkspaceConfigStore, | ||
| 30 | LocalWorkspaceStateStore, | ||
| 31 | SourceArtifactStore, | ||
| 32 | WorkspaceConfigStore, | ||
| 33 | WorkspaceStateStore, | ||
| 34 | } from "./local-storage"; | ||
| 24 | 35 | ||
| 25 | /** Run an Effect as a Promise, preserving the original error (not FiberFailure). */ | 36 | /** Run an Effect as a Promise, preserving the original error (not FiberFailure). */ |
| 26 | const runEffect = async <A>( | 37 | const runEffect = async <A>( |
| 27 | effect: Effect.Effect<A, unknown, never>, | 38 | effect: Effect.Effect<A, unknown, LocalStorageServices>, |
| 28 | runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null = null, | 39 | _runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null = null, |
| 29 | ): Promise<A> => { | 40 | ): Promise<A> => { |
| 30 | const provided = | 41 | const baseLayer = Layer.mergeAll( |
| 31 | runtimeLocalWorkspace === null | 42 | Layer.succeed(InstallationStore, LocalInstallationStore), |
| 32 | ? effect | 43 | Layer.succeed(WorkspaceConfigStore, LocalWorkspaceConfigStore), |
| 33 | : effect.pipe( | 44 | Layer.succeed(WorkspaceStateStore, LocalWorkspaceStateStore), |
| 34 | Effect.provideService( | 45 | Layer.succeed(SourceArtifactStore, LocalSourceArtifactStore), |
| 35 | RuntimeLocalWorkspaceService, | 46 | ); |
| 36 | runtimeLocalWorkspace, | 47 | const exit = await Effect.runPromiseExit(effect.pipe(Effect.provide(baseLayer))); |
| 37 | ), | ||
| 38 | ); | ||
| 39 | const exit = await Effect.runPromiseExit(provided); | ||
| 40 | if (Exit.isSuccess(exit)) return exit.value; | 48 | if (Exit.isSuccess(exit)) return exit.value; |
| 41 | throw Cause.squash(exit.cause); | 49 | throw Cause.squash(exit.cause); |
| 42 | }; | 50 | }; |
| @@ -1,19 +1,9 @@ | |||
| 1 | import * as Context from "effect/Context"; | ||
| 2 | import * as Effect from "effect/Effect"; | 1 | import * as Effect from "effect/Effect"; |
| 3 | import * as Exit from "effect/Exit"; | 2 | import * as Exit from "effect/Exit"; |
| 4 | import * as Layer from "effect/Layer"; | 3 | import * as Layer from "effect/Layer"; |
| 5 | import * as Scope from "effect/Scope"; | 4 | import * as Scope from "effect/Scope"; |
| 6 | 5 | ||
| 7 | import { type ControlPlaneApiRuntimeContext } from "#api"; | 6 | import { type ControlPlaneApiRuntimeContext } from "#api"; |
| 8 | import { | ||
| 9 | SqlControlPlanePersistenceLive, | ||
| 10 | SqlControlPlanePersistenceService, | ||
| 11 | SqlControlPlaneRowsLive, | ||
| 12 | SqlControlPlaneRowsService, | ||
| 13 | SqlPersistenceBootstrapError, | ||
| 14 | type CreateSqlRuntimeOptions, | ||
| 15 | type SqlControlPlanePersistence, | ||
| 16 | } from "#persistence"; | ||
| 17 | import type { LocalInstallation } from "#schema"; | 7 | import type { LocalInstallation } from "#schema"; |
| 18 | 8 | ||
| 19 | import { type ResolveExecutionEnvironment } from "./execution-state"; | 9 | import { type ResolveExecutionEnvironment } from "./execution-state"; |
| @@ -22,14 +12,33 @@ import { · 2 unmodified lines | |||
| 22 | LiveExecutionManagerLive, | 12 | LiveExecutionManagerLive, |
| 23 | LiveExecutionManagerService, | 13 | LiveExecutionManagerService, |
| 24 | } from "./live-execution"; | 14 | } from "./live-execution"; |
| 25 | import { getOrProvisionLocalInstallation } from "./local-installation"; | ||
| 26 | import { | 15 | import { |
| 27 | loadLocalExecutorConfig, | 16 | createLocalControlPlanePersistence, |
| 17 | type LocalControlPlanePersistence, | ||
| 18 | } from "./local-control-plane-store"; | ||
| 19 | import { | ||
| 28 | resolveLocalWorkspaceContext, | 20 | resolveLocalWorkspaceContext, |
| 29 | } from "./local-config"; | 21 | } from "./local-config"; |
| 30 | import { RuntimeLocalWorkspaceService } from "./local-runtime-context"; | 22 | import { |
| 23 | InstallationStore, | ||
| 24 | LocalInstallationStore, | ||
| 25 | LocalInstallationStoreLive, | ||
| 26 | LocalSourceArtifactStore, | ||
| 27 | LocalSourceArtifactStoreLive, | ||
| 28 | SourceArtifactStore, | ||
| 29 | LocalWorkspaceConfigStore, | ||
| 30 | LocalWorkspaceConfigStoreLive, | ||
| 31 | LocalWorkspaceStateStore, | ||
| 32 | LocalWorkspaceStateStoreLive, | ||
| 33 | WorkspaceConfigStore, | ||
| 34 | WorkspaceStateStore, | ||
| 35 | } from "./local-storage"; | ||
| 36 | import { | ||
| 37 | type RuntimeLocalWorkspaceState, | ||
| 38 | RuntimeLocalWorkspaceService, | ||
| 39 | } from "./local-runtime-context"; | ||
| 31 | import { synchronizeLocalWorkspaceState } from "./local-workspace-sync"; | 40 | import { synchronizeLocalWorkspaceState } from "./local-workspace-sync"; |
| 32 | import { ControlPlaneStore, ControlPlaneStoreLive } from "./store"; | 41 | import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store"; |
| 33 | import { | 42 | import { |
| 34 | createRuntimeSourceAuthService, | 43 | createRuntimeSourceAuthService, |
| 35 | RuntimeSourceAuthServiceLive, | 44 | RuntimeSourceAuthServiceLive, |
| @@ -48,6 +57,7 @@ export * from "./executor-tools"; · 12 unmodified lines | |||
| 48 | export * from "./live-execution"; | 57 | export * from "./live-execution"; |
| 49 | export * from "./local-config"; | 58 | export * from "./local-config"; |
| 50 | export * from "./local-installation"; | 59 | export * from "./local-installation"; |
| 60 | export * from "./local-storage"; | ||
| 51 | export * from "./local-source-artifacts"; | 61 | export * from "./local-source-artifacts"; |
| 52 | export * from "./local-tools"; | 62 | export * from "./local-tools"; |
| 53 | export * from "./schema-type-signature"; | 63 | export * from "./schema-type-signature"; |
| @@ -73,12 +83,9 @@ const detailsFromCause = (cause: unknown): string => · 19 unmodified lines | |||
| 73 | 83 | ||
| 74 | const toLocalRuntimeBootstrapError = ( | 84 | const toLocalRuntimeBootstrapError = ( |
| 75 | cause: unknown, | 85 | cause: unknown, |
| 76 | ): SqlPersistenceBootstrapError => { | 86 | ): Error => { |
| 77 | const details = detailsFromCause(cause); | 87 | const details = detailsFromCause(cause); |
| 78 | return new SqlPersistenceBootstrapError({ | 88 | return new Error(`Failed initializing local runtime: ${details}`); |
| 79 | message: `Failed initializing local runtime: ${details}`, | ||
| 80 | details, | ||
| 81 | }); | ||
| 82 | }; | 89 | }; |
| 83 | 90 | ||
| 84 | const closeScope = (scope: Scope.CloseableScope) => | 91 | const closeScope = (scope: Scope.CloseableScope) => |
| @@ -90,88 +97,57 @@ export type RuntimeControlPlaneLayer = Layer.Layer< · 5 unmodified lines | |||
| 90 | never | 97 | never |
| 91 | >; | 98 | >; |
| 92 | 99 | ||
| 93 | const runtimeContextTags = [ | ||
| 94 | ControlPlaneStore, | ||
| 95 | LiveExecutionManagerService, | ||
| 96 | RuntimeLocalWorkspaceService, | ||
| 97 | RuntimeSourceAuthServiceTag, | ||
| 98 | RuntimeExecutionResolverService, | ||
| 99 | ] as const; | ||
| 100 | |||
| 101 | const createRuntimeLayerFromContext = ( | ||
| 102 | context: Context.Context< | ||
| 103 | | ControlPlaneStore | ||
| 104 | | LiveExecutionManagerService | ||
| 105 | | RuntimeLocalWorkspaceService | ||
| 106 | | RuntimeSourceAuthServiceTag | ||
| 107 | | RuntimeExecutionResolverService | ||
| 108 | >, | ||
| 109 | ): RuntimeControlPlaneLayer => { | ||
| 110 | const runtimeContext = context.pipe( | ||
| 111 | Context.pick(...runtimeContextTags), | ||
| 112 | ) as Context.Context< | ||
| 113 | | ControlPlaneStore | ||
| 114 | | LiveExecutionManagerService | ||
| 115 | | RuntimeLocalWorkspaceService | ||
| 116 | | RuntimeSourceAuthServiceTag | ||
| 117 | | RuntimeExecutionResolverService | ||
| 118 | >; | ||
| 119 | |||
| 120 | return Layer.succeedContext(runtimeContext) as RuntimeControlPlaneLayer; | ||
| 121 | }; | ||
| 122 | |||
| 123 | export const createRuntimeControlPlaneLayer = ( | 100 | export const createRuntimeControlPlaneLayer = ( |
| 124 | options: RuntimeControlPlaneOptions = {}, | 101 | input: RuntimeControlPlaneOptions & { |
| 102 | store: ControlPlaneStoreShape; | ||
| 103 | localWorkspaceState: RuntimeLocalWorkspaceState; | ||
| 104 | }, | ||
| 125 | ) => { | 105 | ) => { |
| 126 | const liveExecutionManagerLayer = LiveExecutionManagerLive; | 106 | const baseLayer = Layer.mergeAll( |
| 107 | Layer.succeed(ControlPlaneStore, input.store), | ||
| 108 | Layer.succeed(RuntimeLocalWorkspaceService, input.localWorkspaceState), | ||
| 109 | LocalInstallationStoreLive, | ||
| 110 | LocalWorkspaceConfigStoreLive, | ||
| 111 | LocalWorkspaceStateStoreLive, | ||
| 112 | LocalSourceArtifactStoreLive, | ||
| 113 | LiveExecutionManagerLive, | ||
| 114 | ); | ||
| 115 | |||
| 127 | const sourceAuthLayer = RuntimeSourceAuthServiceLive({ | 116 | const sourceAuthLayer = RuntimeSourceAuthServiceLive({ |
| 128 | getLocalServerBaseUrl: options.getLocalServerBaseUrl, | 117 | getLocalServerBaseUrl: input.getLocalServerBaseUrl, |
| 129 | }).pipe(Layer.provide(liveExecutionManagerLayer)); | 118 | localConfig: input.localWorkspaceState.loadedConfig.config, |
| 119 | workspaceRoot: input.localWorkspaceState.context.workspaceRoot, | ||
| 120 | localWorkspaceState: input.localWorkspaceState, | ||
| 121 | }).pipe(Layer.provide(baseLayer)); | ||
| 122 | |||
| 130 | const executionResolverLayer = RuntimeExecutionResolverLive({ | 123 | const executionResolverLayer = RuntimeExecutionResolverLive({ |
| 131 | executionResolver: options.executionResolver, | 124 | executionResolver: input.executionResolver, |
| 132 | resolveSecretMaterial: options.resolveSecretMaterial, | 125 | resolveSecretMaterial: input.resolveSecretMaterial, |
| 133 | }).pipe(Layer.provide(sourceAuthLayer)); | 126 | }).pipe( |
| 127 | Layer.provide(Layer.mergeAll(baseLayer, sourceAuthLayer)), | ||
| 128 | ); | ||
| 134 | 129 | ||
| 135 | return Layer.mergeAll( | 130 | return Layer.mergeAll( |
| 136 | ControlPlaneStoreLive, | 131 | baseLayer, |
| 137 | liveExecutionManagerLayer, | ||
| 138 | sourceAuthLayer, | 132 | sourceAuthLayer, |
| 139 | executionResolverLayer, | 133 | executionResolverLayer, |
| 140 | ); | 134 | ) as RuntimeControlPlaneLayer; |
| 141 | }; | 135 | }; |
| 142 | 136 | ||
| 143 | export type ControlPlaneRuntime = { | 137 | export type ControlPlaneRuntime = { |
| 144 | persistence: SqlControlPlanePersistence; | 138 | persistence: LocalControlPlanePersistence; |
| 145 | localInstallation: LocalInstallation; | 139 | localInstallation: LocalInstallation; |
| 146 | runtimeLayer: RuntimeControlPlaneLayer; | 140 | runtimeLayer: RuntimeControlPlaneLayer; |
| 147 | close: () => Promise<void>; | 141 | close: () => Promise<void>; |
| 148 | }; | 142 | }; |
| 149 | 143 | ||
| 150 | export type CreateControlPlaneRuntimeOptions = CreateSqlRuntimeOptions & | 144 | export type CreateControlPlaneRuntimeOptions = RuntimeControlPlaneOptions; |
| 151 | RuntimeControlPlaneOptions; | ||
| 152 | 145 | ||
| 153 | export const createControlPlaneRuntime = ( | 146 | export const createControlPlaneRuntime = ( |
| 154 | options: CreateControlPlaneRuntimeOptions, | 147 | options: CreateControlPlaneRuntimeOptions, |
| 155 | ): Effect.Effect<ControlPlaneRuntime, SqlPersistenceBootstrapError> => | 148 | ): Effect.Effect<ControlPlaneRuntime, Error> => |
| 156 | Effect.gen(function* () { | 149 | Effect.gen(function* () { |
| 157 | const scope = yield* Scope.make(); | 150 | const scope = yield* Scope.make(); |
| 158 | const persistenceAndRowsLayer = SqlControlPlaneRowsLive.pipe( | ||
| 159 | Layer.provideMerge(SqlControlPlanePersistenceLive(options)), | ||
| 160 | ); | ||
| 161 | const baseContext = yield* Layer.buildWithScope( | ||
| 162 | persistenceAndRowsLayer, | ||
| 163 | scope, | ||
| 164 | ).pipe( | ||
| 165 | Effect.catchAll((error) => | ||
| 166 | closeScope(scope).pipe(Effect.zipRight(Effect.fail(error))), | ||
| 167 | ), | ||
| 168 | ); | ||
| 169 | |||
| 170 | const persistence = Context.get( | ||
| 171 | baseContext, | ||
| 172 | SqlControlPlanePersistenceService, | ||
| 173 | ); | ||
| 174 | const rows = Context.get(baseContext, SqlControlPlaneRowsService); | ||
| 175 | 151 | ||
| 176 | const localWorkspaceContext = yield* resolveLocalWorkspaceContext({ | 152 | const localWorkspaceContext = yield* resolveLocalWorkspaceContext({ |
| 177 | workspaceRoot: options.workspaceRoot, | 153 | workspaceRoot: options.workspaceRoot, |
| @@ -182,7 +158,12 @@ export const createControlPlaneRuntime = ( · 4 unmodified lines | |||
| 182 | ), | 158 | ), |
| 183 | ); | 159 | ); |
| 184 | 160 | ||
| 185 | const localInstallation = yield* getOrProvisionLocalInstallation({ | 161 | const installationStore = LocalInstallationStore; |
| 162 | const workspaceConfigStore = LocalWorkspaceConfigStore; | ||
| 163 | const workspaceStateStore = LocalWorkspaceStateStore; | ||
| 164 | const sourceArtifactStore = LocalSourceArtifactStore; | ||
| 165 | |||
| 166 | const localInstallation = yield* installationStore.getOrProvision({ | ||
| 186 | context: localWorkspaceContext, | 167 | context: localWorkspaceContext, |
| 187 | }).pipe( | 168 | }).pipe( |
| 188 | Effect.mapError(toLocalRuntimeBootstrapError), | 169 | Effect.mapError(toLocalRuntimeBootstrapError), |
| @@ -191,7 +172,10 @@ export const createControlPlaneRuntime = ( · 2 unmodified lines | |||
| 191 | ), | 172 | ), |
| 192 | ); | 173 | ); |
| 193 | 174 | ||
| 194 | const loadedLocalConfig = yield* loadLocalExecutorConfig( | 175 | const persistence = createLocalControlPlanePersistence(localWorkspaceContext); |
| 176 | const rows = persistence.rows; | ||
| 177 | |||
| 178 | const loadedLocalConfig = yield* workspaceConfigStore.load( | ||
| 195 | localWorkspaceContext, | 179 | localWorkspaceContext, |
| 196 | ).pipe( | 180 | ).pipe( |
| 197 | Effect.mapError(toLocalRuntimeBootstrapError), | 181 | Effect.mapError(toLocalRuntimeBootstrapError), |
| @@ -237,38 +221,42 @@ export const createControlPlaneRuntime = ( · 39 unmodified lines | |||
| 237 | }, | 221 | }, |
| 238 | }, | 222 | }, |
| 239 | }); | 223 | }); |
| 224 | |||
| 225 | const runtimeLocalWorkspaceState: RuntimeLocalWorkspaceState = { | ||
| 226 | context: localWorkspaceContext, | ||
| 227 | installation: { | ||
| 228 | workspaceId: localInstallation.workspaceId, | ||
| 229 | accountId: localInstallation.accountId, | ||
| 230 | }, | ||
| 231 | loadedConfig: { | ||
| 232 | ...loadedLocalConfig, | ||
| 233 | config: effectiveLocalConfig, | ||
| 234 | }, | ||
| 235 | }; | ||
| 236 | |||
| 240 | const executionResolver = | 237 | const executionResolver = |
| 241 | options.executionResolver ?? | 238 | options.executionResolver ?? |
| 242 | createWorkspaceExecutionEnvironmentResolver({ | 239 | createWorkspaceExecutionEnvironmentResolver({ |
| 243 | rows, | 240 | rows, |
| 244 | sourceAuthService, | 241 | sourceAuthService, |
| 245 | resolveSecretMaterial, | 242 | resolveSecretMaterial, |
| 243 | workspaceConfigStore, | ||
| 244 | workspaceStateStore, | ||
| 245 | sourceArtifactStore, | ||
| 246 | }); | 246 | }); |
| 247 | 247 | ||
| 248 | const runtimeContext = Context.empty().pipe( | 248 | const concreteRuntimeLayer = createRuntimeControlPlaneLayer({ |
| 249 | Context.add(ControlPlaneStore, rows), | 249 | ...options, |
| 250 | Context.add(LiveExecutionManagerService, liveExecutionManager), | 250 | store: rows, |
| 251 | Context.add(RuntimeLocalWorkspaceService, { | 251 | localWorkspaceState: runtimeLocalWorkspaceState, |
| 252 | context: localWorkspaceContext, | 252 | resolveSecretMaterial, |
| 253 | installation: { | 253 | executionResolver, |
| 254 | workspaceId: localInstallation.workspaceId, | 254 | }); |
| 255 | accountId: localInstallation.accountId, | ||
| 256 | }, | ||
| 257 | loadedConfig: { | ||
| 258 | ...loadedLocalConfig, | ||
| 259 | config: effectiveLocalConfig, | ||
| 260 | }, | ||
| 261 | }), | ||
| 262 | Context.add(RuntimeSourceAuthServiceTag, sourceAuthService), | ||
| 263 | Context.add(RuntimeExecutionResolverService, executionResolver), | ||
| 264 | ); | ||
| 265 | const concreteRuntimeLayer = createRuntimeLayerFromContext(runtimeContext); | ||
| 266 | 255 | ||
| 267 | return { | 256 | return { |
| 268 | persistence, | 257 | persistence, |
| 269 | localInstallation, | 258 | localInstallation, |
| 270 | runtimeLayer: concreteRuntimeLayer, | 259 | runtimeLayer: concreteRuntimeLayer, |
| 271 | close: () => | 260 | close: () => Effect.runPromise(Scope.close(scope, Exit.void)).catch(() => {}), |
| 272 | Effect.runPromise(Scope.close(scope, Exit.void)).catch(() => {}), | ||
| 273 | }; | 261 | }; |
| 274 | }); | 262 | }); |
| @@ -9,7 +9,7 @@ describe("live-execution", () => { · 8 unmodified lines | |||
| 9 | content: { | 9 | content: { |
| 10 | authKind: "bearer", | 10 | authKind: "bearer", |
| 11 | tokenRef: { | 11 | tokenRef: { |
| 12 | providerId: "postgres", | 12 | providerId: "local", |
| 13 | handle: "sec_123", | 13 | handle: "sec_123", |
| 14 | }, | 14 | }, |
| 15 | }, | 15 | }, |
| @@ -2,7 +2,6 @@ import type { · 1 unmodified line | |||
| 2 | ElicitationResponse, | 2 | ElicitationResponse, |
| 3 | OnElicitation, | 3 | OnElicitation, |
| 4 | } from "@executor/codemode-core"; | 4 | } from "@executor/codemode-core"; |
| 5 | import { type SqlControlPlaneRows } from "#persistence"; | ||
| 6 | import { | 5 | import { |
| 7 | ExecutionInteractionIdSchema, | 6 | ExecutionInteractionIdSchema, |
| 8 | type Execution, | 7 | type Execution, |
| @@ -13,6 +12,8 @@ import * as Deferred from "effect/Deferred"; · 4 unmodified lines | |||
| 13 | import * as Effect from "effect/Effect"; | 12 | import * as Effect from "effect/Effect"; |
| 14 | import * as Layer from "effect/Layer"; | 13 | import * as Layer from "effect/Layer"; |
| 15 | 14 | ||
| 15 | import type { ControlPlaneStoreShape } from "./store"; | ||
| 16 | |||
| 16 | type VisibleExecutionState = | 17 | type VisibleExecutionState = |
| 17 | | "waiting_for_interaction" | 18 | | "waiting_for_interaction" |
| 18 | | "completed" | 19 | | "completed" |
| @@ -35,7 +36,7 @@ type LiveExecutionManagerShape = { · 16 unmodified lines | |||
| 35 | executionId: Execution["id"], | 36 | executionId: Execution["id"], |
| 36 | ) => Effect.Effect<Deferred.Deferred<VisibleExecutionState>>; | 37 | ) => Effect.Effect<Deferred.Deferred<VisibleExecutionState>>; |
| 37 | createOnElicitation: (input: { | 38 | createOnElicitation: (input: { |
| 38 | rows: SqlControlPlaneRows; | 39 | rows: ControlPlaneStoreShape; |
| 39 | executionId: Execution["id"]; | 40 | executionId: Execution["id"]; |
| 40 | }) => OnElicitation; | 41 | }) => OnElicitation; |
| 41 | resolveInteraction: (input: { | 42 | resolveInteraction: (input: { |
| @@ -0,0 +1,1054 @@ | |||
| 1 | import { randomUUID } from "node:crypto"; | ||
| 2 | import { existsSync } from "node:fs"; | ||
| 3 | import { mkdir, readFile, rename, writeFile } from "node:fs/promises"; | ||
| 4 | import { dirname, join, resolve } from "node:path"; | ||
| 5 | |||
| 6 | import { | ||
| 7 | AuthArtifactSchema, | ||
| 8 | AuthLeaseSchema, | ||
| 9 | type AuthArtifact, | ||
| 10 | type AuthLease, | ||
| 11 | type Execution, | ||
| 12 | type ExecutionInteraction, | ||
| 13 | type ExecutionStep, | ||
| 14 | ExecutionInteractionSchema, | ||
| 15 | ExecutionSchema, | ||
| 16 | ExecutionStepSchema, | ||
| 17 | SecretMaterialSchema, | ||
| 18 | type SecretMaterial, | ||
| 19 | type SourceAuthSession, | ||
| 20 | SourceAuthSessionSchema, | ||
| 21 | type WorkspaceSourceOauthClient, | ||
| 22 | WorkspaceSourceOauthClientSchema, | ||
| 23 | } from "#schema"; | ||
| 24 | import * as Effect from "effect/Effect"; | ||
| 25 | import * as Option from "effect/Option"; | ||
| 26 | import * as Schema from "effect/Schema"; | ||
| 27 | |||
| 28 | import { authArtifactSecretMaterialRefs } from "./auth-artifacts"; | ||
| 29 | import type { ResolvedLocalWorkspaceContext } from "./local-config"; | ||
| 30 | import { loadLocalExecutorConfig } from "./local-config"; | ||
| 31 | import { | ||
| 32 | LocalFileSystemError, | ||
| 33 | unknownLocalErrorDetails, | ||
| 34 | } from "./local-errors"; | ||
| 35 | |||
| 36 | const LOCAL_CONTROL_PLANE_STATE_VERSION = 1 as const; | ||
| 37 | const LOCAL_CONTROL_PLANE_STATE_BASENAME = "control-plane-state.json"; | ||
| 38 | |||
| 39 | const LocalControlPlaneStateSchema = Schema.Struct({ | ||
| 40 | version: Schema.Literal(LOCAL_CONTROL_PLANE_STATE_VERSION), | ||
| 41 | authArtifacts: Schema.Array(AuthArtifactSchema), | ||
| 42 | authLeases: Schema.Array(AuthLeaseSchema), | ||
| 43 | sourceOauthClients: Schema.Array(WorkspaceSourceOauthClientSchema), | ||
| 44 | sourceAuthSessions: Schema.Array(SourceAuthSessionSchema), | ||
| 45 | secretMaterials: Schema.Array(SecretMaterialSchema), | ||
| 46 | executions: Schema.Array(ExecutionSchema), | ||
| 47 | executionInteractions: Schema.Array(ExecutionInteractionSchema), | ||
| 48 | executionSteps: Schema.Array(ExecutionStepSchema), | ||
| 49 | }); | ||
| 50 | |||
| 51 | export type LocalControlPlaneState = typeof LocalControlPlaneStateSchema.Type; | ||
| 52 | |||
| 53 | export type LocalControlPlanePersistence = { | ||
| 54 | rows: LocalControlPlaneStore; | ||
| 55 | close: () => Promise<void>; | ||
| 56 | }; | ||
| 57 | |||
| 58 | const decodeLocalControlPlaneState = Schema.decodeUnknownSync( | ||
| 59 | LocalControlPlaneStateSchema, | ||
| 60 | ); | ||
| 61 | |||
| 62 | const defaultLocalControlPlaneState = (): LocalControlPlaneState => ({ | ||
| 63 | version: LOCAL_CONTROL_PLANE_STATE_VERSION, | ||
| 64 | authArtifacts: [], | ||
| 65 | authLeases: [], | ||
| 66 | sourceOauthClients: [], | ||
| 67 | sourceAuthSessions: [], | ||
| 68 | secretMaterials: [], | ||
| 69 | executions: [], | ||
| 70 | executionInteractions: [], | ||
| 71 | executionSteps: [], | ||
| 72 | }); | ||
| 73 | |||
| 74 | const cloneValue = <T>(value: T): T => | ||
| 75 | JSON.parse(JSON.stringify(value)) as T; | ||
| 76 | |||
| 77 | const mapFileSystemError = (path: string, action: string) => (cause: unknown) => | ||
| 78 | new LocalFileSystemError({ | ||
| 79 | message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`, | ||
| 80 | action, | ||
| 81 | path, | ||
| 82 | details: unknownLocalErrorDetails(cause), | ||
| 83 | }); | ||
| 84 | |||
| 85 | const actorEquals = ( | ||
| 86 | left: string | null | undefined, | ||
| 87 | right: string | null | undefined, | ||
| 88 | ): boolean => (left ?? null) === (right ?? null); | ||
| 89 | |||
| 90 | const sortByUpdatedAtAndIdAsc = <T extends { updatedAt: number; id: string }>( | ||
| 91 | values: readonly T[], | ||
| 92 | ): T[] => | ||
| 93 | [...values].sort((left, right) => | ||
| 94 | left.updatedAt - right.updatedAt || left.id.localeCompare(right.id), | ||
| 95 | ); | ||
| 96 | |||
| 97 | const sortByUpdatedAtAndIdDesc = <T extends { updatedAt: number; id: string }>( | ||
| 98 | values: readonly T[], | ||
| 99 | ): T[] => | ||
| 100 | [...values].sort((left, right) => | ||
| 101 | right.updatedAt - left.updatedAt || right.id.localeCompare(left.id), | ||
| 102 | ); | ||
| 103 | |||
| 104 | const localControlPlaneStatePath = ( | ||
| 105 | context: ResolvedLocalWorkspaceContext, | ||
| 106 | ): string => join(context.stateDirectory, LOCAL_CONTROL_PLANE_STATE_BASENAME); | ||
| 107 | |||
| 108 | const readStateFromDisk = async ( | ||
| 109 | context: ResolvedLocalWorkspaceContext, | ||
| 110 | ): Promise<LocalControlPlaneState> => { | ||
| 111 | const path = localControlPlaneStatePath(context); | ||
| 112 | if (!existsSync(path)) { | ||
| 113 | return defaultLocalControlPlaneState(); | ||
| 114 | } | ||
| 115 | |||
| 116 | const content = await readFile(path, "utf8"); | ||
| 117 | return decodeLocalControlPlaneState(JSON.parse(content) as unknown); | ||
| 118 | }; | ||
| 119 | |||
| 120 | const writeStateToDisk = async ( | ||
| 121 | context: ResolvedLocalWorkspaceContext, | ||
| 122 | state: LocalControlPlaneState, | ||
| 123 | ): Promise<void> => { | ||
| 124 | const path = localControlPlaneStatePath(context); | ||
| 125 | await mkdir(dirname(path), { recursive: true }); | ||
| 126 | const tempPath = `${path}.${randomUUID()}.tmp`; | ||
| 127 | await writeFile(tempPath, `${JSON.stringify(state, null, 2)}\n`, "utf8"); | ||
| 128 | await rename(tempPath, path); | ||
| 129 | }; | ||
| 130 | |||
| 131 | export const loadLocalControlPlaneState = ( | ||
| 132 | context: ResolvedLocalWorkspaceContext, | ||
| 133 | ): Effect.Effect<LocalControlPlaneState, LocalFileSystemError> => | ||
| 134 | Effect.tryPromise({ | ||
| 135 | try: () => readStateFromDisk(context), | ||
| 136 | catch: mapFileSystemError( | ||
| 137 | localControlPlaneStatePath(context), | ||
| 138 | "read control plane state", | ||
| 139 | ), | ||
| 140 | }); | ||
| 141 | |||
| 142 | export const writeLocalControlPlaneState = (input: { | ||
| 143 | context: ResolvedLocalWorkspaceContext; | ||
| 144 | state: LocalControlPlaneState; | ||
| 145 | }): Effect.Effect<void, LocalFileSystemError> => | ||
| 146 | Effect.tryPromise({ | ||
| 147 | try: () => writeStateToDisk(input.context, input.state), | ||
| 148 | catch: mapFileSystemError( | ||
| 149 | localControlPlaneStatePath(input.context), | ||
| 150 | "write control plane state", | ||
| 151 | ), | ||
| 152 | }); | ||
| 153 | |||
| 154 | const mergeById = <T extends { id: string }>( | ||
| 155 | current: readonly T[], | ||
| 156 | imported: readonly T[], | ||
| 157 | ): T[] => { | ||
| 158 | const merged = new Map<string, T>(); | ||
| 159 | |||
| 160 | for (const item of imported) { | ||
| 161 | merged.set(item.id, cloneValue(item)); | ||
| 162 | } | ||
| 163 | |||
| 164 | for (const item of current) { | ||
| 165 | merged.set(item.id, cloneValue(item)); | ||
| 166 | } | ||
| 167 | |||
| 168 | return [...merged.values()]; | ||
| 169 | }; | ||
| 170 | |||
| 171 | const mergeAuthLeases = ( | ||
| 172 | current: readonly AuthLease[], | ||
| 173 | imported: readonly AuthLease[], | ||
| 174 | ): AuthLease[] => { | ||
| 175 | const merged = new Map<string, AuthLease>(); | ||
| 176 | |||
| 177 | for (const lease of imported) { | ||
| 178 | merged.set(lease.authArtifactId, cloneValue(lease)); | ||
| 179 | } | ||
| 180 | |||
| 181 | for (const lease of current) { | ||
| 182 | merged.set(lease.authArtifactId, cloneValue(lease)); | ||
| 183 | } | ||
| 184 | |||
| 185 | return [...merged.values()]; | ||
| 186 | }; | ||
| 187 | |||
| 188 | const mergeAuthArtifacts = ( | ||
| 189 | current: readonly AuthArtifact[], | ||
| 190 | imported: readonly AuthArtifact[], | ||
| 191 | ): AuthArtifact[] => { | ||
| 192 | const merged = new Map<string, AuthArtifact>(); | ||
| 193 | |||
| 194 | for (const artifact of imported) { | ||
| 195 | merged.set( | ||
| 196 | [ | ||
| 197 | artifact.workspaceId, | ||
| 198 | artifact.sourceId, | ||
| 199 | artifact.actorAccountId ?? "", | ||
| 200 | artifact.slot, | ||
| 201 | ].join("::"), | ||
| 202 | cloneValue(artifact), | ||
| 203 | ); | ||
| 204 | } | ||
| 205 | |||
| 206 | for (const artifact of current) { | ||
| 207 | merged.set( | ||
| 208 | [ | ||
| 209 | artifact.workspaceId, | ||
| 210 | artifact.sourceId, | ||
| 211 | artifact.actorAccountId ?? "", | ||
| 212 | artifact.slot, | ||
| 213 | ].join("::"), | ||
| 214 | cloneValue(artifact), | ||
| 215 | ); | ||
| 216 | } | ||
| 217 | |||
| 218 | return [...merged.values()]; | ||
| 219 | }; | ||
| 220 | |||
| 221 | const mergeSourceOauthClients = ( | ||
| 222 | current: readonly WorkspaceSourceOauthClient[], | ||
| 223 | imported: readonly WorkspaceSourceOauthClient[], | ||
| 224 | ): WorkspaceSourceOauthClient[] => { | ||
| 225 | const merged = new Map<string, WorkspaceSourceOauthClient>(); | ||
| 226 | |||
| 227 | for (const oauthClient of imported) { | ||
| 228 | merged.set( | ||
| 229 | [oauthClient.workspaceId, oauthClient.sourceId, oauthClient.providerKey].join( | ||
| 230 | "::", | ||
| 231 | ), | ||
| 232 | cloneValue(oauthClient), | ||
| 233 | ); | ||
| 234 | } | ||
| 235 | |||
| 236 | for (const oauthClient of current) { | ||
| 237 | merged.set( | ||
| 238 | [oauthClient.workspaceId, oauthClient.sourceId, oauthClient.providerKey].join( | ||
| 239 | "::", | ||
| 240 | ), | ||
| 241 | cloneValue(oauthClient), | ||
| 242 | ); | ||
| 243 | } | ||
| 244 | |||
| 245 | return [...merged.values()]; | ||
| 246 | }; | ||
| 247 | |||
| 248 | export const mergeImportedLocalControlPlaneState = (input: { | ||
| 249 | current: LocalControlPlaneState; | ||
| 250 | imported: Partial<Omit<LocalControlPlaneState, "version">>; | ||
| 251 | }): LocalControlPlaneState => ({ | ||
| 252 | version: LOCAL_CONTROL_PLANE_STATE_VERSION, | ||
| 253 | authArtifacts: mergeAuthArtifacts( | ||
| 254 | input.current.authArtifacts, | ||
| 255 | input.imported.authArtifacts ?? [], | ||
| 256 | ), | ||
| 257 | authLeases: mergeAuthLeases( | ||
| 258 | input.current.authLeases, | ||
| 259 | input.imported.authLeases ?? [], | ||
| 260 | ), | ||
| 261 | sourceOauthClients: mergeSourceOauthClients( | ||
| 262 | input.current.sourceOauthClients, | ||
| 263 | input.imported.sourceOauthClients ?? [], | ||
| 264 | ), | ||
| 265 | sourceAuthSessions: mergeById( | ||
| 266 | input.current.sourceAuthSessions, | ||
| 267 | input.imported.sourceAuthSessions ?? [], | ||
| 268 | ), | ||
| 269 | secretMaterials: mergeById( | ||
| 270 | input.current.secretMaterials, | ||
| 271 | input.imported.secretMaterials ?? [], | ||
| 272 | ), | ||
| 273 | executions: mergeById(input.current.executions, input.imported.executions ?? []), | ||
| 274 | executionInteractions: mergeById( | ||
| 275 | input.current.executionInteractions, | ||
| 276 | input.imported.executionInteractions ?? [], | ||
| 277 | ), | ||
| 278 | executionSteps: mergeById( | ||
| 279 | input.current.executionSteps, | ||
| 280 | input.imported.executionSteps ?? [], | ||
| 281 | ), | ||
| 282 | }); | ||
| 283 | |||
| 284 | type StateMutationResult<A> = { | ||
| 285 | state: LocalControlPlaneState; | ||
| 286 | value: A; | ||
| 287 | }; | ||
| 288 | |||
| 289 | const createStateManager = (context: ResolvedLocalWorkspaceContext) => { | ||
| 290 | let cache: LocalControlPlaneState | null = null; | ||
| 291 | let mutationQueue: Promise<void> = Promise.resolve(); | ||
| 292 | |||
| 293 | const ensureLoaded = async (): Promise<LocalControlPlaneState> => { | ||
| 294 | if (cache !== null) { | ||
| 295 | return cache; | ||
| 296 | } | ||
| 297 | |||
| 298 | cache = await readStateFromDisk(context); | ||
| 299 | return cache; | ||
| 300 | }; | ||
| 301 | |||
| 302 | const read = <A>( | ||
| 303 | operation: (state: LocalControlPlaneState) => A | Promise<A>, | ||
| 304 | ): Effect.Effect<A, LocalFileSystemError> => | ||
| 305 | Effect.tryPromise({ | ||
| 306 | try: async () => operation(cloneValue(await ensureLoaded())), | ||
| 307 | catch: mapFileSystemError( | ||
| 308 | localControlPlaneStatePath(context), | ||
| 309 | "read control plane state", | ||
| 310 | ), | ||
| 311 | }); | ||
| 312 | |||
| 313 | const mutate = <A>( | ||
| 314 | operation: ( | ||
| 315 | state: LocalControlPlaneState, | ||
| 316 | ) => StateMutationResult<A> | Promise<StateMutationResult<A>>, | ||
| 317 | ): Effect.Effect<A, LocalFileSystemError> => | ||
| 318 | Effect.tryPromise({ | ||
| 319 | try: async () => { | ||
| 320 | let value!: A; | ||
| 321 | let failure: unknown = null; | ||
| 322 | |||
| 323 | mutationQueue = mutationQueue.then(async () => { | ||
| 324 | try { | ||
| 325 | const current = cloneValue(await ensureLoaded()); | ||
| 326 | const result = await operation(current); | ||
| 327 | cache = result.state; | ||
| 328 | value = result.value; | ||
| 329 | await writeStateToDisk(context, cache); | ||
| 330 | } catch (cause) { | ||
| 331 | failure = cause; | ||
| 332 | } | ||
| 333 | }); | ||
| 334 | |||
| 335 | await mutationQueue; | ||
| 336 | |||
| 337 | if (failure !== null) { | ||
| 338 | throw failure; | ||
| 339 | } | ||
| 340 | |||
| 341 | return value; | ||
| 342 | }, | ||
| 343 | catch: mapFileSystemError( | ||
| 344 | localControlPlaneStatePath(context), | ||
| 345 | "write control plane state", | ||
| 346 | ), | ||
| 347 | }); | ||
| 348 | |||
| 349 | return { | ||
| 350 | read, | ||
| 351 | mutate, | ||
| 352 | }; | ||
| 353 | }; | ||
| 354 | |||
| 355 | export const createLocalControlPlaneStore = ( | ||
| 356 | context: ResolvedLocalWorkspaceContext, | ||
| 357 | ) => { | ||
| 358 | const stateManager = createStateManager(context); | ||
| 359 | |||
| 360 | return { | ||
| 361 | authArtifacts: { | ||
| 362 | listByWorkspaceId: (workspaceId: AuthArtifact["workspaceId"]) => | ||
| 363 | stateManager.read((state) => | ||
| 364 | sortByUpdatedAtAndIdAsc( | ||
| 365 | state.authArtifacts.filter((artifact) => artifact.workspaceId === workspaceId), | ||
| 366 | ), | ||
| 367 | ), | ||
| 368 | |||
| 369 | listByWorkspaceAndSourceId: (input: { | ||
| 370 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 371 | sourceId: AuthArtifact["sourceId"]; | ||
| 372 | }) => | ||
| 373 | stateManager.read((state) => | ||
| 374 | sortByUpdatedAtAndIdAsc( | ||
| 375 | state.authArtifacts.filter( | ||
| 376 | (artifact) => | ||
| 377 | artifact.workspaceId === input.workspaceId | ||
| 378 | && artifact.sourceId === input.sourceId, | ||
| 379 | ), | ||
| 380 | ), | ||
| 381 | ), | ||
| 382 | |||
| 383 | getByWorkspaceSourceAndActor: (input: { | ||
| 384 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 385 | sourceId: AuthArtifact["sourceId"]; | ||
| 386 | actorAccountId: AuthArtifact["actorAccountId"]; | ||
| 387 | slot: AuthArtifact["slot"]; | ||
| 388 | }) => | ||
| 389 | stateManager.read((state) => { | ||
| 390 | const artifact = state.authArtifacts.find( | ||
| 391 | (candidate) => | ||
| 392 | candidate.workspaceId === input.workspaceId | ||
| 393 | && candidate.sourceId === input.sourceId | ||
| 394 | && candidate.slot === input.slot | ||
| 395 | && actorEquals(candidate.actorAccountId, input.actorAccountId), | ||
| 396 | ); | ||
| 397 | |||
| 398 | return artifact ? Option.some(cloneValue(artifact)) : Option.none<AuthArtifact>(); | ||
| 399 | }), | ||
| 400 | |||
| 401 | upsert: (artifact: AuthArtifact) => | ||
| 402 | stateManager.mutate((state) => { | ||
| 403 | const nextArtifacts = state.authArtifacts.filter( | ||
| 404 | (candidate) => | ||
| 405 | !( | ||
| 406 | candidate.workspaceId === artifact.workspaceId | ||
| 407 | && candidate.sourceId === artifact.sourceId | ||
| 408 | && candidate.slot === artifact.slot | ||
| 409 | && actorEquals(candidate.actorAccountId, artifact.actorAccountId) | ||
| 410 | ), | ||
| 411 | ); | ||
| 412 | nextArtifacts.push(cloneValue(artifact)); | ||
| 413 | |||
| 414 | return { | ||
| 415 | state: { | ||
| 416 | ...state, | ||
| 417 | authArtifacts: nextArtifacts, | ||
| 418 | }, | ||
| 419 | value: undefined, | ||
| 420 | } satisfies StateMutationResult<void>; | ||
| 421 | }), | ||
| 422 | |||
| 423 | removeByWorkspaceSourceAndActor: (input: { | ||
| 424 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 425 | sourceId: AuthArtifact["sourceId"]; | ||
| 426 | actorAccountId: AuthArtifact["actorAccountId"]; | ||
| 427 | slot?: AuthArtifact["slot"]; | ||
| 428 | }) => | ||
| 429 | stateManager.mutate((state) => { | ||
| 430 | const nextArtifacts = state.authArtifacts.filter( | ||
| 431 | (candidate) => | ||
| 432 | candidate.workspaceId !== input.workspaceId | ||
| 433 | || candidate.sourceId !== input.sourceId | ||
| 434 | || !actorEquals(candidate.actorAccountId, input.actorAccountId) | ||
| 435 | || (input.slot !== undefined && candidate.slot !== input.slot), | ||
| 436 | ); | ||
| 437 | |||
| 438 | return { | ||
| 439 | state: { | ||
| 440 | ...state, | ||
| 441 | authArtifacts: nextArtifacts, | ||
| 442 | }, | ||
| 443 | value: nextArtifacts.length !== state.authArtifacts.length, | ||
| 444 | } satisfies StateMutationResult<boolean>; | ||
| 445 | }), | ||
| 446 | |||
| 447 | removeByWorkspaceAndSourceId: (input: { | ||
| 448 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 449 | sourceId: AuthArtifact["sourceId"]; | ||
| 450 | }) => | ||
| 451 | stateManager.mutate((state) => { | ||
| 452 | const nextArtifacts = state.authArtifacts.filter( | ||
| 453 | (candidate) => | ||
| 454 | candidate.workspaceId !== input.workspaceId | ||
| 455 | || candidate.sourceId !== input.sourceId, | ||
| 456 | ); | ||
| 457 | |||
| 458 | return { | ||
| 459 | state: { | ||
| 460 | ...state, | ||
| 461 | authArtifacts: nextArtifacts, | ||
| 462 | }, | ||
| 463 | value: state.authArtifacts.length - nextArtifacts.length, | ||
| 464 | } satisfies StateMutationResult<number>; | ||
| 465 | }), | ||
| 466 | }, | ||
| 467 | |||
| 468 | authLeases: { | ||
| 469 | listAll: () => | ||
| 470 | stateManager.read((state) => sortByUpdatedAtAndIdAsc(state.authLeases)), | ||
| 471 | |||
| 472 | getByAuthArtifactId: (authArtifactId: AuthLease["authArtifactId"]) => | ||
| 473 | stateManager.read((state) => { | ||
| 474 | const lease = state.authLeases.find( | ||
| 475 | (candidate) => candidate.authArtifactId === authArtifactId, | ||
| 476 | ); | ||
| 477 | return lease ? Option.some(cloneValue(lease)) : Option.none<AuthLease>(); | ||
| 478 | }), | ||
| 479 | |||
| 480 | upsert: (lease: AuthLease) => | ||
| 481 | stateManager.mutate((state) => { | ||
| 482 | const nextLeases = state.authLeases.filter( | ||
| 483 | (candidate) => candidate.authArtifactId !== lease.authArtifactId, | ||
| 484 | ); | ||
| 485 | nextLeases.push(cloneValue(lease)); | ||
| 486 | |||
| 487 | return { | ||
| 488 | state: { | ||
| 489 | ...state, | ||
| 490 | authLeases: nextLeases, | ||
| 491 | }, | ||
| 492 | value: undefined, | ||
| 493 | } satisfies StateMutationResult<void>; | ||
| 494 | }), | ||
| 495 | |||
| 496 | removeByAuthArtifactId: (authArtifactId: AuthLease["authArtifactId"]) => | ||
| 497 | stateManager.mutate((state) => { | ||
| 498 | const nextLeases = state.authLeases.filter( | ||
| 499 | (candidate) => candidate.authArtifactId !== authArtifactId, | ||
| 500 | ); | ||
| 501 | |||
| 502 | return { | ||
| 503 | state: { | ||
| 504 | ...state, | ||
| 505 | authLeases: nextLeases, | ||
| 506 | }, | ||
| 507 | value: nextLeases.length !== state.authLeases.length, | ||
| 508 | } satisfies StateMutationResult<boolean>; | ||
| 509 | }), | ||
| 510 | }, | ||
| 511 | |||
| 512 | sourceOauthClients: { | ||
| 513 | getByWorkspaceSourceAndProvider: (input: { | ||
| 514 | workspaceId: WorkspaceSourceOauthClient["workspaceId"]; | ||
| 515 | sourceId: WorkspaceSourceOauthClient["sourceId"]; | ||
| 516 | providerKey: string; | ||
| 517 | }) => | ||
| 518 | stateManager.read((state) => { | ||
| 519 | const oauthClient = state.sourceOauthClients.find( | ||
| 520 | (candidate) => | ||
| 521 | candidate.workspaceId === input.workspaceId | ||
| 522 | && candidate.sourceId === input.sourceId | ||
| 523 | && candidate.providerKey === input.providerKey, | ||
| 524 | ); | ||
| 525 | |||
| 526 | return oauthClient | ||
| 527 | ? Option.some(cloneValue(oauthClient)) | ||
| 528 | : Option.none<WorkspaceSourceOauthClient>(); | ||
| 529 | }), | ||
| 530 | |||
| 531 | upsert: (oauthClient: WorkspaceSourceOauthClient) => | ||
| 532 | stateManager.mutate((state) => { | ||
| 533 | const nextOauthClients = state.sourceOauthClients.filter( | ||
| 534 | (candidate) => | ||
| 535 | !( | ||
| 536 | candidate.workspaceId === oauthClient.workspaceId | ||
| 537 | && candidate.sourceId === oauthClient.sourceId | ||
| 538 | && candidate.providerKey === oauthClient.providerKey | ||
| 539 | ), | ||
| 540 | ); | ||
| 541 | nextOauthClients.push(cloneValue(oauthClient)); | ||
| 542 | |||
| 543 | return { | ||
| 544 | state: { | ||
| 545 | ...state, | ||
| 546 | sourceOauthClients: nextOauthClients, | ||
| 547 | }, | ||
| 548 | value: undefined, | ||
| 549 | } satisfies StateMutationResult<void>; | ||
| 550 | }), | ||
| 551 | |||
| 552 | removeByWorkspaceAndSourceId: (input: { | ||
| 553 | workspaceId: WorkspaceSourceOauthClient["workspaceId"]; | ||
| 554 | sourceId: WorkspaceSourceOauthClient["sourceId"]; | ||
| 555 | }) => | ||
| 556 | stateManager.mutate((state) => { | ||
| 557 | const nextOauthClients = state.sourceOauthClients.filter( | ||
| 558 | (candidate) => | ||
| 559 | candidate.workspaceId !== input.workspaceId | ||
| 560 | || candidate.sourceId !== input.sourceId, | ||
| 561 | ); | ||
| 562 | |||
| 563 | return { | ||
| 564 | state: { | ||
| 565 | ...state, | ||
| 566 | sourceOauthClients: nextOauthClients, | ||
| 567 | }, | ||
| 568 | value: state.sourceOauthClients.length - nextOauthClients.length, | ||
| 569 | } satisfies StateMutationResult<number>; | ||
| 570 | }), | ||
| 571 | }, | ||
| 572 | |||
| 573 | sourceAuthSessions: { | ||
| 574 | listAll: () => | ||
| 575 | stateManager.read((state) => sortByUpdatedAtAndIdAsc(state.sourceAuthSessions)), | ||
| 576 | |||
| 577 | listByWorkspaceId: (workspaceId: SourceAuthSession["workspaceId"]) => | ||
| 578 | stateManager.read((state) => | ||
| 579 | sortByUpdatedAtAndIdAsc( | ||
| 580 | state.sourceAuthSessions.filter( | ||
| 581 | (session) => session.workspaceId === workspaceId, | ||
| 582 | ), | ||
| 583 | ), | ||
| 584 | ), | ||
| 585 | |||
| 586 | getById: (id: SourceAuthSession["id"]) => | ||
| 587 | stateManager.read((state) => { | ||
| 588 | const session = state.sourceAuthSessions.find( | ||
| 589 | (candidate) => candidate.id === id, | ||
| 590 | ); | ||
| 591 | return session | ||
| 592 | ? Option.some(cloneValue(session)) | ||
| 593 | : Option.none<SourceAuthSession>(); | ||
| 594 | }), | ||
| 595 | |||
| 596 | getByState: (stateValue: SourceAuthSession["state"]) => | ||
| 597 | stateManager.read((state) => { | ||
| 598 | const session = state.sourceAuthSessions.find( | ||
| 599 | (candidate) => candidate.state === stateValue, | ||
| 600 | ); | ||
| 601 | return session | ||
| 602 | ? Option.some(cloneValue(session)) | ||
| 603 | : Option.none<SourceAuthSession>(); | ||
| 604 | }), | ||
| 605 | |||
| 606 | getPendingByWorkspaceSourceAndActor: (input: { | ||
| 607 | workspaceId: SourceAuthSession["workspaceId"]; | ||
| 608 | sourceId: SourceAuthSession["sourceId"]; | ||
| 609 | actorAccountId: SourceAuthSession["actorAccountId"]; | ||
| 610 | credentialSlot?: SourceAuthSession["credentialSlot"]; | ||
| 611 | }) => | ||
| 612 | stateManager.read((state) => { | ||
| 613 | const session = sortByUpdatedAtAndIdAsc( | ||
| 614 | state.sourceAuthSessions.filter( | ||
| 615 | (candidate) => | ||
| 616 | candidate.workspaceId === input.workspaceId | ||
| 617 | && candidate.sourceId === input.sourceId | ||
| 618 | && candidate.status === "pending" | ||
| 619 | && actorEquals(candidate.actorAccountId, input.actorAccountId) | ||
| 620 | && (input.credentialSlot === undefined | ||
| 621 | || candidate.credentialSlot === input.credentialSlot), | ||
| 622 | ), | ||
| 623 | )[0] ?? null; | ||
| 624 | |||
| 625 | return session | ||
| 626 | ? Option.some(cloneValue(session)) | ||
| 627 | : Option.none<SourceAuthSession>(); | ||
| 628 | }), | ||
| 629 | |||
| 630 | insert: (session: SourceAuthSession) => | ||
| 631 | stateManager.mutate((state) => ({ | ||
| 632 | state: { | ||
| 633 | ...state, | ||
| 634 | sourceAuthSessions: [...state.sourceAuthSessions, cloneValue(session)], | ||
| 635 | }, | ||
| 636 | value: undefined, | ||
| 637 | } satisfies StateMutationResult<void>)), | ||
| 638 | |||
| 639 | update: ( | ||
| 640 | id: SourceAuthSession["id"], | ||
| 641 | patch: Partial<Omit<SourceAuthSession, "id" | "workspaceId" | "sourceId" | "createdAt">>, | ||
| 642 | ) => | ||
| 643 | stateManager.mutate((state) => { | ||
| 644 | let updated: SourceAuthSession | null = null; | ||
| 645 | const nextSessions = state.sourceAuthSessions.map((session) => { | ||
| 646 | if (session.id !== id) { | ||
| 647 | return session; | ||
| 648 | } | ||
| 649 | |||
| 650 | updated = { | ||
| 651 | ...session, | ||
| 652 | ...cloneValue(patch), | ||
| 653 | } satisfies SourceAuthSession; | ||
| 654 | return updated; | ||
| 655 | }); | ||
| 656 | |||
| 657 | return { | ||
| 658 | state: { | ||
| 659 | ...state, | ||
| 660 | sourceAuthSessions: nextSessions, | ||
| 661 | }, | ||
| 662 | value: updated ? Option.some(cloneValue(updated)) : Option.none<SourceAuthSession>(), | ||
| 663 | } satisfies StateMutationResult<Option.Option<SourceAuthSession>>; | ||
| 664 | }), | ||
| 665 | |||
| 666 | upsert: (session: SourceAuthSession) => | ||
| 667 | stateManager.mutate((state) => { | ||
| 668 | const nextSessions = state.sourceAuthSessions.filter( | ||
| 669 | (candidate) => candidate.id !== session.id, | ||
| 670 | ); | ||
| 671 | nextSessions.push(cloneValue(session)); | ||
| 672 | |||
| 673 | return { | ||
| 674 | state: { | ||
| 675 | ...state, | ||
| 676 | sourceAuthSessions: nextSessions, | ||
| 677 | }, | ||
| 678 | value: undefined, | ||
| 679 | } satisfies StateMutationResult<void>; | ||
| 680 | }), | ||
| 681 | |||
| 682 | removeByWorkspaceAndSourceId: ( | ||
| 683 | workspaceId: SourceAuthSession["workspaceId"], | ||
| 684 | sourceId: SourceAuthSession["sourceId"], | ||
| 685 | ) => | ||
| 686 | stateManager.mutate((state) => { | ||
| 687 | const nextSessions = state.sourceAuthSessions.filter( | ||
| 688 | (candidate) => | ||
| 689 | candidate.workspaceId !== workspaceId || candidate.sourceId !== sourceId, | ||
| 690 | ); | ||
| 691 | |||
| 692 | return { | ||
| 693 | state: { | ||
| 694 | ...state, | ||
| 695 | sourceAuthSessions: nextSessions, | ||
| 696 | }, | ||
| 697 | value: nextSessions.length !== state.sourceAuthSessions.length, | ||
| 698 | } satisfies StateMutationResult<boolean>; | ||
| 699 | }), | ||
| 700 | }, | ||
| 701 | |||
| 702 | secretMaterials: { | ||
| 703 | getById: (id: SecretMaterial["id"]) => | ||
| 704 | stateManager.read((state) => { | ||
| 705 | const material = state.secretMaterials.find( | ||
| 706 | (candidate) => candidate.id === id, | ||
| 707 | ); | ||
| 708 | return material | ||
| 709 | ? Option.some(cloneValue(material)) | ||
| 710 | : Option.none<SecretMaterial>(); | ||
| 711 | }), | ||
| 712 | |||
| 713 | listAll: () => | ||
| 714 | stateManager.read((state) => sortByUpdatedAtAndIdDesc(state.secretMaterials)), | ||
| 715 | |||
| 716 | upsert: (material: SecretMaterial) => | ||
| 717 | stateManager.mutate((state) => { | ||
| 718 | const nextMaterials = state.secretMaterials.filter( | ||
| 719 | (candidate) => candidate.id !== material.id, | ||
| 720 | ); | ||
| 721 | nextMaterials.push(cloneValue(material)); | ||
| 722 | |||
| 723 | return { | ||
| 724 | state: { | ||
| 725 | ...state, | ||
| 726 | secretMaterials: nextMaterials, | ||
| 727 | }, | ||
| 728 | value: undefined, | ||
| 729 | } satisfies StateMutationResult<void>; | ||
| 730 | }), | ||
| 731 | |||
| 732 | updateById: ( | ||
| 733 | id: SecretMaterial["id"], | ||
| 734 | update: { name?: string | null; value?: string }, | ||
| 735 | ) => | ||
| 736 | stateManager.mutate((state) => { | ||
| 737 | let updated: SecretMaterial | null = null; | ||
| 738 | const nextMaterials = state.secretMaterials.map((material) => { | ||
| 739 | if (material.id !== id) { | ||
| 740 | return material; | ||
| 741 | } | ||
| 742 | |||
| 743 | updated = { | ||
| 744 | ...material, | ||
| 745 | ...(update.name !== undefined ? { name: update.name } : {}), | ||
| 746 | ...(update.value !== undefined ? { value: update.value } : {}), | ||
| 747 | updatedAt: Date.now(), | ||
| 748 | } satisfies SecretMaterial; | ||
| 749 | return updated; | ||
| 750 | }); | ||
| 751 | |||
| 752 | return { | ||
| 753 | state: { | ||
| 754 | ...state, | ||
| 755 | secretMaterials: nextMaterials, | ||
| 756 | }, | ||
| 757 | value: updated ? Option.some(cloneValue(updated)) : Option.none<SecretMaterial>(), | ||
| 758 | } satisfies StateMutationResult<Option.Option<SecretMaterial>>; | ||
| 759 | }), | ||
| 760 | |||
| 761 | removeById: (id: SecretMaterial["id"]) => | ||
| 762 | stateManager.mutate((state) => { | ||
| 763 | const nextMaterials = state.secretMaterials.filter( | ||
| 764 | (candidate) => candidate.id !== id, | ||
| 765 | ); | ||
| 766 | |||
| 767 | return { | ||
| 768 | state: { | ||
| 769 | ...state, | ||
| 770 | secretMaterials: nextMaterials, | ||
| 771 | }, | ||
| 772 | value: nextMaterials.length !== state.secretMaterials.length, | ||
| 773 | } satisfies StateMutationResult<boolean>; | ||
| 774 | }), | ||
| 775 | |||
| 776 | listLinkedSources: () => | ||
| 777 | Effect.gen(function* () { | ||
| 778 | const [state, loadedConfig] = yield* Effect.all([ | ||
| 779 | stateManager.read((current) => current), | ||
| 780 | loadLocalExecutorConfig(context), | ||
| 781 | ]); | ||
| 782 | |||
| 783 | const sourceNames = new Map<string, string>(); | ||
| 784 | for (const [sourceId, source] of Object.entries( | ||
| 785 | loadedConfig.config?.sources ?? {}, | ||
| 786 | )) { | ||
| 787 | sourceNames.set(sourceId, source.name ?? sourceId); | ||
| 788 | } | ||
| 789 | |||
| 790 | const materialIds = new Set( | ||
| 791 | state.secretMaterials.map((material) => String(material.id)), | ||
| 792 | ); | ||
| 793 | const linkedSources = new Map<string, Array<{ sourceId: string; sourceName: string }>>(); | ||
| 794 | |||
| 795 | for (const artifact of state.authArtifacts) { | ||
| 796 | for (const ref of authArtifactSecretMaterialRefs(artifact)) { | ||
| 797 | if (!materialIds.has(ref.handle)) { | ||
| 798 | continue; | ||
| 799 | } | ||
| 800 | |||
| 801 | const existing = linkedSources.get(ref.handle) ?? []; | ||
| 802 | if (!existing.some((link) => link.sourceId === artifact.sourceId)) { | ||
| 803 | existing.push({ | ||
| 804 | sourceId: artifact.sourceId, | ||
| 805 | sourceName: sourceNames.get(artifact.sourceId) ?? artifact.sourceId, | ||
| 806 | }); | ||
| 807 | linkedSources.set(ref.handle, existing); | ||
| 808 | } | ||
| 809 | } | ||
| 810 | } | ||
| 811 | |||
| 812 | return linkedSources; | ||
| 813 | }), | ||
| 814 | }, | ||
| 815 | |||
| 816 | executions: { | ||
| 817 | getById: (executionId: Execution["id"]) => | ||
| 818 | stateManager.read((state) => { | ||
| 819 | const execution = state.executions.find( | ||
| 820 | (candidate) => candidate.id === executionId, | ||
| 821 | ); | ||
| 822 | return execution | ||
| 823 | ? Option.some(cloneValue(execution)) | ||
| 824 | : Option.none<Execution>(); | ||
| 825 | }), | ||
| 826 | |||
| 827 | getByWorkspaceAndId: ( | ||
| 828 | workspaceId: Execution["workspaceId"], | ||
| 829 | executionId: Execution["id"], | ||
| 830 | ) => | ||
| 831 | stateManager.read((state) => { | ||
| 832 | const execution = state.executions.find( | ||
| 833 | (candidate) => | ||
| 834 | candidate.workspaceId === workspaceId && candidate.id === executionId, | ||
| 835 | ); | ||
| 836 | return execution | ||
| 837 | ? Option.some(cloneValue(execution)) | ||
| 838 | : Option.none<Execution>(); | ||
| 839 | }), | ||
| 840 | |||
| 841 | insert: (execution: Execution) => | ||
| 842 | stateManager.mutate((state) => ({ | ||
| 843 | state: { | ||
| 844 | ...state, | ||
| 845 | executions: [...state.executions, cloneValue(execution)], | ||
| 846 | }, | ||
| 847 | value: undefined, | ||
| 848 | } satisfies StateMutationResult<void>)), | ||
| 849 | |||
| 850 | update: ( | ||
| 851 | executionId: Execution["id"], | ||
| 852 | patch: Partial< | ||
| 853 | Omit<Execution, "id" | "workspaceId" | "createdByAccountId" | "createdAt"> | ||
| 854 | >, | ||
| 855 | ) => | ||
| 856 | stateManager.mutate((state) => { | ||
| 857 | let updated: Execution | null = null; | ||
| 858 | const nextExecutions = state.executions.map((execution) => { | ||
| 859 | if (execution.id !== executionId) { | ||
| 860 | return execution; | ||
| 861 | } | ||
| 862 | |||
| 863 | updated = { | ||
| 864 | ...execution, | ||
| 865 | ...cloneValue(patch), | ||
| 866 | } satisfies Execution; | ||
| 867 | return updated; | ||
| 868 | }); | ||
| 869 | |||
| 870 | return { | ||
| 871 | state: { | ||
| 872 | ...state, | ||
| 873 | executions: nextExecutions, | ||
| 874 | }, | ||
| 875 | value: updated ? Option.some(cloneValue(updated)) : Option.none<Execution>(), | ||
| 876 | } satisfies StateMutationResult<Option.Option<Execution>>; | ||
| 877 | }), | ||
| 878 | }, | ||
| 879 | |||
| 880 | executionInteractions: { | ||
| 881 | getById: (interactionId: ExecutionInteraction["id"]) => | ||
| 882 | stateManager.read((state) => { | ||
| 883 | const interaction = state.executionInteractions.find( | ||
| 884 | (candidate) => candidate.id === interactionId, | ||
| 885 | ); | ||
| 886 | return interaction | ||
| 887 | ? Option.some(cloneValue(interaction)) | ||
| 888 | : Option.none<ExecutionInteraction>(); | ||
| 889 | }), | ||
| 890 | |||
| 891 | listByExecutionId: (executionId: ExecutionInteraction["executionId"]) => | ||
| 892 | stateManager.read((state) => | ||
| 893 | sortByUpdatedAtAndIdDesc( | ||
| 894 | state.executionInteractions.filter( | ||
| 895 | (interaction) => interaction.executionId === executionId, | ||
| 896 | ), | ||
| 897 | ), | ||
| 898 | ), | ||
| 899 | |||
| 900 | getPendingByExecutionId: (executionId: ExecutionInteraction["executionId"]) => | ||
| 901 | stateManager.read((state) => { | ||
| 902 | const interaction = sortByUpdatedAtAndIdDesc( | ||
| 903 | state.executionInteractions.filter( | ||
| 904 | (candidate) => | ||
| 905 | candidate.executionId === executionId && candidate.status === "pending", | ||
| 906 | ), | ||
| 907 | )[0] ?? null; | ||
| 908 | |||
| 909 | return interaction | ||
| 910 | ? Option.some(cloneValue(interaction)) | ||
| 911 | : Option.none<ExecutionInteraction>(); | ||
| 912 | }), | ||
| 913 | |||
| 914 | insert: (interaction: ExecutionInteraction) => | ||
| 915 | stateManager.mutate((state) => ({ | ||
| 916 | state: { | ||
| 917 | ...state, | ||
| 918 | executionInteractions: [ | ||
| 919 | ...state.executionInteractions, | ||
| 920 | cloneValue(interaction), | ||
| 921 | ], | ||
| 922 | }, | ||
| 923 | value: undefined, | ||
| 924 | } satisfies StateMutationResult<void>)), | ||
| 925 | |||
| 926 | update: ( | ||
| 927 | interactionId: ExecutionInteraction["id"], | ||
| 928 | patch: Partial< | ||
| 929 | Omit< | ||
| 930 | ExecutionInteraction, | ||
| 931 | "id" | "executionId" | "createdAt" | ||
| 932 | > | ||
| 933 | >, | ||
| 934 | ) => | ||
| 935 | stateManager.mutate((state) => { | ||
| 936 | let updated: ExecutionInteraction | null = null; | ||
| 937 | const nextInteractions = state.executionInteractions.map((interaction) => { | ||
| 938 | if (interaction.id !== interactionId) { | ||
| 939 | return interaction; | ||
| 940 | } | ||
| 941 | |||
| 942 | updated = { | ||
| 943 | ...interaction, | ||
| 944 | ...cloneValue(patch), | ||
| 945 | } as ExecutionInteraction; | ||
| 946 | return updated; | ||
| 947 | }); | ||
| 948 | |||
| 949 | return { | ||
| 950 | state: { | ||
| 951 | ...state, | ||
| 952 | executionInteractions: nextInteractions, | ||
| 953 | }, | ||
| 954 | value: updated | ||
| 955 | ? Option.some(cloneValue(updated)) | ||
| 956 | : Option.none<ExecutionInteraction>(), | ||
| 957 | } satisfies StateMutationResult<Option.Option<ExecutionInteraction>>; | ||
| 958 | }), | ||
| 959 | }, | ||
| 960 | |||
| 961 | executionSteps: { | ||
| 962 | getByExecutionAndSequence: ( | ||
| 963 | executionId: ExecutionStep["executionId"], | ||
| 964 | sequence: ExecutionStep["sequence"], | ||
| 965 | ) => | ||
| 966 | stateManager.read((state) => { | ||
| 967 | const step = state.executionSteps.find( | ||
| 968 | (candidate) => | ||
| 969 | candidate.executionId === executionId && candidate.sequence === sequence, | ||
| 970 | ); | ||
| 971 | return step | ||
| 972 | ? Option.some(cloneValue(step)) | ||
| 973 | : Option.none<ExecutionStep>(); | ||
| 974 | }), | ||
| 975 | |||
| 976 | listByExecutionId: (executionId: ExecutionStep["executionId"]) => | ||
| 977 | stateManager.read((state) => | ||
| 978 | [...state.executionSteps] | ||
| 979 | .filter((step) => step.executionId === executionId) | ||
| 980 | .sort( | ||
| 981 | (left, right) => | ||
| 982 | left.sequence - right.sequence | ||
| 983 | || right.updatedAt - left.updatedAt, | ||
| 984 | ), | ||
| 985 | ), | ||
| 986 | |||
| 987 | insert: (step: ExecutionStep) => | ||
| 988 | stateManager.mutate((state) => ({ | ||
| 989 | state: { | ||
| 990 | ...state, | ||
| 991 | executionSteps: [...state.executionSteps, cloneValue(step)], | ||
| 992 | }, | ||
| 993 | value: undefined, | ||
| 994 | } satisfies StateMutationResult<void>)), | ||
| 995 | |||
| 996 | deleteByExecutionId: (executionId: ExecutionStep["executionId"]) => | ||
| 997 | stateManager.mutate((state) => ({ | ||
| 998 | state: { | ||
| 999 | ...state, | ||
| 1000 | executionSteps: state.executionSteps.filter( | ||
| 1001 | (step) => step.executionId !== executionId, | ||
| 1002 | ), | ||
| 1003 | }, | ||
| 1004 | value: undefined, | ||
| 1005 | } satisfies StateMutationResult<void>)), | ||
| 1006 | |||
| 1007 | updateByExecutionAndSequence: ( | ||
| 1008 | executionId: ExecutionStep["executionId"], | ||
| 1009 | sequence: ExecutionStep["sequence"], | ||
| 1010 | patch: Partial< | ||
| 1011 | Omit< | ||
| 1012 | ExecutionStep, | ||
| 1013 | "id" | "executionId" | "sequence" | "createdAt" | ||
| 1014 | > | ||
| 1015 | >, | ||
| 1016 | ) => | ||
| 1017 | stateManager.mutate((state) => { | ||
| 1018 | let updated: ExecutionStep | null = null; | ||
| 1019 | const nextSteps = state.executionSteps.map((step) => { | ||
| 1020 | if (step.executionId !== executionId || step.sequence !== sequence) { | ||
| 1021 | return step; | ||
| 1022 | } | ||
| 1023 | |||
| 1024 | updated = { | ||
| 1025 | ...step, | ||
| 1026 | ...cloneValue(patch), | ||
| 1027 | } as ExecutionStep; | ||
| 1028 | return updated; | ||
| 1029 | }); | ||
| 1030 | |||
| 1031 | return { | ||
| 1032 | state: { | ||
| 1033 | ...state, | ||
| 1034 | executionSteps: nextSteps, | ||
| 1035 | }, | ||
| 1036 | value: updated | ||
| 1037 | ? Option.some(cloneValue(updated)) | ||
| 1038 | : Option.none<ExecutionStep>(), | ||
| 1039 | } satisfies StateMutationResult<Option.Option<ExecutionStep>>; | ||
| 1040 | }), | ||
| 1041 | }, | ||
| 1042 | }; | ||
| 1043 | }; | ||
| 1044 | |||
| 1045 | export type LocalControlPlaneStore = ReturnType<typeof createLocalControlPlaneStore>; | ||
| 1046 | |||
| 1047 | export const createLocalControlPlanePersistence = ( | ||
| 1048 | context: ResolvedLocalWorkspaceContext, | ||
| 1049 | ): LocalControlPlanePersistence => ({ | ||
| 1050 | rows: createLocalControlPlaneStore(context), | ||
| 1051 | close: async () => {}, | ||
| 1052 | }); | ||
| 1053 | |||
| 1054 | export { localControlPlaneStatePath }; | ||
| @@ -1,4 +1,3 @@ | |||
| 1 | import { ControlPlanePersistenceError } from "#persistence"; | ||
| 2 | import type { | 1 | import type { |
| 3 | Execution, | 2 | Execution, |
| 4 | ExecutionInteraction, | 3 | ExecutionInteraction, |
| @@ -12,13 +11,13 @@ import { · 7 unmodified lines | |||
| 12 | import * as Effect from "effect/Effect"; | 11 | import * as Effect from "effect/Effect"; |
| 13 | import * as Option from "effect/Option"; | 12 | import * as Option from "effect/Option"; |
| 14 | 13 | ||
| 15 | import { loadLocalInstallation } from "./local-installation"; | ||
| 16 | import { submitExecutionInteractionResponse } from "./execution-service"; | 14 | import { submitExecutionInteractionResponse } from "./execution-service"; |
| 17 | import { LiveExecutionManagerService } from "./live-execution"; | 15 | import { LiveExecutionManagerService } from "./live-execution"; |
| 18 | import { | 16 | import { |
| 19 | operationErrors, | 17 | operationErrors, |
| 20 | } from "./operation-errors"; | 18 | } from "./operation-errors"; |
| 21 | import { resolveLocalWorkspaceContext } from "./local-config"; | 19 | import { resolveLocalWorkspaceContext } from "./local-config"; |
| 20 | import { InstallationStore, LocalInstallationStore } from "./local-storage"; | ||
| 22 | import { getRuntimeLocalWorkspaceOption } from "./local-runtime-context"; | 21 | import { getRuntimeLocalWorkspaceOption } from "./local-runtime-context"; |
| 23 | import { RuntimeSourceAuthServiceTag } from "./source-auth-service"; | 22 | import { RuntimeSourceAuthServiceTag } from "./source-auth-service"; |
| 24 | import { | 23 | import { |
| @@ -130,12 +129,10 @@ const loadSourceCredentialInteraction = (input: { · 105 unmodified lines | |||
| 130 | 129 | ||
| 131 | const stored = yield* store.executionInteractions.getById(input.interactionId).pipe( | 130 | const stored = yield* store.executionInteractions.getById(input.interactionId).pipe( |
| 132 | Effect.mapError((error) => | 131 | Effect.mapError((error) => |
| 133 | error instanceof ControlPlanePersistenceError | 132 | input.operation.unknownStorage( |
| 134 | ? input.operation.storage(error) | 133 | error, |
| 135 | : input.operation.unknownStorage( | 134 | `Failed loading execution interaction ${input.interactionId}`, |
| 136 | error, | 135 | ), |
| 137 | `Failed loading execution interaction ${input.interactionId}`, | ||
| 138 | ), | ||
| 139 | ), | 136 | ), |
| 140 | ); | 137 | ); |
| 141 | 138 | ||
| @@ -194,7 +191,13 @@ export const getLocalInstallation = () => · 52 unmodified lines | |||
| 194 | ), | 191 | ), |
| 195 | )); | 192 | )); |
| 196 | 193 | ||
| 197 | return yield* loadLocalInstallation(context); | 194 | const installationStore = runtimeLocalWorkspace !== null |
| 195 | ? yield* InstallationStore | ||
| 196 | : LocalInstallationStore; | ||
| 197 | |||
| 198 | return yield* installationStore.load( | ||
| 199 | context, | ||
| 200 | ); | ||
| 198 | }); | 201 | }); |
| 199 | 202 | ||
| 200 | export const getSourceCredentialInteraction = (input: { | 203 | export const getSourceCredentialInteraction = (input: { |
| @@ -331,12 +334,10 @@ export const submitSourceCredentialInteraction = (input: { · 130 unmodified lines | |||
| 331 | value: token, | 334 | value: token, |
| 332 | }).pipe( | 335 | }).pipe( |
| 333 | Effect.mapError((error) => | 336 | Effect.mapError((error) => |
| 334 | error instanceof ControlPlanePersistenceError | 337 | localOps.sourceCredentialSubmit.unknownStorage( |
| 335 | ? localOps.sourceCredentialSubmit.storage(error) | 338 | error, |
| 336 | : localOps.sourceCredentialSubmit.unknownStorage( | 339 | `Failed storing credential material for interaction ${interaction.interactionId}`, |
| 337 | error, | 340 | ), |
| 338 | `Failed storing credential material for interaction ${interaction.interactionId}`, | ||
| 339 | ), | ||
| 340 | ), | 341 | ), |
| 341 | ); | 342 | ); |
| 342 | const resumed = yield* liveExecutionManager.resolveInteraction({ | 343 | const resumed = yield* liveExecutionManager.resolveInteraction({ |
| @@ -0,0 +1,154 @@ | |||
| 1 | import type { LocalInstallation, LocalExecutorConfig } from "#schema"; | ||
| 2 | import * as Context from "effect/Context"; | ||
| 3 | import type * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | |||
| 6 | import type { | ||
| 7 | LoadedLocalExecutorConfig, | ||
| 8 | ResolvedLocalWorkspaceContext, | ||
| 9 | } from "./local-config"; | ||
| 10 | import { | ||
| 11 | loadLocalExecutorConfig, | ||
| 12 | resolveConfigRelativePath, | ||
| 13 | writeProjectLocalExecutorConfig, | ||
| 14 | } from "./local-config"; | ||
| 15 | import { | ||
| 16 | getOrProvisionLocalInstallation, | ||
| 17 | loadLocalInstallation, | ||
| 18 | } from "./local-installation"; | ||
| 19 | import type { | ||
| 20 | LocalSourceArtifact, | ||
| 21 | } from "./local-source-artifacts"; | ||
| 22 | import { | ||
| 23 | buildLocalSourceArtifact, | ||
| 24 | readLocalSourceArtifact, | ||
| 25 | removeLocalSourceArtifact, | ||
| 26 | writeLocalSourceArtifact, | ||
| 27 | } from "./local-source-artifacts"; | ||
| 28 | import type { LocalWorkspaceState } from "./local-workspace-state"; | ||
| 29 | import { | ||
| 30 | loadLocalWorkspaceState, | ||
| 31 | writeLocalWorkspaceState, | ||
| 32 | } from "./local-workspace-state"; | ||
| 33 | import type { SourceRecipeMaterialization } from "./source-recipe-support"; | ||
| 34 | import type { Source } from "#schema"; | ||
| 35 | |||
| 36 | export type InstallationStoreShape = { | ||
| 37 | load: ( | ||
| 38 | context: ResolvedLocalWorkspaceContext, | ||
| 39 | ) => Effect.Effect<LocalInstallation, never, never>; | ||
| 40 | getOrProvision: (input: { | ||
| 41 | context: ResolvedLocalWorkspaceContext; | ||
| 42 | }) => Effect.Effect<LocalInstallation, never, never>; | ||
| 43 | }; | ||
| 44 | |||
| 45 | export class InstallationStore extends Context.Tag( | ||
| 46 | "#runtime/InstallationStore", | ||
| 47 | )<InstallationStore, InstallationStoreShape>() {} | ||
| 48 | |||
| 49 | export type WorkspaceConfigStoreShape = { | ||
| 50 | load: ( | ||
| 51 | context: ResolvedLocalWorkspaceContext, | ||
| 52 | ) => Effect.Effect<LoadedLocalExecutorConfig, Error, never>; | ||
| 53 | writeProject: (input: { | ||
| 54 | context: ResolvedLocalWorkspaceContext; | ||
| 55 | config: LocalExecutorConfig; | ||
| 56 | }) => Effect.Effect<void, Error, never>; | ||
| 57 | resolveRelativePath: (input: { path: string; workspaceRoot: string }) => string; | ||
| 58 | }; | ||
| 59 | |||
| 60 | export class WorkspaceConfigStore extends Context.Tag( | ||
| 61 | "#runtime/WorkspaceConfigStore", | ||
| 62 | )<WorkspaceConfigStore, WorkspaceConfigStoreShape>() {} | ||
| 63 | |||
| 64 | export type WorkspaceStateStoreShape = { | ||
| 65 | load: ( | ||
| 66 | context: ResolvedLocalWorkspaceContext, | ||
| 67 | ) => Effect.Effect<LocalWorkspaceState, Error, never>; | ||
| 68 | write: (input: { | ||
| 69 | context: ResolvedLocalWorkspaceContext; | ||
| 70 | state: LocalWorkspaceState; | ||
| 71 | }) => Effect.Effect<void, Error, never>; | ||
| 72 | }; | ||
| 73 | |||
| 74 | export class WorkspaceStateStore extends Context.Tag( | ||
| 75 | "#runtime/WorkspaceStateStore", | ||
| 76 | )<WorkspaceStateStore, WorkspaceStateStoreShape>() {} | ||
| 77 | |||
| 78 | export type SourceArtifactStoreShape = { | ||
| 79 | build: (input: { | ||
| 80 | source: Source; | ||
| 81 | materialization: SourceRecipeMaterialization; | ||
| 82 | }) => LocalSourceArtifact; | ||
| 83 | read: (input: { | ||
| 84 | context: ResolvedLocalWorkspaceContext; | ||
| 85 | sourceId: string; | ||
| 86 | }) => Effect.Effect<LocalSourceArtifact | null, Error, never>; | ||
| 87 | write: (input: { | ||
| 88 | context: ResolvedLocalWorkspaceContext; | ||
| 89 | sourceId: string; | ||
| 90 | artifact: LocalSourceArtifact; | ||
| 91 | }) => Effect.Effect<void, Error, never>; | ||
| 92 | remove: (input: { | ||
| 93 | context: ResolvedLocalWorkspaceContext; | ||
| 94 | sourceId: string; | ||
| 95 | }) => Effect.Effect<void, Error, never>; | ||
| 96 | }; | ||
| 97 | |||
| 98 | export class SourceArtifactStore extends Context.Tag( | ||
| 99 | "#runtime/SourceArtifactStore", | ||
| 100 | )<SourceArtifactStore, SourceArtifactStoreShape>() {} | ||
| 101 | |||
| 102 | export type LocalStorageServices = | ||
| 103 | | InstallationStore | ||
| 104 | | WorkspaceConfigStore | ||
| 105 | | WorkspaceStateStore | ||
| 106 | | SourceArtifactStore; | ||
| 107 | |||
| 108 | export type WorkspaceStorageServices = | ||
| 109 | | WorkspaceConfigStore | ||
| 110 | | WorkspaceStateStore | ||
| 111 | | SourceArtifactStore; | ||
| 112 | |||
| 113 | export const LocalInstallationStore: InstallationStoreShape = { | ||
| 114 | load: loadLocalInstallation, | ||
| 115 | getOrProvision: getOrProvisionLocalInstallation, | ||
| 116 | }; | ||
| 117 | |||
| 118 | export const LocalInstallationStoreLive = Layer.succeed( | ||
| 119 | InstallationStore, | ||
| 120 | LocalInstallationStore, | ||
| 121 | ); | ||
| 122 | |||
| 123 | export const LocalWorkspaceConfigStore: WorkspaceConfigStoreShape = { | ||
| 124 | load: loadLocalExecutorConfig, | ||
| 125 | writeProject: writeProjectLocalExecutorConfig, | ||
| 126 | resolveRelativePath: resolveConfigRelativePath, | ||
| 127 | }; | ||
| 128 | |||
| 129 | export const LocalWorkspaceConfigStoreLive = Layer.succeed( | ||
| 130 | WorkspaceConfigStore, | ||
| 131 | LocalWorkspaceConfigStore, | ||
| 132 | ); | ||
| 133 | |||
| 134 | export const LocalWorkspaceStateStore: WorkspaceStateStoreShape = { | ||
| 135 | load: loadLocalWorkspaceState, | ||
| 136 | write: writeLocalWorkspaceState, | ||
| 137 | }; | ||
| 138 | |||
| 139 | export const LocalWorkspaceStateStoreLive = Layer.succeed( | ||
| 140 | WorkspaceStateStore, | ||
| 141 | LocalWorkspaceStateStore, | ||
| 142 | ); | ||
| 143 | |||
| 144 | export const LocalSourceArtifactStore: SourceArtifactStoreShape = { | ||
| 145 | build: buildLocalSourceArtifact, | ||
| 146 | read: readLocalSourceArtifact, | ||
| 147 | write: writeLocalSourceArtifact, | ||
| 148 | remove: removeLocalSourceArtifact, | ||
| 149 | }; | ||
| 150 | |||
| 151 | export const LocalSourceArtifactStoreLive = Layer.succeed( | ||
| 152 | SourceArtifactStore, | ||
| 153 | LocalSourceArtifactStore, | ||
| 154 | ); | ||
| @@ -540,7 +540,7 @@ const importLocalToolModule = (input: { · 539 unmodified lines | |||
| 540 | toolPath: ToolPath; | 540 | toolPath: ToolPath; |
| 541 | }): Effect.Effect<ToolInput, LocalToolImportError | LocalToolDefinitionError> => | 541 | }): Effect.Effect<ToolInput, LocalToolImportError | LocalToolDefinitionError> => |
| 542 | Effect.tryPromise({ | 542 | Effect.tryPromise({ |
| 543 | try: () => import(pathToFileURL(resolve(input.artifactPath)).href), | 543 | try: () => import(/* @vite-ignore */ pathToFileURL(resolve(input.artifactPath)).href), |
| 544 | catch: (cause) => | 544 | catch: (cause) => |
| 545 | new LocalToolImportError({ | 545 | new LocalToolImportError({ |
| 546 | message: `Failed to import local tool ${input.sourcePath}`, | 546 | message: `Failed to import local tool ${input.sourcePath}`, |
| @@ -3,25 +3,8 @@ import { · 2 unmodified lines | |||
| 3 | ControlPlaneNotFoundError, | 3 | ControlPlaneNotFoundError, |
| 4 | ControlPlaneStorageError, | 4 | ControlPlaneStorageError, |
| 5 | } from "../api/errors"; | 5 | } from "../api/errors"; |
| 6 | import { ControlPlanePersistenceError } from "#persistence"; | ||
| 7 | import * as Effect from "effect/Effect"; | 6 | import * as Effect from "effect/Effect"; |
| 8 | 7 | ||
| 9 | const unknownPersistenceError = ( | ||
| 10 | operation: string, | ||
| 11 | cause: unknown, | ||
| 12 | details: string, | ||
| 13 | ): ControlPlanePersistenceError => | ||
| 14 | new ControlPlanePersistenceError({ | ||
| 15 | operation, | ||
| 16 | message: cause instanceof Error ? cause.message : String(cause), | ||
| 17 | details, | ||
| 18 | code: null, | ||
| 19 | constraint: null, | ||
| 20 | table: null, | ||
| 21 | kind: "unknown", | ||
| 22 | cause, | ||
| 23 | }); | ||
| 24 | |||
| 25 | export type OperationErrors<TOperation extends string = string> = { | 8 | export type OperationErrors<TOperation extends string = string> = { |
| 26 | readonly operation: TOperation; | 9 | readonly operation: TOperation; |
| 27 | readonly child: <TSuffix extends string>( | 10 | readonly child: <TSuffix extends string>( |
| @@ -36,18 +19,14 @@ export type OperationErrors<TOperation extends string = string> = { · 8 unmodified lines | |||
| 36 | details: string, | 19 | details: string, |
| 37 | ) => ControlPlaneNotFoundError; | 20 | ) => ControlPlaneNotFoundError; |
| 38 | readonly storage: ( | 21 | readonly storage: ( |
| 39 | error: ControlPlanePersistenceError, | 22 | error: Error, |
| 40 | ) => ControlPlaneStorageError; | 23 | ) => ControlPlaneStorageError; |
| 41 | readonly unknownPersistence: ( | ||
| 42 | cause: unknown, | ||
| 43 | details: string, | ||
| 44 | ) => ControlPlanePersistenceError; | ||
| 45 | readonly unknownStorage: ( | 24 | readonly unknownStorage: ( |
| 46 | cause: unknown, | 25 | cause: unknown, |
| 47 | details: string, | 26 | details: string, |
| 48 | ) => ControlPlaneStorageError; | 27 | ) => ControlPlaneStorageError; |
| 49 | readonly mapStorage: <A>( | 28 | readonly mapStorage: <A, E extends Error>( |
| 50 | effect: Effect.Effect<A, ControlPlanePersistenceError>, | 29 | effect: Effect.Effect<A, E>, |
| 51 | ) => Effect.Effect<A, ControlPlaneStorageError>; | 30 | ) => Effect.Effect<A, ControlPlaneStorageError>; |
| 52 | }; | 31 | }; |
| 53 | 32 | ||
| @@ -76,15 +55,17 @@ export const operationErrors = <TOperation extends string>( · 22 unmodified lines | |||
| 76 | new ControlPlaneStorageError({ | 55 | new ControlPlaneStorageError({ |
| 77 | operation, | 56 | operation, |
| 78 | message: error.message, | 57 | message: error.message, |
| 79 | details: error.details ?? "Persistence operation failed", | 58 | details: error.message, |
| 80 | }), | 59 | }), |
| 81 | unknownPersistence: (cause, details) => | ||
| 82 | unknownPersistenceError(operation, cause, details), | ||
| 83 | unknownStorage: (cause, details) => | 60 | unknownStorage: (cause, details) => |
| 84 | self.storage(self.unknownPersistence(cause, details)), | 61 | self.storage( |
| 62 | cause instanceof Error | ||
| 63 | ? new Error(`${cause.message}: ${details}`) | ||
| 64 | : new Error(details), | ||
| 65 | ), | ||
| 85 | mapStorage: (effect) => | 66 | mapStorage: (effect) => |
| 86 | effect.pipe( | 67 | effect.pipe( |
| 87 | Effect.mapError(self.storage), | 68 | Effect.mapError((error) => self.storage(error)), |
| 88 | ), | 69 | ), |
| 89 | }; | 70 | }; |
| 90 | 71 | ||
| @@ -2,7 +2,6 @@ import { · 1 unmodified line | |||
| 2 | ControlPlaneBadRequestError, | 2 | ControlPlaneBadRequestError, |
| 3 | ControlPlaneStorageError, | 3 | ControlPlaneStorageError, |
| 4 | } from "../api/errors"; | 4 | } from "../api/errors"; |
| 5 | import { ControlPlanePersistenceError } from "#persistence"; | ||
| 6 | import * as Effect from "effect/Effect"; | 5 | import * as Effect from "effect/Effect"; |
| 7 | 6 | ||
| 8 | import { | 7 | import { |
| @@ -11,26 +10,14 @@ import { · 2 unmodified lines | |||
| 11 | } from "./operation-errors"; | 10 | } from "./operation-errors"; |
| 12 | export type Mutable<T> = { -readonly [K in keyof T]: T[K] }; | 11 | export type Mutable<T> = { -readonly [K in keyof T]: T[K] }; |
| 13 | 12 | ||
| 14 | const isUniqueViolation = (error: ControlPlanePersistenceError): boolean => | 13 | export const mapPersistenceError = <A, R>( |
| 15 | error.kind === "unique_violation"; | ||
| 16 | |||
| 17 | export const mapPersistenceError = <A>( | ||
| 18 | operation: OperationErrorsLike, | 14 | operation: OperationErrorsLike, |
| 19 | effect: Effect.Effect<A, ControlPlanePersistenceError | Error>, | 15 | effect: Effect.Effect<A, Error, R>, |
| 20 | ): Effect.Effect<A, ControlPlaneBadRequestError | ControlPlaneStorageError> => | 16 | ): Effect.Effect<A, ControlPlaneBadRequestError | ControlPlaneStorageError, R> => |
| 21 | effect.pipe( | 17 | effect.pipe( |
| 22 | Effect.mapError((error) => { | 18 | Effect.mapError((error) => { |
| 23 | const errors = asOperationErrors(operation); | 19 | const errors = asOperationErrors(operation); |
| 24 | if (error instanceof ControlPlanePersistenceError) { | 20 | return errors.storage(error); |
| 25 | return isUniqueViolation(error) | ||
| 26 | ? errors.badRequest("Unique constraint violation", error.details ?? "duplicate key") | ||
| 27 | : errors.storage(error); | ||
| 28 | } | ||
| 29 | |||
| 30 | return errors.unknownStorage( | ||
| 31 | error, | ||
| 32 | error.message, | ||
| 33 | ); | ||
| 34 | }), | 21 | }), |
| 35 | ); | 22 | ); |
| 36 | 23 | ||
| @@ -13,17 +13,21 @@ import { · 12 unmodified lines | |||
| 13 | } from "#schema"; | 13 | } from "#schema"; |
| 14 | import * as Effect from "effect/Effect"; | 14 | import * as Effect from "effect/Effect"; |
| 15 | 15 | ||
| 16 | import { | ||
| 17 | loadLocalExecutorConfig, | ||
| 18 | writeProjectLocalExecutorConfig, | ||
| 19 | } from "./local-config"; | ||
| 20 | import { | 16 | import { |
| 21 | requireRuntimeLocalWorkspace, | 17 | requireRuntimeLocalWorkspace, |
| 22 | } from "./local-runtime-context"; | 18 | } from "./local-runtime-context"; |
| 19 | import type { | ||
| 20 | WorkspaceStorageServices, | ||
| 21 | WorkspaceConfigStoreShape, | ||
| 22 | WorkspaceStateStoreShape, | ||
| 23 | } from "./local-storage"; | ||
| 24 | import { | ||
| 25 | WorkspaceConfigStore, | ||
| 26 | WorkspaceStateStore, | ||
| 27 | } from "./local-storage"; | ||
| 28 | import type { ResolvedLocalWorkspaceContext } from "./local-config"; | ||
| 23 | import { | 29 | import { |
| 24 | loadLocalWorkspaceState, | ||
| 25 | type LocalWorkspaceState, | 30 | type LocalWorkspaceState, |
| 26 | writeLocalWorkspaceState, | ||
| 27 | } from "./local-workspace-state"; | 31 | } from "./local-workspace-state"; |
| 28 | import { | 32 | import { |
| 29 | derivePolicyConfigKey, | 33 | derivePolicyConfigKey, |
| @@ -77,8 +81,14 @@ const toLocalWorkspacePolicy = (input: { · 47 unmodified lines | |||
| 77 | export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) => | 81 | export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) => |
| 78 | Effect.gen(function* () { | 82 | Effect.gen(function* () { |
| 79 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId); | 83 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId); |
| 80 | const loadedConfig = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context); | 84 | const workspaceConfigStore = yield* WorkspaceConfigStore; |
| 81 | const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context); | 85 | const workspaceStateStore = yield* WorkspaceStateStore; |
| 86 | const loadedConfig = yield* workspaceConfigStore.load( | ||
| 87 | runtimeLocalWorkspace.context, | ||
| 88 | ); | ||
| 89 | const workspaceState = yield* workspaceStateStore.load( | ||
| 90 | runtimeLocalWorkspace.context, | ||
| 91 | ); | ||
| 82 | 92 | ||
| 83 | const policies = Object.entries(loadedConfig.config?.policies ?? {}).map(([key, policyConfig]) => | 93 | const policies = Object.entries(loadedConfig.config?.policies ?? {}).map(([key, policyConfig]) => |
| 84 | toLocalWorkspacePolicy({ | 94 | toLocalWorkspacePolicy({ |
| @@ -99,16 +109,18 @@ export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) => · 14 unmodified lines | |||
| 99 | 109 | ||
| 100 | const writeLocalPolicyFiles = (input: { | 110 | const writeLocalPolicyFiles = (input: { |
| 101 | operation: OperationErrors; | 111 | operation: OperationErrors; |
| 102 | context: Parameters<typeof writeProjectLocalExecutorConfig>[0]["context"]; | 112 | context: ResolvedLocalWorkspaceContext; |
| 113 | workspaceConfigStore: WorkspaceConfigStoreShape; | ||
| 114 | workspaceStateStore: WorkspaceStateStoreShape; | ||
| 103 | projectConfig: LocalExecutorConfig; | 115 | projectConfig: LocalExecutorConfig; |
| 104 | workspaceState: LocalWorkspaceState; | 116 | workspaceState: LocalWorkspaceState; |
| 105 | }) => | 117 | }) => |
| 106 | Effect.all([ | 118 | Effect.all([ |
| 107 | writeProjectLocalExecutorConfig({ | 119 | input.workspaceConfigStore.writeProject({ |
| 108 | context: input.context, | 120 | context: input.context, |
| 109 | config: input.projectConfig, | 121 | config: input.projectConfig, |
| 110 | }), | 122 | }), |
| 111 | writeLocalWorkspaceState({ | 123 | input.workspaceStateStore.write({ |
| 112 | context: input.context, | 124 | context: input.context, |
| 113 | state: input.workspaceState, | 125 | state: input.workspaceState, |
| 114 | }), | 126 | }), |
| @@ -154,6 +166,8 @@ export const createPolicy = (input: { · 39 unmodified lines | |||
| 154 | }) => | 166 | }) => |
| 155 | Effect.gen(function* () { | 167 | Effect.gen(function* () { |
| 156 | const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(policyOps.create, input.workspaceId); | 168 | const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(policyOps.create, input.workspaceId); |
| 169 | const workspaceConfigStore = yield* WorkspaceConfigStore; | ||
| 170 | const workspaceStateStore = yield* WorkspaceStateStore; | ||
| 157 | const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe( | 171 | const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe( |
| 158 | Effect.mapError((cause) => | 172 | Effect.mapError((cause) => |
| 159 | policyOps.create.unknownStorage( | 173 | policyOps.create.unknownStorage( |
| @@ -201,6 +215,8 @@ export const createPolicy = (input: { · 41 unmodified lines | |||
| 201 | yield* writeLocalPolicyFiles({ | 215 | yield* writeLocalPolicyFiles({ |
| 202 | operation: policyOps.create, | 216 | operation: policyOps.create, |
| 203 | context: runtimeLocalWorkspace.context, | 217 | context: runtimeLocalWorkspace.context, |
| 218 | workspaceConfigStore, | ||
| 219 | workspaceStateStore, | ||
| 204 | projectConfig: { | 220 | projectConfig: { |
| 205 | ...projectConfig, | 221 | ...projectConfig, |
| 206 | policies, | 222 | policies, |
| @@ -250,6 +266,8 @@ export const updatePolicy = (input: { · 43 unmodified lines | |||
| 250 | }) => | 266 | }) => |
| 251 | Effect.gen(function* () { | 267 | Effect.gen(function* () { |
| 252 | const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(policyOps.update, input.workspaceId); | 268 | const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(policyOps.update, input.workspaceId); |
| 269 | const workspaceConfigStore = yield* WorkspaceConfigStore; | ||
| 270 | const workspaceStateStore = yield* WorkspaceStateStore; | ||
| 253 | const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe( | 271 | const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe( |
| 254 | Effect.mapError((cause) => | 272 | Effect.mapError((cause) => |
| 255 | policyOps.update.unknownStorage( | 273 | policyOps.update.unknownStorage( |
| @@ -300,6 +318,8 @@ export const updatePolicy = (input: { · 44 unmodified lines | |||
| 300 | yield* writeLocalPolicyFiles({ | 318 | yield* writeLocalPolicyFiles({ |
| 301 | operation: policyOps.update, | 319 | operation: policyOps.update, |
| 302 | context: runtimeLocalWorkspace.context, | 320 | context: runtimeLocalWorkspace.context, |
| 321 | workspaceConfigStore, | ||
| 322 | workspaceStateStore, | ||
| 303 | projectConfig: { | 323 | projectConfig: { |
| 304 | ...projectConfig, | 324 | ...projectConfig, |
| 305 | policies, | 325 | policies, |
| @@ -322,6 +342,8 @@ export const removePolicy = (input: { · 16 unmodified lines | |||
| 322 | }) => | 342 | }) => |
| 323 | Effect.gen(function* () { | 343 | Effect.gen(function* () { |
| 324 | const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(policyOps.remove, input.workspaceId); | 344 | const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(policyOps.remove, input.workspaceId); |
| 345 | const workspaceConfigStore = yield* WorkspaceConfigStore; | ||
| 346 | const workspaceStateStore = yield* WorkspaceStateStore; | ||
| 325 | const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe( | 347 | const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe( |
| 326 | Effect.mapError((cause) => | 348 | Effect.mapError((cause) => |
| 327 | policyOps.remove.unknownStorage( | 349 | policyOps.remove.unknownStorage( |
| @@ -343,6 +365,8 @@ export const removePolicy = (input: { · 15 unmodified lines | |||
| 343 | yield* writeLocalPolicyFiles({ | 365 | yield* writeLocalPolicyFiles({ |
| 344 | operation: policyOps.remove, | 366 | operation: policyOps.remove, |
| 345 | context: runtimeLocalWorkspace.context, | 367 | context: runtimeLocalWorkspace.context, |
| 368 | workspaceConfigStore, | ||
| 369 | workspaceStateStore, | ||
| 346 | projectConfig: { | 370 | projectConfig: { |
| 347 | ...projectConfig, | 371 | ...projectConfig, |
| 348 | policies, | 372 | policies, |
| @@ -4,7 +4,6 @@ import { promises as fs } from "node:fs"; · 3 unmodified lines | |||
| 4 | import { lstatSync, realpathSync } from "node:fs"; | 4 | import { lstatSync, realpathSync } from "node:fs"; |
| 5 | import { isAbsolute } from "node:path"; | 5 | import { isAbsolute } from "node:path"; |
| 6 | 6 | ||
| 7 | import type { SqlControlPlaneRows } from "#persistence"; | ||
| 8 | import { | 7 | import { |
| 9 | type LocalConfigSecretProvider, | 8 | type LocalConfigSecretProvider, |
| 10 | type LocalExecutorConfig, | 9 | type LocalExecutorConfig, |
| @@ -17,15 +16,16 @@ import * as Option from "effect/Option"; · 6 unmodified lines | |||
| 17 | 16 | ||
| 18 | import { resolveConfigRelativePath } from "./local-config"; | 17 | import { resolveConfigRelativePath } from "./local-config"; |
| 19 | import { fromConfigSecretProviderId } from "./local-config-secrets"; | 18 | import { fromConfigSecretProviderId } from "./local-config-secrets"; |
| 19 | import type { ControlPlaneStoreShape } from "./store"; | ||
| 20 | 20 | ||
| 21 | export const ENV_SECRET_PROVIDER_ID = "env"; | 21 | export const ENV_SECRET_PROVIDER_ID = "env"; |
| 22 | export const PARAMS_SECRET_PROVIDER_ID = "params"; | 22 | export const PARAMS_SECRET_PROVIDER_ID = "params"; |
| 23 | export const KEYCHAIN_SECRET_PROVIDER_ID = "keychain"; | 23 | export const KEYCHAIN_SECRET_PROVIDER_ID = "keychain"; |
| 24 | export const POSTGRES_SECRET_PROVIDER_ID = "postgres"; | 24 | export const LOCAL_SECRET_PROVIDER_ID = "local"; |
| 25 | 25 | ||
| 26 | export type SecretStoreProviderId = | 26 | export type SecretStoreProviderId = |
| 27 | | typeof KEYCHAIN_SECRET_PROVIDER_ID | 27 | | typeof KEYCHAIN_SECRET_PROVIDER_ID |
| 28 | | typeof POSTGRES_SECRET_PROVIDER_ID; | 28 | | typeof LOCAL_SECRET_PROVIDER_ID; |
| 29 | 29 | ||
| 30 | export type SecretMaterialResolveContext = { | 30 | export type SecretMaterialResolveContext = { |
| 31 | params?: Readonly<Record<string, string | undefined>>; | 31 | params?: Readonly<Record<string, string | undefined>>; |
| @@ -47,7 +47,7 @@ export type DeleteSecretMaterial = ( · 15 unmodified lines | |||
| 47 | ) => Effect.Effect<boolean, Error, never>; | 47 | ) => Effect.Effect<boolean, Error, never>; |
| 48 | 48 | ||
| 49 | type SecretMaterialProviderRuntime = { | 49 | type SecretMaterialProviderRuntime = { |
| 50 | rows: SqlControlPlaneRows; | 50 | rows: ControlPlaneStoreShape; |
| 51 | env: NodeJS.ProcessEnv; | 51 | env: NodeJS.ProcessEnv; |
| 52 | dangerouslyAllowEnvSecrets: boolean; | 52 | dangerouslyAllowEnvSecrets: boolean; |
| 53 | keychainServiceName: string; | 53 | keychainServiceName: string; |
| @@ -106,8 +106,10 @@ export const parseSecretStoreProviderId = (value: string | undefined): SecretSto · 52 unmodified lines | |||
| 106 | return KEYCHAIN_SECRET_PROVIDER_ID; | 106 | return KEYCHAIN_SECRET_PROVIDER_ID; |
| 107 | } | 107 | } |
| 108 | 108 | ||
| 109 | if (normalized === POSTGRES_SECRET_PROVIDER_ID) { | 109 | if ( |
| 110 | return POSTGRES_SECRET_PROVIDER_ID; | 110 | normalized === LOCAL_SECRET_PROVIDER_ID |
| 111 | ) { | ||
| 112 | return LOCAL_SECRET_PROVIDER_ID; | ||
| 111 | } | 113 | } |
| 112 | 114 | ||
| 113 | return null; | 115 | return null; |
| @@ -119,7 +121,7 @@ const resolveDangerouslyAllowEnvSecrets = (value: boolean | undefined): boolean · 5 unmodified lines | |||
| 119 | const resolveSecretStoreProviderId = (value: SecretStoreProviderId | undefined): SecretStoreProviderId => | 121 | const resolveSecretStoreProviderId = (value: SecretStoreProviderId | undefined): SecretStoreProviderId => |
| 120 | value | 122 | value |
| 121 | ?? parseSecretStoreProviderId(process.env[SECRET_STORE_PROVIDER_ENV]) | 123 | ?? parseSecretStoreProviderId(process.env[SECRET_STORE_PROVIDER_ENV]) |
| 122 | ?? POSTGRES_SECRET_PROVIDER_ID; | 124 | ?? LOCAL_SECRET_PROVIDER_ID; |
| 123 | 125 | ||
| 124 | const resolveKeychainServiceName = (value: string | undefined): string => | 126 | const resolveKeychainServiceName = (value: string | undefined): string => |
| 125 | trimOrNull(value) | 127 | trimOrNull(value) |
| @@ -239,7 +241,7 @@ const createEnvSecretMaterialProvider = (): SecretMaterialProvider => ({ · 113 unmodified lines | |||
| 239 | remove: () => Effect.succeed(false), | 241 | remove: () => Effect.succeed(false), |
| 240 | }); | 242 | }); |
| 241 | 243 | ||
| 242 | const createPostgresSecretMaterialProvider = (): SecretMaterialProvider => ({ | 244 | const createLocalSecretMaterialProvider = (): SecretMaterialProvider => ({ |
| 243 | resolve: ({ ref, runtime }) => | 245 | resolve: ({ ref, runtime }) => |
| 244 | Effect.gen(function* () { | 246 | Effect.gen(function* () { |
| 245 | const materialId = SecretMaterialIdSchema.make(ref.handle); | 247 | const materialId = SecretMaterialIdSchema.make(ref.handle); |
| @@ -265,7 +267,7 @@ const createPostgresSecretMaterialProvider = (): SecretMaterialProvider => ({ · 19 unmodified lines | |||
| 265 | }); | 267 | }); |
| 266 | 268 | ||
| 267 | return { | 269 | return { |
| 268 | providerId: POSTGRES_SECRET_PROVIDER_ID, | 270 | providerId: LOCAL_SECRET_PROVIDER_ID, |
| 269 | handle: id, | 271 | handle: id, |
| 270 | } satisfies SecretRef; | 272 | } satisfies SecretRef; |
| 271 | }), | 273 | }), |
| @@ -467,7 +469,7 @@ const createSecretMaterialProviderRegistry = (): SecretMaterialProviderRegistry · 195 unmodified lines | |||
| 467 | [PARAMS_SECRET_PROVIDER_ID, createParamsSecretMaterialProvider()], | 469 | [PARAMS_SECRET_PROVIDER_ID, createParamsSecretMaterialProvider()], |
| 468 | [ENV_SECRET_PROVIDER_ID, createEnvSecretMaterialProvider()], | 470 | [ENV_SECRET_PROVIDER_ID, createEnvSecretMaterialProvider()], |
| 469 | [KEYCHAIN_SECRET_PROVIDER_ID, createKeychainSecretMaterialProvider()], | 471 | [KEYCHAIN_SECRET_PROVIDER_ID, createKeychainSecretMaterialProvider()], |
| 470 | [POSTGRES_SECRET_PROVIDER_ID, createPostgresSecretMaterialProvider()], | 472 | [LOCAL_SECRET_PROVIDER_ID, createLocalSecretMaterialProvider()], |
| 471 | ]); | 473 | ]); |
| 472 | 474 | ||
| 473 | const getSecretMaterialProvider = (input: { | 475 | const getSecretMaterialProvider = (input: { |
| @@ -483,7 +485,7 @@ const getSecretMaterialProvider = (input: { · 9 unmodified lines | |||
| 483 | }; | 485 | }; |
| 484 | 486 | ||
| 485 | const createSecretMaterialProviderRuntime = (input: { | 487 | const createSecretMaterialProviderRuntime = (input: { |
| 486 | rows: SqlControlPlaneRows; | 488 | rows: ControlPlaneStoreShape; |
| 487 | dangerouslyAllowEnvSecrets?: boolean; | 489 | dangerouslyAllowEnvSecrets?: boolean; |
| 488 | keychainServiceName?: string; | 490 | keychainServiceName?: string; |
| 489 | localConfig?: LocalExecutorConfig | null; | 491 | localConfig?: LocalExecutorConfig | null; |
| @@ -644,7 +646,7 @@ const resolveConfiguredSecretProvider = (input: { · 154 unmodified lines | |||
| 644 | }; | 646 | }; |
| 645 | 647 | ||
| 646 | export const createDefaultSecretMaterialResolver = (input: { | 648 | export const createDefaultSecretMaterialResolver = (input: { |
| 647 | rows: SqlControlPlaneRows; | 649 | rows: ControlPlaneStoreShape; |
| 648 | dangerouslyAllowEnvSecrets?: boolean; | 650 | dangerouslyAllowEnvSecrets?: boolean; |
| 649 | keychainServiceName?: string; | 651 | keychainServiceName?: string; |
| 650 | localConfig?: LocalExecutorConfig | null; | 652 | localConfig?: LocalExecutorConfig | null; |
| @@ -682,7 +684,7 @@ export const createDefaultSecretMaterialResolver = (input: { · 31 unmodified lines | |||
| 682 | }; | 684 | }; |
| 683 | 685 | ||
| 684 | export const createDefaultSecretMaterialStorer = (input: { | 686 | export const createDefaultSecretMaterialStorer = (input: { |
| 685 | rows: SqlControlPlaneRows; | 687 | rows: ControlPlaneStoreShape; |
| 686 | storeProviderId?: SecretStoreProviderId; | 688 | storeProviderId?: SecretStoreProviderId; |
| 687 | dangerouslyAllowEnvSecrets?: boolean; | 689 | dangerouslyAllowEnvSecrets?: boolean; |
| 688 | keychainServiceName?: string; | 690 | keychainServiceName?: string; |
| @@ -714,7 +716,7 @@ export const createDefaultSecretMaterialStorer = (input: { · 25 unmodified lines | |||
| 714 | }; | 716 | }; |
| 715 | 717 | ||
| 716 | export const createDefaultSecretMaterialDeleter = (input: { | 718 | export const createDefaultSecretMaterialDeleter = (input: { |
| 717 | rows: SqlControlPlaneRows; | 719 | rows: ControlPlaneStoreShape; |
| 718 | dangerouslyAllowEnvSecrets?: boolean; | 720 | dangerouslyAllowEnvSecrets?: boolean; |
| 719 | keychainServiceName?: string; | 721 | keychainServiceName?: string; |
| 720 | }): DeleteSecretMaterial => { | 722 | }): DeleteSecretMaterial => { |
| @@ -5,7 +5,6 @@ import { · 4 unmodified lines | |||
| 5 | makeToolInvokerFromTools, | 5 | makeToolInvokerFromTools, |
| 6 | typeSignatureFromSchemaJson, | 6 | typeSignatureFromSchemaJson, |
| 7 | } from "@executor/codemode-core"; | 7 | } from "@executor/codemode-core"; |
| 8 | import type { SqlControlPlaneRows } from "#persistence"; | ||
| 9 | import type { | 8 | import type { |
| 10 | GraphqlToolManifest, | 9 | GraphqlToolManifest, |
| 11 | } from "../graphql-tools"; | 10 | } from "../graphql-tools"; |
| @@ -41,7 +40,6 @@ import { · 29 unmodified lines | |||
| 41 | import type { | 40 | import type { |
| 42 | SourceAdapter, | 41 | SourceAdapter, |
| 43 | SourceAdapterMaterialization, | 42 | SourceAdapterMaterialization, |
| 44 | SourceAdapterRepairRevisionInput, | ||
| 45 | } from "./types"; | 43 | } from "./types"; |
| 46 | import { | 44 | import { |
| 47 | ConnectHttpAuthSchema, | 45 | ConnectHttpAuthSchema, |
| @@ -285,75 +283,6 @@ const primaryDocument = ( · 237 unmodified lines | |||
| 285 | documents.find((document) => document.documentKind === "graphql_introspection") | 283 | documents.find((document) => document.documentKind === "graphql_introspection") |
| 286 | ?? null; | 284 | ?? null; |
| 287 | 285 | ||
| 288 | export const rebuildGraphqlRecipeRevisionContent = (input: { | ||
| 289 | rows: SqlControlPlaneRows; | ||
| 290 | sourceRecord: SourceAdapterRepairRevisionInput["sourceRecord"]; | ||
| 291 | revision: SourceAdapterRepairRevisionInput["revision"]; | ||
| 292 | documents: readonly StoredSourceRecipeDocumentRecord[]; | ||
| 293 | }): Effect.Effect<void, Error, never> => | ||
| 294 | Effect.gen(function* () { | ||
| 295 | const manifest = | ||
| 296 | input.revision.manifestJson !== null | ||
| 297 | ? yield* parseJsonValue<GraphqlToolManifest>({ | ||
| 298 | label: `GraphQL manifest for ${input.sourceRecord.id}`, | ||
| 299 | value: input.revision.manifestJson, | ||
| 300 | }) | ||
| 301 | : null; | ||
| 302 | const document = primaryDocument(input.documents); | ||
| 303 | const resolvedManifest = | ||
| 304 | manifest | ||
| 305 | ?? (document !== null | ||
| 306 | ? yield* extractGraphqlManifest( | ||
| 307 | input.sourceRecord.name, | ||
| 308 | document.contentText, | ||
| 309 | ) | ||
| 310 | : null); | ||
| 311 | |||
| 312 | if (resolvedManifest === null) { | ||
| 313 | return yield* Effect.fail( | ||
| 314 | new Error( | ||
| 315 | `Missing GraphQL manifest and document for ${input.sourceRecord.id}`, | ||
| 316 | ), | ||
| 317 | ); | ||
| 318 | } | ||
| 319 | |||
| 320 | const now = Math.max(input.revision.updatedAt, input.sourceRecord.updatedAt); | ||
| 321 | const definitions = compileGraphqlToolDefinitions(resolvedManifest); | ||
| 322 | const schemaBundle = toGraphqlSchemaBundleRecord({ | ||
| 323 | recipeRevisionId: input.revision.id, | ||
| 324 | manifest: resolvedManifest, | ||
| 325 | now, | ||
| 326 | }); | ||
| 327 | |||
| 328 | yield* input.rows.sourceRecipeRevisions.update(input.revision.id, { | ||
| 329 | manifestJson: JSON.stringify(resolvedManifest), | ||
| 330 | manifestHash: resolvedManifest.sourceHash, | ||
| 331 | updatedAt: now, | ||
| 332 | }); | ||
| 333 | yield* input.rows.sourceRecipeOperations.replaceForRevision({ | ||
| 334 | recipeRevisionId: input.revision.id, | ||
| 335 | operations: definitions.map((definition) => | ||
| 336 | toGraphqlRecipeOperationRecord({ | ||
| 337 | recipeRevisionId: input.revision.id, | ||
| 338 | definition, | ||
| 339 | manifest: resolvedManifest, | ||
| 340 | now, | ||
| 341 | }), | ||
| 342 | ), | ||
| 343 | }); | ||
| 344 | yield* input.rows.sourceRecipeSchemaBundles.replaceForRevision({ | ||
| 345 | recipeRevisionId: input.revision.id, | ||
| 346 | bundles: schemaBundle ? [schemaBundle] : [], | ||
| 347 | }); | ||
| 348 | |||
| 349 | if (input.sourceRecord.sourceHash !== resolvedManifest.sourceHash) { | ||
| 350 | yield* input.rows.sources.update(input.sourceRecord.workspaceId, input.sourceRecord.id, { | ||
| 351 | sourceHash: resolvedManifest.sourceHash, | ||
| 352 | updatedAt: now, | ||
| 353 | }); | ||
| 354 | } | ||
| 355 | }); | ||
| 356 | |||
| 357 | export const graphqlSourceAdapter: SourceAdapter = { | 286 | export const graphqlSourceAdapter: SourceAdapter = { |
| 358 | key: "graphql", | 287 | key: "graphql", |
| 359 | displayName: "GraphQL", | 288 | displayName: "GraphQL", |
| @@ -9,7 +9,6 @@ import { · 8 unmodified lines | |||
| 9 | type McpToolManifest, | 9 | type McpToolManifest, |
| 10 | type McpToolManifestEntry, | 10 | type McpToolManifestEntry, |
| 11 | } from "@executor/codemode-mcp"; | 11 | } from "@executor/codemode-mcp"; |
| 12 | import type { SqlControlPlaneRows } from "#persistence"; | ||
| 13 | import type { | 12 | import type { |
| 14 | Source, | 13 | Source, |
| 15 | SourceRecipeRevisionId, | 14 | SourceRecipeRevisionId, |
| @@ -25,7 +24,6 @@ import * as Schema from "effect/Schema"; · 9 unmodified lines | |||
| 25 | import { | 24 | import { |
| 26 | contentHash, | 25 | contentHash, |
| 27 | normalizeSearchText, | 26 | normalizeSearchText, |
| 28 | persistRecipeMaterialization, | ||
| 29 | type SourceRecipeMaterialization, | 27 | type SourceRecipeMaterialization, |
| 30 | } from "../source-recipe-support"; | 28 | } from "../source-recipe-support"; |
| 31 | import { namespaceFromSourceName } from "../source-names"; | 29 | import { namespaceFromSourceName } from "../source-names"; |
| @@ -170,23 +168,6 @@ const toMcpRecipeOperationRecord = (input: { · 138 unmodified lines | |||
| 170 | updatedAt: input.now, | 168 | updatedAt: input.now, |
| 171 | }); | 169 | }); |
| 172 | 170 | ||
| 173 | export const persistMcpRecipeRevisionFromManifestEntries = (input: { | ||
| 174 | rows: SqlControlPlaneRows; | ||
| 175 | source: Source; | ||
| 176 | manifestEntries: readonly McpToolManifestEntry[]; | ||
| 177 | }): Effect.Effect<void, Error, never> => | ||
| 178 | Effect.gen(function* () { | ||
| 179 | yield* persistRecipeMaterialization({ | ||
| 180 | rows: input.rows, | ||
| 181 | source: input.source, | ||
| 182 | materialization: materializationFromMcpManifestEntries({ | ||
| 183 | recipeRevisionId: "src_recipe_rev_materialization" as SourceRecipeRevisionId, | ||
| 184 | endpoint: input.source.endpoint, | ||
| 185 | manifestEntries: input.manifestEntries, | ||
| 186 | }), | ||
| 187 | }); | ||
| 188 | }); | ||
| 189 | |||
| 190 | export const materializationFromMcpManifestEntries = (input: { | 171 | export const materializationFromMcpManifestEntries = (input: { |
| 191 | recipeRevisionId: SourceRecipeRevisionId; | 172 | recipeRevisionId: SourceRecipeRevisionId; |
| 192 | endpoint: string; | 173 | endpoint: string; |
| @@ -3,7 +3,6 @@ import { · 2 unmodified lines | |||
| 3 | HttpClient, | 3 | HttpClient, |
| 4 | HttpClientRequest, | 4 | HttpClientRequest, |
| 5 | } from "@effect/platform"; | 5 | } from "@effect/platform"; |
| 6 | import type { SqlControlPlaneRows } from "#persistence"; | ||
| 7 | import { | 6 | import { |
| 8 | applyCookiePlacementsToHeaders, | 7 | applyCookiePlacementsToHeaders, |
| 9 | applyHttpQueryPlacementsToUrl, | 8 | applyHttpQueryPlacementsToUrl, |
| @@ -41,7 +40,6 @@ import { · 31 unmodified lines | |||
| 41 | import type { | 40 | import type { |
| 42 | SourceAdapter, | 41 | SourceAdapter, |
| 43 | SourceAdapterMaterialization, | 42 | SourceAdapterMaterialization, |
| 44 | SourceAdapterRepairRevisionInput, | ||
| 45 | } from "./types"; | 43 | } from "./types"; |
| 46 | import { | 44 | import { |
| 47 | ConnectHttpAuthSchema, | 45 | ConnectHttpAuthSchema, |
| @@ -330,74 +328,6 @@ const primaryDocument = ( · 282 unmodified lines | |||
| 330 | ): StoredSourceRecipeDocumentRecord | null => | 328 | ): StoredSourceRecipeDocumentRecord | null => |
| 331 | documents.find((document) => document.documentKind === "openapi") ?? null; | 329 | documents.find((document) => document.documentKind === "openapi") ?? null; |
| 332 | 330 | ||
| 333 | export const rebuildOpenApiRecipeRevisionContent = (input: { | ||
| 334 | rows: SqlControlPlaneRows; | ||
| 335 | sourceRecord: SourceAdapterRepairRevisionInput["sourceRecord"]; | ||
| 336 | revision: SourceAdapterRepairRevisionInput["revision"]; | ||
| 337 | documents: readonly StoredSourceRecipeDocumentRecord[]; | ||
| 338 | }): Effect.Effect<void, Error, never> => | ||
| 339 | Effect.gen(function* () { | ||
| 340 | const manifest = | ||
| 341 | input.revision.manifestJson !== null | ||
| 342 | ? ((yield* parseJsonValue<OpenApiToolManifest>({ | ||
| 343 | label: `OpenAPI manifest for ${input.sourceRecord.id}`, | ||
| 344 | value: input.revision.manifestJson, | ||
| 345 | })) as OpenApiToolManifest | null) | ||
| 346 | : null; | ||
| 347 | const document = primaryDocument(input.documents); | ||
| 348 | const resolvedManifest = | ||
| 349 | manifest | ||
| 350 | ?? (document !== null | ||
| 351 | ? yield* extractOpenApiManifest( | ||
| 352 | input.sourceRecord.name, | ||
| 353 | document.contentText, | ||
| 354 | ) | ||
| 355 | : null); | ||
| 356 | |||
| 357 | if (resolvedManifest === null) { | ||
| 358 | return yield* Effect.fail( | ||
| 359 | new Error( | ||
| 360 | `Missing OpenAPI manifest and document for ${input.sourceRecord.id}`, | ||
| 361 | ), | ||
| 362 | ); | ||
| 363 | } | ||
| 364 | |||
| 365 | const now = Math.max(input.revision.updatedAt, input.sourceRecord.updatedAt); | ||
| 366 | const definitions = compileOpenApiToolDefinitions(resolvedManifest); | ||
| 367 | const schemaBundle = toOpenApiSchemaBundleRecord({ | ||
| 368 | recipeRevisionId: input.revision.id, | ||
| 369 | refTable: resolvedManifest.refHintTable, | ||
| 370 | now, | ||
| 371 | }); | ||
| 372 | |||
| 373 | yield* input.rows.sourceRecipeRevisions.update(input.revision.id, { | ||
| 374 | manifestJson: JSON.stringify(resolvedManifest), | ||
| 375 | manifestHash: resolvedManifest.sourceHash, | ||
| 376 | updatedAt: now, | ||
| 377 | }); | ||
| 378 | yield* input.rows.sourceRecipeOperations.replaceForRevision({ | ||
| 379 | recipeRevisionId: input.revision.id, | ||
| 380 | operations: definitions.map((definition) => | ||
| 381 | toOpenApiRecipeOperationRecord({ | ||
| 382 | recipeRevisionId: input.revision.id, | ||
| 383 | definition, | ||
| 384 | now, | ||
| 385 | }), | ||
| 386 | ), | ||
| 387 | }); | ||
| 388 | yield* input.rows.sourceRecipeSchemaBundles.replaceForRevision({ | ||
| 389 | recipeRevisionId: input.revision.id, | ||
| 390 | bundles: schemaBundle ? [schemaBundle] : [], | ||
| 391 | }); | ||
| 392 | |||
| 393 | if (input.sourceRecord.sourceHash !== resolvedManifest.sourceHash) { | ||
| 394 | yield* input.rows.sources.update(input.sourceRecord.workspaceId, input.sourceRecord.id, { | ||
| 395 | sourceHash: resolvedManifest.sourceHash, | ||
| 396 | updatedAt: now, | ||
| 397 | }); | ||
| 398 | } | ||
| 399 | }); | ||
| 400 | |||
| 401 | export const openApiSourceAdapter: SourceAdapter = { | 331 | export const openApiSourceAdapter: SourceAdapter = { |
| 402 | key: "openapi", | 332 | key: "openapi", |
| 403 | displayName: "OpenAPI", | 333 | displayName: "OpenAPI", |
| @@ -3,7 +3,6 @@ import type { · 2 unmodified lines | |||
| 3 | ToolDescriptor, | 3 | ToolDescriptor, |
| 4 | ToolSchemaBundle, | 4 | ToolSchemaBundle, |
| 5 | } from "@executor/codemode-core"; | 5 | } from "@executor/codemode-core"; |
| 6 | import type { SqlControlPlaneRows } from "#persistence"; | ||
| 7 | import type { | 6 | import type { |
| 8 | AccountId, | 7 | AccountId, |
| 9 | CredentialSlot, | 8 | CredentialSlot, |
| @@ -25,6 +24,7 @@ import * as Schema from "effect/Schema"; · 15 unmodified lines | |||
| 25 | 24 | ||
| 26 | import type { ResolvedSourceAuthMaterial } from "../source-auth-material"; | 25 | import type { ResolvedSourceAuthMaterial } from "../source-auth-material"; |
| 27 | import type { ResolveSecretMaterial } from "../secret-material-providers"; | 26 | import type { ResolveSecretMaterial } from "../secret-material-providers"; |
| 27 | import type { ControlPlaneStoreShape } from "../store"; | ||
| 28 | 28 | ||
| 29 | export type SourceAdapterFamily = "http_api" | "mcp" | "internal"; | 29 | export type SourceAdapterFamily = "http_api" | "mcp" | "internal"; |
| 30 | export type SourceAdapterInputSchema = Schema.Schema<any, any, never>; | 30 | export type SourceAdapterInputSchema = Schema.Schema<any, any, never>; |
| @@ -81,17 +81,8 @@ export type SourceAdapterOauth2SetupConfig = { · 50 unmodified lines | |||
| 81 | authorizationParams?: Readonly<Record<string, string>>; | 81 | authorizationParams?: Readonly<Record<string, string>>; |
| 82 | }; | 82 | }; |
| 83 | 83 | ||
| 84 | export type SourceAdapterRepairRevisionInput = { | ||
| 85 | rows: SqlControlPlaneRows; | ||
| 86 | sourceRecord: StoredSourceRecord; | ||
| 87 | revision: StoredSourceRecipeRevisionRecord; | ||
| 88 | documents: readonly StoredSourceRecipeDocumentRecord[]; | ||
| 89 | operations: readonly StoredSourceRecipeOperationRecord[]; | ||
| 90 | schemaBundles: readonly ToolSchemaBundle[]; | ||
| 91 | }; | ||
| 92 | |||
| 93 | export type SourceAdapterInvokePersistedToolInput = { | 84 | export type SourceAdapterInvokePersistedToolInput = { |
| 94 | rows: SqlControlPlaneRows; | 85 | rows: ControlPlaneStoreShape; |
| 95 | workspaceId: Source["workspaceId"]; | 86 | workspaceId: Source["workspaceId"]; |
| 96 | accountId: AccountId; | 87 | accountId: AccountId; |
| 97 | source: Source; | 88 | source: Source; |
| @@ -64,7 +64,7 @@ describe("source-auth-material", () => { · 63 unmodified lines | |||
| 64 | headerName: "X-Api-Key", | 64 | headerName: "X-Api-Key", |
| 65 | prefix: "Token ", | 65 | prefix: "Token ", |
| 66 | token: { | 66 | token: { |
| 67 | providerId: "postgres", | 67 | providerId: "local", |
| 68 | handle: "sec_bearer", | 68 | handle: "sec_bearer", |
| 69 | }, | 69 | }, |
| 70 | }, | 70 | }, |
| @@ -75,7 +75,7 @@ describe("source-auth-material", () => { · 4 unmodified lines | |||
| 75 | }, | 75 | }, |
| 76 | })); | 76 | })); |
| 77 | 77 | ||
| 78 | expect(calls).toEqual(["postgres:sec_bearer"]); | 78 | expect(calls).toEqual(["local:sec_bearer"]); |
| 79 | expect(auth).toEqual({ | 79 | expect(auth).toEqual({ |
| 80 | placements: [ | 80 | placements: [ |
| 81 | { | 81 | { |
| @@ -110,11 +110,11 @@ describe("source-auth-material", () => { · 28 unmodified lines | |||
| 110 | headerName: "Authorization", | 110 | headerName: "Authorization", |
| 111 | prefix: "Bearer ", | 111 | prefix: "Bearer ", |
| 112 | accessToken: { | 112 | accessToken: { |
| 113 | providerId: "postgres", | 113 | providerId: "local", |
| 114 | handle: "sec_access", | 114 | handle: "sec_access", |
| 115 | }, | 115 | }, |
| 116 | refreshToken: { | 116 | refreshToken: { |
| 117 | providerId: "postgres", | 117 | providerId: "local", |
| 118 | handle: "sec_refresh", | 118 | handle: "sec_refresh", |
| 119 | }, | 119 | }, |
| 120 | }, | 120 | }, |
| @@ -125,7 +125,7 @@ describe("source-auth-material", () => { · 4 unmodified lines | |||
| 125 | }, | 125 | }, |
| 126 | })); | 126 | })); |
| 127 | 127 | ||
| 128 | expect(calls).toEqual(["postgres:sec_access"]); | 128 | expect(calls).toEqual(["local:sec_access"]); |
| 129 | expect(auth).toEqual({ | 129 | expect(auth).toEqual({ |
| 130 | placements: [ | 130 | placements: [ |
| 131 | { | 131 | { |
| @@ -1,4 +1,3 @@ | |||
| 1 | import type { SqlControlPlaneRows } from "#persistence"; | ||
| 2 | import type { | 1 | import type { |
| 3 | AccountId, | 2 | AccountId, |
| 4 | CredentialSlot, | 3 | CredentialSlot, |
| @@ -8,6 +7,7 @@ import type { · 3 unmodified lines | |||
| 8 | import * as Effect from "effect/Effect"; | 7 | import * as Effect from "effect/Effect"; |
| 9 | import * as Option from "effect/Option"; | 8 | import * as Option from "effect/Option"; |
| 10 | 9 | ||
| 10 | import type { ControlPlaneStoreShape } from "./store"; | ||
| 11 | import { | 11 | import { |
| 12 | authArtifactFromSourceAuth, | 12 | authArtifactFromSourceAuth, |
| 13 | resolveAuthArtifactMaterial, | 13 | resolveAuthArtifactMaterial, |
| @@ -42,7 +42,7 @@ export const resolveSourceAuthMaterial = (input: { · 28 unmodified lines | |||
| 42 | source: Source; | 42 | source: Source; |
| 43 | slot?: CredentialSlot; | 43 | slot?: CredentialSlot; |
| 44 | actorAccountId?: AccountId | null; | 44 | actorAccountId?: AccountId | null; |
| 45 | rows?: SqlControlPlaneRows; | 45 | rows?: ControlPlaneStoreShape; |
| 46 | resolveSecretMaterial: ResolveSecretMaterial; | 46 | resolveSecretMaterial: ResolveSecretMaterial; |
| 47 | context?: SecretMaterialResolveContext; | 47 | context?: SecretMaterialResolveContext; |
| 48 | }): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> => | 48 | }): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> => |
| @@ -1,608 +0,0 @@ | |||
| 1 | import { mkdtempSync } from "node:fs"; | ||
| 2 | import { createServer } from "node:http"; | ||
| 3 | import { tmpdir } from "node:os"; | ||
| 4 | import { join } from "node:path"; | ||
| 5 | |||
| 6 | import { describe, expect, it } from "vitest"; | ||
| 7 | import * as Schema from "effect/Schema"; | ||
| 8 | import * as Effect from "effect/Effect"; | ||
| 9 | import * as Option from "effect/Option"; | ||
| 10 | |||
| 11 | import { | ||
| 12 | AccountIdSchema, | ||
| 13 | McpSourceAuthSessionDataJsonSchema, | ||
| 14 | OAuth2PkceSourceAuthSessionDataJsonSchema, | ||
| 15 | SecretMaterialIdSchema, | ||
| 16 | type Source, | ||
| 17 | type SourceAuthSession, | ||
| 18 | WorkspaceIdSchema, | ||
| 19 | decodeAuthLeasePlacementTemplates, | ||
| 20 | decodeBuiltInAuthArtifactConfig, | ||
| 21 | } from "#schema"; | ||
| 22 | import { | ||
| 23 | createSqlControlPlanePersistence, | ||
| 24 | type SqlControlPlanePersistence, | ||
| 25 | } from "#persistence"; | ||
| 26 | |||
| 27 | import { | ||
| 28 | createRuntimeSourceAuthService, | ||
| 29 | createTerminalSourceAuthSessionPatch, | ||
| 30 | } from "./source-auth-service"; | ||
| 31 | import { createLiveExecutionManager } from "./live-execution"; | ||
| 32 | import { | ||
| 33 | loadLocalExecutorConfig, | ||
| 34 | resolveLocalWorkspaceContext, | ||
| 35 | } from "./local-config"; | ||
| 36 | import { | ||
| 37 | readLocalSourceArtifact, | ||
| 38 | } from "./local-source-artifacts"; | ||
| 39 | import { | ||
| 40 | RuntimeLocalWorkspaceService, | ||
| 41 | type RuntimeLocalWorkspaceState, | ||
| 42 | } from "./local-runtime-context"; | ||
| 43 | import { createDefaultSecretMaterialResolver } from "./secret-material-providers"; | ||
| 44 | import { resolveSourceAuthMaterial } from "./source-auth-material"; | ||
| 45 | |||
| 46 | const makeExistingOpenApiSource = (auth: Source["auth"]): Source => ({ | ||
| 47 | id: "src_test" as Source["id"], | ||
| 48 | workspaceId: "ws_test" as Source["workspaceId"], | ||
| 49 | name: "GitHub", | ||
| 50 | kind: "openapi", | ||
| 51 | endpoint: "https://api.github.com", | ||
| 52 | status: "connected", | ||
| 53 | enabled: true, | ||
| 54 | namespace: "github", | ||
| 55 | bindingVersion: 1, | ||
| 56 | binding: { | ||
| 57 | specUrl: "https://example.com/openapi.json", | ||
| 58 | defaultHeaders: null, | ||
| 59 | }, | ||
| 60 | importAuthPolicy: "reuse_runtime", | ||
| 61 | importAuth: { kind: "none" }, | ||
| 62 | auth, | ||
| 63 | sourceHash: null, | ||
| 64 | lastError: null, | ||
| 65 | createdAt: 1, | ||
| 66 | updatedAt: 1, | ||
| 67 | }); | ||
| 68 | |||
| 69 | const fetchLiveDiscoveryDocument = async (): Promise<string> => { | ||
| 70 | const response = await fetch("https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest", { | ||
| 71 | signal: AbortSignal.timeout(20_000), | ||
| 72 | }); | ||
| 73 | if (!response.ok) { | ||
| 74 | throw new Error(`Failed to fetch Sheets discovery doc: ${response.status}`); | ||
| 75 | } | ||
| 76 | |||
| 77 | return response.text(); | ||
| 78 | }; | ||
| 79 | |||
| 80 | const withGoogleAuthTestServer = async <T>(input: { | ||
| 81 | discoveryDocument: string; | ||
| 82 | handler: (server: { | ||
| 83 | discoveryUrl: string; | ||
| 84 | tokenUrl: string; | ||
| 85 | tokenRequests: Array<URLSearchParams>; | ||
| 86 | }) => Promise<T>; | ||
| 87 | }): Promise<T> => { | ||
| 88 | const tokenRequests: Array<URLSearchParams> = []; | ||
| 89 | const server = createServer(async (request, response) => { | ||
| 90 | const requestUrl = new URL( | ||
| 91 | request.url ?? "/", | ||
| 92 | "http://127.0.0.1", | ||
| 93 | ); | ||
| 94 | |||
| 95 | if (request.method === "GET" && requestUrl.pathname === "/discovery") { | ||
| 96 | response.statusCode = 200; | ||
| 97 | response.setHeader("content-type", "application/json"); | ||
| 98 | response.end(input.discoveryDocument); | ||
| 99 | return; | ||
| 100 | } | ||
| 101 | |||
| 102 | if (request.method === "POST" && requestUrl.pathname === "/token") { | ||
| 103 | const chunks: Buffer[] = []; | ||
| 104 | for await (const chunk of request) { | ||
| 105 | chunks.push(Buffer.from(chunk)); | ||
| 106 | } | ||
| 107 | const form = new URLSearchParams(Buffer.concat(chunks).toString("utf8")); | ||
| 108 | tokenRequests.push(form); | ||
| 109 | |||
| 110 | response.statusCode = 200; | ||
| 111 | response.setHeader("content-type", "application/json"); | ||
| 112 | if (form.get("grant_type") === "authorization_code") { | ||
| 113 | response.end( | ||
| 114 | JSON.stringify({ | ||
| 115 | access_token: "access-token-initial", | ||
| 116 | refresh_token: "refresh-token-live", | ||
| 117 | expires_in: 3600, | ||
| 118 | scope: "https://www.googleapis.com/auth/spreadsheets.readonly", | ||
| 119 | token_type: "Bearer", | ||
| 120 | }), | ||
| 121 | ); | ||
| 122 | return; | ||
| 123 | } | ||
| 124 | |||
| 125 | response.end( | ||
| 126 | JSON.stringify({ | ||
| 127 | access_token: "access-token-refreshed", | ||
| 128 | expires_in: 3600, | ||
| 129 | token_type: "Bearer", | ||
| 130 | }), | ||
| 131 | ); | ||
| 132 | return; | ||
| 133 | } | ||
| 134 | |||
| 135 | response.statusCode = 404; | ||
| 136 | response.end(); | ||
| 137 | }); | ||
| 138 | |||
| 139 | await new Promise<void>((resolve, reject) => { | ||
| 140 | server.listen(0, "127.0.0.1", (error?: Error) => { | ||
| 141 | if (error) { | ||
| 142 | reject(error); | ||
| 143 | return; | ||
| 144 | } | ||
| 145 | resolve(); | ||
| 146 | }); | ||
| 147 | }); | ||
| 148 | |||
| 149 | try { | ||
| 150 | const address = server.address(); | ||
| 151 | if (!address || typeof address === "string") { | ||
| 152 | throw new Error("Failed to resolve Google auth test server address"); | ||
| 153 | } | ||
| 154 | const baseUrl = `http://127.0.0.1:${address.port}`; | ||
| 155 | return await input.handler({ | ||
| 156 | discoveryUrl: `${baseUrl}/discovery`, | ||
| 157 | tokenUrl: `${baseUrl}/token`, | ||
| 158 | tokenRequests, | ||
| 159 | }); | ||
| 160 | } finally { | ||
| 161 | await new Promise<void>((resolve, reject) => { | ||
| 162 | server.close((error) => { | ||
| 163 | if (error) { | ||
| 164 | reject(error); | ||
| 165 | return; | ||
| 166 | } | ||
| 167 | resolve(); | ||
| 168 | }); | ||
| 169 | }); | ||
| 170 | } | ||
| 171 | }; | ||
| 172 | |||
| 173 | const makePersistence = async (): Promise<SqlControlPlanePersistence> => | ||
| 174 | Effect.runPromise( | ||
| 175 | createSqlControlPlanePersistence({ | ||
| 176 | localDataDir: ":memory:", | ||
| 177 | }), | ||
| 178 | ); | ||
| 179 | |||
| 180 | const makeRuntimeLocalWorkspaceState = async ( | ||
| 181 | workspaceId: ReturnType<typeof WorkspaceIdSchema.make>, | ||
| 182 | accountId: ReturnType<typeof AccountIdSchema.make>, | ||
| 183 | ): Promise<RuntimeLocalWorkspaceState> => | ||
| 184 | Effect.runPromise( | ||
| 185 | Effect.gen(function* () { | ||
| 186 | const workspaceRoot = mkdtempSync( | ||
| 187 | join(tmpdir(), "executor-source-auth-service-"), | ||
| 188 | ); | ||
| 189 | const context = yield* resolveLocalWorkspaceContext({ workspaceRoot }); | ||
| 190 | const loadedConfig = yield* loadLocalExecutorConfig(context); | ||
| 191 | |||
| 192 | return { | ||
| 193 | context, | ||
| 194 | installation: { | ||
| 195 | workspaceId, | ||
| 196 | accountId, | ||
| 197 | }, | ||
| 198 | loadedConfig, | ||
| 199 | } satisfies RuntimeLocalWorkspaceState; | ||
| 200 | }), | ||
| 201 | ); | ||
| 202 | |||
| 203 | describe("source-auth-service", () => { | ||
| 204 | const encodeSessionDataJson = Schema.encodeSync(McpSourceAuthSessionDataJsonSchema); | ||
| 205 | |||
| 206 | const baseSessionDataJson = encodeSessionDataJson({ | ||
| 207 | kind: "mcp_oauth", | ||
| 208 | endpoint: "https://example.com/resource", | ||
| 209 | redirectUri: "http://127.0.0.1/callback", | ||
| 210 | scope: null, | ||
| 211 | resourceMetadataUrl: "https://example.com/resource", | ||
| 212 | authorizationServerUrl: "https://example.com/as", | ||
| 213 | resourceMetadata: { | ||
| 214 | issuer: "https://example.com", | ||
| 215 | }, | ||
| 216 | authorizationServerMetadata: { | ||
| 217 | token_endpoint: "https://example.com/token", | ||
| 218 | }, | ||
| 219 | clientInformation: { | ||
| 220 | client_id: "abc", | ||
| 221 | }, | ||
| 222 | codeVerifier: "verifier", | ||
| 223 | authorizationUrl: "https://example.com/auth", | ||
| 224 | }); | ||
| 225 | |||
| 226 | it("clears ephemeral OAuth session fields when failing a session", () => { | ||
| 227 | const patch = createTerminalSourceAuthSessionPatch({ | ||
| 228 | sessionDataJson: baseSessionDataJson, | ||
| 229 | status: "failed", | ||
| 230 | now: 123, | ||
| 231 | errorText: "OAuth authorization failed", | ||
| 232 | }); | ||
| 233 | |||
| 234 | expect(patch).toMatchObject({ | ||
| 235 | sessionDataJson: baseSessionDataJson, | ||
| 236 | status: "failed", | ||
| 237 | errorText: "OAuth authorization failed", | ||
| 238 | completedAt: 123, | ||
| 239 | updatedAt: 123, | ||
| 240 | }); | ||
| 241 | }); | ||
| 242 | |||
| 243 | it("clears ephemeral OAuth session fields when completing a session", () => { | ||
| 244 | const patch = createTerminalSourceAuthSessionPatch({ | ||
| 245 | sessionDataJson: baseSessionDataJson, | ||
| 246 | status: "completed", | ||
| 247 | now: 456, | ||
| 248 | errorText: null, | ||
| 249 | }); | ||
| 250 | |||
| 251 | expect(patch).toMatchObject({ | ||
| 252 | sessionDataJson: baseSessionDataJson, | ||
| 253 | status: "completed", | ||
| 254 | errorText: null, | ||
| 255 | completedAt: 456, | ||
| 256 | updatedAt: 456, | ||
| 257 | }); | ||
| 258 | }); | ||
| 259 | |||
| 260 | it("completes Google Discovery desktop OAuth into a refreshable auth artifact and lease", async () => { | ||
| 261 | const discoveryDocument = await fetchLiveDiscoveryDocument(); | ||
| 262 | |||
| 263 | const persistence = await makePersistence(); | ||
| 264 | try { | ||
| 265 | const workspaceId = WorkspaceIdSchema.make("ws_google_oauth"); | ||
| 266 | const accountId = AccountIdSchema.make("acc_google_oauth"); | ||
| 267 | const runtimeLocalWorkspace = await makeRuntimeLocalWorkspaceState( | ||
| 268 | workspaceId, | ||
| 269 | accountId, | ||
| 270 | ); | ||
| 271 | |||
| 272 | const service = createRuntimeSourceAuthService({ | ||
| 273 | rows: persistence.rows, | ||
| 274 | liveExecutionManager: createLiveExecutionManager(), | ||
| 275 | getLocalServerBaseUrl: () => "http://localhost:8788", | ||
| 276 | localConfig: runtimeLocalWorkspace.loadedConfig.config, | ||
| 277 | workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot, | ||
| 278 | localWorkspaceState: runtimeLocalWorkspace, | ||
| 279 | }); | ||
| 280 | |||
| 281 | await withGoogleAuthTestServer({ | ||
| 282 | discoveryDocument, | ||
| 283 | handler: async ({ discoveryUrl, tokenUrl, tokenRequests }) => { | ||
| 284 | const originalFetch = globalThis.fetch; | ||
| 285 | globalThis.fetch = (async (input, init) => { | ||
| 286 | const url = typeof input === "string" | ||
| 287 | ? input | ||
| 288 | : input instanceof URL | ||
| 289 | ? input.toString() | ||
| 290 | : input.url; | ||
| 291 | if (url === "https://oauth2.googleapis.com/token") { | ||
| 292 | return originalFetch(tokenUrl, init); | ||
| 293 | } | ||
| 294 | |||
| 295 | return originalFetch(input as any, init); | ||
| 296 | }) as typeof fetch; | ||
| 297 | |||
| 298 | try { | ||
| 299 | const addResult = await Effect.runPromise( | ||
| 300 | service.addExecutorSource({ | ||
| 301 | kind: "google_discovery", | ||
| 302 | workspaceId, | ||
| 303 | actorAccountId: accountId, | ||
| 304 | executionId: null, | ||
| 305 | interactionId: null, | ||
| 306 | service: "sheets", | ||
| 307 | version: "v4", | ||
| 308 | discoveryUrl, | ||
| 309 | scopes: ["https://www.googleapis.com/auth/spreadsheets.readonly"], | ||
| 310 | oauthClient: { | ||
| 311 | clientId: "google-test-client", | ||
| 312 | clientSecret: "google-test-secret", | ||
| 313 | }, | ||
| 314 | }).pipe( | ||
| 315 | Effect.provideService( | ||
| 316 | RuntimeLocalWorkspaceService, | ||
| 317 | runtimeLocalWorkspace, | ||
| 318 | ), | ||
| 319 | ), | ||
| 320 | ); | ||
| 321 | |||
| 322 | expect(addResult.kind).toBe("oauth_required"); | ||
| 323 | if (addResult.kind !== "oauth_required") { | ||
| 324 | return; | ||
| 325 | } | ||
| 326 | |||
| 327 | const sessionOption = await Effect.runPromise( | ||
| 328 | persistence.rows.sourceAuthSessions.getById(addResult.sessionId), | ||
| 329 | ); | ||
| 330 | expect(Option.isSome(sessionOption)).toBe(true); | ||
| 331 | const session = Option.getOrNull(sessionOption) as SourceAuthSession; | ||
| 332 | const oauthClientOption = await Effect.runPromise( | ||
| 333 | persistence.rows.sourceOauthClients.getByWorkspaceSourceAndProvider({ | ||
| 334 | workspaceId, | ||
| 335 | sourceId: addResult.source.id, | ||
| 336 | providerKey: "google_workspace", | ||
| 337 | }), | ||
| 338 | ); | ||
| 339 | expect(Option.isSome(oauthClientOption)).toBe(true); | ||
| 340 | const oauthClient = Option.getOrNull(oauthClientOption)!; | ||
| 341 | expect(oauthClient.clientId).toBe("google-test-client"); | ||
| 342 | expect(oauthClient.clientSecretProviderId).toBe("postgres"); | ||
| 343 | expect(oauthClient.clientSecretHandle).toBeTruthy(); | ||
| 344 | expect(session.providerKind).toBe("oauth2_pkce"); | ||
| 345 | const sessionData = Schema.decodeUnknownSync( | ||
| 346 | OAuth2PkceSourceAuthSessionDataJsonSchema, | ||
| 347 | )(session.sessionDataJson); | ||
| 348 | expect(sessionData.redirectUri).toMatch(/^http:\/\/127\.0\.0\.1:\d+$/); | ||
| 349 | expect(sessionData.tokenEndpoint).toBe("https://oauth2.googleapis.com/token"); | ||
| 350 | expect(sessionData.authorizationUrl).toContain( | ||
| 351 | encodeURIComponent(sessionData.redirectUri), | ||
| 352 | ); | ||
| 353 | |||
| 354 | const loopbackResponse = await fetch( | ||
| 355 | `${sessionData.redirectUri}/?state=${encodeURIComponent(session.state)}&code=loopback-code`, | ||
| 356 | { | ||
| 357 | redirect: "manual", | ||
| 358 | }, | ||
| 359 | ); | ||
| 360 | expect(loopbackResponse.status).toBe(302); | ||
| 361 | const completionLocation = loopbackResponse.headers.get("location"); | ||
| 362 | expect(completionLocation).toBeTruthy(); | ||
| 363 | if (!completionLocation) { | ||
| 364 | return; | ||
| 365 | } | ||
| 366 | const completionUrl = new URL(completionLocation); | ||
| 367 | expect(completionUrl.origin).toBe("http://localhost:8788"); | ||
| 368 | expect(completionUrl.searchParams.get("state")).toBe(session.state); | ||
| 369 | expect(completionUrl.searchParams.get("code")).toBe("loopback-code"); | ||
| 370 | |||
| 371 | const connectedSource = await Effect.runPromise( | ||
| 372 | service.completeSourceCredentialSetup({ | ||
| 373 | workspaceId, | ||
| 374 | sourceId: addResult.source.id, | ||
| 375 | actorAccountId: accountId, | ||
| 376 | state: session.state, | ||
| 377 | code: "authorization-code", | ||
| 378 | }), | ||
| 379 | ); | ||
| 380 | |||
| 381 | expect(connectedSource.status).toBe("connected"); | ||
| 382 | expect(connectedSource.auth.kind).toBe("oauth2_authorized_user"); | ||
| 383 | if (connectedSource.auth.kind !== "oauth2_authorized_user") { | ||
| 384 | return; | ||
| 385 | } | ||
| 386 | expect(connectedSource.auth.clientId).toBe("google-test-client"); | ||
| 387 | expect(connectedSource.auth.tokenEndpoint).toBe("https://oauth2.googleapis.com/token"); | ||
| 388 | expect(connectedSource.auth.grantSet).toEqual([ | ||
| 389 | "https://www.googleapis.com/auth/spreadsheets.readonly", | ||
| 390 | ]); | ||
| 391 | |||
| 392 | expect(tokenRequests).toHaveLength(1); | ||
| 393 | expect(tokenRequests[0]?.get("grant_type")).toBe("authorization_code"); | ||
| 394 | expect(tokenRequests[0]?.get("client_id")).toBe("google-test-client"); | ||
| 395 | expect(tokenRequests[0]?.get("client_secret")).toBe("google-test-secret"); | ||
| 396 | expect(tokenRequests[0]?.get("code")).toBe("authorization-code"); | ||
| 397 | expect(tokenRequests[0]?.get("redirect_uri")).toBe(sessionData.redirectUri); | ||
| 398 | expect(tokenRequests[0]?.get("code_verifier")).toBe(sessionData.codeVerifier); | ||
| 399 | |||
| 400 | const artifactOption = await Effect.runPromise( | ||
| 401 | persistence.rows.authArtifacts.getByWorkspaceSourceAndActor({ | ||
| 402 | workspaceId, | ||
| 403 | sourceId: connectedSource.id, | ||
| 404 | actorAccountId: accountId, | ||
| 405 | slot: "runtime", | ||
| 406 | }), | ||
| 407 | ); | ||
| 408 | expect(Option.isSome(artifactOption)).toBe(true); | ||
| 409 | const artifact = Option.getOrNull(artifactOption)!; | ||
| 410 | const decodedArtifact = decodeBuiltInAuthArtifactConfig(artifact); | ||
| 411 | expect(decodedArtifact?.artifactKind).toBe("oauth2_authorized_user"); | ||
| 412 | |||
| 413 | const leaseOption = await Effect.runPromise( | ||
| 414 | persistence.rows.authLeases.getByAuthArtifactId(artifact.id), | ||
| 415 | ); | ||
| 416 | expect(Option.isSome(leaseOption)).toBe(true); | ||
| 417 | const initialLease = Option.getOrNull(leaseOption)!; | ||
| 418 | const initialTemplates = decodeAuthLeasePlacementTemplates(initialLease); | ||
| 419 | expect(initialTemplates).toEqual([ | ||
| 420 | { | ||
| 421 | location: "header", | ||
| 422 | name: "Authorization", | ||
| 423 | parts: [ | ||
| 424 | { | ||
| 425 | kind: "literal", | ||
| 426 | value: "Bearer ", | ||
| 427 | }, | ||
| 428 | { | ||
| 429 | kind: "secret_ref", | ||
| 430 | ref: { | ||
| 431 | providerId: "postgres", | ||
| 432 | handle: expect.any(String), | ||
| 433 | }, | ||
| 434 | }, | ||
| 435 | ], | ||
| 436 | }, | ||
| 437 | ]); | ||
| 438 | |||
| 439 | const resolver = createDefaultSecretMaterialResolver({ | ||
| 440 | rows: persistence.rows, | ||
| 441 | }); | ||
| 442 | const resolvedInitialAuth = await Effect.runPromise( | ||
| 443 | resolveSourceAuthMaterial({ | ||
| 444 | source: connectedSource, | ||
| 445 | slot: "runtime", | ||
| 446 | actorAccountId: accountId, | ||
| 447 | rows: persistence.rows, | ||
| 448 | resolveSecretMaterial: resolver, | ||
| 449 | }), | ||
| 450 | ); | ||
| 451 | expect(resolvedInitialAuth.headers.Authorization).toBe( | ||
| 452 | "Bearer access-token-initial", | ||
| 453 | ); | ||
| 454 | |||
| 455 | await Effect.runPromise( | ||
| 456 | persistence.rows.authLeases.upsert({ | ||
| 457 | ...initialLease, | ||
| 458 | refreshAfter: Date.now() - 1, | ||
| 459 | expiresAt: Date.now() + 10_000, | ||
| 460 | updatedAt: Date.now(), | ||
| 461 | }), | ||
| 462 | ); | ||
| 463 | |||
| 464 | const previousAccessTokenHandle = initialTemplates?.[0]?.parts[1]; | ||
| 465 | const refreshedAuth = await Effect.runPromise( | ||
| 466 | resolveSourceAuthMaterial({ | ||
| 467 | source: connectedSource, | ||
| 468 | slot: "runtime", | ||
| 469 | actorAccountId: accountId, | ||
| 470 | rows: persistence.rows, | ||
| 471 | resolveSecretMaterial: resolver, | ||
| 472 | }), | ||
| 473 | ); | ||
| 474 | expect(refreshedAuth.headers.Authorization).toBe( | ||
| 475 | "Bearer access-token-refreshed", | ||
| 476 | ); | ||
| 477 | expect(tokenRequests).toHaveLength(2); | ||
| 478 | expect(tokenRequests[1]?.get("grant_type")).toBe("refresh_token"); | ||
| 479 | expect(tokenRequests[1]?.get("refresh_token")).toBe("refresh-token-live"); | ||
| 480 | |||
| 481 | if (previousAccessTokenHandle?.kind === "secret_ref") { | ||
| 482 | const oldSecret = await Effect.runPromise( | ||
| 483 | persistence.rows.secretMaterials.getById( | ||
| 484 | SecretMaterialIdSchema.make(previousAccessTokenHandle.ref.handle), | ||
| 485 | ), | ||
| 486 | ); | ||
| 487 | expect(Option.isNone(oldSecret)).toBe(true); | ||
| 488 | } | ||
| 489 | |||
| 490 | const localArtifact = await Effect.runPromise(readLocalSourceArtifact({ | ||
| 491 | context: runtimeLocalWorkspace.context, | ||
| 492 | sourceId: connectedSource.id, | ||
| 493 | })); | ||
| 494 | expect(localArtifact).not.toBeNull(); | ||
| 495 | expect(localArtifact?.operations.length).toBeGreaterThan(0); | ||
| 496 | expect( | ||
| 497 | localArtifact?.operations.some( | ||
| 498 | (operation) => operation.toolId === "spreadsheets.values.get", | ||
| 499 | ), | ||
| 500 | ).toBe(true); | ||
| 501 | } finally { | ||
| 502 | globalThis.fetch = originalFetch; | ||
| 503 | } | ||
| 504 | }, | ||
| 505 | }); | ||
| 506 | } finally { | ||
| 507 | await persistence.close(); | ||
| 508 | } | ||
| 509 | }, 60_000); | ||
| 510 | |||
| 511 | it("uses the request origin callback for Google Discovery OAuth started from the web app", async () => { | ||
| 512 | const discoveryDocument = await fetchLiveDiscoveryDocument(); | ||
| 513 | |||
| 514 | const persistence = await makePersistence(); | ||
| 515 | try { | ||
| 516 | const workspaceId = WorkspaceIdSchema.make("ws_google_oauth_web"); | ||
| 517 | const accountId = AccountIdSchema.make("acc_google_oauth_web"); | ||
| 518 | const runtimeLocalWorkspace = await makeRuntimeLocalWorkspaceState( | ||
| 519 | workspaceId, | ||
| 520 | accountId, | ||
| 521 | ); | ||
| 522 | |||
| 523 | const service = createRuntimeSourceAuthService({ | ||
| 524 | rows: persistence.rows, | ||
| 525 | liveExecutionManager: createLiveExecutionManager(), | ||
| 526 | getLocalServerBaseUrl: () => "http://127.0.0.1:8788", | ||
| 527 | localConfig: runtimeLocalWorkspace.loadedConfig.config, | ||
| 528 | workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot, | ||
| 529 | localWorkspaceState: runtimeLocalWorkspace, | ||
| 530 | }); | ||
| 531 | |||
| 532 | await withGoogleAuthTestServer({ | ||
| 533 | discoveryDocument, | ||
| 534 | handler: async ({ discoveryUrl, tokenUrl }) => { | ||
| 535 | const originalFetch = globalThis.fetch; | ||
| 536 | globalThis.fetch = (async (input, init) => { | ||
| 537 | const url = typeof input === "string" | ||
| 538 | ? input | ||
| 539 | : input instanceof URL | ||
| 540 | ? input.toString() | ||
| 541 | : input.url; | ||
| 542 | if (url === "https://oauth2.googleapis.com/token") { | ||
| 543 | return originalFetch(tokenUrl, init); | ||
| 544 | } | ||
| 545 | |||
| 546 | return originalFetch(input as any, init); | ||
| 547 | }) as typeof fetch; | ||
| 548 | |||
| 549 | try { | ||
| 550 | const addResult = await Effect.runPromise( | ||
| 551 | service.addExecutorSource( | ||
| 552 | { | ||
| 553 | kind: "google_discovery", | ||
| 554 | workspaceId, | ||
| 555 | actorAccountId: accountId, | ||
| 556 | executionId: null, | ||
| 557 | interactionId: null, | ||
| 558 | service: "sheets", | ||
| 559 | version: "v4", | ||
| 560 | discoveryUrl, | ||
| 561 | scopes: ["https://www.googleapis.com/auth/spreadsheets.readonly"], | ||
| 562 | oauthClient: { | ||
| 563 | clientId: "google-test-client", | ||
| 564 | clientSecret: "google-test-secret", | ||
| 565 | }, | ||
| 566 | }, | ||
| 567 | { | ||
| 568 | baseUrl: "https://app.executor.dev", | ||
| 569 | }, | ||
| 570 | ).pipe( | ||
| 571 | Effect.provideService( | ||
| 572 | RuntimeLocalWorkspaceService, | ||
| 573 | runtimeLocalWorkspace, | ||
| 574 | ), | ||
| 575 | ), | ||
| 576 | ); | ||
| 577 | |||
| 578 | expect(addResult.kind).toBe("oauth_required"); | ||
| 579 | if (addResult.kind !== "oauth_required") { | ||
| 580 | return; | ||
| 581 | } | ||
| 582 | |||
| 583 | const sessionOption = await Effect.runPromise( | ||
| 584 | persistence.rows.sourceAuthSessions.getById(addResult.sessionId), | ||
| 585 | ); | ||
| 586 | expect(Option.isSome(sessionOption)).toBe(true); | ||
| 587 | const session = Option.getOrNull(sessionOption) as SourceAuthSession; | ||
| 588 | const sessionData = Schema.decodeUnknownSync( | ||
| 589 | OAuth2PkceSourceAuthSessionDataJsonSchema, | ||
| 590 | )(session.sessionDataJson); | ||
| 591 | |||
| 592 | expect(sessionData.redirectUri).toBe( | ||
| 593 | `https://app.executor.dev/v1/workspaces/${encodeURIComponent(workspaceId)}/sources/${encodeURIComponent(addResult.source.id)}/credentials/oauth/complete`, | ||
| 594 | ); | ||
| 595 | expect(sessionData.authorizationUrl).toContain( | ||
| 596 | encodeURIComponent(sessionData.redirectUri), | ||
| 597 | ); | ||
| 598 | expect(sessionData.redirectUri).not.toMatch(/^http:\/\/127\.0\.0\.1:\d+$/); | ||
| 599 | } finally { | ||
| 600 | globalThis.fetch = originalFetch; | ||
| 601 | } | ||
| 602 | }, | ||
| 603 | }); | ||
| 604 | } finally { | ||
| 605 | await persistence.close(); | ||
| 606 | } | ||
| 607 | }, 60_000); | ||
| 608 | }); | ||
| @@ -3,10 +3,6 @@ import { · 2 unmodified lines | |||
| 3 | discoverMcpToolsFromConnector, | 3 | discoverMcpToolsFromConnector, |
| 4 | type McpDiscoveryElicitationContext, | 4 | type McpDiscoveryElicitationContext, |
| 5 | } from "@executor/codemode-mcp"; | 5 | } from "@executor/codemode-mcp"; |
| 6 | import { | ||
| 7 | SqlControlPlaneRowsService, | ||
| 8 | type SqlControlPlaneRows, | ||
| 9 | } from "#persistence"; | ||
| 10 | import { | 6 | import { |
| 11 | AccountId, | 7 | AccountId, |
| 12 | type CredentialSlot, | 8 | type CredentialSlot, |
| @@ -86,6 +82,8 @@ import { · 73 unmodified lines | |||
| 86 | loadSourcesInWorkspace, | 82 | loadSourcesInWorkspace, |
| 87 | persistSource, | 83 | persistSource, |
| 88 | } from "./source-store"; | 84 | } from "./source-store"; |
| 85 | import type { WorkspaceStorageServices } from "./local-storage"; | ||
| 86 | import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store"; | ||
| 89 | 87 | ||
| 90 | const trimOrNull = (value: string | null | undefined): string | null => { | 88 | const trimOrNull = (value: string | null | undefined): string | null => { |
| 91 | if (value === null || value === undefined) { | 89 | if (value === null || value === undefined) { |
| @@ -303,7 +301,7 @@ const mergeOauth2PkceSourceAuthSessionData = (input: { · 211 unmodified lines | |||
| 303 | 301 | ||
| 304 | 302 | ||
| 305 | const completeLiveInteraction = (input: { | 303 | const completeLiveInteraction = (input: { |
| 306 | rows: SqlControlPlaneRows; | 304 | rows: ControlPlaneStoreShape; |
| 307 | liveExecutionManager: LiveExecutionManager; | 305 | liveExecutionManager: LiveExecutionManager; |
| 308 | session: SourceAuthSession; | 306 | session: SourceAuthSession; |
| 309 | response: { | 307 | response: { |
| @@ -360,7 +358,7 @@ const serializeJson = (value: unknown): string | null => { · 50 unmodified lines | |||
| 360 | return JSON.stringify(value); | 358 | return JSON.stringify(value); |
| 361 | }; | 359 | }; |
| 362 | 360 | ||
| 363 | const updateSourceStatus = (rows: SqlControlPlaneRows, source: Source, input: { | 361 | const updateSourceStatus = (rows: ControlPlaneStoreShape, source: Source, input: { |
| 364 | actorAccountId?: AccountId | null; | 362 | actorAccountId?: AccountId | null; |
| 365 | status: Source["status"]; | 363 | status: Source["status"]; |
| 366 | lastError?: string | null; | 364 | lastError?: string | null; |
| @@ -743,7 +741,7 @@ const sourceOauthClientSecretRef = (client: { · 376 unmodified lines | |||
| 743 | : null; | 741 | : null; |
| 744 | 742 | ||
| 745 | const upsertSourceOauthClient = (input: { | 743 | const upsertSourceOauthClient = (input: { |
| 746 | rows: SqlControlPlaneRows; | 744 | rows: ControlPlaneStoreShape; |
| 747 | source: Source; | 745 | source: Source; |
| 748 | oauthClient: SourceOauthClientInput; | 746 | oauthClient: SourceOauthClientInput; |
| 749 | storeSecretMaterial: StoreSecretMaterial; | 747 | storeSecretMaterial: StoreSecretMaterial; |
| @@ -825,7 +823,7 @@ const upsertSourceOauthClient = (input: { · 75 unmodified lines | |||
| 825 | }); | 823 | }); |
| 826 | 824 | ||
| 827 | const resolveExistingSourceOauthClient = (input: { | 825 | const resolveExistingSourceOauthClient = (input: { |
| 828 | rows: SqlControlPlaneRows; | 826 | rows: ControlPlaneStoreShape; |
| 829 | source: Source; | 827 | source: Source; |
| 830 | }): Effect.Effect<ResolvedSourceOauthClient | null, Error, never> => | 828 | }): Effect.Effect<ResolvedSourceOauthClient | null, Error, never> => |
| 831 | Effect.gen(function* () { | 829 | Effect.gen(function* () { |
| @@ -858,7 +856,7 @@ const resolveExistingSourceOauthClient = (input: { · 26 unmodified lines | |||
| 858 | }); | 856 | }); |
| 859 | 857 | ||
| 860 | const startOauth2PkceSourceCredentialSetup = (input: { | 858 | const startOauth2PkceSourceCredentialSetup = (input: { |
| 861 | rows: SqlControlPlaneRows; | 859 | rows: ControlPlaneStoreShape; |
| 862 | source: Source; | 860 | source: Source; |
| 863 | actorAccountId?: AccountId | null; | 861 | actorAccountId?: AccountId | null; |
| 864 | executionId?: SourceAuthSession["executionId"]; | 862 | executionId?: SourceAuthSession["executionId"]; |
| @@ -866,7 +864,11 @@ const startOauth2PkceSourceCredentialSetup = (input: { · 1 unmodified line | |||
| 866 | baseUrl: string; | 864 | baseUrl: string; |
| 867 | redirectModeOverride?: WorkspaceSourceOauthClientRedirectMode; | 865 | redirectModeOverride?: WorkspaceSourceOauthClientRedirectMode; |
| 868 | storeSecretMaterial: StoreSecretMaterial; | 866 | storeSecretMaterial: StoreSecretMaterial; |
| 869 | }): Effect.Effect<Extract<ExecutorSourceAddResult, { kind: "oauth_required" }> | null, Error, never> => | 867 | }): Effect.Effect< |
| 868 | Extract<ExecutorSourceAddResult, { kind: "oauth_required" }> | null, | ||
| 869 | Error, | ||
| 870 | WorkspaceStorageServices | ||
| 871 | > => | ||
| 870 | Effect.gen(function* () { | 872 | Effect.gen(function* () { |
| 871 | const adapter = getSourceAdapterForSource(input.source); | 873 | const adapter = getSourceAdapterForSource(input.source); |
| 872 | const setupConfig = adapter.getOauth2SetupConfig | 874 | const setupConfig = adapter.getOauth2SetupConfig |
| @@ -971,7 +973,7 @@ const startOauth2PkceSourceCredentialSetup = (input: { · 98 unmodified lines | |||
| 971 | }); | 973 | }); |
| 972 | 974 | ||
| 973 | const connectMcpSourceInternal = (input: { | 975 | const connectMcpSourceInternal = (input: { |
| 974 | rows: SqlControlPlaneRows; | 976 | rows: ControlPlaneStoreShape; |
| 975 | getLocalServerBaseUrl?: () => string | undefined; | 977 | getLocalServerBaseUrl?: () => string | undefined; |
| 976 | baseUrl?: string | null; | 978 | baseUrl?: string | null; |
| 977 | workspaceId: WorkspaceId; | 979 | workspaceId: WorkspaceId; |
| @@ -988,7 +990,7 @@ const connectMcpSourceInternal = (input: { · 10 unmodified lines | |||
| 988 | headers?: StringMap | null; | 990 | headers?: StringMap | null; |
| 989 | mcpDiscoveryElicitation?: McpDiscoveryElicitationContext; | 991 | mcpDiscoveryElicitation?: McpDiscoveryElicitationContext; |
| 990 | resolveSecretMaterial: ResolveSecretMaterial; | 992 | resolveSecretMaterial: ResolveSecretMaterial; |
| 991 | }): Effect.Effect<McpSourceConnectResult, Error, never> => | 993 | }): Effect.Effect<McpSourceConnectResult, Error, WorkspaceStorageServices> => |
| 992 | Effect.gen(function* () { | 994 | Effect.gen(function* () { |
| 993 | const normalizedEndpoint = normalizeEndpoint(input.endpoint); | 995 | const normalizedEndpoint = normalizeEndpoint(input.endpoint); |
| 994 | const existing = yield* ( | 996 | const existing = yield* ( |
| @@ -1200,13 +1202,13 @@ const connectMcpSourceInternal = (input: { · 205 unmodified lines | |||
| 1200 | }); | 1202 | }); |
| 1201 | 1203 | ||
| 1202 | const addExecutorHttpSource = (input: { | 1204 | const addExecutorHttpSource = (input: { |
| 1203 | rows: SqlControlPlaneRows; | 1205 | rows: ControlPlaneStoreShape; |
| 1204 | sourceInput: Extract<ExecutorAddSourceInput, { kind: "openapi" | "graphql" }>; | 1206 | sourceInput: Extract<ExecutorAddSourceInput, { kind: "openapi" | "graphql" }>; |
| 1205 | storeSecretMaterial: StoreSecretMaterial; | 1207 | storeSecretMaterial: StoreSecretMaterial; |
| 1206 | resolveSecretMaterial: ResolveSecretMaterial; | 1208 | resolveSecretMaterial: ResolveSecretMaterial; |
| 1207 | getLocalServerBaseUrl?: () => string | undefined; | 1209 | getLocalServerBaseUrl?: () => string | undefined; |
| 1208 | baseUrl?: string | null; | 1210 | baseUrl?: string | null; |
| 1209 | }): Effect.Effect<ExecutorSourceAddResult, Error, never> => | 1211 | }): Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices> => |
| 1210 | Effect.gen(function* () { | 1212 | Effect.gen(function* () { |
| 1211 | const normalizedEndpoint = normalizeEndpoint(input.sourceInput.endpoint); | 1213 | const normalizedEndpoint = normalizeEndpoint(input.sourceInput.endpoint); |
| 1212 | const normalizedSpecUrl = input.sourceInput.kind === "openapi" | 1214 | const normalizedSpecUrl = input.sourceInput.kind === "openapi" |
| @@ -1403,13 +1405,13 @@ const addExecutorHttpSource = (input: { · 190 unmodified lines | |||
| 1403 | }); | 1405 | }); |
| 1404 | 1406 | ||
| 1405 | const addExecutorGoogleDiscoverySource = (input: { | 1407 | const addExecutorGoogleDiscoverySource = (input: { |
| 1406 | rows: SqlControlPlaneRows; | 1408 | rows: ControlPlaneStoreShape; |
| 1407 | sourceInput: Extract<ExecutorAddSourceInput, { kind: "google_discovery" }>; | 1409 | sourceInput: Extract<ExecutorAddSourceInput, { kind: "google_discovery" }>; |
| 1408 | storeSecretMaterial: StoreSecretMaterial; | 1410 | storeSecretMaterial: StoreSecretMaterial; |
| 1409 | resolveSecretMaterial: ResolveSecretMaterial; | 1411 | resolveSecretMaterial: ResolveSecretMaterial; |
| 1410 | getLocalServerBaseUrl?: () => string | undefined; | 1412 | getLocalServerBaseUrl?: () => string | undefined; |
| 1411 | baseUrl?: string | null; | 1413 | baseUrl?: string | null; |
| 1412 | }): Effect.Effect<ExecutorSourceAddResult, Error, never> => | 1414 | }): Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices> => |
| 1413 | Effect.gen(function* () { | 1415 | Effect.gen(function* () { |
| 1414 | const normalizedService = input.sourceInput.service.trim(); | 1416 | const normalizedService = input.sourceInput.service.trim(); |
| 1415 | const normalizedVersion = input.sourceInput.version.trim(); | 1417 | const normalizedVersion = input.sourceInput.version.trim(); |
| @@ -1627,7 +1629,7 @@ type RuntimeSourceAuthServiceShape = { · 211 unmodified lines | |||
| 1627 | workspaceId: WorkspaceId; | 1629 | workspaceId: WorkspaceId; |
| 1628 | sourceId: Source["id"]; | 1630 | sourceId: Source["id"]; |
| 1629 | actorAccountId?: AccountId | null; | 1631 | actorAccountId?: AccountId | null; |
| 1630 | }) => Effect.Effect<Source, Error, never>; | 1632 | }) => Effect.Effect<Source, Error, WorkspaceStorageServices>; |
| 1631 | getLocalServerBaseUrl: () => string | null; | 1633 | getLocalServerBaseUrl: () => string | null; |
| 1632 | storeSecretMaterial: (input: { | 1634 | storeSecretMaterial: (input: { |
| 1633 | purpose: SecretMaterialPurpose; | 1635 | purpose: SecretMaterialPurpose; |
| @@ -1639,10 +1641,10 @@ type RuntimeSourceAuthServiceShape = { · 5 unmodified lines | |||
| 1639 | mcpDiscoveryElicitation?: McpDiscoveryElicitationContext; | 1641 | mcpDiscoveryElicitation?: McpDiscoveryElicitationContext; |
| 1640 | baseUrl?: string | null; | 1642 | baseUrl?: string | null; |
| 1641 | }, | 1643 | }, |
| 1642 | ) => Effect.Effect<ExecutorSourceAddResult, Error, never>; | 1644 | ) => Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices>; |
| 1643 | connectMcpSource: ( | 1645 | connectMcpSource: ( |
| 1644 | input: ConnectMcpSourceInput, | 1646 | input: ConnectMcpSourceInput, |
| 1645 | ) => Effect.Effect<McpSourceConnectResult, Error, never>; | 1647 | ) => Effect.Effect<McpSourceConnectResult, Error, WorkspaceStorageServices>; |
| 1646 | startSourceOAuthSession: ( | 1648 | startSourceOAuthSession: ( |
| 1647 | input: StartSourceOAuthSessionInput, | 1649 | input: StartSourceOAuthSessionInput, |
| 1648 | ) => Effect.Effect<StartSourceOAuthSessionResult, Error, never>; | 1650 | ) => Effect.Effect<StartSourceOAuthSessionResult, Error, never>; |
| @@ -1651,7 +1653,7 @@ type RuntimeSourceAuthServiceShape = { · 2 unmodified lines | |||
| 1651 | code?: string | null; | 1653 | code?: string | null; |
| 1652 | error?: string | null; | 1654 | error?: string | null; |
| 1653 | errorDescription?: string | null; | 1655 | errorDescription?: string | null; |
| 1654 | }) => Effect.Effect<CompleteSourceOAuthSessionResult, Error, never>; | 1656 | }) => Effect.Effect<CompleteSourceOAuthSessionResult, Error, WorkspaceStorageServices>; |
| 1655 | completeSourceCredentialSetup: (input: { | 1657 | completeSourceCredentialSetup: (input: { |
| 1656 | workspaceId: WorkspaceId; | 1658 | workspaceId: WorkspaceId; |
| 1657 | sourceId: Source["id"]; | 1659 | sourceId: Source["id"]; |
| @@ -1660,11 +1662,11 @@ type RuntimeSourceAuthServiceShape = { · 2 unmodified lines | |||
| 1660 | code?: string | null; | 1662 | code?: string | null; |
| 1661 | error?: string | null; | 1663 | error?: string | null; |
| 1662 | errorDescription?: string | null; | 1664 | errorDescription?: string | null; |
| 1663 | }) => Effect.Effect<Source, Error, never>; | 1665 | }) => Effect.Effect<Source, Error, WorkspaceStorageServices>; |
| 1664 | }; | 1666 | }; |
| 1665 | 1667 | ||
| 1666 | export const createRuntimeSourceAuthService = (input: { | 1668 | export const createRuntimeSourceAuthService = (input: { |
| 1667 | rows: SqlControlPlaneRows; | 1669 | rows: ControlPlaneStoreShape; |
| 1668 | liveExecutionManager: LiveExecutionManager; | 1670 | liveExecutionManager: LiveExecutionManager; |
| 1669 | getLocalServerBaseUrl?: () => string | undefined; | 1671 | getLocalServerBaseUrl?: () => string | undefined; |
| 1670 | localConfig?: LocalExecutorConfig | null; | 1672 | localConfig?: LocalExecutorConfig | null; |
| @@ -1681,11 +1683,11 @@ export const createRuntimeSourceAuthService = (input: { · 10 unmodified lines | |||
| 1681 | }); | 1683 | }); |
| 1682 | const mirrorLocalSourceResult = ( | 1684 | const mirrorLocalSourceResult = ( |
| 1683 | result: ExecutorSourceAddResult, | 1685 | result: ExecutorSourceAddResult, |
| 1684 | ): Effect.Effect<ExecutorSourceAddResult, Error, never> => | 1686 | ): Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices> => |
| 1685 | Effect.succeed(result); | 1687 | Effect.succeed(result); |
| 1686 | const mirrorLocalMcpSourceResult = ( | 1688 | const mirrorLocalMcpSourceResult = ( |
| 1687 | result: McpSourceConnectResult, | 1689 | result: McpSourceConnectResult, |
| 1688 | ): Effect.Effect<McpSourceConnectResult, Error, never> => | 1690 | ): Effect.Effect<McpSourceConnectResult, Error, WorkspaceStorageServices> => |
| 1689 | Effect.succeed(result); | 1691 | Effect.succeed(result); |
| 1690 | const provideLocalWorkspace = <A, E, R>(effect: Effect.Effect<A, E, R>) => | 1692 | const provideLocalWorkspace = <A, E, R>(effect: Effect.Effect<A, E, R>) => |
| 1691 | input.localWorkspaceState | 1693 | input.localWorkspaceState |
| @@ -2250,7 +2252,7 @@ export const RuntimeSourceAuthServiceLive = (input: { · 558 unmodified lines | |||
| 2250 | Layer.effect( | 2252 | Layer.effect( |
| 2251 | RuntimeSourceAuthServiceTag, | 2253 | RuntimeSourceAuthServiceTag, |
| 2252 | Effect.gen(function* () { | 2254 | Effect.gen(function* () { |
| 2253 | const rows = yield* SqlControlPlaneRowsService; | 2255 | const rows = yield* ControlPlaneStore; |
| 2254 | const liveExecutionManager = yield* LiveExecutionManagerService; | 2256 | const liveExecutionManager = yield* LiveExecutionManagerService; |
| 2255 | 2257 | ||
| 2256 | return createRuntimeSourceAuthService({ | 2258 | return createRuntimeSourceAuthService({ |
| @@ -129,7 +129,7 @@ describe("source-definitions", () => { · 128 unmodified lines | |||
| 129 | headerName: "Authorization", | 129 | headerName: "Authorization", |
| 130 | prefix: "Bearer ", | 130 | prefix: "Bearer ", |
| 131 | token: { | 131 | token: { |
| 132 | providerId: "postgres", | 132 | providerId: "local", |
| 133 | handle: "sec_token", | 133 | handle: "sec_token", |
| 134 | }, | 134 | }, |
| 135 | }, | 135 | }, |
| @@ -166,7 +166,7 @@ describe("source-definitions", () => { · 30 unmodified lines | |||
| 166 | headerName: " ", | 166 | headerName: " ", |
| 167 | prefix: undefined, | 167 | prefix: undefined, |
| 168 | accessToken: { | 168 | accessToken: { |
| 169 | providerId: " postgres ", | 169 | providerId: " local ", |
| 170 | handle: " sec_access ", | 170 | handle: " sec_access ", |
| 171 | }, | 171 | }, |
| 172 | refreshToken: null, | 172 | refreshToken: null, |
| @@ -180,7 +180,7 @@ describe("source-definitions", () => { · 7 unmodified lines | |||
| 180 | headerName: "Authorization", | 180 | headerName: "Authorization", |
| 181 | prefix: "Bearer ", | 181 | prefix: "Bearer ", |
| 182 | accessToken: { | 182 | accessToken: { |
| 183 | providerId: "postgres", | 183 | providerId: "local", |
| 184 | handle: "sec_access", | 184 | handle: "sec_access", |
| 185 | }, | 185 | }, |
| 186 | refreshToken: null, | 186 | refreshToken: null, |
| @@ -222,11 +222,11 @@ describe("source-definitions", () => { · 35 unmodified lines | |||
| 222 | headerName: "Authorization", | 222 | headerName: "Authorization", |
| 223 | prefix: "Bearer ", | 223 | prefix: "Bearer ", |
| 224 | accessToken: { | 224 | accessToken: { |
| 225 | providerId: "postgres", | 225 | providerId: "local", |
| 226 | handle: "sec_access", | 226 | handle: "sec_access", |
| 227 | }, | 227 | }, |
| 228 | refreshToken: { | 228 | refreshToken: { |
| 229 | providerId: "postgres", | 229 | providerId: "local", |
| 230 | handle: " ", | 230 | handle: " ", |
| 231 | }, | 231 | }, |
| 232 | } as never, | 232 | } as never, |
| @@ -319,7 +319,7 @@ describe("source-definitions", () => { · 86 unmodified lines | |||
| 319 | headerName: " ", | 319 | headerName: " ", |
| 320 | prefix: "Token ", | 320 | prefix: "Token ", |
| 321 | token: { | 321 | token: { |
| 322 | providerId: " postgres ", | 322 | providerId: " local ", |
| 323 | handle: " sec_token ", | 323 | handle: " sec_token ", |
| 324 | }, | 324 | }, |
| 325 | } as never, | 325 | } as never, |
| @@ -332,7 +332,7 @@ describe("source-definitions", () => { · 6 unmodified lines | |||
| 332 | headerName: "Authorization", | 332 | headerName: "Authorization", |
| 333 | prefix: "Token ", | 333 | prefix: "Token ", |
| 334 | token: { | 334 | token: { |
| 335 | providerId: "postgres", | 335 | providerId: "local", |
| 336 | handle: "sec_token", | 336 | handle: "sec_token", |
| 337 | }, | 337 | }, |
| 338 | }); | 338 | }); |
| @@ -353,7 +353,7 @@ describe("source-definitions", () => { · 14 unmodified lines | |||
| 353 | headerName: "Authorization", | 353 | headerName: "Authorization", |
| 354 | prefix: "Bearer ", | 354 | prefix: "Bearer ", |
| 355 | token: { | 355 | token: { |
| 356 | providerId: "postgres", | 356 | providerId: "local", |
| 357 | handle: "sec_bearer", | 357 | handle: "sec_bearer", |
| 358 | }, | 358 | }, |
| 359 | }, | 359 | }, |
| @@ -397,11 +397,11 @@ describe("source-definitions", () => { · 37 unmodified lines | |||
| 397 | headerName: "Authorization", | 397 | headerName: "Authorization", |
| 398 | prefix: "Bearer ", | 398 | prefix: "Bearer ", |
| 399 | accessToken: { | 399 | accessToken: { |
| 400 | providerId: "postgres", | 400 | providerId: "local", |
| 401 | handle: "sec_access", | 401 | handle: "sec_access", |
| 402 | }, | 402 | }, |
| 403 | refreshToken: { | 403 | refreshToken: { |
| 404 | providerId: "postgres", | 404 | providerId: "local", |
| 405 | handle: "sec_refresh", | 405 | handle: "sec_refresh", |
| 406 | }, | 406 | }, |
| 407 | }, | 407 | }, |
| @@ -416,7 +416,7 @@ describe("source-definitions", () => { · 8 unmodified lines | |||
| 416 | headerName: "Authorization", | 416 | headerName: "Authorization", |
| 417 | prefix: "Bearer ", | 417 | prefix: "Bearer ", |
| 418 | accessToken: { | 418 | accessToken: { |
| 419 | providerId: "postgres", | 419 | providerId: "local", |
| 420 | handle: "sec_access", | 420 | handle: "sec_access", |
| 421 | }, | 421 | }, |
| 422 | refreshToken: null, | 422 | refreshToken: null, |
| @@ -1,344 +0,0 @@ | |||
| 1 | import { mkdtempSync } from "node:fs"; | ||
| 2 | import { tmpdir } from "node:os"; | ||
| 3 | import { join } from "node:path"; | ||
| 4 | |||
| 5 | import { describe, expect, it } from "vitest"; | ||
| 6 | import * as Effect from "effect/Effect"; | ||
| 7 | |||
| 8 | import { | ||
| 9 | AccountIdSchema, | ||
| 10 | type LocalConfigSource, | ||
| 11 | SourceIdSchema, | ||
| 12 | SourceRecipeRevisionIdSchema, | ||
| 13 | SourceRecipeSchemaBundleIdSchema, | ||
| 14 | WorkspaceIdSchema, | ||
| 15 | type Source, | ||
| 16 | type StoredSourceRecipeOperationRecord, | ||
| 17 | } from "#schema"; | ||
| 18 | import { | ||
| 19 | createSqlControlPlanePersistence, | ||
| 20 | type SqlControlPlanePersistence, | ||
| 21 | } from "#persistence"; | ||
| 22 | |||
| 23 | import { | ||
| 24 | loadLocalExecutorConfig, | ||
| 25 | resolveLocalWorkspaceContext, | ||
| 26 | writeProjectLocalExecutorConfig, | ||
| 27 | } from "./local-config"; | ||
| 28 | import { | ||
| 29 | buildLocalSourceArtifact, | ||
| 30 | writeLocalSourceArtifact, | ||
| 31 | } from "./local-source-artifacts"; | ||
| 32 | import { | ||
| 33 | getSourceInspection, | ||
| 34 | getSourceInspectionSchemaBundle, | ||
| 35 | getSourceInspectionToolDetail, | ||
| 36 | } from "./source-inspection"; | ||
| 37 | import { | ||
| 38 | RuntimeLocalWorkspaceService, | ||
| 39 | type RuntimeLocalWorkspaceState, | ||
| 40 | } from "./local-runtime-context"; | ||
| 41 | import { | ||
| 42 | writeLocalWorkspaceState, | ||
| 43 | } from "./local-workspace-state"; | ||
| 44 | import { ControlPlaneStore } from "./store"; | ||
| 45 | |||
| 46 | const makePersistence = () => | ||
| 47 | Effect.runPromise( | ||
| 48 | createSqlControlPlanePersistence({ | ||
| 49 | localDataDir: ":memory:", | ||
| 50 | }), | ||
| 51 | ); | ||
| 52 | |||
| 53 | const makeRuntimeLocalWorkspaceState = ( | ||
| 54 | workspaceId: ReturnType<typeof WorkspaceIdSchema.make>, | ||
| 55 | accountId: ReturnType<typeof AccountIdSchema.make>, | ||
| 56 | ) => | ||
| 57 | Effect.runPromise( | ||
| 58 | Effect.gen(function* () { | ||
| 59 | const workspaceRoot = mkdtempSync( | ||
| 60 | join(tmpdir(), "executor-source-inspection-"), | ||
| 61 | ); | ||
| 62 | const context = yield* resolveLocalWorkspaceContext({ workspaceRoot }); | ||
| 63 | const loadedConfig = yield* loadLocalExecutorConfig(context); | ||
| 64 | |||
| 65 | return { | ||
| 66 | context, | ||
| 67 | installation: { | ||
| 68 | workspaceId, | ||
| 69 | accountId, | ||
| 70 | }, | ||
| 71 | loadedConfig, | ||
| 72 | } satisfies RuntimeLocalWorkspaceState; | ||
| 73 | }), | ||
| 74 | ); | ||
| 75 | |||
| 76 | const makeSource = (input: { | ||
| 77 | workspaceId: Source["workspaceId"]; | ||
| 78 | sourceId: Source["id"]; | ||
| 79 | }): Source => ({ | ||
| 80 | id: input.sourceId, | ||
| 81 | workspaceId: input.workspaceId, | ||
| 82 | name: "Cloudflare API", | ||
| 83 | kind: "openapi", | ||
| 84 | endpoint: "https://api.cloudflare.com/client/v4", | ||
| 85 | status: "connected", | ||
| 86 | enabled: true, | ||
| 87 | namespace: "cloudflare.api", | ||
| 88 | bindingVersion: 1, | ||
| 89 | binding: { | ||
| 90 | specUrl: "https://example.com/openapi.json", | ||
| 91 | defaultHeaders: null, | ||
| 92 | }, | ||
| 93 | importAuthPolicy: "reuse_runtime", | ||
| 94 | importAuth: { kind: "none" }, | ||
| 95 | auth: { kind: "none" }, | ||
| 96 | sourceHash: null, | ||
| 97 | lastError: null, | ||
| 98 | createdAt: 1000, | ||
| 99 | updatedAt: 1000, | ||
| 100 | }); | ||
| 101 | |||
| 102 | const makeOperation = ( | ||
| 103 | overrides: Partial<StoredSourceRecipeOperationRecord> = {}, | ||
| 104 | ): StoredSourceRecipeOperationRecord => ({ | ||
| 105 | id: "src_recipe_op_inspection", | ||
| 106 | recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_inspection"), | ||
| 107 | operationKey: "zones.listZones", | ||
| 108 | transportKind: "http", | ||
| 109 | toolId: "zones.listZones", | ||
| 110 | title: "List zones", | ||
| 111 | description: "List Cloudflare zones", | ||
| 112 | operationKind: "read", | ||
| 113 | searchText: "zones list cloudflare", | ||
| 114 | inputSchemaJson: JSON.stringify({ | ||
| 115 | type: "object", | ||
| 116 | properties: { | ||
| 117 | page: { type: "number" }, | ||
| 118 | }, | ||
| 119 | }), | ||
| 120 | outputSchemaJson: JSON.stringify({ | ||
| 121 | type: "object", | ||
| 122 | properties: { | ||
| 123 | result: { | ||
| 124 | type: "array", | ||
| 125 | items: { type: "string" }, | ||
| 126 | }, | ||
| 127 | }, | ||
| 128 | }), | ||
| 129 | providerKind: "openapi", | ||
| 130 | providerDataJson: JSON.stringify({ | ||
| 131 | kind: "openapi", | ||
| 132 | toolId: "zones.listZones", | ||
| 133 | rawToolId: "zones_listZones", | ||
| 134 | operationId: "zones.listZones", | ||
| 135 | group: "zones", | ||
| 136 | leaf: "listZones", | ||
| 137 | tags: ["zones"], | ||
| 138 | method: "get", | ||
| 139 | path: "/zones", | ||
| 140 | operationHash: "hash", | ||
| 141 | invocation: { | ||
| 142 | method: "get", | ||
| 143 | pathTemplate: "/zones", | ||
| 144 | parameters: [], | ||
| 145 | requestBody: null, | ||
| 146 | }, | ||
| 147 | }), | ||
| 148 | createdAt: 1000, | ||
| 149 | updatedAt: 1000, | ||
| 150 | ...overrides, | ||
| 151 | }); | ||
| 152 | |||
| 153 | const configSourceFromSource = (source: Source): LocalConfigSource => ({ | ||
| 154 | kind: "openapi", | ||
| 155 | ...(source.name !== source.id ? { name: source.name } : {}), | ||
| 156 | ...(source.namespace && source.namespace !== source.id | ||
| 157 | ? { namespace: source.namespace } | ||
| 158 | : {}), | ||
| 159 | connection: { | ||
| 160 | endpoint: source.endpoint, | ||
| 161 | }, | ||
| 162 | binding: { | ||
| 163 | specUrl: | ||
| 164 | typeof source.binding.specUrl === "string" | ||
| 165 | ? source.binding.specUrl | ||
| 166 | : "https://example.com/openapi.json", | ||
| 167 | defaultHeaders: | ||
| 168 | (source.binding.defaultHeaders as Record<string, string> | null | undefined) | ||
| 169 | ?? null, | ||
| 170 | }, | ||
| 171 | }); | ||
| 172 | |||
| 173 | describe("source-inspection", () => { | ||
| 174 | it("returns a lightweight inspection bundle and loads rich detail on demand", async () => { | ||
| 175 | const persistence = await makePersistence(); | ||
| 176 | try { | ||
| 177 | const workspaceId = WorkspaceIdSchema.make("ws_source_inspection"); | ||
| 178 | const accountId = AccountIdSchema.make("acc_source_inspection"); | ||
| 179 | const sourceId = SourceIdSchema.make("src_source_inspection"); | ||
| 180 | const recipeRevisionId = | ||
| 181 | SourceRecipeRevisionIdSchema.make("src_recipe_rev_materialization"); | ||
| 182 | const runtimeLocalWorkspace = await makeRuntimeLocalWorkspaceState( | ||
| 183 | workspaceId, | ||
| 184 | accountId, | ||
| 185 | ); | ||
| 186 | const hugeDocument = JSON.stringify({ | ||
| 187 | openapi: "3.0.3", | ||
| 188 | info: { | ||
| 189 | title: "Cloudflare API", | ||
| 190 | version: "1.0.0", | ||
| 191 | }, | ||
| 192 | paths: Object.fromEntries( | ||
| 193 | Array.from({ length: 256 }, (_, index) => [ | ||
| 194 | `/zones/${index}`, | ||
| 195 | { | ||
| 196 | get: { | ||
| 197 | operationId: `zones.listZones${index}`, | ||
| 198 | responses: { 200: { description: "ok" } }, | ||
| 199 | }, | ||
| 200 | }, | ||
| 201 | ]), | ||
| 202 | ), | ||
| 203 | }); | ||
| 204 | |||
| 205 | const source = makeSource({ | ||
| 206 | workspaceId, | ||
| 207 | sourceId, | ||
| 208 | }); | ||
| 209 | await Effect.runPromise(writeProjectLocalExecutorConfig({ | ||
| 210 | context: runtimeLocalWorkspace.context, | ||
| 211 | config: { | ||
| 212 | sources: { | ||
| 213 | [sourceId]: configSourceFromSource(source), | ||
| 214 | }, | ||
| 215 | }, | ||
| 216 | })); | ||
| 217 | await Effect.runPromise(writeLocalSourceArtifact({ | ||
| 218 | context: runtimeLocalWorkspace.context, | ||
| 219 | sourceId, | ||
| 220 | artifact: buildLocalSourceArtifact({ | ||
| 221 | source: { | ||
| 222 | ...source, | ||
| 223 | sourceHash: "manifest_hash", | ||
| 224 | }, | ||
| 225 | materialization: { | ||
| 226 | manifestJson: JSON.stringify({ | ||
| 227 | sourceHash: "manifest_hash", | ||
| 228 | }), | ||
| 229 | manifestHash: "manifest_hash", | ||
| 230 | sourceHash: "manifest_hash", | ||
| 231 | documents: [{ | ||
| 232 | id: "src_recipe_doc_inspection", | ||
| 233 | recipeRevisionId, | ||
| 234 | documentKind: "openapi", | ||
| 235 | documentKey: "https://example.com/openapi.json", | ||
| 236 | contentText: hugeDocument, | ||
| 237 | contentHash: "doc_hash", | ||
| 238 | fetchedAt: 1000, | ||
| 239 | createdAt: 1000, | ||
| 240 | updatedAt: 1000, | ||
| 241 | }], | ||
| 242 | schemaBundles: [{ | ||
| 243 | id: SourceRecipeSchemaBundleIdSchema.make( | ||
| 244 | "src_recipe_bundle_inspection", | ||
| 245 | ), | ||
| 246 | recipeRevisionId, | ||
| 247 | bundleKind: "json_schema_ref_map", | ||
| 248 | refsJson: JSON.stringify({ | ||
| 249 | "#/components/schemas/Pagination": { | ||
| 250 | type: "object", | ||
| 251 | properties: { | ||
| 252 | page: { type: "number" }, | ||
| 253 | }, | ||
| 254 | }, | ||
| 255 | }), | ||
| 256 | contentHash: "bundle_hash", | ||
| 257 | createdAt: 1000, | ||
| 258 | updatedAt: 1000, | ||
| 259 | }], | ||
| 260 | operations: [makeOperation({ | ||
| 261 | recipeRevisionId, | ||
| 262 | })], | ||
| 263 | }, | ||
| 264 | }), | ||
| 265 | })); | ||
| 266 | await Effect.runPromise(writeLocalWorkspaceState({ | ||
| 267 | context: runtimeLocalWorkspace.context, | ||
| 268 | state: { | ||
| 269 | version: 1, | ||
| 270 | sources: { | ||
| 271 | [sourceId]: { | ||
| 272 | status: "connected", | ||
| 273 | lastError: null, | ||
| 274 | sourceHash: "manifest_hash", | ||
| 275 | createdAt: 1000, | ||
| 276 | updatedAt: 1000, | ||
| 277 | }, | ||
| 278 | }, | ||
| 279 | policies: {}, | ||
| 280 | }, | ||
| 281 | })); | ||
| 282 | |||
| 283 | const inspection = await Effect.runPromise( | ||
| 284 | getSourceInspection({ | ||
| 285 | workspaceId, | ||
| 286 | sourceId, | ||
| 287 | }).pipe( | ||
| 288 | Effect.provideService(ControlPlaneStore, persistence.rows), | ||
| 289 | Effect.provideService( | ||
| 290 | RuntimeLocalWorkspaceService, | ||
| 291 | runtimeLocalWorkspace, | ||
| 292 | ), | ||
| 293 | ), | ||
| 294 | ); | ||
| 295 | |||
| 296 | expect(inspection.toolCount).toBe(1); | ||
| 297 | expect(inspection.tools[0]?.path).toBe("cloudflare.api.zones.listZones"); | ||
| 298 | expect("manifestJson" in inspection).toBe(false); | ||
| 299 | expect("rawDocumentText" in inspection).toBe(false); | ||
| 300 | expect("definitionsJson" in inspection).toBe(false); | ||
| 301 | |||
| 302 | const detail = await Effect.runPromise( | ||
| 303 | getSourceInspectionToolDetail({ | ||
| 304 | workspaceId, | ||
| 305 | sourceId, | ||
| 306 | toolPath: "cloudflare.api.zones.listZones", | ||
| 307 | }).pipe( | ||
| 308 | Effect.provideService(ControlPlaneStore, persistence.rows), | ||
| 309 | Effect.provideService( | ||
| 310 | RuntimeLocalWorkspaceService, | ||
| 311 | runtimeLocalWorkspace, | ||
| 312 | ), | ||
| 313 | ), | ||
| 314 | ); | ||
| 315 | |||
| 316 | expect(detail.summary.method).toBe("get"); | ||
| 317 | expect(detail.summary.inputType).toContain("page"); | ||
| 318 | expect(detail.summary.outputType).toContain("result"); | ||
| 319 | expect(detail.providerDataJson).toContain("/zones"); | ||
| 320 | expect(detail.inputSchemaJson).toContain("\"page\""); | ||
| 321 | expect(detail.outputSchemaJson).toContain("\"result\""); | ||
| 322 | expect(detail.schemaBundleId).toBe("src_recipe_bundle_inspection"); | ||
| 323 | |||
| 324 | const schemaBundle = await Effect.runPromise( | ||
| 325 | getSourceInspectionSchemaBundle({ | ||
| 326 | workspaceId, | ||
| 327 | sourceId, | ||
| 328 | schemaBundleId: "src_recipe_bundle_inspection", | ||
| 329 | }).pipe( | ||
| 330 | Effect.provideService(ControlPlaneStore, persistence.rows), | ||
| 331 | Effect.provideService( | ||
| 332 | RuntimeLocalWorkspaceService, | ||
| 333 | runtimeLocalWorkspace, | ||
| 334 | ), | ||
| 335 | ), | ||
| 336 | ); | ||
| 337 | |||
| 338 | expect(schemaBundle.kind).toBe("json_schema_ref_map"); | ||
| 339 | expect(schemaBundle.refsJson).toContain("Pagination"); | ||
| 340 | } finally { | ||
| 341 | await persistence.close(); | ||
| 342 | } | ||
| 343 | }); | ||
| 344 | }); | ||
| @@ -1,4 +1,3 @@ | |||
| 1 | import type { SqlControlPlaneRows } from "#persistence"; | ||
| 2 | import type { | 1 | import type { |
| 3 | AccountId, | 2 | AccountId, |
| 4 | Source, | 3 | Source, |
| @@ -6,16 +5,16 @@ import type { · 1 unmodified line | |||
| 6 | } from "#schema"; | 5 | } from "#schema"; |
| 7 | import * as Effect from "effect/Effect"; | 6 | import * as Effect from "effect/Effect"; |
| 8 | 7 | ||
| 9 | import { | 8 | import type { ControlPlaneStoreShape } from "./store"; |
| 10 | buildLocalSourceArtifact, | ||
| 11 | writeLocalSourceArtifact, | ||
| 12 | } from "./local-source-artifacts"; | ||
| 13 | import { | 9 | import { |
| 14 | requireRuntimeLocalWorkspace, | 10 | requireRuntimeLocalWorkspace, |
| 15 | } from "./local-runtime-context"; | 11 | } from "./local-runtime-context"; |
| 16 | import { | 12 | import { |
| 17 | loadLocalWorkspaceState, | 13 | type WorkspaceStorageServices, |
| 18 | writeLocalWorkspaceState, | 14 | SourceArtifactStore, |
| 15 | WorkspaceStateStore, | ||
| 16 | } from "./local-storage"; | ||
| 17 | import { | ||
| 19 | type LocalWorkspaceState, | 18 | type LocalWorkspaceState, |
| 20 | } from "./local-workspace-state"; | 19 | } from "./local-workspace-state"; |
| 21 | import { resolveSourceAuthMaterial } from "./source-auth-material"; | 20 | import { resolveSourceAuthMaterial } from "./source-auth-material"; |
| @@ -24,7 +23,6 @@ import { · 2 unmodified lines | |||
| 24 | } from "./source-adapters"; | 23 | } from "./source-adapters"; |
| 25 | import { | 24 | import { |
| 26 | materializationFromMcpManifestEntries, | 25 | materializationFromMcpManifestEntries, |
| 27 | persistMcpRecipeRevisionFromManifestEntries, | ||
| 28 | } from "./source-adapters/mcp"; | 26 | } from "./source-adapters/mcp"; |
| 29 | import type { | 27 | import type { |
| 30 | ResolveSecretMaterial as ResolveSourceSecretMaterial, | 28 | ResolveSecretMaterial as ResolveSourceSecretMaterial, |
| @@ -36,16 +34,20 @@ const shouldIndexSource = (source: Source): boolean => · 5 unmodified lines | |||
| 36 | && getSourceAdapterForSource(source).family !== "internal"; | 34 | && getSourceAdapterForSource(source).family !== "internal"; |
| 37 | 35 | ||
| 38 | export const syncSourceMaterialization = (input: { | 36 | export const syncSourceMaterialization = (input: { |
| 39 | rows: SqlControlPlaneRows; | 37 | rows: ControlPlaneStoreShape; |
| 40 | source: Source; | 38 | source: Source; |
| 41 | actorAccountId?: AccountId | null; | 39 | actorAccountId?: AccountId | null; |
| 42 | resolveSecretMaterial: ResolveSourceSecretMaterial; | 40 | resolveSecretMaterial: ResolveSourceSecretMaterial; |
| 43 | }): Effect.Effect<void, Error, never> => | 41 | }): Effect.Effect<void, Error, WorkspaceStorageServices> => |
| 44 | Effect.gen(function* () { | 42 | Effect.gen(function* () { |
| 45 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.source.workspaceId); | 43 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.source.workspaceId); |
| 44 | const workspaceStateStore = yield* WorkspaceStateStore; | ||
| 45 | const sourceArtifactStore = yield* SourceArtifactStore; | ||
| 46 | 46 | ||
| 47 | if (!shouldIndexSource(input.source)) { | 47 | if (!shouldIndexSource(input.source)) { |
| 48 | const state = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context); | 48 | const state = yield* workspaceStateStore.load( |
| 49 | runtimeLocalWorkspace.context, | ||
| 50 | ); | ||
| 49 | const existingSourceState = state.sources[input.source.id]; | 51 | const existingSourceState = state.sources[input.source.id]; |
| 50 | const nextState: LocalWorkspaceState = { | 52 | const nextState: LocalWorkspaceState = { |
| 51 | ...state, | 53 | ...state, |
| @@ -60,7 +62,7 @@ export const syncSourceMaterialization = (input: { · 8 unmodified lines | |||
| 60 | }, | 62 | }, |
| 61 | }, | 63 | }, |
| 62 | }; | 64 | }; |
| 63 | yield* writeLocalWorkspaceState({ | 65 | yield* workspaceStateStore.write({ |
| 64 | context: runtimeLocalWorkspace.context, | 66 | context: runtimeLocalWorkspace.context, |
| 65 | state: nextState, | 67 | state: nextState, |
| 66 | }); | 68 | }); |
| @@ -80,16 +82,18 @@ export const syncSourceMaterialization = (input: { · 13 unmodified lines | |||
| 80 | resolveSecretMaterial: input.resolveSecretMaterial, | 82 | resolveSecretMaterial: input.resolveSecretMaterial, |
| 81 | }), | 83 | }), |
| 82 | }); | 84 | }); |
| 83 | yield* writeLocalSourceArtifact({ | 85 | yield* sourceArtifactStore.write({ |
| 84 | context: runtimeLocalWorkspace.context, | 86 | context: runtimeLocalWorkspace.context, |
| 85 | sourceId: input.source.id, | 87 | sourceId: input.source.id, |
| 86 | artifact: buildLocalSourceArtifact({ | 88 | artifact: sourceArtifactStore.build({ |
| 87 | source: input.source, | 89 | source: input.source, |
| 88 | materialization, | 90 | materialization, |
| 89 | }), | 91 | }), |
| 90 | }); | 92 | }); |
| 91 | 93 | ||
| 92 | const state = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context); | 94 | const state = yield* workspaceStateStore.load( |
| 95 | runtimeLocalWorkspace.context, | ||
| 96 | ); | ||
| 93 | const existingSourceState = state.sources[input.source.id]; | 97 | const existingSourceState = state.sources[input.source.id]; |
| 94 | const nextState: LocalWorkspaceState = { | 98 | const nextState: LocalWorkspaceState = { |
| 95 | ...state, | 99 | ...state, |
| @@ -104,31 +108,30 @@ export const syncSourceMaterialization = (input: { · 8 unmodified lines | |||
| 104 | }, | 108 | }, |
| 105 | }, | 109 | }, |
| 106 | }; | 110 | }; |
| 107 | yield* writeLocalWorkspaceState({ | 111 | yield* workspaceStateStore.write({ |
| 108 | context: runtimeLocalWorkspace.context, | 112 | context: runtimeLocalWorkspace.context, |
| 109 | state: nextState, | 113 | state: nextState, |
| 110 | }); | 114 | }); |
| 111 | }); | 115 | }); |
| 112 | 116 | ||
| 113 | export const persistMcpRecipeMaterializationFromManifest = (input: { | 117 | export const persistMcpRecipeMaterializationFromManifest = (input: { |
| 114 | rows: SqlControlPlaneRows; | 118 | rows: ControlPlaneStoreShape; |
| 115 | source: Source; | 119 | source: Source; |
| 116 | manifestEntries: Parameters< | 120 | manifestEntries: Parameters<typeof materializationFromMcpManifestEntries>[0]["manifestEntries"]; |
| 117 | typeof persistMcpRecipeRevisionFromManifestEntries | 121 | }): Effect.Effect<void, Error, WorkspaceStorageServices> => |
| 118 | >[0]["manifestEntries"]; | ||
| 119 | }): Effect.Effect<void, Error, never> => | ||
| 120 | Effect.gen(function* () { | 122 | Effect.gen(function* () { |
| 121 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.source.workspaceId); | 123 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.source.workspaceId); |
| 124 | const sourceArtifactStore = yield* SourceArtifactStore; | ||
| 122 | const materialization = materializationFromMcpManifestEntries({ | 125 | const materialization = materializationFromMcpManifestEntries({ |
| 123 | recipeRevisionId: "src_recipe_rev_materialization" as never, | 126 | recipeRevisionId: "src_recipe_rev_materialization" as never, |
| 124 | endpoint: input.source.endpoint, | 127 | endpoint: input.source.endpoint, |
| 125 | manifestEntries: input.manifestEntries, | 128 | manifestEntries: input.manifestEntries, |
| 126 | }); | 129 | }); |
| 127 | 130 | ||
| 128 | yield* writeLocalSourceArtifact({ | 131 | yield* sourceArtifactStore.write({ |
| 129 | context: runtimeLocalWorkspace.context, | 132 | context: runtimeLocalWorkspace.context, |
| 130 | sourceId: input.source.id, | 133 | sourceId: input.source.id, |
| 131 | artifact: buildLocalSourceArtifact({ | 134 | artifact: sourceArtifactStore.build({ |
| 132 | source: input.source, | 135 | source: input.source, |
| 133 | materialization, | 136 | materialization, |
| 134 | }), | 137 | }), |
| @@ -1,22 +1,10 @@ | |||
| 1 | import { sha256Hex } from "@executor/codemode-core"; | 1 | import { sha256Hex } from "@executor/codemode-core"; |
| 2 | 2 | ||
| 3 | import type { | 3 | import type { |
| 4 | Source, | ||
| 5 | StoredSourceRecipeDocumentRecord, | 4 | StoredSourceRecipeDocumentRecord, |
| 6 | StoredSourceRecipeOperationRecord, | 5 | StoredSourceRecipeOperationRecord, |
| 7 | StoredSourceRecipeSchemaBundleRecord, | 6 | StoredSourceRecipeSchemaBundleRecord, |
| 8 | StoredSourceRecipeRevisionRecord, | ||
| 9 | } from "#schema"; | 7 | } from "#schema"; |
| 10 | import type { SqlControlPlaneRows } from "#persistence"; | ||
| 11 | import * as Effect from "effect/Effect"; | ||
| 12 | import * as Option from "effect/Option"; | ||
| 13 | |||
| 14 | type SourceBindingRecord = | ||
| 15 | Awaited< | ||
| 16 | ReturnType<SqlControlPlaneRows["sources"]["getByWorkspaceAndId"]> | ||
| 17 | > extends Effect.Effect<Option.Option<infer T>, unknown, never> | ||
| 18 | ? T | ||
| 19 | : never; | ||
| 20 | 8 | ||
| 21 | export const normalizeSearchText = ( | 9 | export const normalizeSearchText = ( |
| 22 | ...parts: ReadonlyArray<string | null | undefined> | 10 | ...parts: ReadonlyArray<string | null | undefined> |
| @@ -27,32 +15,7 @@ export const normalizeSearchText = ( · 4 unmodified lines | |||
| 27 | .join(" ") | 15 | .join(" ") |
| 28 | .toLowerCase(); | 16 | .toLowerCase(); |
| 29 | 17 | ||
| 30 | export const contentHash = (value: string): string => | 18 | export const contentHash = (value: string): string => sha256Hex(value); |
| 31 | sha256Hex(value); | ||
| 32 | |||
| 33 | export const loadSourceBindingRecord = (input: { | ||
| 34 | rows: SqlControlPlaneRows; | ||
| 35 | source: Source; | ||
| 36 | }): Effect.Effect<SourceBindingRecord, Error, never> => | ||
| 37 | Effect.gen(function* () { | ||
| 38 | const sourceRecord = yield* input.rows.sources | ||
| 39 | .getByWorkspaceAndId(input.source.workspaceId, input.source.id) | ||
| 40 | .pipe( | ||
| 41 | Effect.mapError((cause) => | ||
| 42 | cause instanceof Error ? cause : new Error(String(cause)), | ||
| 43 | ), | ||
| 44 | ); | ||
| 45 | |||
| 46 | if (Option.isNone(sourceRecord)) { | ||
| 47 | return yield* Effect.fail( | ||
| 48 | new Error( | ||
| 49 | `Source disappeared while syncing recipe data for ${input.source.id}`, | ||
| 50 | ), | ||
| 51 | ); | ||
| 52 | } | ||
| 53 | |||
| 54 | return sourceRecord.value; | ||
| 55 | }); | ||
| 56 | 19 | ||
| 57 | export type SourceRecipeMaterialization = { | 20 | export type SourceRecipeMaterialization = { |
| 58 | manifestJson: string | null; | 21 | manifestJson: string | null; |
| @@ -62,313 +25,3 @@ export type SourceRecipeMaterialization = { · 3 unmodified lines | |||
| 62 | schemaBundles: readonly StoredSourceRecipeSchemaBundleRecord[]; | 25 | schemaBundles: readonly StoredSourceRecipeSchemaBundleRecord[]; |
| 63 | operations: readonly StoredSourceRecipeOperationRecord[]; | 26 | operations: readonly StoredSourceRecipeOperationRecord[]; |
| 64 | }; | 27 | }; |
| 65 | |||
| 66 | const canonicalMaterializationHash = (input: SourceRecipeMaterialization): string => { | ||
| 67 | const documents = [...input.documents] | ||
| 68 | .map((document) => ({ | ||
| 69 | documentKind: document.documentKind, | ||
| 70 | documentKey: document.documentKey, | ||
| 71 | contentHash: document.contentHash, | ||
| 72 | })) | ||
| 73 | .sort((left, right) => | ||
| 74 | left.documentKind.localeCompare(right.documentKind) | ||
| 75 | || left.documentKey.localeCompare(right.documentKey) | ||
| 76 | || left.contentHash.localeCompare(right.contentHash) | ||
| 77 | ); | ||
| 78 | const schemaBundles = [...input.schemaBundles] | ||
| 79 | .map((bundle) => ({ | ||
| 80 | bundleKind: bundle.bundleKind, | ||
| 81 | contentHash: bundle.contentHash, | ||
| 82 | })) | ||
| 83 | .sort((left, right) => | ||
| 84 | left.bundleKind.localeCompare(right.bundleKind) | ||
| 85 | || left.contentHash.localeCompare(right.contentHash) | ||
| 86 | ); | ||
| 87 | const operations = [...input.operations] | ||
| 88 | .map((operation) => ({ | ||
| 89 | operationKey: operation.operationKey, | ||
| 90 | transportKind: operation.transportKind, | ||
| 91 | toolId: operation.toolId, | ||
| 92 | title: operation.title, | ||
| 93 | description: operation.description, | ||
| 94 | operationKind: operation.operationKind, | ||
| 95 | searchText: operation.searchText, | ||
| 96 | inputSchemaJson: operation.inputSchemaJson, | ||
| 97 | outputSchemaJson: operation.outputSchemaJson, | ||
| 98 | providerKind: operation.providerKind, | ||
| 99 | providerDataJson: operation.providerDataJson, | ||
| 100 | })) | ||
| 101 | .sort((left, right) => left.operationKey.localeCompare(right.operationKey)); | ||
| 102 | |||
| 103 | return contentHash(JSON.stringify({ | ||
| 104 | schemaVersion: 1, | ||
| 105 | manifestHash: input.manifestHash, | ||
| 106 | manifestJson: input.manifestJson, | ||
| 107 | documents, | ||
| 108 | schemaBundles, | ||
| 109 | operations, | ||
| 110 | })); | ||
| 111 | }; | ||
| 112 | |||
| 113 | const replaceRevisionContent = (input: { | ||
| 114 | rows: SqlControlPlaneRows; | ||
| 115 | recipeRevisionId: StoredSourceRecipeRevisionRecord["id"]; | ||
| 116 | materializationHash: string; | ||
| 117 | materialization: SourceRecipeMaterialization; | ||
| 118 | updatedAt: number; | ||
| 119 | }) => | ||
| 120 | Effect.gen(function* () { | ||
| 121 | const boundDocuments = input.materialization.documents.map((document) => ({ | ||
| 122 | ...document, | ||
| 123 | recipeRevisionId: input.recipeRevisionId, | ||
| 124 | })); | ||
| 125 | const boundSchemaBundles = input.materialization.schemaBundles.map((bundle) => ({ | ||
| 126 | ...bundle, | ||
| 127 | recipeRevisionId: input.recipeRevisionId, | ||
| 128 | })); | ||
| 129 | const boundOperations = input.materialization.operations.map((operation) => ({ | ||
| 130 | ...operation, | ||
| 131 | recipeRevisionId: input.recipeRevisionId, | ||
| 132 | })); | ||
| 133 | |||
| 134 | yield* input.rows.sourceRecipeRevisions | ||
| 135 | .update(input.recipeRevisionId, { | ||
| 136 | manifestJson: input.materialization.manifestJson, | ||
| 137 | manifestHash: input.materialization.manifestHash, | ||
| 138 | materializationHash: input.materializationHash, | ||
| 139 | updatedAt: input.updatedAt, | ||
| 140 | }) | ||
| 141 | .pipe( | ||
| 142 | Effect.mapError((cause) => | ||
| 143 | cause instanceof Error ? cause : new Error(String(cause)), | ||
| 144 | ), | ||
| 145 | ); | ||
| 146 | |||
| 147 | yield* input.rows.sourceRecipeDocuments | ||
| 148 | .replaceForRevision({ | ||
| 149 | recipeRevisionId: input.recipeRevisionId, | ||
| 150 | documents: boundDocuments, | ||
| 151 | }) | ||
| 152 | .pipe( | ||
| 153 | Effect.mapError((cause) => | ||
| 154 | cause instanceof Error ? cause : new Error(String(cause)), | ||
| 155 | ), | ||
| 156 | ); | ||
| 157 | |||
| 158 | yield* input.rows.sourceRecipeSchemaBundles | ||
| 159 | .replaceForRevision({ | ||
| 160 | recipeRevisionId: input.recipeRevisionId, | ||
| 161 | bundles: boundSchemaBundles, | ||
| 162 | }) | ||
| 163 | .pipe( | ||
| 164 | Effect.mapError((cause) => | ||
| 165 | cause instanceof Error ? cause : new Error(String(cause)), | ||
| 166 | ), | ||
| 167 | ); | ||
| 168 | |||
| 169 | yield* input.rows.sourceRecipeOperations | ||
| 170 | .replaceForRevision({ | ||
| 171 | recipeRevisionId: input.recipeRevisionId, | ||
| 172 | operations: boundOperations, | ||
| 173 | }) | ||
| 174 | .pipe( | ||
| 175 | Effect.mapError((cause) => | ||
| 176 | cause instanceof Error ? cause : new Error(String(cause)), | ||
| 177 | ), | ||
| 178 | ); | ||
| 179 | }); | ||
| 180 | |||
| 181 | const cleanupOrphanedRevision = (input: { | ||
| 182 | rows: SqlControlPlaneRows; | ||
| 183 | recipeRevisionId: StoredSourceRecipeRevisionRecord["id"]; | ||
| 184 | }) => | ||
| 185 | Effect.gen(function* () { | ||
| 186 | const referenceCount = yield* input.rows.sources.countByRecipeRevisionId(input.recipeRevisionId); | ||
| 187 | if (referenceCount > 0) { | ||
| 188 | return; | ||
| 189 | } | ||
| 190 | |||
| 191 | yield* input.rows.sourceRecipeDocuments.removeByRevisionId(input.recipeRevisionId); | ||
| 192 | yield* input.rows.sourceRecipeSchemaBundles.removeByRevisionId(input.recipeRevisionId); | ||
| 193 | yield* input.rows.sourceRecipeOperations.removeByRevisionId(input.recipeRevisionId); | ||
| 194 | yield* input.rows.sourceRecipeRevisions.removeById(input.recipeRevisionId); | ||
| 195 | }); | ||
| 196 | |||
| 197 | const updateRecipeLatestRevision = (input: { | ||
| 198 | rows: SqlControlPlaneRows; | ||
| 199 | recipeId: SourceBindingRecord["recipeId"]; | ||
| 200 | latestRevisionId: StoredSourceRecipeRevisionRecord["id"]; | ||
| 201 | updatedAt: number; | ||
| 202 | }) => | ||
| 203 | Effect.gen(function* () { | ||
| 204 | const recipe = yield* input.rows.sourceRecipes.getById(input.recipeId); | ||
| 205 | if (Option.isNone(recipe)) { | ||
| 206 | return yield* Effect.fail( | ||
| 207 | new Error(`Recipe disappeared while syncing recipe data for ${input.recipeId}`), | ||
| 208 | ); | ||
| 209 | } | ||
| 210 | |||
| 211 | yield* input.rows.sourceRecipes.upsert({ | ||
| 212 | ...recipe.value, | ||
| 213 | latestRevisionId: input.latestRevisionId, | ||
| 214 | updatedAt: input.updatedAt, | ||
| 215 | }); | ||
| 216 | }); | ||
| 217 | |||
| 218 | export const persistRecipeMaterialization = (input: { | ||
| 219 | rows: SqlControlPlaneRows; | ||
| 220 | source: Source; | ||
| 221 | materialization: SourceRecipeMaterialization; | ||
| 222 | }): Effect.Effect<void, Error, never> => | ||
| 223 | Effect.gen(function* () { | ||
| 224 | const sourceRecord = yield* loadSourceBindingRecord({ | ||
| 225 | rows: input.rows, | ||
| 226 | source: input.source, | ||
| 227 | }); | ||
| 228 | const now = Date.now(); | ||
| 229 | const materializationHash = canonicalMaterializationHash(input.materialization); | ||
| 230 | const currentRevision = yield* input.rows.sourceRecipeRevisions.getById( | ||
| 231 | sourceRecord.recipeRevisionId, | ||
| 232 | ); | ||
| 233 | |||
| 234 | if (Option.isNone(currentRevision)) { | ||
| 235 | return yield* Effect.fail( | ||
| 236 | new Error( | ||
| 237 | `Recipe revision disappeared while syncing recipe data for ${input.source.id}`, | ||
| 238 | ), | ||
| 239 | ); | ||
| 240 | } | ||
| 241 | |||
| 242 | const existingMaterializedRevision = yield* input.rows.sourceRecipeRevisions | ||
| 243 | .getByRecipeAndMaterializationHash({ | ||
| 244 | recipeId: sourceRecord.recipeId, | ||
| 245 | materializationHash, | ||
| 246 | }); | ||
| 247 | |||
| 248 | if ( | ||
| 249 | Option.isSome(existingMaterializedRevision) | ||
| 250 | && existingMaterializedRevision.value.id !== currentRevision.value.id | ||
| 251 | ) { | ||
| 252 | const movedSource = yield* input.rows.sources.update( | ||
| 253 | input.source.workspaceId, | ||
| 254 | input.source.id, | ||
| 255 | { | ||
| 256 | recipeRevisionId: existingMaterializedRevision.value.id, | ||
| 257 | sourceHash: input.materialization.sourceHash, | ||
| 258 | updatedAt: now, | ||
| 259 | }, | ||
| 260 | ); | ||
| 261 | if (Option.isNone(movedSource)) { | ||
| 262 | return yield* Effect.fail( | ||
| 263 | new Error(`Source disappeared while updating recipe materialization for ${input.source.id}`), | ||
| 264 | ); | ||
| 265 | } | ||
| 266 | |||
| 267 | yield* updateRecipeLatestRevision({ | ||
| 268 | rows: input.rows, | ||
| 269 | recipeId: sourceRecord.recipeId, | ||
| 270 | latestRevisionId: existingMaterializedRevision.value.id, | ||
| 271 | updatedAt: now, | ||
| 272 | }); | ||
| 273 | yield* cleanupOrphanedRevision({ | ||
| 274 | rows: input.rows, | ||
| 275 | recipeRevisionId: currentRevision.value.id, | ||
| 276 | }); | ||
| 277 | return; | ||
| 278 | } | ||
| 279 | |||
| 280 | const needsFreshRevision = | ||
| 281 | currentRevision.value.materializationHash !== null | ||
| 282 | && currentRevision.value.materializationHash !== materializationHash; | ||
| 283 | |||
| 284 | const nextRevisionNumber = needsFreshRevision | ||
| 285 | ? yield* input.rows.sourceRecipeRevisions.nextRevisionNumber(sourceRecord.recipeId) | ||
| 286 | : null; | ||
| 287 | |||
| 288 | const targetRevision = needsFreshRevision | ||
| 289 | ? createFreshTargetRevision({ | ||
| 290 | currentRevision: currentRevision.value, | ||
| 291 | materializationHash, | ||
| 292 | now, | ||
| 293 | nextRevisionNumber: nextRevisionNumber!, | ||
| 294 | }) | ||
| 295 | : { | ||
| 296 | revision: currentRevision.value, | ||
| 297 | movedFromRevisionId: null as StoredSourceRecipeRevisionRecord["id"] | null, | ||
| 298 | }; | ||
| 299 | |||
| 300 | if (needsFreshRevision) { | ||
| 301 | yield* input.rows.sourceRecipeRevisions.upsert(targetRevision.revision); | ||
| 302 | const movedSource = yield* input.rows.sources.update( | ||
| 303 | input.source.workspaceId, | ||
| 304 | input.source.id, | ||
| 305 | { | ||
| 306 | recipeRevisionId: targetRevision.revision.id, | ||
| 307 | sourceHash: input.materialization.sourceHash, | ||
| 308 | updatedAt: now, | ||
| 309 | }, | ||
| 310 | ); | ||
| 311 | if (Option.isNone(movedSource)) { | ||
| 312 | return yield* Effect.fail( | ||
| 313 | new Error(`Source disappeared while switching recipe materialization for ${input.source.id}`), | ||
| 314 | ); | ||
| 315 | } | ||
| 316 | } else { | ||
| 317 | const updatedSource = yield* input.rows.sources.update( | ||
| 318 | input.source.workspaceId, | ||
| 319 | input.source.id, | ||
| 320 | { | ||
| 321 | sourceHash: input.materialization.sourceHash, | ||
| 322 | updatedAt: now, | ||
| 323 | }, | ||
| 324 | ); | ||
| 325 | if (Option.isNone(updatedSource)) { | ||
| 326 | return yield* Effect.fail( | ||
| 327 | new Error(`Source disappeared while updating sync metadata for ${input.source.id}`), | ||
| 328 | ); | ||
| 329 | } | ||
| 330 | } | ||
| 331 | |||
| 332 | yield* replaceRevisionContent({ | ||
| 333 | rows: input.rows, | ||
| 334 | recipeRevisionId: targetRevision.revision.id, | ||
| 335 | materializationHash, | ||
| 336 | materialization: input.materialization, | ||
| 337 | updatedAt: now, | ||
| 338 | }); | ||
| 339 | yield* updateRecipeLatestRevision({ | ||
| 340 | rows: input.rows, | ||
| 341 | recipeId: sourceRecord.recipeId, | ||
| 342 | latestRevisionId: targetRevision.revision.id, | ||
| 343 | updatedAt: now, | ||
| 344 | }); | ||
| 345 | |||
| 346 | if (targetRevision.movedFromRevisionId !== null) { | ||
| 347 | yield* cleanupOrphanedRevision({ | ||
| 348 | rows: input.rows, | ||
| 349 | recipeRevisionId: targetRevision.movedFromRevisionId, | ||
| 350 | }); | ||
| 351 | } | ||
| 352 | }); | ||
| 353 | |||
| 354 | const createFreshTargetRevision = (input: { | ||
| 355 | currentRevision: StoredSourceRecipeRevisionRecord; | ||
| 356 | materializationHash: string; | ||
| 357 | now: number; | ||
| 358 | nextRevisionNumber: number; | ||
| 359 | }): { | ||
| 360 | revision: StoredSourceRecipeRevisionRecord; | ||
| 361 | movedFromRevisionId: StoredSourceRecipeRevisionRecord["id"] | null; | ||
| 362 | } => ({ | ||
| 363 | revision: { | ||
| 364 | ...input.currentRevision, | ||
| 365 | id: `src_recipe_rev_${crypto.randomUUID()}` as StoredSourceRecipeRevisionRecord["id"], | ||
| 366 | revisionNumber: input.nextRevisionNumber, | ||
| 367 | manifestJson: null, | ||
| 368 | manifestHash: null, | ||
| 369 | materializationHash: input.materializationHash, | ||
| 370 | createdAt: input.now, | ||
| 371 | updatedAt: input.now, | ||
| 372 | }, | ||
| 373 | movedFromRevisionId: input.currentRevision.id, | ||
| 374 | }); | ||
| @@ -1,858 +0,0 @@ | |||
| 1 | import { mkdtempSync } from "node:fs"; | ||
| 2 | import { tmpdir } from "node:os"; | ||
| 3 | import { join } from "node:path"; | ||
| 4 | |||
| 5 | import { describe, expect, it } from "vitest"; | ||
| 6 | import * as Effect from "effect/Effect"; | ||
| 7 | import { | ||
| 8 | buildOpenApiToolPresentation, | ||
| 9 | compileOpenApiToolDefinitions, | ||
| 10 | extractOpenApiManifest, | ||
| 11 | } from "@executor/codemode-openapi"; | ||
| 12 | |||
| 13 | import { | ||
| 14 | AccountIdSchema, | ||
| 15 | SourceIdSchema, | ||
| 16 | type LocalConfigSource, | ||
| 17 | SourceRecipeIdSchema, | ||
| 18 | SourceRecipeRevisionIdSchema, | ||
| 19 | WorkspaceIdSchema, | ||
| 20 | type Source, | ||
| 21 | type StoredSourceRecipeOperationRecord, | ||
| 22 | } from "#schema"; | ||
| 23 | import { | ||
| 24 | createSqlControlPlanePersistence, | ||
| 25 | type SqlControlPlanePersistence, | ||
| 26 | } from "#persistence"; | ||
| 27 | |||
| 28 | import { | ||
| 29 | loadLocalExecutorConfig, | ||
| 30 | resolveLocalWorkspaceContext, | ||
| 31 | writeProjectLocalExecutorConfig, | ||
| 32 | } from "./local-config"; | ||
| 33 | import { | ||
| 34 | buildLocalSourceArtifact, | ||
| 35 | writeLocalSourceArtifact, | ||
| 36 | } from "./local-source-artifacts"; | ||
| 37 | import { | ||
| 38 | RuntimeLocalWorkspaceService, | ||
| 39 | type RuntimeLocalWorkspaceState, | ||
| 40 | } from "./local-runtime-context"; | ||
| 41 | import { | ||
| 42 | writeLocalWorkspaceState, | ||
| 43 | } from "./local-workspace-state"; | ||
| 44 | import { | ||
| 45 | expandRecipeTools, | ||
| 46 | loadSourceWithRecipe, | ||
| 47 | loadWorkspaceSourceRecipes, | ||
| 48 | recipeToolDescriptor, | ||
| 49 | recipeToolPath, | ||
| 50 | recipeToolSearchNamespace, | ||
| 51 | type LoadedSourceRecipe, | ||
| 52 | } from "./source-recipes-runtime"; | ||
| 53 | |||
| 54 | const makePersistence = () => | ||
| 55 | Effect.runPromise( | ||
| 56 | createSqlControlPlanePersistence({ | ||
| 57 | localDataDir: ":memory:", | ||
| 58 | }), | ||
| 59 | ); | ||
| 60 | |||
| 61 | const makeRuntimeLocalWorkspaceState = ( | ||
| 62 | workspaceId: ReturnType<typeof WorkspaceIdSchema.make>, | ||
| 63 | ) => | ||
| 64 | Effect.runPromise( | ||
| 65 | Effect.gen(function* () { | ||
| 66 | const workspaceRoot = mkdtempSync( | ||
| 67 | join(tmpdir(), "executor-source-recipes-runtime-"), | ||
| 68 | ); | ||
| 69 | const context = yield* resolveLocalWorkspaceContext({ workspaceRoot }); | ||
| 70 | const loadedConfig = yield* loadLocalExecutorConfig(context); | ||
| 71 | |||
| 72 | return { | ||
| 73 | context, | ||
| 74 | installation: { | ||
| 75 | workspaceId, | ||
| 76 | accountId: AccountIdSchema.make("acc_local_source_recipes"), | ||
| 77 | }, | ||
| 78 | loadedConfig, | ||
| 79 | } satisfies RuntimeLocalWorkspaceState; | ||
| 80 | }), | ||
| 81 | ); | ||
| 82 | |||
| 83 | const withRuntimeLocalWorkspace = <A, E>( | ||
| 84 | effect: Effect.Effect<A, E, never>, | ||
| 85 | runtimeLocalWorkspace: RuntimeLocalWorkspaceState, | ||
| 86 | ) => | ||
| 87 | effect.pipe( | ||
| 88 | Effect.provideService(RuntimeLocalWorkspaceService, runtimeLocalWorkspace), | ||
| 89 | ); | ||
| 90 | |||
| 91 | const openApiBindingConfigJson = (specUrl: string): string => | ||
| 92 | JSON.stringify({ | ||
| 93 | adapterKey: "openapi", | ||
| 94 | version: 1, | ||
| 95 | payload: { | ||
| 96 | specUrl, | ||
| 97 | defaultHeaders: null, | ||
| 98 | }, | ||
| 99 | }); | ||
| 100 | |||
| 101 | const graphqlBindingConfigJson = (): string => | ||
| 102 | JSON.stringify({ | ||
| 103 | adapterKey: "graphql", | ||
| 104 | version: 1, | ||
| 105 | payload: { | ||
| 106 | defaultHeaders: null, | ||
| 107 | }, | ||
| 108 | }); | ||
| 109 | |||
| 110 | const makeSource = (overrides: Partial<Source> = {}): Source => { | ||
| 111 | const kind = overrides.kind ?? "openapi"; | ||
| 112 | const endpoint = overrides.endpoint | ||
| 113 | ?? (kind === "graphql" | ||
| 114 | ? "https://example.com/graphql" | ||
| 115 | : kind === "mcp" | ||
| 116 | ? "https://example.com/mcp" | ||
| 117 | : "https://api.github.com"); | ||
| 118 | const binding = | ||
| 119 | overrides.binding | ||
| 120 | ?? (kind === "openapi" | ||
| 121 | ? { | ||
| 122 | specUrl: "https://api.github.com/openapi.json", | ||
| 123 | defaultHeaders: null, | ||
| 124 | } | ||
| 125 | : kind === "graphql" | ||
| 126 | ? { | ||
| 127 | defaultHeaders: null, | ||
| 128 | } | ||
| 129 | : kind === "mcp" | ||
| 130 | ? { | ||
| 131 | transport: null, | ||
| 132 | queryParams: null, | ||
| 133 | headers: null, | ||
| 134 | } | ||
| 135 | : {}); | ||
| 136 | |||
| 137 | return { | ||
| 138 | id: SourceIdSchema.make("src_runtime_recipe"), | ||
| 139 | workspaceId: WorkspaceIdSchema.make("ws_runtime_recipe"), | ||
| 140 | name: "GitHub", | ||
| 141 | kind, | ||
| 142 | endpoint, | ||
| 143 | status: "connected", | ||
| 144 | enabled: true, | ||
| 145 | namespace: "github", | ||
| 146 | bindingVersion: 1, | ||
| 147 | binding, | ||
| 148 | importAuthPolicy: "reuse_runtime", | ||
| 149 | importAuth: { kind: "none" }, | ||
| 150 | auth: { kind: "none" }, | ||
| 151 | sourceHash: null, | ||
| 152 | lastError: null, | ||
| 153 | createdAt: 1000, | ||
| 154 | updatedAt: 1000, | ||
| 155 | ...overrides, | ||
| 156 | }; | ||
| 157 | }; | ||
| 158 | |||
| 159 | const makeOperation = ( | ||
| 160 | overrides: Partial<StoredSourceRecipeOperationRecord> = {}, | ||
| 161 | ): StoredSourceRecipeOperationRecord => ({ | ||
| 162 | id: "src_recipe_op_runtime", | ||
| 163 | recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_runtime"), | ||
| 164 | operationKey: "getRepo", | ||
| 165 | transportKind: "http", | ||
| 166 | toolId: "getRepo", | ||
| 167 | title: "Get Repo", | ||
| 168 | description: "Read a repository", | ||
| 169 | operationKind: "read", | ||
| 170 | searchText: "get repo github", | ||
| 171 | inputSchemaJson: JSON.stringify({ | ||
| 172 | type: "object", | ||
| 173 | additionalProperties: false, | ||
| 174 | }), | ||
| 175 | outputSchemaJson: JSON.stringify({ | ||
| 176 | type: "object", | ||
| 177 | properties: { | ||
| 178 | full_name: { type: "string" }, | ||
| 179 | }, | ||
| 180 | }), | ||
| 181 | providerKind: "openapi", | ||
| 182 | providerDataJson: JSON.stringify({ | ||
| 183 | kind: "openapi", | ||
| 184 | toolId: "getRepo", | ||
| 185 | rawToolId: "repos_getRepo", | ||
| 186 | operationId: "repos.getRepo", | ||
| 187 | group: "repos", | ||
| 188 | leaf: "getRepo", | ||
| 189 | tags: ["repos"], | ||
| 190 | method: "get", | ||
| 191 | path: "/repos/{owner}/{repo}", | ||
| 192 | operationHash: "hash", | ||
| 193 | invocation: { | ||
| 194 | method: "get", | ||
| 195 | pathTemplate: "/repos/{owner}/{repo}", | ||
| 196 | parameters: [], | ||
| 197 | requestBody: null, | ||
| 198 | }, | ||
| 199 | }), | ||
| 200 | createdAt: 1000, | ||
| 201 | updatedAt: 1000, | ||
| 202 | ...overrides, | ||
| 203 | }); | ||
| 204 | |||
| 205 | const makeGraphqlOperation = ( | ||
| 206 | overrides: Partial<StoredSourceRecipeOperationRecord> = {}, | ||
| 207 | ): StoredSourceRecipeOperationRecord => ({ | ||
| 208 | id: "src_recipe_op_graphql_runtime", | ||
| 209 | recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_runtime"), | ||
| 210 | operationKey: "viewer", | ||
| 211 | transportKind: "graphql", | ||
| 212 | toolId: "viewer", | ||
| 213 | title: "Viewer", | ||
| 214 | description: "Query the current viewer", | ||
| 215 | operationKind: "read", | ||
| 216 | searchText: "viewer graphql query", | ||
| 217 | inputSchemaJson: JSON.stringify({ | ||
| 218 | type: "object", | ||
| 219 | additionalProperties: false, | ||
| 220 | }), | ||
| 221 | outputSchemaJson: JSON.stringify({ | ||
| 222 | type: "object", | ||
| 223 | properties: { | ||
| 224 | login: { type: "string" }, | ||
| 225 | }, | ||
| 226 | }), | ||
| 227 | providerKind: "graphql", | ||
| 228 | providerDataJson: JSON.stringify({ | ||
| 229 | kind: "graphql", | ||
| 230 | toolKind: "field", | ||
| 231 | toolId: "viewer", | ||
| 232 | rawToolId: "viewer", | ||
| 233 | group: "query", | ||
| 234 | leaf: "viewer", | ||
| 235 | fieldName: "viewer", | ||
| 236 | operationType: "query", | ||
| 237 | operationName: "viewer", | ||
| 238 | operationDocument: "query Viewer { viewer { login } }", | ||
| 239 | queryTypeName: "Query", | ||
| 240 | mutationTypeName: null, | ||
| 241 | subscriptionTypeName: null, | ||
| 242 | }), | ||
| 243 | createdAt: 1000, | ||
| 244 | updatedAt: 1000, | ||
| 245 | ...overrides, | ||
| 246 | }); | ||
| 247 | |||
| 248 | const makeMcpOperation = ( | ||
| 249 | overrides: Partial<StoredSourceRecipeOperationRecord> = {}, | ||
| 250 | ): StoredSourceRecipeOperationRecord => ({ | ||
| 251 | id: "src_recipe_op_mcp_runtime", | ||
| 252 | recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_runtime"), | ||
| 253 | operationKey: "echo", | ||
| 254 | transportKind: "mcp", | ||
| 255 | toolId: "echo", | ||
| 256 | title: "Echo", | ||
| 257 | description: "Echo a value", | ||
| 258 | operationKind: "unknown", | ||
| 259 | searchText: "echo mcp", | ||
| 260 | inputSchemaJson: null, | ||
| 261 | outputSchemaJson: null, | ||
| 262 | providerKind: "mcp", | ||
| 263 | providerDataJson: JSON.stringify({ | ||
| 264 | kind: "mcp", | ||
| 265 | toolId: "echo", | ||
| 266 | toolName: "echo", | ||
| 267 | description: "Echo a value", | ||
| 268 | }), | ||
| 269 | createdAt: 1000, | ||
| 270 | updatedAt: 1000, | ||
| 271 | ...overrides, | ||
| 272 | }); | ||
| 273 | |||
| 274 | const configSourceFromSource = (source: Source): LocalConfigSource => { | ||
| 275 | switch (source.kind) { | ||
| 276 | case "openapi": | ||
| 277 | return { | ||
| 278 | kind: "openapi" as const, | ||
| 279 | ...(source.name !== source.id ? { name: source.name } : {}), | ||
| 280 | ...(source.namespace && source.namespace !== source.id | ||
| 281 | ? { namespace: source.namespace } | ||
| 282 | : {}), | ||
| 283 | ...(source.enabled ? {} : { enabled: false }), | ||
| 284 | connection: { | ||
| 285 | endpoint: source.endpoint, | ||
| 286 | }, | ||
| 287 | binding: { | ||
| 288 | specUrl: | ||
| 289 | typeof source.binding.specUrl === "string" | ||
| 290 | ? source.binding.specUrl | ||
| 291 | : "https://example.com/openapi.json", | ||
| 292 | defaultHeaders: | ||
| 293 | (source.binding.defaultHeaders as Record<string, string> | null | undefined) | ||
| 294 | ?? null, | ||
| 295 | }, | ||
| 296 | }; | ||
| 297 | case "graphql": | ||
| 298 | return { | ||
| 299 | kind: "graphql" as const, | ||
| 300 | ...(source.name !== source.id ? { name: source.name } : {}), | ||
| 301 | ...(source.namespace && source.namespace !== source.id | ||
| 302 | ? { namespace: source.namespace } | ||
| 303 | : {}), | ||
| 304 | ...(source.enabled ? {} : { enabled: false }), | ||
| 305 | connection: { | ||
| 306 | endpoint: source.endpoint, | ||
| 307 | }, | ||
| 308 | binding: { | ||
| 309 | defaultHeaders: | ||
| 310 | (source.binding.defaultHeaders as Record<string, string> | null | undefined) | ||
| 311 | ?? null, | ||
| 312 | }, | ||
| 313 | }; | ||
| 314 | case "mcp": | ||
| 315 | return { | ||
| 316 | kind: "mcp" as const, | ||
| 317 | ...(source.name !== source.id ? { name: source.name } : {}), | ||
| 318 | ...(source.namespace && source.namespace !== source.id | ||
| 319 | ? { namespace: source.namespace } | ||
| 320 | : {}), | ||
| 321 | ...(source.enabled ? {} : { enabled: false }), | ||
| 322 | connection: { | ||
| 323 | endpoint: source.endpoint, | ||
| 324 | }, | ||
| 325 | binding: { | ||
| 326 | transport: | ||
| 327 | (source.binding.transport as "auto" | "streamable-http" | "sse" | null | undefined) | ||
| 328 | ?? null, | ||
| 329 | queryParams: | ||
| 330 | (source.binding.queryParams as Record<string, string> | null | undefined) | ||
| 331 | ?? null, | ||
| 332 | headers: | ||
| 333 | (source.binding.headers as Record<string, string> | null | undefined) | ||
| 334 | ?? null, | ||
| 335 | }, | ||
| 336 | }; | ||
| 337 | default: | ||
| 338 | throw new Error(`Unsupported source kind in test fixture: ${source.kind}`); | ||
| 339 | } | ||
| 340 | }; | ||
| 341 | |||
| 342 | const seedLocalWorkspaceSources = async (input: { | ||
| 343 | runtimeLocalWorkspace: RuntimeLocalWorkspaceState; | ||
| 344 | fixtures: ReadonlyArray<{ | ||
| 345 | source: Source; | ||
| 346 | materialization: { | ||
| 347 | manifestJson: string | null; | ||
| 348 | manifestHash: string | null; | ||
| 349 | sourceHash: string | null; | ||
| 350 | documents: ReadonlyArray<any>; | ||
| 351 | schemaBundles: ReadonlyArray<any>; | ||
| 352 | operations: ReadonlyArray<StoredSourceRecipeOperationRecord>; | ||
| 353 | }; | ||
| 354 | }>; | ||
| 355 | }) => { | ||
| 356 | await Effect.runPromise(writeProjectLocalExecutorConfig({ | ||
| 357 | context: input.runtimeLocalWorkspace.context, | ||
| 358 | config: { | ||
| 359 | sources: Object.fromEntries( | ||
| 360 | input.fixtures.map(({ source }) => [source.id, configSourceFromSource(source)]), | ||
| 361 | ), | ||
| 362 | }, | ||
| 363 | })); | ||
| 364 | |||
| 365 | await Promise.all( | ||
| 366 | input.fixtures.map(({ source, materialization }) => | ||
| 367 | Effect.runPromise(writeLocalSourceArtifact({ | ||
| 368 | context: input.runtimeLocalWorkspace.context, | ||
| 369 | sourceId: source.id, | ||
| 370 | artifact: buildLocalSourceArtifact({ | ||
| 371 | source, | ||
| 372 | materialization, | ||
| 373 | }), | ||
| 374 | })), | ||
| 375 | ), | ||
| 376 | ); | ||
| 377 | |||
| 378 | await Effect.runPromise(writeLocalWorkspaceState({ | ||
| 379 | context: input.runtimeLocalWorkspace.context, | ||
| 380 | state: { | ||
| 381 | version: 1, | ||
| 382 | sources: Object.fromEntries( | ||
| 383 | input.fixtures.map(({ source }) => [ | ||
| 384 | source.id, | ||
| 385 | { | ||
| 386 | status: source.status, | ||
| 387 | lastError: source.lastError, | ||
| 388 | sourceHash: source.sourceHash, | ||
| 389 | createdAt: source.createdAt, | ||
| 390 | updatedAt: source.updatedAt, | ||
| 391 | }, | ||
| 392 | ]), | ||
| 393 | ), | ||
| 394 | policies: {}, | ||
| 395 | }, | ||
| 396 | })); | ||
| 397 | }; | ||
| 398 | |||
| 399 | describe("source-recipes-runtime", () => { | ||
| 400 | describe("recipe tool helpers", () => { | ||
| 401 | it("derives tool paths from explicit and implicit namespaces", () => { | ||
| 402 | const source = makeSource(); | ||
| 403 | const operation = makeOperation(); | ||
| 404 | |||
| 405 | expect(recipeToolPath({ | ||
| 406 | source, | ||
| 407 | operation, | ||
| 408 | })).toBe("github.getRepo"); | ||
| 409 | expect(recipeToolPath({ | ||
| 410 | source: makeSource({ | ||
| 411 | namespace: null, | ||
| 412 | name: "My GitHub", | ||
| 413 | }), | ||
| 414 | operation, | ||
| 415 | })).toBe("my.github.getRepo"); | ||
| 416 | expect(recipeToolPath({ | ||
| 417 | source: makeSource({ | ||
| 418 | namespace: "", | ||
| 419 | }), | ||
| 420 | operation, | ||
| 421 | })).toBe("getRepo"); | ||
| 422 | }); | ||
| 423 | |||
| 424 | it("derives search namespaces from path segments and graphql namespaces", () => { | ||
| 425 | const openApiSource = makeSource(); | ||
| 426 | const graphqlSource = makeSource({ | ||
| 427 | kind: "graphql", | ||
| 428 | namespace: "issues", | ||
| 429 | }); | ||
| 430 | |||
| 431 | expect(recipeToolSearchNamespace({ | ||
| 432 | source: openApiSource, | ||
| 433 | path: "github.getRepo", | ||
| 434 | operation: makeOperation(), | ||
| 435 | })).toBe("github.getRepo"); | ||
| 436 | expect(recipeToolSearchNamespace({ | ||
| 437 | source: openApiSource, | ||
| 438 | path: "a.b.c", | ||
| 439 | operation: makeOperation(), | ||
| 440 | })).toBe("a.b"); | ||
| 441 | expect(recipeToolSearchNamespace({ | ||
| 442 | source: graphqlSource, | ||
| 443 | path: "ignored.path", | ||
| 444 | operation: makeGraphqlOperation(), | ||
| 445 | })).toBe("issues"); | ||
| 446 | }); | ||
| 447 | |||
| 448 | it("computes interaction modes and descriptor fields correctly", () => { | ||
| 449 | expect(recipeToolDescriptor({ | ||
| 450 | source: makeSource(), | ||
| 451 | operation: makeOperation(), | ||
| 452 | path: "github.getRepo", | ||
| 453 | includeSchemas: true, | ||
| 454 | }).interaction).toBe("auto"); | ||
| 455 | |||
| 456 | expect(recipeToolDescriptor({ | ||
| 457 | source: makeSource(), | ||
| 458 | operation: makeOperation({ | ||
| 459 | operationKind: "delete", | ||
| 460 | }), | ||
| 461 | path: "github.deleteRepo", | ||
| 462 | includeSchemas: true, | ||
| 463 | }).interaction).toBe("required"); | ||
| 464 | |||
| 465 | expect(recipeToolDescriptor({ | ||
| 466 | source: makeSource({ | ||
| 467 | kind: "graphql", | ||
| 468 | }), | ||
| 469 | operation: makeGraphqlOperation(), | ||
| 470 | path: "graphql.viewer", | ||
| 471 | includeSchemas: true, | ||
| 472 | }).interaction).toBe("auto"); | ||
| 473 | |||
| 474 | expect(recipeToolDescriptor({ | ||
| 475 | source: makeSource({ | ||
| 476 | kind: "graphql", | ||
| 477 | }), | ||
| 478 | operation: makeGraphqlOperation({ | ||
| 479 | operationKind: "write", | ||
| 480 | }), | ||
| 481 | path: "graphql.createIssue", | ||
| 482 | includeSchemas: true, | ||
| 483 | }).interaction).toBe("required"); | ||
| 484 | |||
| 485 | expect(recipeToolDescriptor({ | ||
| 486 | source: makeSource({ | ||
| 487 | kind: "mcp", | ||
| 488 | binding: { | ||
| 489 | transport: "streamable-http", | ||
| 490 | queryParams: null, | ||
| 491 | headers: null, | ||
| 492 | }, | ||
| 493 | }), | ||
| 494 | operation: makeMcpOperation(), | ||
| 495 | path: "mcp.echo", | ||
| 496 | includeSchemas: true, | ||
| 497 | }).interaction).toBe("auto"); | ||
| 498 | |||
| 499 | const descriptor = recipeToolDescriptor({ | ||
| 500 | source: makeSource(), | ||
| 501 | operation: makeOperation({ | ||
| 502 | description: null, | ||
| 503 | title: "Fallback Title", | ||
| 504 | providerDataJson: null, | ||
| 505 | }), | ||
| 506 | path: "github.getRepo", | ||
| 507 | includeSchemas: false, | ||
| 508 | }); | ||
| 509 | |||
| 510 | expect(descriptor.description).toBe("Fallback Title"); | ||
| 511 | expect(descriptor.inputSchemaJson).toBeUndefined(); | ||
| 512 | expect(descriptor.outputSchemaJson).toBeUndefined(); | ||
| 513 | expect(descriptor).not.toHaveProperty("providerDataJson"); | ||
| 514 | }); | ||
| 515 | |||
| 516 | it("expands recipes into lower-cased searchable tools and handles empty operation sets", () => { | ||
| 517 | const recipe: LoadedSourceRecipe = { | ||
| 518 | source: makeSource({ | ||
| 519 | name: "GITHUB API", | ||
| 520 | }), | ||
| 521 | sourceRecord: { | ||
| 522 | id: SourceIdSchema.make("src_runtime_recipe"), | ||
| 523 | workspaceId: WorkspaceIdSchema.make("ws_runtime_recipe"), | ||
| 524 | recipeId: SourceRecipeIdSchema.make("src_recipe_runtime"), | ||
| 525 | recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_runtime"), | ||
| 526 | name: "GITHUB API", | ||
| 527 | kind: "openapi", | ||
| 528 | endpoint: "https://api.github.com", | ||
| 529 | status: "connected", | ||
| 530 | enabled: true, | ||
| 531 | namespace: "github", | ||
| 532 | importAuthPolicy: "reuse_runtime", | ||
| 533 | bindingConfigJson: openApiBindingConfigJson("https://api.github.com/openapi.json"), | ||
| 534 | sourceHash: null, | ||
| 535 | lastError: null, | ||
| 536 | createdAt: 1000, | ||
| 537 | updatedAt: 1000, | ||
| 538 | }, | ||
| 539 | revision: { | ||
| 540 | id: SourceRecipeRevisionIdSchema.make("src_recipe_rev_runtime"), | ||
| 541 | recipeId: SourceRecipeIdSchema.make("src_recipe_runtime"), | ||
| 542 | revisionNumber: 1, | ||
| 543 | sourceConfigJson: "{}", | ||
| 544 | manifestJson: null, | ||
| 545 | manifestHash: null, | ||
| 546 | materializationHash: null, | ||
| 547 | createdAt: 1000, | ||
| 548 | updatedAt: 1000, | ||
| 549 | }, | ||
| 550 | documents: [], | ||
| 551 | schemaBundles: [], | ||
| 552 | operations: [makeOperation({ | ||
| 553 | searchText: "", | ||
| 554 | })], | ||
| 555 | manifest: null, | ||
| 556 | }; | ||
| 557 | |||
| 558 | const expanded = Effect.runSync( | ||
| 559 | expandRecipeTools({ | ||
| 560 | recipes: [recipe], | ||
| 561 | includeSchemas: false, | ||
| 562 | }), | ||
| 563 | ); | ||
| 564 | expect(expanded).toHaveLength(1); | ||
| 565 | expect(expanded[0]?.searchText).toBe( | ||
| 566 | "github.getrepo github.getrepo github api github.getrepo getrepo get repo read a repository repos_getrepo repos.getrepo get /repos/{owner}/{repo} repos getrepo repos", | ||
| 567 | ); | ||
| 568 | |||
| 569 | expect(Effect.runSync(expandRecipeTools({ | ||
| 570 | recipes: [{ | ||
| 571 | ...recipe, | ||
| 572 | operations: [], | ||
| 573 | }], | ||
| 574 | includeSchemas: false, | ||
| 575 | }))).toEqual([]); | ||
| 576 | }); | ||
| 577 | }); | ||
| 578 | |||
| 579 | describe("recipe loading", () => { | ||
| 580 | it("loads multiple sources sharing the same recipe revision", async () => { | ||
| 581 | const persistence = await makePersistence(); | ||
| 582 | try { | ||
| 583 | const workspaceId = WorkspaceIdSchema.make("ws_shared_revision"); | ||
| 584 | const runtimeLocalWorkspace = | ||
| 585 | await makeRuntimeLocalWorkspaceState(workspaceId); | ||
| 586 | const openApiDocument = JSON.stringify({ | ||
| 587 | openapi: "3.0.3", | ||
| 588 | info: { | ||
| 589 | title: "GitHub", | ||
| 590 | version: "1.0.0", | ||
| 591 | }, | ||
| 592 | paths: { | ||
| 593 | "/repos/{owner}/{repo}": { | ||
| 594 | get: { | ||
| 595 | operationId: "repos.getRepo", | ||
| 596 | parameters: [ | ||
| 597 | { | ||
| 598 | name: "owner", | ||
| 599 | in: "path", | ||
| 600 | required: true, | ||
| 601 | schema: { type: "string" }, | ||
| 602 | }, | ||
| 603 | { | ||
| 604 | name: "repo", | ||
| 605 | in: "path", | ||
| 606 | required: true, | ||
| 607 | schema: { type: "string" }, | ||
| 608 | }, | ||
| 609 | ], | ||
| 610 | responses: { | ||
| 611 | 200: { | ||
| 612 | description: "ok", | ||
| 613 | }, | ||
| 614 | }, | ||
| 615 | }, | ||
| 616 | }, | ||
| 617 | }, | ||
| 618 | }); | ||
| 619 | const manifest = await Effect.runPromise(extractOpenApiManifest("GitHub", openApiDocument)); | ||
| 620 | const definition = compileOpenApiToolDefinitions(manifest)[0]!; | ||
| 621 | const presentation = buildOpenApiToolPresentation({ | ||
| 622 | definition, | ||
| 623 | }); | ||
| 624 | |||
| 625 | await seedLocalWorkspaceSources({ | ||
| 626 | runtimeLocalWorkspace, | ||
| 627 | fixtures: ["GitHub One", "GitHub Two"].map((name, index) => ({ | ||
| 628 | source: makeSource({ | ||
| 629 | id: SourceIdSchema.make(`src_shared_revision_${index}`), | ||
| 630 | workspaceId, | ||
| 631 | name, | ||
| 632 | sourceHash: manifest.sourceHash, | ||
| 633 | createdAt: 1000 + index, | ||
| 634 | updatedAt: 1000 + index, | ||
| 635 | }), | ||
| 636 | materialization: { | ||
| 637 | manifestJson: JSON.stringify(manifest), | ||
| 638 | manifestHash: manifest.sourceHash, | ||
| 639 | sourceHash: manifest.sourceHash, | ||
| 640 | documents: [{ | ||
| 641 | id: `src_recipe_doc_shared_revision_${index}`, | ||
| 642 | recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_materialization"), | ||
| 643 | documentKind: "openapi", | ||
| 644 | documentKey: "https://api.github.com/openapi.json", | ||
| 645 | contentText: openApiDocument, | ||
| 646 | contentHash: manifest.sourceHash, | ||
| 647 | fetchedAt: 1000, | ||
| 648 | createdAt: 1000, | ||
| 649 | updatedAt: 1000, | ||
| 650 | }], | ||
| 651 | schemaBundles: [], | ||
| 652 | operations: [makeOperation({ | ||
| 653 | id: `src_recipe_op_shared_revision_${index}`, | ||
| 654 | recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_materialization"), | ||
| 655 | operationKey: definition.toolId, | ||
| 656 | toolId: definition.toolId, | ||
| 657 | title: definition.name, | ||
| 658 | description: definition.description, | ||
| 659 | searchText: `${definition.toolId} ${definition.name}`.toLowerCase(), | ||
| 660 | inputSchemaJson: presentation.inputSchemaJson ?? null, | ||
| 661 | outputSchemaJson: presentation.outputSchemaJson ?? null, | ||
| 662 | providerDataJson: presentation.providerDataJson, | ||
| 663 | })], | ||
| 664 | }, | ||
| 665 | })), | ||
| 666 | }); | ||
| 667 | |||
| 668 | const recipes = await Effect.runPromise( | ||
| 669 | withRuntimeLocalWorkspace( | ||
| 670 | loadWorkspaceSourceRecipes({ | ||
| 671 | rows: persistence.rows, | ||
| 672 | workspaceId, | ||
| 673 | }), | ||
| 674 | runtimeLocalWorkspace, | ||
| 675 | ), | ||
| 676 | ); | ||
| 677 | |||
| 678 | expect(recipes).toHaveLength(2); | ||
| 679 | expect(recipes[0]?.documents).toHaveLength(1); | ||
| 680 | expect(recipes[0]?.operations).toHaveLength(1); | ||
| 681 | expect(recipes[0]?.documents[0]?.contentText).toBe( | ||
| 682 | recipes[1]?.documents[0]?.contentText, | ||
| 683 | ); | ||
| 684 | expect(recipes[0]?.operations[0]?.providerDataJson).toBe( | ||
| 685 | recipes[1]?.operations[0]?.providerDataJson, | ||
| 686 | ); | ||
| 687 | } finally { | ||
| 688 | await persistence.close(); | ||
| 689 | } | ||
| 690 | }, 60_000); | ||
| 691 | |||
| 692 | it("loads sources with empty recipe documents and operations", async () => { | ||
| 693 | const persistence = await makePersistence(); | ||
| 694 | try { | ||
| 695 | const workspaceId = WorkspaceIdSchema.make("ws_empty_recipe_rows"); | ||
| 696 | const sourceId = SourceIdSchema.make("src_empty_recipe_rows"); | ||
| 697 | const runtimeLocalWorkspace = | ||
| 698 | await makeRuntimeLocalWorkspaceState(workspaceId); | ||
| 699 | |||
| 700 | await seedLocalWorkspaceSources({ | ||
| 701 | runtimeLocalWorkspace, | ||
| 702 | fixtures: [{ | ||
| 703 | source: makeSource({ | ||
| 704 | id: sourceId, | ||
| 705 | workspaceId, | ||
| 706 | name: "GraphQL Demo", | ||
| 707 | kind: "graphql", | ||
| 708 | endpoint: "https://example.com/graphql", | ||
| 709 | namespace: "graphql", | ||
| 710 | binding: { | ||
| 711 | defaultHeaders: null, | ||
| 712 | }, | ||
| 713 | }), | ||
| 714 | materialization: { | ||
| 715 | manifestJson: null, | ||
| 716 | manifestHash: null, | ||
| 717 | sourceHash: null, | ||
| 718 | documents: [], | ||
| 719 | schemaBundles: [], | ||
| 720 | operations: [], | ||
| 721 | }, | ||
| 722 | }], | ||
| 723 | }); | ||
| 724 | |||
| 725 | const recipes = await Effect.runPromise( | ||
| 726 | withRuntimeLocalWorkspace( | ||
| 727 | loadWorkspaceSourceRecipes({ | ||
| 728 | rows: persistence.rows, | ||
| 729 | workspaceId, | ||
| 730 | }), | ||
| 731 | runtimeLocalWorkspace, | ||
| 732 | ), | ||
| 733 | ); | ||
| 734 | |||
| 735 | expect(recipes).toHaveLength(1); | ||
| 736 | expect(recipes[0]?.documents).toEqual([]); | ||
| 737 | expect(recipes[0]?.operations).toEqual([]); | ||
| 738 | expect(recipes[0]?.manifest).toBeNull(); | ||
| 739 | } finally { | ||
| 740 | await persistence.close(); | ||
| 741 | } | ||
| 742 | }, 60_000); | ||
| 743 | |||
| 744 | it("fails clearly when loading a missing source, missing revision, or invalid manifest", async () => { | ||
| 745 | const persistence = await makePersistence(); | ||
| 746 | try { | ||
| 747 | const missingWorkspaceId = WorkspaceIdSchema.make("ws_missing_source"); | ||
| 748 | const missingRuntimeLocalWorkspace = | ||
| 749 | await makeRuntimeLocalWorkspaceState(missingWorkspaceId); | ||
| 750 | |||
| 751 | await expect( | ||
| 752 | Effect.runPromise( | ||
| 753 | withRuntimeLocalWorkspace( | ||
| 754 | loadSourceWithRecipe({ | ||
| 755 | rows: persistence.rows, | ||
| 756 | workspaceId: missingWorkspaceId, | ||
| 757 | sourceId: SourceIdSchema.make("src_missing_source"), | ||
| 758 | }), | ||
| 759 | missingRuntimeLocalWorkspace, | ||
| 760 | ), | ||
| 761 | ), | ||
| 762 | ).rejects.toThrow("Source not found"); | ||
| 763 | |||
| 764 | const workspaceId = WorkspaceIdSchema.make("ws_bad_recipe_runtime"); | ||
| 765 | const sourceId = SourceIdSchema.make("src_bad_recipe_runtime"); | ||
| 766 | const runtimeLocalWorkspace = | ||
| 767 | await makeRuntimeLocalWorkspaceState(workspaceId); | ||
| 768 | |||
| 769 | await Effect.runPromise(writeProjectLocalExecutorConfig({ | ||
| 770 | context: runtimeLocalWorkspace.context, | ||
| 771 | config: { | ||
| 772 | sources: { | ||
| 773 | [sourceId]: configSourceFromSource( | ||
| 774 | makeSource({ | ||
| 775 | id: sourceId, | ||
| 776 | workspaceId, | ||
| 777 | name: "Broken GitHub", | ||
| 778 | }), | ||
| 779 | ), | ||
| 780 | }, | ||
| 781 | }, | ||
| 782 | })); | ||
| 783 | await Effect.runPromise(writeLocalWorkspaceState({ | ||
| 784 | context: runtimeLocalWorkspace.context, | ||
| 785 | state: { | ||
| 786 | version: 1, | ||
| 787 | sources: { | ||
| 788 | [sourceId]: { | ||
| 789 | status: "connected", | ||
| 790 | lastError: null, | ||
| 791 | sourceHash: null, | ||
| 792 | createdAt: 1000, | ||
| 793 | updatedAt: 1000, | ||
| 794 | }, | ||
| 795 | }, | ||
| 796 | policies: {}, | ||
| 797 | }, | ||
| 798 | })); | ||
| 799 | |||
| 800 | await expect( | ||
| 801 | Effect.runPromise( | ||
| 802 | withRuntimeLocalWorkspace( | ||
| 803 | loadSourceWithRecipe({ | ||
| 804 | rows: persistence.rows, | ||
| 805 | workspaceId, | ||
| 806 | sourceId, | ||
| 807 | }), | ||
| 808 | runtimeLocalWorkspace, | ||
| 809 | ), | ||
| 810 | ), | ||
| 811 | ).rejects.toThrow("Recipe artifact missing"); | ||
| 812 | |||
| 813 | const invalidManifestSource = makeSource({ | ||
| 814 | id: sourceId, | ||
| 815 | workspaceId, | ||
| 816 | name: "Broken GitHub", | ||
| 817 | }); | ||
| 818 | const invalidArtifact = buildLocalSourceArtifact({ | ||
| 819 | source: invalidManifestSource, | ||
| 820 | materialization: { | ||
| 821 | manifestJson: "{}", | ||
| 822 | manifestHash: "manifest_hash_invalid", | ||
| 823 | sourceHash: "manifest_hash_invalid", | ||
| 824 | documents: [], | ||
| 825 | schemaBundles: [], | ||
| 826 | operations: [], | ||
| 827 | }, | ||
| 828 | }); | ||
| 829 | await Effect.runPromise(writeLocalSourceArtifact({ | ||
| 830 | context: runtimeLocalWorkspace.context, | ||
| 831 | sourceId, | ||
| 832 | artifact: { | ||
| 833 | ...invalidArtifact, | ||
| 834 | revision: { | ||
| 835 | ...invalidArtifact.revision, | ||
| 836 | manifestJson: "{bad-json", | ||
| 837 | }, | ||
| 838 | }, | ||
| 839 | })); | ||
| 840 | |||
| 841 | await expect( | ||
| 842 | Effect.runPromise( | ||
| 843 | withRuntimeLocalWorkspace( | ||
| 844 | loadSourceWithRecipe({ | ||
| 845 | rows: persistence.rows, | ||
| 846 | workspaceId, | ||
| 847 | sourceId, | ||
| 848 | }), | ||
| 849 | runtimeLocalWorkspace, | ||
| 850 | ), | ||
| 851 | ), | ||
| 852 | ).rejects.toThrow(`Invalid OpenAPI manifest for ${sourceId}`); | ||
| 853 | } finally { | ||
| 854 | await persistence.close(); | ||
| 855 | } | ||
| 856 | }); | ||
| 857 | }); | ||
| 858 | }); | ||
| @@ -1,7 +1,7 @@ | |||
| 1 | import { | 1 | import { |
| 2 | type ToolCatalogEntry, | ||
| 2 | type ToolDescriptor, | 3 | type ToolDescriptor, |
| 3 | } from "@executor/codemode-core"; | 4 | } from "@executor/codemode-core"; |
| 4 | import type { SqlControlPlaneRows } from "#persistence"; | ||
| 5 | import type { | 5 | import type { |
| 6 | AccountId, | 6 | AccountId, |
| 7 | Source, | 7 | Source, |
| @@ -13,19 +13,20 @@ import type { · 5 unmodified lines | |||
| 13 | WorkspaceId, | 13 | WorkspaceId, |
| 14 | } from "#schema"; | 14 | } from "#schema"; |
| 15 | import * as Effect from "effect/Effect"; | 15 | import * as Effect from "effect/Effect"; |
| 16 | |||
| 17 | import { | ||
| 18 | readLocalSourceArtifact, | ||
| 19 | } from "./local-source-artifacts"; | ||
| 20 | import { LocalSourceArtifactMissingError } from "./local-errors"; | 16 | import { LocalSourceArtifactMissingError } from "./local-errors"; |
| 21 | import { | 17 | import { |
| 22 | requireRuntimeLocalWorkspace, | 18 | requireRuntimeLocalWorkspace, |
| 23 | } from "./local-runtime-context"; | 19 | } from "./local-runtime-context"; |
| 20 | import { | ||
| 21 | type WorkspaceStorageServices, | ||
| 22 | SourceArtifactStore, | ||
| 23 | } from "./local-storage"; | ||
| 24 | import { namespaceFromSourceName } from "./source-names"; | 24 | import { namespaceFromSourceName } from "./source-names"; |
| 25 | import { | 25 | import { |
| 26 | getSourceAdapterForOperation, | 26 | getSourceAdapterForOperation, |
| 27 | getSourceAdapterForSource, | 27 | getSourceAdapterForSource, |
| 28 | } from "./source-adapters"; | 28 | } from "./source-adapters"; |
| 29 | import type { ControlPlaneStoreShape } from "./store"; | ||
| 29 | import type { SourceAdapterPersistedOperationMetadata } from "./source-adapters/types"; | 30 | import type { SourceAdapterPersistedOperationMetadata } from "./source-adapters/types"; |
| 30 | import { firstSchemaBundle } from "./source-adapters/shared"; | 31 | import { firstSchemaBundle } from "./source-adapters/shared"; |
| 31 | import { loadSourceById, loadSourcesInWorkspace } from "./source-store"; | 32 | import { loadSourceById, loadSourcesInWorkspace } from "./source-store"; |
| @@ -68,6 +69,16 @@ export type LoadedSourceRecipeToolIndexEntry = { · 36 unmodified lines | |||
| 68 | descriptor: ToolDescriptor; | 69 | descriptor: ToolDescriptor; |
| 69 | }; | 70 | }; |
| 70 | 71 | ||
| 72 | export const recipeToolCatalogEntry = (input: { | ||
| 73 | tool: LoadedSourceRecipeToolIndexEntry; | ||
| 74 | score: (queryTokens: readonly string[]) => number; | ||
| 75 | }): ToolCatalogEntry => ({ | ||
| 76 | descriptor: input.tool.descriptor, | ||
| 77 | namespace: input.tool.searchNamespace, | ||
| 78 | searchText: input.tool.searchText, | ||
| 79 | score: input.score, | ||
| 80 | }); | ||
| 81 | |||
| 71 | const parseManifestForRecipe = (input: { | 82 | const parseManifestForRecipe = (input: { |
| 72 | source: Source; | 83 | source: Source; |
| 73 | revision: StoredSourceRecipeRevisionRecord; | 84 | revision: StoredSourceRecipeRevisionRecord; |
| @@ -157,18 +168,19 @@ const primarySchemaBundleForRevision = (input: { · 83 unmodified lines | |||
| 157 | }; | 168 | }; |
| 158 | 169 | ||
| 159 | export const loadWorkspaceSourceRecipes = (input: { | 170 | export const loadWorkspaceSourceRecipes = (input: { |
| 160 | rows: SqlControlPlaneRows; | 171 | rows: ControlPlaneStoreShape; |
| 161 | workspaceId: WorkspaceId; | 172 | workspaceId: WorkspaceId; |
| 162 | actorAccountId?: AccountId | null; | 173 | actorAccountId?: AccountId | null; |
| 163 | }): Effect.Effect<readonly LoadedSourceRecipe[], Error, never> => | 174 | }): Effect.Effect<readonly LoadedSourceRecipe[], Error, WorkspaceStorageServices> => |
| 164 | Effect.gen(function* () { | 175 | Effect.gen(function* () { |
| 165 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.workspaceId); | 176 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.workspaceId); |
| 177 | const sourceArtifactStore = yield* SourceArtifactStore; | ||
| 166 | const sources = yield* loadSourcesInWorkspace(input.rows, input.workspaceId, { | 178 | const sources = yield* loadSourcesInWorkspace(input.rows, input.workspaceId, { |
| 167 | actorAccountId: input.actorAccountId, | 179 | actorAccountId: input.actorAccountId, |
| 168 | }); | 180 | }); |
| 169 | const localRecipes = yield* Effect.forEach(sources, (source) => | 181 | const localRecipes = yield* Effect.forEach(sources, (source) => |
| 170 | Effect.gen(function* () { | 182 | Effect.gen(function* () { |
| 171 | const artifact = yield* readLocalSourceArtifact({ | 183 | const artifact = yield* sourceArtifactStore.read({ |
| 172 | context: runtimeLocalWorkspace.context, | 184 | context: runtimeLocalWorkspace.context, |
| 173 | sourceId: source.id, | 185 | sourceId: source.id, |
| 174 | }); | 186 | }); |
| @@ -215,19 +227,20 @@ export const loadWorkspaceSourceRecipes = (input: { · 40 unmodified lines | |||
| 215 | }); | 227 | }); |
| 216 | 228 | ||
| 217 | export const loadSourceWithRecipe = (input: { | 229 | export const loadSourceWithRecipe = (input: { |
| 218 | rows: SqlControlPlaneRows; | 230 | rows: ControlPlaneStoreShape; |
| 219 | workspaceId: WorkspaceId; | 231 | workspaceId: WorkspaceId; |
| 220 | sourceId: Source["id"]; | 232 | sourceId: Source["id"]; |
| 221 | actorAccountId?: AccountId | null; | 233 | actorAccountId?: AccountId | null; |
| 222 | }): Effect.Effect<LoadedSourceRecipe, Error | LocalSourceArtifactMissingError, never> => | 234 | }): Effect.Effect<LoadedSourceRecipe, Error | LocalSourceArtifactMissingError, WorkspaceStorageServices> => |
| 223 | Effect.gen(function* () { | 235 | Effect.gen(function* () { |
| 224 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.workspaceId); | 236 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.workspaceId); |
| 237 | const sourceArtifactStore = yield* SourceArtifactStore; | ||
| 225 | const source = yield* loadSourceById(input.rows, { | 238 | const source = yield* loadSourceById(input.rows, { |
| 226 | workspaceId: input.workspaceId, | 239 | workspaceId: input.workspaceId, |
| 227 | sourceId: input.sourceId, | 240 | sourceId: input.sourceId, |
| 228 | actorAccountId: input.actorAccountId, | 241 | actorAccountId: input.actorAccountId, |
| 229 | }); | 242 | }); |
| 230 | const artifact = yield* readLocalSourceArtifact({ | 243 | const artifact = yield* sourceArtifactStore.read({ |
| 231 | context: runtimeLocalWorkspace.context, | 244 | context: runtimeLocalWorkspace.context, |
| 232 | sourceId: source.id, | 245 | sourceId: source.id, |
| 233 | }); | 246 | }); |
| @@ -335,11 +348,11 @@ export const expandRecipeTools = (input: { · 101 unmodified lines | |||
| 335 | ); | 348 | ); |
| 336 | 349 | ||
| 337 | export const loadWorkspaceSourceRecipeToolIndex = (input: { | 350 | export const loadWorkspaceSourceRecipeToolIndex = (input: { |
| 338 | rows: SqlControlPlaneRows; | 351 | rows: ControlPlaneStoreShape; |
| 339 | workspaceId: WorkspaceId; | 352 | workspaceId: WorkspaceId; |
| 340 | actorAccountId?: AccountId | null; | 353 | actorAccountId?: AccountId | null; |
| 341 | includeSchemas: boolean; | 354 | includeSchemas: boolean; |
| 342 | }): Effect.Effect<readonly LoadedSourceRecipeToolIndexEntry[], Error, never> => | 355 | }): Effect.Effect<readonly LoadedSourceRecipeToolIndexEntry[], Error, WorkspaceStorageServices> => |
| 343 | Effect.gen(function* () { | 356 | Effect.gen(function* () { |
| 344 | const recipes = yield* loadWorkspaceSourceRecipes({ | 357 | const recipes = yield* loadWorkspaceSourceRecipes({ |
| 345 | rows: input.rows, | 358 | rows: input.rows, |
| @@ -364,12 +377,12 @@ export const loadWorkspaceSourceRecipeToolIndex = (input: { · 18 unmodified lines | |||
| 364 | }); | 377 | }); |
| 365 | 378 | ||
| 366 | export const loadWorkspaceSourceRecipeToolByPath = (input: { | 379 | export const loadWorkspaceSourceRecipeToolByPath = (input: { |
| 367 | rows: SqlControlPlaneRows; | 380 | rows: ControlPlaneStoreShape; |
| 368 | workspaceId: WorkspaceId; | 381 | workspaceId: WorkspaceId; |
| 369 | path: string; | 382 | path: string; |
| 370 | actorAccountId?: AccountId | null; | 383 | actorAccountId?: AccountId | null; |
| 371 | includeSchemas: boolean; | 384 | includeSchemas: boolean; |
| 372 | }): Effect.Effect<LoadedSourceRecipeToolIndexEntry | null, Error, never> => | 385 | }): Effect.Effect<LoadedSourceRecipeToolIndexEntry | null, Error, WorkspaceStorageServices> => |
| 373 | Effect.gen(function* () { | 386 | Effect.gen(function* () { |
| 374 | const recipes = yield* loadWorkspaceSourceRecipes({ | 387 | const recipes = yield* loadWorkspaceSourceRecipes({ |
| 375 | rows: input.rows, | 388 | rows: input.rows, |
| @@ -1,310 +0,0 @@ | |||
| 1 | import { mkdtempSync } from "node:fs"; | ||
| 2 | import { tmpdir } from "node:os"; | ||
| 3 | import { join } from "node:path"; | ||
| 4 | |||
| 5 | import { describe, expect, it } from "@effect/vitest"; | ||
| 6 | |||
| 7 | import { | ||
| 8 | AccountIdSchema, | ||
| 9 | decodeBuiltInAuthArtifactConfig, | ||
| 10 | McpSourceAuthSessionDataJsonSchema, | ||
| 11 | SecretMaterialIdSchema, | ||
| 12 | SourceAuthSessionIdSchema, | ||
| 13 | SourceIdSchema, | ||
| 14 | WorkspaceIdSchema, | ||
| 15 | type Source, | ||
| 16 | } from "#schema"; | ||
| 17 | import * as Effect from "effect/Effect"; | ||
| 18 | import * as Option from "effect/Option"; | ||
| 19 | import * as Schema from "effect/Schema"; | ||
| 20 | import * as Scope from "effect/Scope"; | ||
| 21 | |||
| 22 | import { | ||
| 23 | createSqlControlPlanePersistence, | ||
| 24 | type SqlControlPlanePersistence, | ||
| 25 | } from "../persistence"; | ||
| 26 | import { | ||
| 27 | loadLocalExecutorConfig, | ||
| 28 | resolveLocalWorkspaceContext, | ||
| 29 | } from "./local-config"; | ||
| 30 | import { | ||
| 31 | RuntimeLocalWorkspaceService, | ||
| 32 | type RuntimeLocalWorkspaceState, | ||
| 33 | } from "./local-runtime-context"; | ||
| 34 | import { | ||
| 35 | loadLocalWorkspaceState, | ||
| 36 | } from "./local-workspace-state"; | ||
| 37 | import { persistSource, removeSourceById } from "./source-store"; | ||
| 38 | |||
| 39 | const makePersistence: Effect.Effect<SqlControlPlanePersistence, unknown, Scope.Scope> = | ||
| 40 | Effect.acquireRelease( | ||
| 41 | createSqlControlPlanePersistence({ | ||
| 42 | localDataDir: ":memory:", | ||
| 43 | }), | ||
| 44 | (persistence) => | ||
| 45 | Effect.tryPromise({ | ||
| 46 | try: () => persistence.close(), | ||
| 47 | catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), | ||
| 48 | }).pipe(Effect.orDie), | ||
| 49 | ); | ||
| 50 | |||
| 51 | const encodeSessionDataJson = Schema.encodeSync(McpSourceAuthSessionDataJsonSchema); | ||
| 52 | |||
| 53 | const makeRuntimeLocalWorkspace = (input: { | ||
| 54 | workspaceId: Source["workspaceId"]; | ||
| 55 | accountId: ReturnType<typeof AccountIdSchema.make>; | ||
| 56 | }) => | ||
| 57 | Effect.gen(function* () { | ||
| 58 | const workspaceRoot = mkdtempSync(join(tmpdir(), "executor-source-store-")); | ||
| 59 | const context = yield* resolveLocalWorkspaceContext({ workspaceRoot }); | ||
| 60 | const loadedConfig = yield* loadLocalExecutorConfig(context); | ||
| 61 | |||
| 62 | return { | ||
| 63 | context, | ||
| 64 | installation: { | ||
| 65 | workspaceId: input.workspaceId, | ||
| 66 | accountId: input.accountId, | ||
| 67 | }, | ||
| 68 | loadedConfig, | ||
| 69 | } satisfies RuntimeLocalWorkspaceState; | ||
| 70 | }); | ||
| 71 | |||
| 72 | const withRuntimeLocalWorkspace = <A, E>( | ||
| 73 | effect: Effect.Effect<A, E, never>, | ||
| 74 | runtimeLocalWorkspace: RuntimeLocalWorkspaceState, | ||
| 75 | ) => | ||
| 76 | effect.pipe( | ||
| 77 | Effect.provideService(RuntimeLocalWorkspaceService, runtimeLocalWorkspace), | ||
| 78 | ); | ||
| 79 | |||
| 80 | const makeOpenApiSource = (input: { | ||
| 81 | workspaceId: Source["workspaceId"]; | ||
| 82 | sourceId: Source["id"]; | ||
| 83 | now: number; | ||
| 84 | updatedAt?: number; | ||
| 85 | name?: string; | ||
| 86 | endpoint?: string; | ||
| 87 | specUrl?: string; | ||
| 88 | auth: Source["auth"]; | ||
| 89 | }): Source => ({ | ||
| 90 | id: input.sourceId, | ||
| 91 | workspaceId: input.workspaceId, | ||
| 92 | name: input.name ?? "GitHub", | ||
| 93 | kind: "openapi", | ||
| 94 | endpoint: input.endpoint ?? "https://api.github.com", | ||
| 95 | status: "connected", | ||
| 96 | enabled: true, | ||
| 97 | namespace: "github", | ||
| 98 | bindingVersion: 1, | ||
| 99 | binding: { | ||
| 100 | specUrl: input.specUrl ?? "https://example.com/openapi.json", | ||
| 101 | defaultHeaders: null, | ||
| 102 | }, | ||
| 103 | importAuthPolicy: "reuse_runtime", | ||
| 104 | importAuth: { kind: "none" }, | ||
| 105 | auth: input.auth, | ||
| 106 | sourceHash: null, | ||
| 107 | lastError: null, | ||
| 108 | createdAt: input.now, | ||
| 109 | updatedAt: input.updatedAt ?? input.now, | ||
| 110 | }); | ||
| 111 | |||
| 112 | describe("source-store", () => { | ||
| 113 | it.scoped("replaces superseded secrets and removes source auth state cleanly", () => | ||
| 114 | Effect.gen(function* () { | ||
| 115 | const persistence = yield* makePersistence; | ||
| 116 | const now = Date.now(); | ||
| 117 | const accountId = AccountIdSchema.make("acc_source_store"); | ||
| 118 | const workspaceId = WorkspaceIdSchema.make("ws_source_store"); | ||
| 119 | const sourceId = SourceIdSchema.make("github"); | ||
| 120 | const firstTokenId = SecretMaterialIdSchema.make("sec_source_store_first"); | ||
| 121 | const secondTokenId = SecretMaterialIdSchema.make("sec_source_store_second"); | ||
| 122 | const runtimeLocalWorkspace = yield* makeRuntimeLocalWorkspace({ | ||
| 123 | workspaceId, | ||
| 124 | accountId, | ||
| 125 | }); | ||
| 126 | |||
| 127 | yield* persistence.rows.secretMaterials.upsert({ | ||
| 128 | id: firstTokenId, | ||
| 129 | name: null, | ||
| 130 | purpose: "auth_material", | ||
| 131 | value: "ghp_first", | ||
| 132 | createdAt: now, | ||
| 133 | updatedAt: now, | ||
| 134 | }); | ||
| 135 | yield* persistence.rows.secretMaterials.upsert({ | ||
| 136 | id: secondTokenId, | ||
| 137 | name: null, | ||
| 138 | purpose: "auth_material", | ||
| 139 | value: "ghp_second", | ||
| 140 | createdAt: now, | ||
| 141 | updatedAt: now, | ||
| 142 | }); | ||
| 143 | |||
| 144 | yield* withRuntimeLocalWorkspace( | ||
| 145 | persistSource( | ||
| 146 | persistence.rows, | ||
| 147 | makeOpenApiSource({ | ||
| 148 | workspaceId, | ||
| 149 | sourceId, | ||
| 150 | now, | ||
| 151 | auth: { | ||
| 152 | kind: "bearer", | ||
| 153 | headerName: "Authorization", | ||
| 154 | prefix: "Bearer ", | ||
| 155 | token: { | ||
| 156 | providerId: "postgres", | ||
| 157 | handle: firstTokenId, | ||
| 158 | }, | ||
| 159 | }, | ||
| 160 | }), | ||
| 161 | ), | ||
| 162 | runtimeLocalWorkspace, | ||
| 163 | ); | ||
| 164 | |||
| 165 | yield* persistence.rows.sourceAuthSessions.upsert({ | ||
| 166 | id: SourceAuthSessionIdSchema.make("src_auth_source_store"), | ||
| 167 | workspaceId, | ||
| 168 | sourceId, | ||
| 169 | actorAccountId: accountId, | ||
| 170 | executionId: null, | ||
| 171 | interactionId: null, | ||
| 172 | providerKind: "mcp_oauth", | ||
| 173 | credentialSlot: "runtime", | ||
| 174 | status: "pending", | ||
| 175 | state: "state_source_store", | ||
| 176 | sessionDataJson: encodeSessionDataJson({ | ||
| 177 | kind: "mcp_oauth", | ||
| 178 | endpoint: "https://api.github.com", | ||
| 179 | redirectUri: "http://127.0.0.1/callback", | ||
| 180 | scope: null, | ||
| 181 | resourceMetadataUrl: null, | ||
| 182 | authorizationServerUrl: null, | ||
| 183 | resourceMetadata: null, | ||
| 184 | authorizationServerMetadata: null, | ||
| 185 | clientInformation: null, | ||
| 186 | codeVerifier: "verifier", | ||
| 187 | authorizationUrl: "https://example.com/auth", | ||
| 188 | }), | ||
| 189 | errorText: null, | ||
| 190 | completedAt: null, | ||
| 191 | createdAt: now, | ||
| 192 | updatedAt: now, | ||
| 193 | }); | ||
| 194 | |||
| 195 | yield* withRuntimeLocalWorkspace( | ||
| 196 | persistSource( | ||
| 197 | persistence.rows, | ||
| 198 | makeOpenApiSource({ | ||
| 199 | workspaceId, | ||
| 200 | sourceId, | ||
| 201 | now, | ||
| 202 | updatedAt: now + 1, | ||
| 203 | auth: { | ||
| 204 | kind: "bearer", | ||
| 205 | headerName: "Authorization", | ||
| 206 | prefix: "Bearer ", | ||
| 207 | token: { | ||
| 208 | providerId: "postgres", | ||
| 209 | handle: secondTokenId, | ||
| 210 | }, | ||
| 211 | }, | ||
| 212 | }), | ||
| 213 | ), | ||
| 214 | runtimeLocalWorkspace, | ||
| 215 | ); | ||
| 216 | |||
| 217 | expect(Option.isNone(yield* persistence.rows.secretMaterials.getById(firstTokenId))).toBe(true); | ||
| 218 | const authArtifacts = yield* persistence.rows.authArtifacts.listByWorkspaceId(workspaceId); | ||
| 219 | expect(authArtifacts).toHaveLength(1); | ||
| 220 | const decoded = authArtifacts[0] ? decodeBuiltInAuthArtifactConfig(authArtifacts[0]) : null; | ||
| 221 | expect( | ||
| 222 | decoded !== null && decoded.artifactKind === "static_bearer" | ||
| 223 | ? decoded.config.token.handle | ||
| 224 | : null, | ||
| 225 | ).toBe(secondTokenId); | ||
| 226 | |||
| 227 | const removed = yield* withRuntimeLocalWorkspace( | ||
| 228 | removeSourceById(persistence.rows, { | ||
| 229 | workspaceId, | ||
| 230 | sourceId, | ||
| 231 | }), | ||
| 232 | runtimeLocalWorkspace, | ||
| 233 | ); | ||
| 234 | expect(removed).toBe(true); | ||
| 235 | |||
| 236 | expect(Option.isNone(yield* persistence.rows.secretMaterials.getById(secondTokenId))).toBe(true); | ||
| 237 | expect(yield* persistence.rows.authArtifacts.listByWorkspaceId(workspaceId)).toHaveLength(0); | ||
| 238 | expect(yield* persistence.rows.sourceAuthSessions.listByWorkspaceId(workspaceId)).toHaveLength(0); | ||
| 239 | const config = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context); | ||
| 240 | expect(config.config?.sources?.[sourceId]).toBeUndefined(); | ||
| 241 | const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context); | ||
| 242 | expect(workspaceState.sources[sourceId]).toBeUndefined(); | ||
| 243 | }), | ||
| 244 | ); | ||
| 245 | |||
| 246 | it.scoped("creates a fresh actor-scoped credential when a shared credential already exists", () => | ||
| 247 | Effect.gen(function* () { | ||
| 248 | const persistence = yield* makePersistence; | ||
| 249 | const now = Date.now(); | ||
| 250 | const accountId = AccountIdSchema.make("acc_actor_scoped"); | ||
| 251 | const workspaceId = WorkspaceIdSchema.make("ws_actor_scoped"); | ||
| 252 | const sourceId = SourceIdSchema.make("github"); | ||
| 253 | const sharedTokenId = SecretMaterialIdSchema.make("sec_actor_scoped_shared"); | ||
| 254 | const runtimeLocalWorkspace = yield* makeRuntimeLocalWorkspace({ | ||
| 255 | workspaceId, | ||
| 256 | accountId, | ||
| 257 | }); | ||
| 258 | yield* persistence.rows.secretMaterials.upsert({ | ||
| 259 | id: sharedTokenId, | ||
| 260 | name: null, | ||
| 261 | purpose: "auth_material", | ||
| 262 | value: "ghp_shared", | ||
| 263 | createdAt: now, | ||
| 264 | updatedAt: now, | ||
| 265 | }); | ||
| 266 | |||
| 267 | const source = makeOpenApiSource({ | ||
| 268 | workspaceId, | ||
| 269 | sourceId, | ||
| 270 | now, | ||
| 271 | auth: { | ||
| 272 | kind: "bearer", | ||
| 273 | headerName: "Authorization", | ||
| 274 | prefix: "Bearer ", | ||
| 275 | token: { | ||
| 276 | providerId: "postgres", | ||
| 277 | handle: sharedTokenId, | ||
| 278 | }, | ||
| 279 | }, | ||
| 280 | }); | ||
| 281 | |||
| 282 | yield* withRuntimeLocalWorkspace( | ||
| 283 | persistSource(persistence.rows, source), | ||
| 284 | runtimeLocalWorkspace, | ||
| 285 | ); | ||
| 286 | yield* withRuntimeLocalWorkspace( | ||
| 287 | persistSource( | ||
| 288 | persistence.rows, | ||
| 289 | { | ||
| 290 | ...source, | ||
| 291 | updatedAt: now + 1, | ||
| 292 | }, | ||
| 293 | { | ||
| 294 | actorAccountId: accountId, | ||
| 295 | }, | ||
| 296 | ), | ||
| 297 | runtimeLocalWorkspace, | ||
| 298 | ); | ||
| 299 | |||
| 300 | const authArtifacts = yield* persistence.rows.authArtifacts.listByWorkspaceAndSourceId({ | ||
| 301 | workspaceId, | ||
| 302 | sourceId, | ||
| 303 | }); | ||
| 304 | expect(authArtifacts).toHaveLength(2); | ||
| 305 | expect(authArtifacts.some((artifact) => artifact.actorAccountId === null)).toBe(true); | ||
| 306 | expect(authArtifacts.some((artifact) => artifact.actorAccountId === accountId)).toBe(true); | ||
| 307 | expect(new Set(authArtifacts.map((artifact) => artifact.id)).size).toBe(2); | ||
| 308 | }), | ||
| 309 | ); | ||
| 310 | }); | ||
| @@ -9,7 +9,6 @@ import type { · 8 unmodified lines | |||
| 9 | WorkspaceId, | 9 | WorkspaceId, |
| 10 | } from "#schema"; | 10 | } from "#schema"; |
| 11 | import { SourceIdSchema } from "#schema"; | 11 | import { SourceIdSchema } from "#schema"; |
| 12 | import { type SqlControlPlaneRows } from "#persistence"; | ||
| 13 | import * as Effect from "effect/Effect"; | 12 | import * as Effect from "effect/Effect"; |
| 14 | 13 | ||
| 15 | import { | 14 | import { |
| @@ -21,15 +20,9 @@ import { · 5 unmodified lines | |||
| 21 | sourceAuthFromAuthArtifact, | 20 | sourceAuthFromAuthArtifact, |
| 22 | } from "./auth-artifacts"; | 21 | } from "./auth-artifacts"; |
| 23 | import { | 22 | import { |
| 24 | loadLocalExecutorConfig, | ||
| 25 | type LoadedLocalExecutorConfig, | 23 | type LoadedLocalExecutorConfig, |
| 26 | type ResolvedLocalWorkspaceContext, | 24 | type ResolvedLocalWorkspaceContext, |
| 27 | writeProjectLocalExecutorConfig, | ||
| 28 | } from "./local-config"; | 25 | } from "./local-config"; |
| 29 | import { | ||
| 30 | readLocalSourceArtifact, | ||
| 31 | removeLocalSourceArtifact, | ||
| 32 | } from "./local-source-artifacts"; | ||
| 33 | import { | 26 | import { |
| 34 | LocalConfiguredSourceNotFoundError, | 27 | LocalConfiguredSourceNotFoundError, |
| 35 | LocalExecutorConfigDecodeError, | 28 | LocalExecutorConfigDecodeError, |
| @@ -42,9 +35,18 @@ import { · 6 unmodified lines | |||
| 42 | import { | 35 | import { |
| 43 | requireRuntimeLocalWorkspace, | 36 | requireRuntimeLocalWorkspace, |
| 44 | } from "./local-runtime-context"; | 37 | } from "./local-runtime-context"; |
| 38 | import type { | ||
| 39 | SourceArtifactStoreShape, | ||
| 40 | WorkspaceStorageServices, | ||
| 41 | WorkspaceConfigStoreShape, | ||
| 42 | WorkspaceStateStoreShape, | ||
| 43 | } from "./local-storage"; | ||
| 44 | import { | ||
| 45 | SourceArtifactStore, | ||
| 46 | WorkspaceConfigStore, | ||
| 47 | WorkspaceStateStore, | ||
| 48 | } from "./local-storage"; | ||
| 45 | import { | 49 | import { |
| 46 | loadLocalWorkspaceState, | ||
| 47 | writeLocalWorkspaceState, | ||
| 48 | type LocalWorkspaceState, | 50 | type LocalWorkspaceState, |
| 49 | } from "./local-workspace-state"; | 51 | } from "./local-workspace-state"; |
| 50 | import { | 52 | import { |
| @@ -54,13 +56,14 @@ import { · 3 unmodified lines | |||
| 54 | import { createDefaultSecretMaterialDeleter } from "./secret-material-providers"; | 56 | import { createDefaultSecretMaterialDeleter } from "./secret-material-providers"; |
| 55 | import { authArtifactSecretMaterialRefs } from "./auth-artifacts"; | 57 | import { authArtifactSecretMaterialRefs } from "./auth-artifacts"; |
| 56 | import { removeAuthLeaseAndSecrets } from "./auth-leases"; | 58 | import { removeAuthLeaseAndSecrets } from "./auth-leases"; |
| 59 | import type { ControlPlaneStoreShape } from "./store"; | ||
| 57 | import { getSourceAdapter } from "./source-adapters"; | 60 | import { getSourceAdapter } from "./source-adapters"; |
| 58 | import { slugify } from "./slug"; | 61 | import { slugify } from "./slug"; |
| 59 | 62 | ||
| 60 | const secretRefKey = (ref: { providerId: string; handle: string }): string => | 63 | const secretRefKey = (ref: { providerId: string; handle: string }): string => |
| 61 | `${ref.providerId}:${ref.handle}`; | 64 | `${ref.providerId}:${ref.handle}`; |
| 62 | 65 | ||
| 63 | const cleanupAuthArtifactSecretRefs = (rows: SqlControlPlaneRows, input: { | 66 | const cleanupAuthArtifactSecretRefs = (rows: ControlPlaneStoreShape, input: { |
| 64 | previous: AuthArtifact | null; | 67 | previous: AuthArtifact | null; |
| 65 | next: AuthArtifact | null; | 68 | next: AuthArtifact | null; |
| 66 | }) => | 69 | }) => |
| @@ -246,6 +249,9 @@ const resolveRuntimeLocalWorkspace = (workspaceId: WorkspaceId): Effect.Effect<{ · 179 unmodified lines | |||
| 246 | workspaceId: WorkspaceId; | 249 | workspaceId: WorkspaceId; |
| 247 | accountId: AccountId; | 250 | accountId: AccountId; |
| 248 | }; | 251 | }; |
| 252 | workspaceConfigStore: WorkspaceConfigStoreShape; | ||
| 253 | workspaceStateStore: WorkspaceStateStoreShape; | ||
| 254 | sourceArtifactStore: SourceArtifactStoreShape; | ||
| 249 | loadedConfig: LoadedLocalExecutorConfig; | 255 | loadedConfig: LoadedLocalExecutorConfig; |
| 250 | workspaceState: LocalWorkspaceState; | 256 | workspaceState: LocalWorkspaceState; |
| 251 | }, | 257 | }, |
| @@ -253,16 +259,27 @@ const resolveRuntimeLocalWorkspace = (workspaceId: WorkspaceId): Effect.Effect<{ · 1 unmodified line | |||
| 253 | | RuntimeLocalWorkspaceMismatchError | 259 | | RuntimeLocalWorkspaceMismatchError |
| 254 | | LocalFileSystemError | 260 | | LocalFileSystemError |
| 255 | | LocalExecutorConfigDecodeError | 261 | | LocalExecutorConfigDecodeError |
| 256 | | LocalWorkspaceStateDecodeError, | 262 | | LocalWorkspaceStateDecodeError |
| 257 | never> => | 263 | | Error, |
| 264 | WorkspaceStorageServices> => | ||
| 258 | Effect.gen(function* () { | 265 | Effect.gen(function* () { |
| 259 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId); | 266 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId); |
| 260 | const loadedConfig = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context); | 267 | const workspaceConfigStore = yield* WorkspaceConfigStore; |
| 261 | const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context); | 268 | const workspaceStateStore = yield* WorkspaceStateStore; |
| 269 | const sourceArtifactStore = yield* SourceArtifactStore; | ||
| 270 | const loadedConfig = yield* workspaceConfigStore.load( | ||
| 271 | runtimeLocalWorkspace.context, | ||
| 272 | ); | ||
| 273 | const workspaceState = yield* workspaceStateStore.load( | ||
| 274 | runtimeLocalWorkspace.context, | ||
| 275 | ); | ||
| 262 | 276 | ||
| 263 | return { | 277 | return { |
| 264 | context: runtimeLocalWorkspace.context, | 278 | context: runtimeLocalWorkspace.context, |
| 265 | installation: runtimeLocalWorkspace.installation, | 279 | installation: runtimeLocalWorkspace.installation, |
| 280 | workspaceConfigStore, | ||
| 281 | workspaceStateStore, | ||
| 282 | sourceArtifactStore, | ||
| 266 | loadedConfig, | 283 | loadedConfig, |
| 267 | workspaceState, | 284 | workspaceState, |
| 268 | }; | 285 | }; |
| @@ -270,6 +287,7 @@ never> => · 1 unmodified line | |||
| 270 | 287 | ||
| 271 | const buildLocalSourceRecord = (input: { | 288 | const buildLocalSourceRecord = (input: { |
| 272 | context: ResolvedLocalWorkspaceContext; | 289 | context: ResolvedLocalWorkspaceContext; |
| 290 | sourceArtifactStore: SourceArtifactStoreShape; | ||
| 273 | workspaceId: WorkspaceId; | 291 | workspaceId: WorkspaceId; |
| 274 | loadedConfig: LoadedLocalExecutorConfig; | 292 | loadedConfig: LoadedLocalExecutorConfig; |
| 275 | workspaceState: LocalWorkspaceState; | 293 | workspaceState: LocalWorkspaceState; |
| @@ -317,7 +335,7 @@ const buildLocalSourceRecord = (input: { · 41 unmodified lines | |||
| 317 | updatedAt: existingState?.updatedAt ?? Date.now(), | 335 | updatedAt: existingState?.updatedAt ?? Date.now(), |
| 318 | }); | 336 | }); |
| 319 | 337 | ||
| 320 | const artifact = yield* readLocalSourceArtifact({ | 338 | const artifact = yield* input.sourceArtifactStore.read({ |
| 321 | context: input.context, | 339 | context: input.context, |
| 322 | sourceId: input.sourceId, | 340 | sourceId: input.sourceId, |
| 323 | }).pipe( | 341 | }).pipe( |
| @@ -379,7 +397,7 @@ const buildLocalSourceRecord = (input: { · 55 unmodified lines | |||
| 379 | }); | 397 | }); |
| 380 | 398 | ||
| 381 | export const loadSourcesInWorkspace = ( | 399 | export const loadSourcesInWorkspace = ( |
| 382 | rows: SqlControlPlaneRows, | 400 | rows: ControlPlaneStoreShape, |
| 383 | workspaceId: WorkspaceId, | 401 | workspaceId: WorkspaceId, |
| 384 | options: { | 402 | options: { |
| 385 | actorAccountId?: AccountId | null; | 403 | actorAccountId?: AccountId | null; |
| @@ -393,7 +411,7 @@ export const loadSourcesInWorkspace = ( · 7 unmodified lines | |||
| 393 | | LocalWorkspaceStateDecodeError | 411 | | LocalWorkspaceStateDecodeError |
| 394 | | LocalConfiguredSourceNotFoundError | 412 | | LocalConfiguredSourceNotFoundError |
| 395 | | Error, | 413 | | Error, |
| 396 | never | 414 | WorkspaceStorageServices |
| 397 | > => | 415 | > => |
| 398 | Effect.gen(function* () { | 416 | Effect.gen(function* () { |
| 399 | const localWorkspace = yield* resolveRuntimeLocalWorkspace(workspaceId); | 417 | const localWorkspace = yield* resolveRuntimeLocalWorkspace(workspaceId); |
| @@ -404,6 +422,7 @@ export const loadSourcesInWorkspace = ( · 4 unmodified lines | |||
| 404 | Effect.map( | 422 | Effect.map( |
| 405 | buildLocalSourceRecord({ | 423 | buildLocalSourceRecord({ |
| 406 | context: localWorkspace.context, | 424 | context: localWorkspace.context, |
| 425 | sourceArtifactStore: localWorkspace.sourceArtifactStore, | ||
| 407 | workspaceId, | 426 | workspaceId, |
| 408 | loadedConfig: localWorkspace.loadedConfig, | 427 | loadedConfig: localWorkspace.loadedConfig, |
| 409 | workspaceState: localWorkspace.workspaceState, | 428 | workspaceState: localWorkspace.workspaceState, |
| @@ -416,7 +435,7 @@ export const loadSourcesInWorkspace = ( · 6 unmodified lines | |||
| 416 | ); | 435 | ); |
| 417 | }); | 436 | }); |
| 418 | 437 | ||
| 419 | export const loadSourceById = (rows: SqlControlPlaneRows, input: { | 438 | export const loadSourceById = (rows: ControlPlaneStoreShape, input: { |
| 420 | workspaceId: WorkspaceId; | 439 | workspaceId: WorkspaceId; |
| 421 | sourceId: Source["id"]; | 440 | sourceId: Source["id"]; |
| 422 | actorAccountId?: AccountId | null; | 441 | actorAccountId?: AccountId | null; |
| @@ -429,7 +448,7 @@ export const loadSourceById = (rows: SqlControlPlaneRows, input: { · 6 unmodified lines | |||
| 429 | | LocalWorkspaceStateDecodeError | 448 | | LocalWorkspaceStateDecodeError |
| 430 | | LocalConfiguredSourceNotFoundError | 449 | | LocalConfiguredSourceNotFoundError |
| 431 | | Error, | 450 | | Error, |
| 432 | never | 451 | WorkspaceStorageServices |
| 433 | > => | 452 | > => |
| 434 | Effect.gen(function* () { | 453 | Effect.gen(function* () { |
| 435 | const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId); | 454 | const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId); |
| @@ -445,6 +464,7 @@ export const loadSourceById = (rows: SqlControlPlaneRows, input: { · 9 unmodified lines | |||
| 445 | 464 | ||
| 446 | const localSource = yield* buildLocalSourceRecord({ | 465 | const localSource = yield* buildLocalSourceRecord({ |
| 447 | context: localWorkspace.context, | 466 | context: localWorkspace.context, |
| 467 | sourceArtifactStore: localWorkspace.sourceArtifactStore, | ||
| 448 | workspaceId: input.workspaceId, | 468 | workspaceId: input.workspaceId, |
| 449 | loadedConfig: localWorkspace.loadedConfig, | 469 | loadedConfig: localWorkspace.loadedConfig, |
| 450 | workspaceState: localWorkspace.workspaceState, | 470 | workspaceState: localWorkspace.workspaceState, |
| @@ -514,7 +534,7 @@ const configSourceFromLocalSource = (input: { · 63 unmodified lines | |||
| 514 | } | 534 | } |
| 515 | }; | 535 | }; |
| 516 | 536 | ||
| 517 | const removeAuthArtifactsForSource = (rows: SqlControlPlaneRows, input: { | 537 | const removeAuthArtifactsForSource = (rows: ControlPlaneStoreShape, input: { |
| 518 | workspaceId: WorkspaceId; | 538 | workspaceId: WorkspaceId; |
| 519 | sourceId: Source["id"]; | 539 | sourceId: Source["id"]; |
| 520 | }) => | 540 | }) => |
| @@ -551,10 +571,10 @@ const removeAuthArtifactsForSource = (rows: SqlControlPlaneRows, input: { · 30 unmodified lines | |||
| 551 | return existingAuthArtifacts.length; | 571 | return existingAuthArtifacts.length; |
| 552 | }); | 572 | }); |
| 553 | 573 | ||
| 554 | export const removeSourceById = (rows: SqlControlPlaneRows, input: { | 574 | export const removeSourceById = (rows: ControlPlaneStoreShape, input: { |
| 555 | workspaceId: WorkspaceId; | 575 | workspaceId: WorkspaceId; |
| 556 | sourceId: Source["id"]; | 576 | sourceId: Source["id"]; |
| 557 | }) => | 577 | }): Effect.Effect<boolean, Error, WorkspaceStorageServices> => |
| 558 | Effect.gen(function* () { | 578 | Effect.gen(function* () { |
| 559 | const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId); | 579 | const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId); |
| 560 | if (!localWorkspace.loadedConfig.config?.sources?.[input.sourceId]) { | 580 | if (!localWorkspace.loadedConfig.config?.sources?.[input.sourceId]) { |
| @@ -566,7 +586,7 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 5 unmodified lines | |||
| 566 | ...(projectConfig.sources ?? {}), | 586 | ...(projectConfig.sources ?? {}), |
| 567 | }; | 587 | }; |
| 568 | delete sources[input.sourceId]; | 588 | delete sources[input.sourceId]; |
| 569 | yield* writeProjectLocalExecutorConfig({ | 589 | yield* localWorkspace.workspaceConfigStore.writeProject({ |
| 570 | context: localWorkspace.context, | 590 | context: localWorkspace.context, |
| 571 | config: { | 591 | config: { |
| 572 | ...projectConfig, | 592 | ...projectConfig, |
| @@ -582,11 +602,11 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 9 unmodified lines | |||
| 582 | ...localWorkspace.workspaceState, | 602 | ...localWorkspace.workspaceState, |
| 583 | sources: remainingSources, | 603 | sources: remainingSources, |
| 584 | }; | 604 | }; |
| 585 | yield* writeLocalWorkspaceState({ | 605 | yield* localWorkspace.workspaceStateStore.write({ |
| 586 | context: localWorkspace.context, | 606 | context: localWorkspace.context, |
| 587 | state: workspaceState, | 607 | state: workspaceState, |
| 588 | }); | 608 | }); |
| 589 | yield* removeLocalSourceArtifact({ | 609 | yield* localWorkspace.sourceArtifactStore.remove({ |
| 590 | context: localWorkspace.context, | 610 | context: localWorkspace.context, |
| 591 | sourceId: input.sourceId, | 611 | sourceId: input.sourceId, |
| 592 | }); | 612 | }); |
| @@ -605,12 +625,12 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 12 unmodified lines | |||
| 605 | }); | 625 | }); |
| 606 | 626 | ||
| 607 | export const persistSource = ( | 627 | export const persistSource = ( |
| 608 | rows: SqlControlPlaneRows, | 628 | rows: ControlPlaneStoreShape, |
| 609 | source: Source, | 629 | source: Source, |
| 610 | options: { | 630 | options: { |
| 611 | actorAccountId?: AccountId | null; | 631 | actorAccountId?: AccountId | null; |
| 612 | } = {}, | 632 | } = {}, |
| 613 | ) => | 633 | ): Effect.Effect<Source, Error, WorkspaceStorageServices> => |
| 614 | Effect.gen(function* () { | 634 | Effect.gen(function* () { |
| 615 | const localWorkspace = yield* resolveRuntimeLocalWorkspace(source.workspaceId); | 635 | const localWorkspace = yield* resolveRuntimeLocalWorkspace(source.workspaceId); |
| 616 | const nextSource = { | 636 | const nextSource = { |
| @@ -648,7 +668,7 @@ export const persistSource = ( · 31 unmodified lines | |||
| 648 | existingConfigAuth: existingConfigSource?.connection.auth, | 668 | existingConfigAuth: existingConfigSource?.connection.auth, |
| 649 | config: localWorkspace.loadedConfig.config, | 669 | config: localWorkspace.loadedConfig.config, |
| 650 | }); | 670 | }); |
| 651 | yield* writeProjectLocalExecutorConfig({ | 671 | yield* localWorkspace.workspaceConfigStore.writeProject({ |
| 652 | context: localWorkspace.context, | 672 | context: localWorkspace.context, |
| 653 | config: { | 673 | config: { |
| 654 | ...projectConfig, | 674 | ...projectConfig, |
| @@ -737,7 +757,7 @@ export const persistSource = ( · 82 unmodified lines | |||
| 737 | }, | 757 | }, |
| 738 | }, | 758 | }, |
| 739 | }; | 759 | }; |
| 740 | yield* writeLocalWorkspaceState({ | 760 | yield* localWorkspace.workspaceStateStore.write({ |
| 741 | context: localWorkspace.context, | 761 | context: localWorkspace.context, |
| 742 | state: workspaceState, | 762 | state: workspaceState, |
| 743 | }); | 763 | }); |
| @@ -1,15 +1,205 @@ | |||
| 1 | import { type SqlControlPlaneRows, SqlControlPlaneRowsService } from "#persistence"; | ||
| 2 | import * as Context from "effect/Context"; | 1 | import * as Context from "effect/Context"; |
| 3 | import * as Effect from "effect/Effect"; | 2 | import type * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | 3 | ||
| 6 | export type ControlPlaneStoreShape = SqlControlPlaneRows; | 4 | import type { |
| 5 | AuthArtifact, | ||
| 6 | AuthLease, | ||
| 7 | Execution, | ||
| 8 | ExecutionInteraction, | ||
| 9 | ExecutionStep, | ||
| 10 | SecretMaterial, | ||
| 11 | SourceAuthSession, | ||
| 12 | WorkspaceSourceOauthClient, | ||
| 13 | } from "#schema"; | ||
| 14 | |||
| 15 | type SecretMaterialSummary = { | ||
| 16 | id: string; | ||
| 17 | name: string | null; | ||
| 18 | purpose: string; | ||
| 19 | createdAt: number; | ||
| 20 | updatedAt: number; | ||
| 21 | }; | ||
| 22 | |||
| 23 | export type ControlPlaneStoreShape = { | ||
| 24 | authArtifacts: { | ||
| 25 | listByWorkspaceId: ( | ||
| 26 | workspaceId: AuthArtifact["workspaceId"], | ||
| 27 | ) => Effect.Effect<readonly AuthArtifact[], Error, never>; | ||
| 28 | listByWorkspaceAndSourceId: (input: { | ||
| 29 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 30 | sourceId: AuthArtifact["sourceId"]; | ||
| 31 | }) => Effect.Effect<readonly AuthArtifact[], Error, never>; | ||
| 32 | getByWorkspaceSourceAndActor: (input: { | ||
| 33 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 34 | sourceId: AuthArtifact["sourceId"]; | ||
| 35 | actorAccountId: AuthArtifact["actorAccountId"]; | ||
| 36 | slot: AuthArtifact["slot"]; | ||
| 37 | }) => Effect.Effect<import("effect/Option").Option<AuthArtifact>, Error, never>; | ||
| 38 | upsert: (artifact: AuthArtifact) => Effect.Effect<void, Error, never>; | ||
| 39 | removeByWorkspaceSourceAndActor: (input: { | ||
| 40 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 41 | sourceId: AuthArtifact["sourceId"]; | ||
| 42 | actorAccountId: AuthArtifact["actorAccountId"]; | ||
| 43 | slot?: AuthArtifact["slot"]; | ||
| 44 | }) => Effect.Effect<boolean, Error, never>; | ||
| 45 | removeByWorkspaceAndSourceId: (input: { | ||
| 46 | workspaceId: AuthArtifact["workspaceId"]; | ||
| 47 | sourceId: AuthArtifact["sourceId"]; | ||
| 48 | }) => Effect.Effect<number, Error, never>; | ||
| 49 | }; | ||
| 50 | authLeases: { | ||
| 51 | listAll: () => Effect.Effect<readonly AuthLease[], Error, never>; | ||
| 52 | getByAuthArtifactId: ( | ||
| 53 | authArtifactId: AuthLease["authArtifactId"], | ||
| 54 | ) => Effect.Effect<import("effect/Option").Option<AuthLease>, Error, never>; | ||
| 55 | upsert: (lease: AuthLease) => Effect.Effect<void, Error, never>; | ||
| 56 | removeByAuthArtifactId: ( | ||
| 57 | authArtifactId: AuthLease["authArtifactId"], | ||
| 58 | ) => Effect.Effect<boolean, Error, never>; | ||
| 59 | }; | ||
| 60 | sourceOauthClients: { | ||
| 61 | getByWorkspaceSourceAndProvider: (input: { | ||
| 62 | workspaceId: WorkspaceSourceOauthClient["workspaceId"]; | ||
| 63 | sourceId: WorkspaceSourceOauthClient["sourceId"]; | ||
| 64 | providerKey: string; | ||
| 65 | }) => Effect.Effect< | ||
| 66 | import("effect/Option").Option<WorkspaceSourceOauthClient>, | ||
| 67 | Error, | ||
| 68 | never | ||
| 69 | >; | ||
| 70 | upsert: ( | ||
| 71 | oauthClient: WorkspaceSourceOauthClient, | ||
| 72 | ) => Effect.Effect<void, Error, never>; | ||
| 73 | removeByWorkspaceAndSourceId: (input: { | ||
| 74 | workspaceId: WorkspaceSourceOauthClient["workspaceId"]; | ||
| 75 | sourceId: WorkspaceSourceOauthClient["sourceId"]; | ||
| 76 | }) => Effect.Effect<number, Error, never>; | ||
| 77 | }; | ||
| 78 | sourceAuthSessions: { | ||
| 79 | listAll: () => Effect.Effect<readonly SourceAuthSession[], Error, never>; | ||
| 80 | listByWorkspaceId: ( | ||
| 81 | workspaceId: SourceAuthSession["workspaceId"], | ||
| 82 | ) => Effect.Effect<readonly SourceAuthSession[], Error, never>; | ||
| 83 | getById: ( | ||
| 84 | id: SourceAuthSession["id"], | ||
| 85 | ) => Effect.Effect<import("effect/Option").Option<SourceAuthSession>, Error, never>; | ||
| 86 | getByState: ( | ||
| 87 | state: SourceAuthSession["state"], | ||
| 88 | ) => Effect.Effect<import("effect/Option").Option<SourceAuthSession>, Error, never>; | ||
| 89 | getPendingByWorkspaceSourceAndActor: (input: { | ||
| 90 | workspaceId: SourceAuthSession["workspaceId"]; | ||
| 91 | sourceId: SourceAuthSession["sourceId"]; | ||
| 92 | actorAccountId: SourceAuthSession["actorAccountId"]; | ||
| 93 | credentialSlot?: SourceAuthSession["credentialSlot"]; | ||
| 94 | }) => Effect.Effect< | ||
| 95 | import("effect/Option").Option<SourceAuthSession>, | ||
| 96 | Error, | ||
| 97 | never | ||
| 98 | >; | ||
| 99 | insert: (session: SourceAuthSession) => Effect.Effect<void, Error, never>; | ||
| 100 | update: ( | ||
| 101 | id: SourceAuthSession["id"], | ||
| 102 | patch: Partial< | ||
| 103 | Omit<SourceAuthSession, "id" | "workspaceId" | "sourceId" | "createdAt"> | ||
| 104 | >, | ||
| 105 | ) => Effect.Effect<import("effect/Option").Option<SourceAuthSession>, Error, never>; | ||
| 106 | upsert: (session: SourceAuthSession) => Effect.Effect<void, Error, never>; | ||
| 107 | removeByWorkspaceAndSourceId: ( | ||
| 108 | workspaceId: SourceAuthSession["workspaceId"], | ||
| 109 | sourceId: SourceAuthSession["sourceId"], | ||
| 110 | ) => Effect.Effect<boolean, Error, never>; | ||
| 111 | }; | ||
| 112 | secretMaterials: { | ||
| 113 | getById: ( | ||
| 114 | id: SecretMaterial["id"], | ||
| 115 | ) => Effect.Effect<import("effect/Option").Option<SecretMaterial>, Error, never>; | ||
| 116 | listAll: () => Effect.Effect<readonly SecretMaterialSummary[], Error, never>; | ||
| 117 | upsert: (material: SecretMaterial) => Effect.Effect<void, Error, never>; | ||
| 118 | updateById: ( | ||
| 119 | id: SecretMaterial["id"], | ||
| 120 | update: { name?: string | null; value?: string }, | ||
| 121 | ) => Effect.Effect< | ||
| 122 | import("effect/Option").Option<SecretMaterialSummary>, | ||
| 123 | Error, | ||
| 124 | never | ||
| 125 | >; | ||
| 126 | removeById: (id: SecretMaterial["id"]) => Effect.Effect<boolean, Error, never>; | ||
| 127 | listLinkedSources: () => Effect.Effect< | ||
| 128 | Map<string, Array<{ sourceId: string; sourceName: string }>>, | ||
| 129 | Error, | ||
| 130 | never | ||
| 131 | >; | ||
| 132 | }; | ||
| 133 | executions: { | ||
| 134 | getById: ( | ||
| 135 | executionId: Execution["id"], | ||
| 136 | ) => Effect.Effect<import("effect/Option").Option<Execution>, Error, never>; | ||
| 137 | getByWorkspaceAndId: ( | ||
| 138 | workspaceId: Execution["workspaceId"], | ||
| 139 | executionId: Execution["id"], | ||
| 140 | ) => Effect.Effect<import("effect/Option").Option<Execution>, Error, never>; | ||
| 141 | insert: (execution: Execution) => Effect.Effect<void, Error, never>; | ||
| 142 | update: ( | ||
| 143 | executionId: Execution["id"], | ||
| 144 | patch: Partial< | ||
| 145 | Omit<Execution, "id" | "workspaceId" | "createdByAccountId" | "createdAt"> | ||
| 146 | >, | ||
| 147 | ) => Effect.Effect<import("effect/Option").Option<Execution>, Error, never>; | ||
| 148 | }; | ||
| 149 | executionInteractions: { | ||
| 150 | getById: ( | ||
| 151 | interactionId: ExecutionInteraction["id"], | ||
| 152 | ) => Effect.Effect< | ||
| 153 | import("effect/Option").Option<ExecutionInteraction>, | ||
| 154 | Error, | ||
| 155 | never | ||
| 156 | >; | ||
| 157 | listByExecutionId: ( | ||
| 158 | executionId: ExecutionInteraction["executionId"], | ||
| 159 | ) => Effect.Effect<readonly ExecutionInteraction[], Error, never>; | ||
| 160 | getPendingByExecutionId: ( | ||
| 161 | executionId: ExecutionInteraction["executionId"], | ||
| 162 | ) => Effect.Effect< | ||
| 163 | import("effect/Option").Option<ExecutionInteraction>, | ||
| 164 | Error, | ||
| 165 | never | ||
| 166 | >; | ||
| 167 | insert: ( | ||
| 168 | interaction: ExecutionInteraction, | ||
| 169 | ) => Effect.Effect<void, Error, never>; | ||
| 170 | update: ( | ||
| 171 | interactionId: ExecutionInteraction["id"], | ||
| 172 | patch: Partial< | ||
| 173 | Omit<ExecutionInteraction, "id" | "executionId" | "createdAt"> | ||
| 174 | >, | ||
| 175 | ) => Effect.Effect< | ||
| 176 | import("effect/Option").Option<ExecutionInteraction>, | ||
| 177 | Error, | ||
| 178 | never | ||
| 179 | >; | ||
| 180 | }; | ||
| 181 | executionSteps: { | ||
| 182 | getByExecutionAndSequence: ( | ||
| 183 | executionId: ExecutionStep["executionId"], | ||
| 184 | sequence: ExecutionStep["sequence"], | ||
| 185 | ) => Effect.Effect<import("effect/Option").Option<ExecutionStep>, Error, never>; | ||
| 186 | listByExecutionId: ( | ||
| 187 | executionId: ExecutionStep["executionId"], | ||
| 188 | ) => Effect.Effect<readonly ExecutionStep[], Error, never>; | ||
| 189 | insert: (step: ExecutionStep) => Effect.Effect<void, Error, never>; | ||
| 190 | deleteByExecutionId: ( | ||
| 191 | executionId: ExecutionStep["executionId"], | ||
| 192 | ) => Effect.Effect<void, Error, never>; | ||
| 193 | updateByExecutionAndSequence: ( | ||
| 194 | executionId: ExecutionStep["executionId"], | ||
| 195 | sequence: ExecutionStep["sequence"], | ||
| 196 | patch: Partial< | ||
| 197 | Omit<ExecutionStep, "id" | "executionId" | "sequence" | "createdAt"> | ||
| 198 | >, | ||
| 199 | ) => Effect.Effect<import("effect/Option").Option<ExecutionStep>, Error, never>; | ||
| 200 | }; | ||
| 201 | }; | ||
| 7 | 202 | ||
| 8 | export class ControlPlaneStore extends Context.Tag( | 203 | export class ControlPlaneStore extends Context.Tag( |
| 9 | "#runtime/ControlPlaneStore", | 204 | "#runtime/ControlPlaneStore", |
| 10 | )<ControlPlaneStore, ControlPlaneStoreShape>() {} | 205 | )<ControlPlaneStore, ControlPlaneStoreShape>() {} |
| 11 | |||
| 12 | export const ControlPlaneStoreLive = Layer.effect( | ||
| 13 | ControlPlaneStore, | ||
| 14 | Effect.map(SqlControlPlaneRowsService, (rows) => rows), | ||
| 15 | ); | ||
| @@ -1,1986 +0,0 @@ | |||
| 1 | import { randomUUID } from "node:crypto"; | ||
| 2 | import { createServer } from "node:http"; | ||
| 3 | import { dirname, join } from "node:path"; | ||
| 4 | |||
| 5 | import { | ||
| 6 | FileSystem, | ||
| 7 | HttpApi, | ||
| 8 | HttpApiBuilder, | ||
| 9 | HttpApiEndpoint, | ||
| 10 | HttpApiGroup, | ||
| 11 | HttpApiSchema, | ||
| 12 | HttpServer, | ||
| 13 | } from "@effect/platform"; | ||
| 14 | import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"; | ||
| 15 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; | ||
| 16 | import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; | ||
| 17 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| 18 | import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; | ||
| 19 | import { | ||
| 20 | buildOpenApiToolPresentation, | ||
| 21 | compileOpenApiToolDefinitions, | ||
| 22 | extractOpenApiManifest, | ||
| 23 | } from "@executor/codemode-openapi"; | ||
| 24 | import { describe, expect, it } from "@effect/vitest"; | ||
| 25 | import { | ||
| 26 | graphql, | ||
| 27 | GraphQLBoolean, | ||
| 28 | GraphQLID, | ||
| 29 | GraphQLInputObjectType, | ||
| 30 | GraphQLInt, | ||
| 31 | GraphQLList, | ||
| 32 | GraphQLNonNull, | ||
| 33 | GraphQLObjectType, | ||
| 34 | GraphQLSchema, | ||
| 35 | GraphQLString, | ||
| 36 | } from "graphql"; | ||
| 37 | import { | ||
| 38 | AccountIdSchema, | ||
| 39 | ExecutionIdSchema, | ||
| 40 | SecretMaterialIdSchema, | ||
| 41 | SourceIdSchema, | ||
| 42 | SourceRecipeSchemaBundleIdSchema, | ||
| 43 | WorkspaceIdSchema, | ||
| 44 | type Source, | ||
| 45 | } from "#schema"; | ||
| 46 | import * as Context from "effect/Context"; | ||
| 47 | import * as Effect from "effect/Effect"; | ||
| 48 | import * as Exit from "effect/Exit"; | ||
| 49 | import * as Layer from "effect/Layer"; | ||
| 50 | import * as Scope from "effect/Scope"; | ||
| 51 | import { Schema } from "effect"; | ||
| 52 | import * as z from "zod/v4"; | ||
| 53 | |||
| 54 | import { | ||
| 55 | createSqlControlPlanePersistence, | ||
| 56 | type SqlControlPlanePersistence, | ||
| 57 | } from "#persistence"; | ||
| 58 | import type { SourceId, WorkspaceId } from "#schema"; | ||
| 59 | |||
| 60 | import { | ||
| 61 | createRuntimeSourceAuthService, | ||
| 62 | type RuntimeSourceAuthService, | ||
| 63 | } from "./source-auth-service"; | ||
| 64 | import { | ||
| 65 | loadLocalExecutorConfig, | ||
| 66 | resolveLocalWorkspaceContext, | ||
| 67 | writeProjectLocalExecutorConfig, | ||
| 68 | } from "./local-config"; | ||
| 69 | import { createLiveExecutionManager } from "./live-execution"; | ||
| 70 | import { | ||
| 71 | RuntimeLocalWorkspaceService, | ||
| 72 | type RuntimeLocalWorkspaceState, | ||
| 73 | } from "./local-runtime-context"; | ||
| 74 | import { | ||
| 75 | buildLocalSourceArtifact, | ||
| 76 | readLocalSourceArtifact, | ||
| 77 | writeLocalSourceArtifact, | ||
| 78 | } from "./local-source-artifacts"; | ||
| 79 | import { | ||
| 80 | loadLocalWorkspaceState, | ||
| 81 | writeLocalWorkspaceState, | ||
| 82 | } from "./local-workspace-state"; | ||
| 83 | import { materializationFromMcpManifestEntries } from "./source-adapters/mcp"; | ||
| 84 | import { createWorkspaceExecutionEnvironmentResolver } from "./workspace-execution-environment"; | ||
| 85 | |||
| 86 | type CountedMcpServer = { | ||
| 87 | endpoint: string; | ||
| 88 | counts: { | ||
| 89 | listTools: number; | ||
| 90 | callTool: number; | ||
| 91 | }; | ||
| 92 | close: () => Promise<void>; | ||
| 93 | }; | ||
| 94 | |||
| 95 | type OpenApiSpecServer = { | ||
| 96 | baseUrl: string; | ||
| 97 | specUrl: string; | ||
| 98 | seenAuthHeaders: Array<string | null>; | ||
| 99 | close: () => Promise<void>; | ||
| 100 | }; | ||
| 101 | |||
| 102 | type GraphqlServer = { | ||
| 103 | endpoint: string; | ||
| 104 | seenAuthHeaders: Array<string | null>; | ||
| 105 | close: () => Promise<void>; | ||
| 106 | }; | ||
| 107 | |||
| 108 | const normalizeSearchText = ( | ||
| 109 | ...parts: ReadonlyArray<string | null | undefined> | ||
| 110 | ): string => | ||
| 111 | parts | ||
| 112 | .flatMap((part) => (part ? [part.trim()] : [])) | ||
| 113 | .filter(Boolean) | ||
| 114 | .join(" ") | ||
| 115 | .toLowerCase(); | ||
| 116 | |||
| 117 | const provideNodeFileSystem = <A, E, R>( | ||
| 118 | effect: Effect.Effect<A, E, R | FileSystem.FileSystem>, | ||
| 119 | ): Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem>> => | ||
| 120 | effect.pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect< | ||
| 121 | A, | ||
| 122 | E, | ||
| 123 | Exclude<R, FileSystem.FileSystem> | ||
| 124 | >; | ||
| 125 | |||
| 126 | const GraphqlUserType = new GraphQLObjectType({ | ||
| 127 | name: "User", | ||
| 128 | fields: { | ||
| 129 | id: { | ||
| 130 | type: new GraphQLNonNull(GraphQLID), | ||
| 131 | }, | ||
| 132 | name: { | ||
| 133 | type: new GraphQLNonNull(GraphQLString), | ||
| 134 | }, | ||
| 135 | }, | ||
| 136 | }); | ||
| 137 | |||
| 138 | const GraphqlTeamType = new GraphQLObjectType({ | ||
| 139 | name: "Team", | ||
| 140 | fields: { | ||
| 141 | id: { | ||
| 142 | type: new GraphQLNonNull(GraphQLID), | ||
| 143 | }, | ||
| 144 | key: { | ||
| 145 | type: new GraphQLNonNull(GraphQLString), | ||
| 146 | }, | ||
| 147 | name: { | ||
| 148 | type: new GraphQLNonNull(GraphQLString), | ||
| 149 | }, | ||
| 150 | }, | ||
| 151 | }); | ||
| 152 | |||
| 153 | const GraphqlIssueType = new GraphQLObjectType({ | ||
| 154 | name: "Issue", | ||
| 155 | fields: { | ||
| 156 | id: { | ||
| 157 | type: new GraphQLNonNull(GraphQLID), | ||
| 158 | }, | ||
| 159 | identifier: { | ||
| 160 | type: new GraphQLNonNull(GraphQLString), | ||
| 161 | }, | ||
| 162 | title: { | ||
| 163 | type: new GraphQLNonNull(GraphQLString), | ||
| 164 | }, | ||
| 165 | team: { | ||
| 166 | type: new GraphQLNonNull(GraphqlTeamType), | ||
| 167 | }, | ||
| 168 | }, | ||
| 169 | }); | ||
| 170 | |||
| 171 | const GraphqlPageInfoType = new GraphQLObjectType({ | ||
| 172 | name: "PageInfo", | ||
| 173 | fields: { | ||
| 174 | hasNextPage: { | ||
| 175 | type: new GraphQLNonNull(GraphQLBoolean), | ||
| 176 | }, | ||
| 177 | endCursor: { | ||
| 178 | type: GraphQLString, | ||
| 179 | }, | ||
| 180 | }, | ||
| 181 | }); | ||
| 182 | |||
| 183 | const GraphqlIssueConnectionType = new GraphQLObjectType({ | ||
| 184 | name: "IssueConnection", | ||
| 185 | fields: { | ||
| 186 | nodes: { | ||
| 187 | type: new GraphQLNonNull( | ||
| 188 | new GraphQLList(new GraphQLNonNull(GraphqlIssueType)), | ||
| 189 | ), | ||
| 190 | }, | ||
| 191 | pageInfo: { | ||
| 192 | type: new GraphQLNonNull(GraphqlPageInfoType), | ||
| 193 | }, | ||
| 194 | }, | ||
| 195 | }); | ||
| 196 | |||
| 197 | const GraphqlCreateIssueInputType = new GraphQLInputObjectType({ | ||
| 198 | name: "CreateIssueInput", | ||
| 199 | fields: { | ||
| 200 | title: { | ||
| 201 | type: new GraphQLNonNull(GraphQLString), | ||
| 202 | }, | ||
| 203 | teamId: { | ||
| 204 | type: GraphQLID, | ||
| 205 | }, | ||
| 206 | }, | ||
| 207 | }); | ||
| 208 | |||
| 209 | const GraphqlQueryType = new GraphQLObjectType({ | ||
| 210 | name: "Query", | ||
| 211 | fields: { | ||
| 212 | viewer: { | ||
| 213 | type: new GraphQLNonNull(GraphqlUserType), | ||
| 214 | description: "The current user", | ||
| 215 | resolve: () => ({ | ||
| 216 | id: "usr_123", | ||
| 217 | name: "Rhys", | ||
| 218 | }), | ||
| 219 | }, | ||
| 220 | issue: { | ||
| 221 | type: GraphqlIssueType, | ||
| 222 | description: "Find a single issue by id", | ||
| 223 | args: { | ||
| 224 | id: { | ||
| 225 | type: new GraphQLNonNull(GraphQLID), | ||
| 226 | }, | ||
| 227 | }, | ||
| 228 | resolve: (_source, args: { id: string }) => ({ | ||
| 229 | id: args.id, | ||
| 230 | identifier: "ENG-123", | ||
| 231 | title: "Investigate GraphQL support", | ||
| 232 | team: { | ||
| 233 | id: "team_eng", | ||
| 234 | key: "ENG", | ||
| 235 | name: "Engineering", | ||
| 236 | }, | ||
| 237 | }), | ||
| 238 | }, | ||
| 239 | issues: { | ||
| 240 | type: new GraphQLNonNull(GraphqlIssueConnectionType), | ||
| 241 | description: "List issues for the workspace", | ||
| 242 | args: { | ||
| 243 | first: { | ||
| 244 | type: GraphQLInt, | ||
| 245 | }, | ||
| 246 | query: { | ||
| 247 | type: GraphQLString, | ||
| 248 | }, | ||
| 249 | }, | ||
| 250 | resolve: () => ({ | ||
| 251 | nodes: [ | ||
| 252 | { | ||
| 253 | id: "iss_1", | ||
| 254 | identifier: "ENG-123", | ||
| 255 | title: "Investigate GraphQL support", | ||
| 256 | team: { | ||
| 257 | id: "team_eng", | ||
| 258 | key: "ENG", | ||
| 259 | name: "Engineering", | ||
| 260 | }, | ||
| 261 | }, | ||
| 262 | { | ||
| 263 | id: "iss_2", | ||
| 264 | identifier: "ENG-124", | ||
| 265 | title: "Ship GraphQL field tools", | ||
| 266 | team: { | ||
| 267 | id: "team_eng", | ||
| 268 | key: "ENG", | ||
| 269 | name: "Engineering", | ||
| 270 | }, | ||
| 271 | }, | ||
| 272 | ], | ||
| 273 | pageInfo: { | ||
| 274 | hasNextPage: false, | ||
| 275 | endCursor: "iss_2", | ||
| 276 | }, | ||
| 277 | }), | ||
| 278 | }, | ||
| 279 | }, | ||
| 280 | }); | ||
| 281 | |||
| 282 | const GraphqlMutationType = new GraphQLObjectType({ | ||
| 283 | name: "Mutation", | ||
| 284 | fields: { | ||
| 285 | noop: { | ||
| 286 | type: GraphQLString, | ||
| 287 | resolve: () => "ok", | ||
| 288 | }, | ||
| 289 | createIssue: { | ||
| 290 | type: new GraphQLNonNull(GraphqlIssueType), | ||
| 291 | description: "Create a new issue", | ||
| 292 | args: { | ||
| 293 | input: { | ||
| 294 | type: new GraphQLNonNull(GraphqlCreateIssueInputType), | ||
| 295 | }, | ||
| 296 | }, | ||
| 297 | resolve: ( | ||
| 298 | _source, | ||
| 299 | args: { input: { title: string; teamId?: string | null } }, | ||
| 300 | ) => ({ | ||
| 301 | id: "iss_created", | ||
| 302 | identifier: "ENG-999", | ||
| 303 | title: args.input.title, | ||
| 304 | team: { | ||
| 305 | id: args.input.teamId ?? "team_eng", | ||
| 306 | key: "ENG", | ||
| 307 | name: "Engineering", | ||
| 308 | }, | ||
| 309 | }), | ||
| 310 | }, | ||
| 311 | }, | ||
| 312 | }); | ||
| 313 | |||
| 314 | const testGraphqlSchema = new GraphQLSchema({ | ||
| 315 | query: GraphqlQueryType, | ||
| 316 | mutation: GraphqlMutationType, | ||
| 317 | }); | ||
| 318 | |||
| 319 | const closeScope = (scope: Scope.CloseableScope) => | ||
| 320 | Scope.close(scope, Exit.void).pipe(Effect.orDie); | ||
| 321 | |||
| 322 | const makePersistence: Effect.Effect< | ||
| 323 | SqlControlPlanePersistence, | ||
| 324 | unknown, | ||
| 325 | Scope.Scope | ||
| 326 | > = Effect.acquireRelease( | ||
| 327 | createSqlControlPlanePersistence({ | ||
| 328 | localDataDir: ":memory:", | ||
| 329 | }), | ||
| 330 | (persistence) => | ||
| 331 | Effect.tryPromise({ | ||
| 332 | try: () => persistence.close(), | ||
| 333 | catch: (cause) => | ||
| 334 | cause instanceof Error ? cause : new Error(String(cause)), | ||
| 335 | }).pipe(Effect.orDie), | ||
| 336 | ); | ||
| 337 | |||
| 338 | const makeCountedMcpServer = Effect.acquireRelease( | ||
| 339 | Effect.promise<CountedMcpServer>( | ||
| 340 | () => | ||
| 341 | new Promise<CountedMcpServer>((resolve, reject) => { | ||
| 342 | const counts = { | ||
| 343 | listTools: 0, | ||
| 344 | callTool: 0, | ||
| 345 | }; | ||
| 346 | let closed = false; | ||
| 347 | const app = createMcpExpressApp({ host: "127.0.0.1" }); | ||
| 348 | |||
| 349 | const createServer = () => { | ||
| 350 | const server = new McpServer( | ||
| 351 | { | ||
| 352 | name: "workspace-execution-environment-test-server", | ||
| 353 | version: "1.0.0", | ||
| 354 | }, | ||
| 355 | { | ||
| 356 | capabilities: { | ||
| 357 | tools: {}, | ||
| 358 | }, | ||
| 359 | }, | ||
| 360 | ); | ||
| 361 | |||
| 362 | server.registerTool( | ||
| 363 | "echo", | ||
| 364 | { | ||
| 365 | description: "Echo the provided string", | ||
| 366 | inputSchema: { | ||
| 367 | value: z.string(), | ||
| 368 | }, | ||
| 369 | }, | ||
| 370 | async ({ value }: { value: string }) => ({ | ||
| 371 | content: [{ type: "text", text: `echo:${value}` }], | ||
| 372 | }), | ||
| 373 | ); | ||
| 374 | |||
| 375 | return server; | ||
| 376 | }; | ||
| 377 | |||
| 378 | const countMethods = (body: unknown) => { | ||
| 379 | const payloads = Array.isArray(body) ? body : [body]; | ||
| 380 | for (const payload of payloads) { | ||
| 381 | if (!payload || typeof payload !== "object") { | ||
| 382 | continue; | ||
| 383 | } | ||
| 384 | |||
| 385 | const method = "method" in payload ? payload.method : undefined; | ||
| 386 | if (method === "tools/list") { | ||
| 387 | counts.listTools += 1; | ||
| 388 | } else if (method === "tools/call") { | ||
| 389 | counts.callTool += 1; | ||
| 390 | } | ||
| 391 | } | ||
| 392 | }; | ||
| 393 | |||
| 394 | const handle = async (req: any, res: any, parsedBody?: unknown) => { | ||
| 395 | countMethods(parsedBody); | ||
| 396 | |||
| 397 | const server = createServer(); | ||
| 398 | const transport = new StreamableHTTPServerTransport({ | ||
| 399 | sessionIdGenerator: undefined, | ||
| 400 | }); | ||
| 401 | |||
| 402 | try { | ||
| 403 | await server.connect(transport); | ||
| 404 | await transport.handleRequest(req, res, parsedBody); | ||
| 405 | } finally { | ||
| 406 | await transport.close().catch(() => undefined); | ||
| 407 | await server.close().catch(() => undefined); | ||
| 408 | } | ||
| 409 | }; | ||
| 410 | |||
| 411 | app.post("/mcp", async (req: any, res: any) => { | ||
| 412 | await handle(req, res, req.body); | ||
| 413 | }); | ||
| 414 | |||
| 415 | app.get("/mcp", async (req: any, res: any) => { | ||
| 416 | await handle(req, res); | ||
| 417 | }); | ||
| 418 | |||
| 419 | app.delete("/mcp", async (req: any, res: any) => { | ||
| 420 | await handle(req, res, req.body); | ||
| 421 | }); | ||
| 422 | |||
| 423 | const listener = app.listen(0, "127.0.0.1", () => { | ||
| 424 | const address = listener.address(); | ||
| 425 | if (!address || typeof address === "string") { | ||
| 426 | reject(new Error("failed to resolve MCP test server address")); | ||
| 427 | return; | ||
| 428 | } | ||
| 429 | |||
| 430 | resolve({ | ||
| 431 | endpoint: `http://127.0.0.1:${address.port}/mcp`, | ||
| 432 | counts, | ||
| 433 | close: async () => { | ||
| 434 | if (closed) { | ||
| 435 | return; | ||
| 436 | } | ||
| 437 | closed = true; | ||
| 438 | |||
| 439 | await new Promise<void>((closeResolve, closeReject) => { | ||
| 440 | listener.close((error: Error | undefined) => { | ||
| 441 | if (error) { | ||
| 442 | closeReject(error); | ||
| 443 | return; | ||
| 444 | } | ||
| 445 | closeResolve(); | ||
| 446 | }); | ||
| 447 | }); | ||
| 448 | }, | ||
| 449 | }); | ||
| 450 | }); | ||
| 451 | |||
| 452 | listener.once("error", reject); | ||
| 453 | }), | ||
| 454 | ), | ||
| 455 | (server) => | ||
| 456 | Effect.tryPromise({ | ||
| 457 | try: () => server.close(), | ||
| 458 | catch: (error: unknown) => | ||
| 459 | error instanceof Error ? error : new Error(String(error)), | ||
| 460 | }).pipe(Effect.orDie), | ||
| 461 | ); | ||
| 462 | |||
| 463 | const ownerParam = HttpApiSchema.param("owner", Schema.String); | ||
| 464 | const repoParam = HttpApiSchema.param("repo", Schema.String); | ||
| 465 | |||
| 466 | class GeneratedReposApi extends HttpApiGroup.make("repos").add( | ||
| 467 | HttpApiEndpoint.get("getRepo")`/repos/${ownerParam}/${repoParam}`.addSuccess( | ||
| 468 | Schema.Struct({ | ||
| 469 | ok: Schema.Boolean, | ||
| 470 | full_name: Schema.String, | ||
| 471 | }), | ||
| 472 | ), | ||
| 473 | ) {} | ||
| 474 | |||
| 475 | class GeneratedApi extends HttpApi.make("generated").add(GeneratedReposApi) {} | ||
| 476 | |||
| 477 | const makeOpenApiSpecServer = Effect.acquireRelease( | ||
| 478 | Effect.gen(function* () { | ||
| 479 | const seenAuthHeaders: OpenApiSpecServer["seenAuthHeaders"] = []; | ||
| 480 | const handlersLayer = HttpApiBuilder.group( | ||
| 481 | GeneratedApi, | ||
| 482 | "repos", | ||
| 483 | (handlers) => | ||
| 484 | handlers.handle("getRepo", ({ path, request }) => | ||
| 485 | Effect.sync(() => { | ||
| 486 | seenAuthHeaders.push( | ||
| 487 | typeof request.headers.authorization === "string" | ||
| 488 | ? request.headers.authorization | ||
1498 more lines aren’t shown here.
This diff is large: its 614 lines aren’t shown here.
This diff is large: its 117 lines aren’t shown here.
This diff is large: its 31 lines aren’t shown here.
This diff is large: its 47 lines aren’t shown here.
This diff is large: its 20 lines aren’t shown here.
This diff is large: its 160 lines aren’t shown here.
This diff is large: its 30 lines aren’t shown here.
This diff is large: its 37 lines aren’t shown here.
This diff is large: its 37 lines aren’t shown here.
This diff is large: its 126 lines aren’t shown here.
This diff is large: its 57 lines aren’t shown here.
This diff is large: its 10 lines aren’t shown here.
This diff is large: its 18 lines aren’t shown here.
This diff is large: its 8 lines aren’t shown here.
This diff is large: its 8 lines aren’t shown here.
This diff is large: its 25 lines aren’t shown here.