Unified · Split
| @@ -46,10 +46,10 @@ When the trace cannot resolve with the files at hand, drop the finding. · 45 unmodified lines | |||
| 46 | 46 | ||
| 47 | ## Severity ladder | 47 | ## Severity ladder |
| 48 | 48 | ||
| 49 | | Level | Criteria | | 49 | | Level | Criteria | |
| 50 | |-------|----------| | 50 | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| 51 | | **medium** | New optimistic-mutation code that bypasses `Atom.optimistic` / `Atom.optimisticFn` and rolls its own pending state. Or a mixed read/write where the read goes through the plain query atom and the write goes through the optimistic mutation. | | 51 | | **medium** | New optimistic-mutation code that bypasses `Atom.optimistic` / `Atom.optimisticFn` and rolls its own pending state. Or a mixed read/write where the read goes through the plain query atom and the write goes through the optimistic mutation. | |
| 52 | | **low** | Subtle defects in an `Atom.optimisticFn` reducer that work today but degrade under racing (clock-based identity, missing `Atom.family` wrapper, computed-once captures of `scopeId`). | | 52 | | **low** | Subtle defects in an `Atom.optimisticFn` reducer that work today but degrade under racing (clock-based identity, missing `Atom.family` wrapper, computed-once captures of `scopeId`). | |
| 53 | 53 | ||
| 54 | Do not invent `high`. Pick `low` when in doubt and explain why. | 54 | Do not invent `high`. Pick `low` when in doubt and explain why. |
| 55 | 55 | ||
| @@ -103,9 +103,7 @@ export const updateSecretOptimistic = Atom.family((scopeId: ScopeId) => · 47 unmodified lines | |||
| 103 | Atom.optimisticFn({ | 103 | Atom.optimisticFn({ |
| 104 | reducer: (current, arg: { path: { secretId: SecretId }; payload: { value: string } }) => | 104 | reducer: (current, arg: { path: { secretId: SecretId }; payload: { value: string } }) => |
| 105 | Result.map(current, (rows) => | 105 | Result.map(current, (rows) => |
| 106 | rows.map((r) => | 106 | rows.map((r) => (r.id === arg.path.secretId ? { ...r, value: arg.payload.value } : r)), |
| 107 | r.id === arg.path.secretId ? { ...r, value: arg.payload.value } : r, | ||
| 108 | ), | ||
| 109 | ), | 107 | ), |
| 110 | fn: updateSecret, | 108 | fn: updateSecret, |
| 111 | }), | 109 | }), |
| @@ -136,8 +134,12 @@ export interface PendingSecret { · 24 unmodified lines | |||
| 136 | readonly value: string; | 134 | readonly value: string; |
| 137 | } | 135 | } |
| 138 | 136 | ||
| 139 | export const useSecretsWithPending = (scopeId: ScopeId) => { /* ... */ }; | 137 | export const useSecretsWithPending = (scopeId: ScopeId) => { |
| 140 | export const usePendingSecrets = () => { /* ... */ }; | 138 | /* ... */ |
| 139 | }; | ||
| 140 | export const usePendingSecrets = () => { | ||
| 141 | /* ... */ | ||
| 142 | }; | ||
| 141 | ``` | 143 | ``` |
| 142 | 144 | ||
| 143 | Flag any new entry in `PendingResource` and any new `use<X>WithPending` / `usePending<X>` hook. The file is closed for new patterns. | 145 | Flag any new entry in `PendingResource` and any new `use<X>WithPending` / `usePending<X>` hook. The file is closed for new patterns. |
| @@ -96,7 +96,7 @@ throw new Error("Missing source"); · 95 unmodified lines | |||
| 96 | Good in `Effect.gen`: | 96 | Good in `Effect.gen`: |
| 97 | 97 | ||
| 98 | ```ts | 98 | ```ts |
| 99 | return yield* new SourceNotFoundError({ sourceId }); | 99 | return yield * new SourceNotFoundError({ sourceId }); |
| 100 | ``` | 100 | ``` |
| 101 | 101 | ||
| 102 | Good in combinators: | 102 | Good in combinators: |
| @@ -112,13 +112,13 @@ If a third-party interface requires throwing, keep the throw at the adapter edge · 9 unmodified lines | |||
| 112 | Prefer yielding the error directly in generator code: | 112 | Prefer yielding the error directly in generator code: |
| 113 | 113 | ||
| 114 | ```ts | 114 | ```ts |
| 115 | return yield* new SourceNotFoundError({ sourceId }); | 115 | return yield * new SourceNotFoundError({ sourceId }); |
| 116 | ``` | 116 | ``` |
| 117 | 117 | ||
| 118 | Do not write: | 118 | Do not write: |
| 119 | 119 | ||
| 120 | ```ts | 120 | ```ts |
| 121 | return yield* Effect.fail(new SourceNotFoundError({ sourceId })); | 121 | return yield * Effect.fail(new SourceNotFoundError({ sourceId })); |
| 122 | ``` | 122 | ``` |
| 123 | 123 | ||
| 124 | Use `Effect.fail(...)` in non-generator combinator code: | 124 | Use `Effect.fail(...)` in non-generator combinator code: |
| @@ -167,16 +167,14 @@ Effect.ignore( · 42 unmodified lines | |||
| 167 | Bad in domain code: | 167 | Bad in domain code: |
| 168 | 168 | ||
| 169 | ```ts | 169 | ```ts |
| 170 | program.pipe(Effect.orDie) | 170 | program.pipe(Effect.orDie); |
| 171 | Effect.die(error) | 171 | Effect.die(error); |
| 172 | ``` | 172 | ``` |
| 173 | 173 | ||
| 174 | Good: | 174 | Good: |
| 175 | 175 | ||
| 176 | ```ts | 176 | ```ts |
| 177 | program.pipe( | 177 | program.pipe(Effect.mapError((cause) => new DomainError({ message: "Operation failed", cause }))); |
| 178 | Effect.mapError((cause) => new DomainError({ message: "Operation failed", cause })), | ||
| 179 | ) | ||
| 180 | ``` | 178 | ``` |
| 181 | 179 | ||
| 182 | `Effect.die`, `Effect.dieMessage`, `Effect.orDie`, and `Effect.orDieWith` turn typed failures into defects. Use them only at a true runtime boundary where the host cannot represent typed failures, and keep that usage behind a narrow lint suppression with a `boundary:` reason. Do not use `orDie` to avoid threading an error type through normal Effect code. | 180 | `Effect.die`, `Effect.dieMessage`, `Effect.orDie`, and `Effect.orDieWith` turn typed failures into defects. Use them only at a true runtime boundary where the host cannot represent typed failures, and keep that usage behind a narrow lint suppression with a `boundary:` reason. Do not use `orDie` to avoid threading an error type through normal Effect code. |
| @@ -305,16 +303,19 @@ Bad: · 122 unmodified lines | |||
| 305 | const connectionError = (message: string) => | 303 | const connectionError = (message: string) => |
| 306 | new McpConnectionError({ transport: "remote", message }); | 304 | new McpConnectionError({ transport: "remote", message }); |
| 307 | 305 | ||
| 308 | return yield* connectionError("Endpoint URL is required"); | 306 | return yield * connectionError("Endpoint URL is required"); |
| 309 | ``` | 307 | ``` |
| 310 | 308 | ||
| 311 | Good: | 309 | Good: |
| 312 | 310 | ||
| 313 | ```ts | 311 | ```ts |
| 314 | return yield* new McpConnectionError({ | 312 | return ( |
| 315 | transport: "remote", | 313 | yield * |
| 316 | message: "Endpoint URL is required", | 314 | new McpConnectionError({ |
| 317 | }); | 315 | transport: "remote", |
| 316 | message: "Endpoint URL is required", | ||
| 317 | }) | ||
| 318 | ); | ||
| 318 | ``` | 319 | ``` |
| 319 | 320 | ||
| 320 | Helpers are allowed only when they do real work, such as: | 321 | Helpers are allowed only when they do real work, such as: |
| @@ -6,16 +6,19 @@ description: Runbook for releasing the `executor` CLI package (stable and beta). · 5 unmodified lines | |||
| 6 | # Executor CLI release runbook | 6 | # Executor CLI release runbook |
| 7 | 7 | ||
| 8 | ## Authoritative doc | 8 | ## Authoritative doc |
| 9 | |||
| 9 | `RELEASING.md` at repo root is the source of truth. This skill encodes the owner's preferences on top of it. | 10 | `RELEASING.md` at repo root is the source of truth. This skill encodes the owner's preferences on top of it. |
| 10 | 11 | ||
| 11 | ## What the `executor` CLI actually ships | 12 | ## What the `executor` CLI actually ships |
| 12 | 13 | ||
| 13 | The CLI binary bundles: | 14 | The CLI binary bundles: |
| 15 | |||
| 14 | - `apps/cli/**` — CLI source + daemon | 16 | - `apps/cli/**` — CLI source + daemon |
| 15 | - `apps/local/**` — the web UI (embedded as a virtual module via `apps/cli/src/build.ts:178`) + drizzle migrations (`build.ts:205`) | 17 | - `apps/local/**` — the web UI (embedded as a virtual module via `apps/cli/src/build.ts:178`) + drizzle migrations (`build.ts:205`) |
| 16 | - `packages/**` — `core`, `kernel`, `hosts/mcp`, `runtime-quickjs`, and every plugin under `packages/plugins/**` | 18 | - `packages/**` — `core`, `kernel`, `hosts/mcp`, `runtime-quickjs`, and every plugin under `packages/plugins/**` |
| 17 | 19 | ||
| 18 | Does **not** ship in the CLI: | 20 | Does **not** ship in the CLI: |
| 21 | |||
| 19 | - `apps/cloud/**` (Cloudflare Workers deployment) | 22 | - `apps/cloud/**` (Cloudflare Workers deployment) |
| 20 | - `apps/marketing/**`, `apps/desktop/**` | 23 | - `apps/marketing/**`, `apps/desktop/**` |
| 21 | - `examples/**`, `tests/**` | 24 | - `examples/**`, `tests/**` |
| @@ -37,6 +40,7 @@ The owner doesn't want GitHub's auto-generated "PR title by @user" list. Release · 15 unmodified lines | |||
| 37 | **`apps/cli/release-notes/next.md` is the canonical user-facing changelog.** Per-package workspace `CHANGELOG.md` files are one-line stubs required by `changesets/action@v1` (the GitHub Action wrapping the CLI in `release.yml`) — it reads each bumped package's `CHANGELOG.md` to build the Version Packages PR description and crashes with `ENOENT` if any is missing. The stubs satisfy that read; don't put release content in them. | 40 | **`apps/cli/release-notes/next.md` is the canonical user-facing changelog.** Per-package workspace `CHANGELOG.md` files are one-line stubs required by `changesets/action@v1` (the GitHub Action wrapping the CLI in `release.yml`) — it reads each bumped package's `CHANGELOG.md` to build the Version Packages PR description and crashes with `ENOENT` if any is missing. The stubs satisfy that read; don't put release content in them. |
| 38 | 41 | ||
| 39 | ### How it's wired | 42 | ### How it's wired |
| 43 | |||
| 40 | `apps/cli/src/release.ts` reads `apps/cli/release-notes/next.md` and uses | 44 | `apps/cli/src/release.ts` reads `apps/cli/release-notes/next.md` and uses |
| 41 | its contents as the GitHub Release body. If the file is missing or empty, | 45 | its contents as the GitHub Release body. If the file is missing or empty, |
| 42 | falls back to `gh release create --generate-notes`. There's no | 46 | falls back to `gh release create --generate-notes`. There's no |
| @@ -44,7 +48,9 @@ per-version archive in the repo — historical release bodies live on · 1 unmodified line | |||
| 44 | GitHub Releases (durable, indexed, linkable). | 48 | GitHub Releases (durable, indexed, linkable). |
| 45 | 49 | ||
| 46 | ### Writing conventions | 50 | ### Writing conventions |
| 51 | |||
| 47 | Structure release-notes files as: | 52 | Structure release-notes files as: |
| 53 | |||
| 48 | ``` | 54 | ``` |
| 49 | ## Highlights | 55 | ## Highlights |
| 50 | ### <user-facing story> # e.g. "Per-user OAuth for OpenAPI and MCP sources" | 56 | ### <user-facing story> # e.g. "Per-user OAuth for OpenAPI and MCP sources" |
| @@ -74,17 +80,20 @@ External contributor bullets end with `Thanks @<user> (#PR)`: · 23 unmodified lines | |||
| 74 | Do not `Thanks` maintainers, bots, or the repo owner — the lint script rejects `@claude`, `@anthropic`, `@github-actions`, `@dependabot`, `@renovate`, `@rhyssullivan`, `@rhys-sullivan`. Run `bun run lint:release-notes` before pushing notes. | 80 | Do not `Thanks` maintainers, bots, or the repo owner — the lint script rejects `@claude`, `@anthropic`, `@github-actions`, `@dependabot`, `@renovate`, `@rhyssullivan`, `@rhys-sullivan`. Run `bun run lint:release-notes` before pushing notes. |
| 75 | 81 | ||
| 76 | ### When drafting from `git log` | 82 | ### When drafting from `git log` |
| 83 | |||
| 77 | - Look at `git diff v<last>..HEAD -- README.md` first — it's the best single view of user-facing changes. | 84 | - Look at `git diff v<last>..HEAD -- README.md` first — it's the best single view of user-facing changes. |
| 78 | - Read commit messages in bulk (`git log --oneline v<last>..HEAD -- apps/cli apps/local packages`), then bucket by theme before writing prose. | 85 | - Read commit messages in bulk (`git log --oneline v<last>..HEAD -- apps/cli apps/local packages`), then bucket by theme before writing prose. |
| 79 | - Don't list every commit. Merge PRs and refactor-chain commits into one line. | 86 | - Don't list every commit. Merge PRs and refactor-chain commits into one line. |
| 80 | 87 | ||
| 81 | ### Pairing with changesets | 88 | ### Pairing with changesets |
| 89 | |||
| 82 | - A `.changeset/*.md` describes the version bump (semver level + a short summary for the Version Packages PR description). It is **not** the user-facing changelog. | 90 | - A `.changeset/*.md` describes the version bump (semver level + a short summary for the Version Packages PR description). It is **not** the user-facing changelog. |
| 83 | - If your PR adds a `.changeset/*.md` for the `executor` package, also edit `apps/cli/release-notes/next.md` for the user-facing story. They have different audiences. | 91 | - If your PR adds a `.changeset/*.md` for the `executor` package, also edit `apps/cli/release-notes/next.md` for the user-facing story. They have different audiences. |
| 84 | - The `.changeset/*.md` body can be a one-liner pointing at the release-notes section it expands; users read the GitHub release body, not the changeset. | 92 | - The `.changeset/*.md` body can be a one-liner pointing at the release-notes section it expands; users read the GitHub release body, not the changeset. |
| 85 | - Frontmatter is `"executor": patch` (or `minor`/`major` if owner says so). | 93 | - Frontmatter is `"executor": patch` (or `minor`/`major` if owner says so). |
| 86 | 94 | ||
| 87 | ### Starting a new release cycle | 95 | ### Starting a new release cycle |
| 96 | |||
| 88 | There's no post-release rename step. When you start work on the next | 97 | There's no post-release rename step. When you start work on the next |
| 89 | release, replace the existing `next.md` content with new entries — the | 98 | release, replace the existing `next.md` content with new entries — the |
| 90 | previous cycle's content is already preserved on the matching `vX.Y.Z` | 99 | previous cycle's content is already preserved on the matching `vX.Y.Z` |
| @@ -16,7 +16,7 @@ sources/connections. · 15 unmodified lines | |||
| 16 | 16 | ||
| 17 | - A list query atom (`Atom.optimistic` wraps the query) | 17 | - A list query atom (`Atom.optimistic` wraps the query) |
| 18 | - Plus one or more mutation atoms that change rows in that list (create / update / delete) | 18 | - Plus one or more mutation atoms that change rows in that list (create / update / delete) |
| 19 | - And you want the UI to reflect the change *immediately* on click, before the server roundtrip | 19 | - And you want the UI to reflect the change _immediately_ on click, before the server roundtrip |
| 20 | 20 | ||
| 21 | If you only need the UI to be "eventually consistent" (i.e. you're fine waiting | 21 | If you only need the UI to be "eventually consistent" (i.e. you're fine waiting |
| 22 | ~200ms for the server response and the existing reactivity refetch), skip | 22 | ~200ms for the server response and the existing reactivity refetch), skip |
| @@ -109,10 +109,7 @@ existing `reactivityKeys` plumbing still applies — pass them in the call. · 86 unmodified lines | |||
| 109 | ```typescript | 109 | ```typescript |
| 110 | import { useAtomValue, useAtomSet } from "@effect-atom/atom-react"; | 110 | import { useAtomValue, useAtomSet } from "@effect-atom/atom-react"; |
| 111 | 111 | ||
| 112 | import { | 112 | import { policiesOptimisticAtom, updatePolicyOptimistic } from "../api/atoms"; |
| 113 | policiesOptimisticAtom, | ||
| 114 | updatePolicyOptimistic, | ||
| 115 | } from "../api/atoms"; | ||
| 116 | import { policyWriteKeys } from "../api/reactivity-keys"; | 113 | import { policyWriteKeys } from "../api/reactivity-keys"; |
| 117 | 114 | ||
| 118 | export function PoliciesPage() { | 115 | export function PoliciesPage() { |
| @@ -203,10 +200,7 @@ export const removePolicyOptimistic = Atom.family((scopeId: ScopeId) => · 84 unmodified lines | |||
| 203 | path: { scopeId: ScopeId; policyId: PolicyId }; | 200 | path: { scopeId: ScopeId; policyId: PolicyId }; |
| 204 | reactivityKeys?: ReadonlyArray<unknown>; | 201 | reactivityKeys?: ReadonlyArray<unknown>; |
| 205 | }, | 202 | }, |
| 206 | ) => | 203 | ) => Result.map(current, (rows) => rows.filter((r) => r.id !== arg.path.policyId)), |
| 207 | Result.map(current, (rows) => | ||
| 208 | rows.filter((r) => r.id !== arg.path.policyId), | ||
| 209 | ), | ||
| 210 | fn: removePolicy, | 204 | fn: removePolicy, |
| 211 | }), | 205 | }), |
| 212 | ), | 206 | ), |
| @@ -244,7 +238,7 @@ read, optimistic create/update/remove, no custom state. · 31 unmodified lines | |||
| 244 | 238 | ||
| 245 | ## When you must NOT use this | 239 | ## When you must NOT use this |
| 246 | 240 | ||
| 247 | - The mutation has cross-cutting effects on data that *isn't* in the same list | 241 | - The mutation has cross-cutting effects on data that _isn't_ in the same list |
| 248 | (e.g. a single mutation invalidates `tools` AND `policies`). Reactivity keys | 242 | (e.g. a single mutation invalidates `tools` AND `policies`). Reactivity keys |
| 249 | still handle that — optimistic only paints the list-local change. | 243 | still handle that — optimistic only paints the list-local change. |
| 250 | - You need to show transient UI state that isn't a row property (toasts, | 244 | - You need to show transient UI state that isn't a row property (toasts, |
| @@ -9,12 +9,12 @@ description: "Manage Git branches and PRs using Graphite CLI (gt) instead of raw · 8 unmodified lines | |||
| 9 | 9 | ||
| 10 | Use `gt` commands instead of their `git` counterparts for commit and branch management. Raw git bypasses Graphite's stack metadata, leaving descendants un-restacked and the CLI's view of the stack out of sync. | 10 | Use `gt` commands instead of their `git` counterparts for commit and branch management. Raw git bypasses Graphite's stack metadata, leaving descendants un-restacked and the CLI's view of the stack out of sync. |
| 11 | 11 | ||
| 12 | | Instead of | Use | | 12 | | Instead of | Use | |
| 13 | |------------|-----| | 13 | | ------------------------ | ----------------------- | |
| 14 | | `git checkout -b <name>` | `gt create <name>` | | 14 | | `git checkout -b <name>` | `gt create <name>` | |
| 15 | | `git commit -m "msg"` | `gt modify -c -m "msg"` | | 15 | | `git commit -m "msg"` | `gt modify -c -m "msg"` | |
| 16 | | `git commit --amend` | `gt modify --amend` | | 16 | | `git commit --amend` | `gt modify --amend` | |
| 17 | | `git commit -am "msg"` | `gt modify -cam "msg"` | | 17 | | `git commit -am "msg"` | `gt modify -cam "msg"` | |
| 18 | 18 | ||
| 19 | Why: `gt` keeps stack metadata consistent and auto-restacks descendant branches when a parent changes. | 19 | Why: `gt` keeps stack metadata consistent and auto-restacks descendant branches when a parent changes. |
| 20 | 20 | ||
| @@ -54,6 +54,7 @@ To bail out: `gt abort`. · 33 unmodified lines | |||
| 54 | Create a new branch stacked on current branch with staged changes. | 54 | Create a new branch stacked on current branch with staged changes. |
| 55 | 55 | ||
| 56 | **Flags:** | 56 | **Flags:** |
| 57 | |||
| 57 | - `-a, --all` - Stage all unstaged changes including untracked files | 58 | - `-a, --all` - Stage all unstaged changes including untracked files |
| 58 | - `-m, --message` - Commit message | 59 | - `-m, --message` - Commit message |
| 59 | - `-i, --insert` - Insert between current branch and its child | 60 | - `-i, --insert` - Insert between current branch and its child |
| @@ -67,6 +68,7 @@ Create a new branch stacked on current branch with staged changes. · 7 unmodified lines | |||
| 67 | Amend current branch's commit or create new commit. Auto-restacks descendants. | 68 | Amend current branch's commit or create new commit. Auto-restacks descendants. |
| 68 | 69 | ||
| 69 | **Flags:** | 70 | **Flags:** |
| 71 | |||
| 70 | - `-a, --all` - Stage all changes | 72 | - `-a, --all` - Stage all changes |
| 71 | - `-c, --commit` - Create new commit instead of amending | 73 | - `-c, --commit` - Create new commit instead of amending |
| 72 | - `-e, --edit` - Open editor for commit message | 74 | - `-e, --edit` - Open editor for commit message |
| @@ -82,6 +84,7 @@ Amend current branch's commit or create new commit. Auto-restacks descendants. · 9 unmodified lines | |||
| 82 | Amend staged changes to relevant commits in current stack. | 84 | Amend staged changes to relevant commits in current stack. |
| 83 | 85 | ||
| 84 | **Flags:** | 86 | **Flags:** |
| 87 | |||
| 85 | - `-a, --all` - Stage all unstaged changes (not untracked) | 88 | - `-a, --all` - Stage all unstaged changes (not untracked) |
| 86 | - `-d, --dry-run` - Print what would happen | 89 | - `-d, --dry-run` - Print what would happen |
| 87 | - `-f, --force` - Skip confirmation | 90 | - `-f, --force` - Skip confirmation |
| @@ -92,6 +95,7 @@ Amend staged changes to relevant commits in current stack. · 4 unmodified lines | |||
| 92 | Squash all commits in current branch into single commit. | 95 | Squash all commits in current branch into single commit. |
| 93 | 96 | ||
| 94 | **Flags:** | 97 | **Flags:** |
| 98 | |||
| 95 | - `-m, --message` - Commit message | 99 | - `-m, --message` - Commit message |
| 96 | - `--edit` - Modify existing message | 100 | - `--edit` - Modify existing message |
| 97 | - `-n, --no-edit` - Keep existing message | 101 | - `-n, --no-edit` - Keep existing message |
| @@ -101,6 +105,7 @@ Squash all commits in current branch into single commit. · 3 unmodified lines | |||
| 101 | Fold branch's changes into parent, restack descendants. | 105 | Fold branch's changes into parent, restack descendants. |
| 102 | 106 | ||
| 103 | **Flags:** | 107 | **Flags:** |
| 108 | |||
| 104 | - `-k, --keep` - Keep current branch name instead of parent's | 109 | - `-k, --keep` - Keep current branch name instead of parent's |
| 105 | 110 | ||
| 106 | ### gt split | 111 | ### gt split |
| @@ -108,6 +113,7 @@ Fold branch's changes into parent, restack descendants. · 1 unmodified line | |||
| 108 | Split current branch into multiple branches. | 113 | Split current branch into multiple branches. |
| 109 | 114 | ||
| 110 | **Flags:** | 115 | **Flags:** |
| 116 | |||
| 111 | - `-c, --commit, --by-commit` - Split by commit history | 117 | - `-c, --commit, --by-commit` - Split by commit history |
| 112 | - `-h, --hunk, --by-hunk` - Split by hunk interactively | 118 | - `-h, --hunk, --by-hunk` - Split by hunk interactively |
| 113 | - `-f, --file, --by-file` - Split files matching pathspecs | 119 | - `-f, --file, --by-file` - Split files matching pathspecs |
| @@ -119,6 +125,7 @@ Split current branch into multiple branches. · 5 unmodified lines | |||
| 119 | Push branches and create/update PRs on GitHub. | 125 | Push branches and create/update PRs on GitHub. |
| 120 | 126 | ||
| 121 | **Flags:** | 127 | **Flags:** |
| 128 | |||
| 122 | - `-s, --stack` - Include descendant branches | 129 | - `-s, --stack` - Include descendant branches |
| 123 | - `-d, --draft` - Create PRs as drafts | 130 | - `-d, --draft` - Create PRs as drafts |
| 124 | - `-p, --publish` - Publish draft PRs | 131 | - `-p, --publish` - Publish draft PRs |
| @@ -148,6 +155,7 @@ Push branches and create/update PRs on GitHub. · 23 unmodified lines | |||
| 148 | Merge PRs from trunk to current branch via Graphite. | 155 | Merge PRs from trunk to current branch via Graphite. |
| 149 | 156 | ||
| 150 | **Flags:** | 157 | **Flags:** |
| 158 | |||
| 151 | - `-c, --confirm` - Confirm before merging | 159 | - `-c, --confirm` - Confirm before merging |
| 152 | - `--dry-run` - Report what would merge | 160 | - `--dry-run` - Report what would merge |
| 153 | 161 | ||
| @@ -158,6 +166,7 @@ Merge PRs from trunk to current branch via Graphite. · 4 unmodified lines | |||
| 158 | Switch to branch. Interactive selector if no branch provided. | 166 | Switch to branch. Interactive selector if no branch provided. |
| 159 | 167 | ||
| 160 | **Flags:** | 168 | **Flags:** |
| 169 | |||
| 161 | - `-a, --all` - Show all trunks in selection | 170 | - `-a, --all` - Show all trunks in selection |
| 162 | - `-u, --show-untracked` - Include untracked branches | 171 | - `-u, --show-untracked` - Include untracked branches |
| 163 | - `-s, --stack` - Only show current stack | 172 | - `-s, --stack` - Only show current stack |
| @@ -168,6 +177,7 @@ Switch to branch. Interactive selector if no branch provided. · 4 unmodified lines | |||
| 168 | Switch to child of current branch. | 177 | Switch to child of current branch. |
| 169 | 178 | ||
| 170 | **Flags:** | 179 | **Flags:** |
| 180 | |||
| 171 | - `-n, --steps` - Levels to traverse | 181 | - `-n, --steps` - Levels to traverse |
| 172 | - `--to` - Target branch to navigate toward | 182 | - `--to` - Target branch to navigate toward |
| 173 | 183 | ||
| @@ -176,6 +186,7 @@ Switch to child of current branch. · 2 unmodified lines | |||
| 176 | Switch to parent of current branch. | 186 | Switch to parent of current branch. |
| 177 | 187 | ||
| 178 | **Flags:** | 188 | **Flags:** |
| 189 | |||
| 179 | - `-n, --steps` - Levels to traverse | 190 | - `-n, --steps` - Levels to traverse |
| 180 | 191 | ||
| 181 | ### gt top | 192 | ### gt top |
| @@ -193,6 +204,7 @@ Switch to branch closest to trunk in current stack. · 11 unmodified lines | |||
| 193 | Ensure each branch has parent in Git history, rebasing if needed. | 204 | Ensure each branch has parent in Git history, rebasing if needed. |
| 194 | 205 | ||
| 195 | **Flags:** | 206 | **Flags:** |
| 207 | |||
| 196 | - `--branch` - Run from specified branch | 208 | - `--branch` - Run from specified branch |
| 197 | - `--downstack` - Only restack branch and ancestors | 209 | - `--downstack` - Only restack branch and ancestors |
| 198 | - `--upstack` - Only restack branch and descendants | 210 | - `--upstack` - Only restack branch and descendants |
| @@ -203,6 +215,7 @@ Ensure each branch has parent in Git history, rebasing if needed. · 4 unmodified lines | |||
| 203 | Rebase current branch onto target, restack descendants. | 215 | Rebase current branch onto target, restack descendants. |
| 204 | 216 | ||
| 205 | **Flags:** | 217 | **Flags:** |
| 218 | |||
| 206 | - `-o, --onto` - Target branch | 219 | - `-o, --onto` - Target branch |
| 207 | - `--source` - Branch to move (default: current) | 220 | - `--source` - Branch to move (default: current) |
| 208 | - `-a, --all` - Show all trunks in selection | 221 | - `-a, --all` - Show all trunks in selection |
| @@ -218,6 +231,7 @@ Reorder branches between trunk and current via editor. · 9 unmodified lines | |||
| 218 | Sync all branches with remote, clean merged/closed PRs, restack. | 231 | Sync all branches with remote, clean merged/closed PRs, restack. |
| 219 | 232 | ||
| 220 | **Flags:** | 233 | **Flags:** |
| 234 | |||
| 221 | - `-a, --all` - Sync across all trunks | 235 | - `-a, --all` - Sync across all trunks |
| 222 | - `-f, --force` - Skip confirmations | 236 | - `-f, --force` - Skip confirmations |
| 223 | - `--restack` / `--no-restack` - Restack after sync (default: true) | 237 | - `--restack` / `--no-restack` - Restack after sync (default: true) |
| @@ -227,6 +241,7 @@ Sync all branches with remote, clean merged/closed PRs, restack. · 3 unmodified lines | |||
| 227 | Sync branches from trunk to specified branch/PR from remote. | 241 | Sync branches from trunk to specified branch/PR from remote. |
| 228 | 242 | ||
| 229 | **Flags:** | 243 | **Flags:** |
| 244 | |||
| 230 | - `-d, --downstack` - Don't sync upstack branches | 245 | - `-d, --downstack` - Don't sync upstack branches |
| 231 | - `-f, --force` - Overwrite with remote | 246 | - `-f, --force` - Overwrite with remote |
| 232 | - `--restack` / `--no-restack` - Restack after (default: true) | 247 | - `--restack` / `--no-restack` - Restack after (default: true) |
| @@ -239,6 +254,7 @@ Sync branches from trunk to specified branch/PR from remote. · 6 unmodified lines | |||
| 239 | Delete branch and Graphite metadata. Children restack to parent. | 254 | Delete branch and Graphite metadata. Children restack to parent. |
| 240 | 255 | ||
| 241 | **Flags:** | 256 | **Flags:** |
| 257 | |||
| 242 | - `-f, --force` - Delete even if not merged/closed | 258 | - `-f, --force` - Delete even if not merged/closed |
| 243 | - `-c, --close` - Close associated PR on GitHub | 259 | - `-c, --close` - Close associated PR on GitHub |
| 244 | - `--downstack` - Also delete ancestors | 260 | - `--downstack` - Also delete ancestors |
| @@ -249,6 +265,7 @@ Delete branch and Graphite metadata. Children restack to parent. · 4 unmodified lines | |||
| 249 | Rename branch. Removes PR association. | 265 | Rename branch. Removes PR association. |
| 250 | 266 | ||
| 251 | **Flags:** | 267 | **Flags:** |
| 268 | |||
| 252 | - `-f, --force` - Allow rename with open PR | 269 | - `-f, --force` - Allow rename with open PR |
| 253 | 270 | ||
| 254 | ### gt track [branch] | 271 | ### gt track [branch] |
| @@ -256,6 +273,7 @@ Rename branch. Removes PR association. · 1 unmodified line | |||
| 256 | Start tracking branch with Graphite by selecting parent. | 273 | Start tracking branch with Graphite by selecting parent. |
| 257 | 274 | ||
| 258 | **Flags:** | 275 | **Flags:** |
| 276 | |||
| 259 | - `-f, --force` - Use most recent tracked ancestor as parent | 277 | - `-f, --force` - Use most recent tracked ancestor as parent |
| 260 | - `-p, --parent` - Specify parent branch | 278 | - `-p, --parent` - Specify parent branch |
| 261 | 279 | ||
| @@ -264,6 +282,7 @@ Start tracking branch with Graphite by selecting parent. · 2 unmodified lines | |||
| 264 | Stop tracking branch. Children also untracked. | 282 | Stop tracking branch. Children also untracked. |
| 265 | 283 | ||
| 266 | **Flags:** | 284 | **Flags:** |
| 285 | |||
| 267 | - `-f, --force` - Skip confirmation | 286 | - `-f, --force` - Skip confirmation |
| 268 | 287 | ||
| 269 | ### gt pop | 288 | ### gt pop |
| @@ -285,6 +304,7 @@ Unfreeze branch and upstack. · 15 unmodified lines | |||
| 285 | Show stacks. Forms: `gt log`, `gt log short`, `gt log long`. | 304 | Show stacks. Forms: `gt log`, `gt log short`, `gt log long`. |
| 286 | 305 | ||
| 287 | **Flags:** | 306 | **Flags:** |
| 307 | |||
| 288 | - `-a, --all` - Show all trunks | 308 | - `-a, --all` - Show all trunks |
| 289 | - `-r, --reverse` - Print upside down | 309 | - `-r, --reverse` - Print upside down |
| 290 | - `-u, --show-untracked` - Include untracked | 310 | - `-u, --show-untracked` - Include untracked |
| @@ -297,6 +317,7 @@ Show stacks. Forms: `gt log`, `gt log short`, `gt log long`. · 6 unmodified lines | |||
| 297 | Display branch information. | 317 | Display branch information. |
| 298 | 318 | ||
| 299 | **Flags:** | 319 | **Flags:** |
| 320 | |||
| 300 | - `-b, --body` - Show PR body | 321 | - `-b, --body` - Show PR body |
| 301 | - `-d, --diff` - Show diff vs parent | 322 | - `-d, --diff` - Show diff vs parent |
| 302 | - `-p, --patch` - Show commit changes | 323 | - `-p, --patch` - Show commit changes |
| @@ -307,6 +328,7 @@ Display branch information. · 4 unmodified lines | |||
| 307 | Show parent, children, or trunk of current branch. | 328 | Show parent, children, or trunk of current branch. |
| 308 | 329 | ||
| 309 | `gt trunk` flags: | 330 | `gt trunk` flags: |
| 331 | |||
| 310 | - `--add` - Add trunk | 332 | - `--add` - Add trunk |
| 311 | - `-a, --all` - Show all trunks | 333 | - `-a, --all` - Show all trunks |
| 312 | 334 | ||
| @@ -315,6 +337,7 @@ Show parent, children, or trunk of current branch. · 2 unmodified lines | |||
| 315 | Open PR page for branch or PR number. | 337 | Open PR page for branch or PR number. |
| 316 | 338 | ||
| 317 | **Flags:** | 339 | **Flags:** |
| 340 | |||
| 318 | - `--stack` - Open stack page | 341 | - `--stack` - Open stack page |
| 319 | 342 | ||
| 320 | ## Conflict Resolution | 343 | ## Conflict Resolution |
| @@ -324,6 +347,7 @@ Open PR page for branch or PR number. · 3 unmodified lines | |||
| 324 | Continue command halted by rebase conflict. | 347 | Continue command halted by rebase conflict. |
| 325 | 348 | ||
| 326 | **Flags:** | 349 | **Flags:** |
| 350 | |||
| 327 | - `-a, --all` - Stage all changes first | 351 | - `-a, --all` - Stage all changes first |
| 328 | 352 | ||
| 329 | ### gt abort | 353 | ### gt abort |
| @@ -331,6 +355,7 @@ Continue command halted by rebase conflict. · 1 unmodified line | |||
| 331 | Abort current command halted by conflict. | 355 | Abort current command halted by conflict. |
| 332 | 356 | ||
| 333 | **Flags:** | 357 | **Flags:** |
| 358 | |||
| 334 | - `-f, --force` - Skip confirmation | 359 | - `-f, --force` - Skip confirmation |
| 335 | 360 | ||
| 336 | ### gt undo | 361 | ### gt undo |
| @@ -338,6 +363,7 @@ Abort current command halted by conflict. · 1 unmodified line | |||
| 338 | Undo most recent Graphite mutations. | 363 | Undo most recent Graphite mutations. |
| 339 | 364 | ||
| 340 | **Flags:** | 365 | **Flags:** |
| 366 | |||
| 341 | - `-f, --force` - Skip confirmation | 367 | - `-f, --force` - Skip confirmation |
| 342 | 368 | ||
| 343 | ## Setup | 369 | ## Setup |
| @@ -347,6 +373,7 @@ Undo most recent Graphite mutations. · 3 unmodified lines | |||
| 347 | Initialize Graphite by selecting trunk branch. | 373 | Initialize Graphite by selecting trunk branch. |
| 348 | 374 | ||
| 349 | **Flags:** | 375 | **Flags:** |
| 376 | |||
| 350 | - `--trunk` - Trunk branch name | 377 | - `--trunk` - Trunk branch name |
| 351 | - `--reset` - Untrack all branches | 378 | - `--reset` - Untrack all branches |
| 352 | 379 | ||
| @@ -355,6 +382,7 @@ Initialize Graphite by selecting trunk branch. · 2 unmodified lines | |||
| 355 | Add auth token for GitHub. | 382 | Add auth token for GitHub. |
| 356 | 383 | ||
| 357 | **Flags:** | 384 | **Flags:** |
| 385 | |||
| 358 | - `-t, --token` - Auth token from https://app.graphite.com/activate | 386 | - `-t, --token` - Auth token from https://app.graphite.com/activate |
| 359 | 387 | ||
| 360 | ### gt config | 388 | ### gt config |
| @@ -368,6 +396,7 @@ Update CLI to latest stable version. · 7 unmodified lines | |||
| 368 | ## Git Passthroughs | 396 | ## Git Passthroughs |
| 369 | 397 | ||
| 370 | These pass arguments directly to git: | 398 | These pass arguments directly to git: |
| 399 | |||
| 371 | - `gt add [args..]` | 400 | - `gt add [args..]` |
| 372 | - `gt cherry-pick [args..]` | 401 | - `gt cherry-pick [args..]` |
| 373 | - `gt rebase [args..]` | 402 | - `gt rebase [args..]` |
| @@ -379,4 +408,5 @@ These pass arguments directly to git: · 5 unmodified lines | |||
| 379 | Create branch reverting a trunk commit. | 408 | Create branch reverting a trunk commit. |
| 380 | 409 | ||
| 381 | **Flags:** | 410 | **Flags:** |
| 411 | |||
| 382 | - `-e, --edit` - Edit commit message | 412 | - `-e, --edit` - Edit commit message |
| @@ -74,14 +74,18 @@ Use this section structure (mirrors what's already in `next.md`): · 73 unmodified lines | |||
| 74 | 74 | ||
| 75 | ```markdown | 75 | ```markdown |
| 76 | ## Highlights | 76 | ## Highlights |
| 77 | |||
| 77 | ### <user-facing story> | 78 | ### <user-facing story> |
| 78 | bullets of concrete user value | 79 | |
| 80 | bullets of concrete user value | ||
| 79 | 81 | ||
| 80 | ## Fixes | 82 | ## Fixes |
| 81 | 83 | ||
| 82 | ## Breaking changes | 84 | ## Breaking changes |
| 85 | |||
| 83 | ### <specific surface> | 86 | ### <specific surface> |
| 84 | before / after code blocks for migrations | 87 | |
| 88 | before / after code blocks for migrations | ||
| 85 | ``` | 89 | ``` |
| 86 | 90 | ||
| 87 | Lead with **user-visible stories**, not commit subjects. Group related | 91 | Lead with **user-visible stories**, not commit subjects. Group related |
| @@ -1,17 +1,21 @@ | |||
| 1 | ## Highlights | 1 | ## Highlights |
| 2 | 2 | ||
| 3 | ### Install paths fixed | 3 | ### Install paths fixed |
| 4 | |||
| 4 | `npm i -g executor` and `bun i -g executor` both work cleanly on a fresh machine. For machines without node, `curl … install.sh | bash` does the same thing. | 5 | `npm i -g executor` and `bun i -g executor` both work cleanly on a fresh machine. For machines without node, `curl … install.sh | bash` does the same thing. |
| 5 | 6 | ||
| 6 | > 1.4.13 and 1.4.14 were partial releases that didn't make it to npm cleanly. If you tried `npm i -g executor@latest` and got an older version, that's why. 1.4.15 is the first complete release of the new packaging. | 7 | > 1.4.13 and 1.4.14 were partial releases that didn't make it to npm cleanly. If you tried `npm i -g executor@latest` and got an older version, that's why. 1.4.15 is the first complete release of the new packaging. |
| 7 | 8 | ||
| 8 | ### MCP sources honor upstream `destructiveHint` | 9 | ### MCP sources honor upstream `destructiveHint` |
| 10 | |||
| 9 | MCP sources now read `destructiveHint` from upstream tool annotations. Tools marked destructive will require approval before running, surfaced via MCP elicitation. Refresh existing sources (or remove + re-add) to pick up annotations on tools added before this change. | 11 | MCP sources now read `destructiveHint` from upstream tool annotations. Tools marked destructive will require approval before running, surfaced via MCP elicitation. Refresh existing sources (or remove + re-add) to pick up annotations on tools added before this change. |
| 10 | 12 | ||
| 11 | ### Set tool policies from the Tools page | 13 | ### Set tool policies from the Tools page |
| 14 | |||
| 12 | The local UI gains a **Policies** tab for managing approval rules, plus a per-row action menu on the Tools tree. Hover any tool or category and pick **Always run / Require approval / Block / Clear** — leaf rows save a rule for the exact tool id, group rows save a `prefix.*` wildcard. New rules are auto-placed by specificity so a freshly-added group rule never silently shadows an existing leaf rule. The same menu is available from the tool detail header and from any source-detail page. | 15 | The local UI gains a **Policies** tab for managing approval rules, plus a per-row action menu on the Tools tree. Hover any tool or category and pick **Always run / Require approval / Block / Clear** — leaf rows save a rule for the exact tool id, group rows save a `prefix.*` wildcard. New rules are auto-placed by specificity so a freshly-added group rule never silently shadows an existing leaf rule. The same menu is available from the tool detail header and from any source-detail page. |
| 13 | 16 | ||
| 14 | ### Per-user OAuth for OpenAPI and MCP sources | 17 | ### Per-user OAuth for OpenAPI and MCP sources |
| 18 | |||
| 15 | OpenAPI and MCP sources now carry first-class **Connections** — a per-user sign-in state decoupled from the source definition itself. | 19 | OpenAPI and MCP sources now carry first-class **Connections** — a per-user sign-in state decoupled from the source definition itself. |
| 16 | 20 | ||
| 17 | - Save an OAuth2 OpenAPI or MCP source **before** signing in; users sign in later from the source page. | 21 | - Save an OAuth2 OpenAPI or MCP source **before** signing in; users sign in later from the source page. |
| @@ -20,15 +24,18 @@ OpenAPI and MCP sources now carry first-class **Connections** — a per-user sig · 2 unmodified lines | |||
| 20 | - Existing OpenAPI + MCP + google-discovery OAuth rows migrate into Connections automatically on first launch — no user action required. | 24 | - Existing OpenAPI + MCP + google-discovery OAuth rows migrate into Connections automatically on first launch — no user action required. |
| 21 | 25 | ||
| 22 | ### OpenAPI: client-credentials, non-JSON bodies, source refresh | 26 | ### OpenAPI: client-credentials, non-JSON bodies, source refresh |
| 27 | |||
| 23 | - Full **OAuth2 client-credentials** flow end-to-end. | 28 | - Full **OAuth2 client-credentials** flow end-to-end. |
| 24 | - **Non-JSON request bodies** dispatch correctly by content type; Executor honors OAS3 `encoding` and multi-content operations, and lets the caller pick which content type to send. | 29 | - **Non-JSON request bodies** dispatch correctly by content type; Executor honors OAS3 `encoding` and multi-content operations, and lets the caller pick which content type to send. |
| 25 | - Relative OAuth2 URLs resolve against the source's `baseUrl`. | 30 | - Relative OAuth2 URLs resolve against the source's `baseUrl`. |
| 26 | - Refresh a source by re-fetching its origin URL from the edit page. | 31 | - Refresh a source by re-fetching its origin URL from the edit page. |
| 27 | 32 | ||
| 28 | ### Layered scope isolation | 33 | ### Layered scope isolation |
| 34 | |||
| 29 | Multi-tenant deployments get a proper security primitive. Every read and write now passes through a layered `ScopeStack`, with the write scope declared explicitly. Plugins have adopted the API; the UI exposes it via `CreatableSecretPicker`; and WorkOS sources enforce tenant-ownership on every access. Per-scope blob and secret lookups are batched into single `IN` queries, so the extra check doesn't cost a round-trip. | 35 | Multi-tenant deployments get a proper security primitive. Every read and write now passes through a layered `ScopeStack`, with the write scope declared explicitly. Plugins have adopted the API; the UI exposes it via `CreatableSecretPicker`; and WorkOS sources enforce tenant-ownership on every access. Per-scope blob and secret lookups are batched into single `IN` queries, so the extra check doesn't cost a round-trip. |
| 30 | 36 | ||
| 31 | ### Natural CLI for tool discovery and invocation | 37 | ### Natural CLI for tool discovery and invocation |
| 38 | |||
| 32 | Call tools by path instead of writing TypeScript: | 39 | Call tools by path instead of writing TypeScript: |
| 33 | 40 | ||
| 34 | ```bash | 41 | ```bash |
| @@ -41,6 +48,7 @@ executor tools describe github.issues.create · 6 unmodified lines | |||
| 41 | `executor call <path> --help` browses namespaces → resources → methods, with `--match <text> --limit <n>` to narrow huge namespaces. Errors are normalized for agent consumption, and the resume / help UX is cleaner for non-interactive flows. | 48 | `executor call <path> --help` browses namespaces → resources → methods, with `--match <text> --limit <n>` to narrow huge namespaces. Errors are normalized for agent consumption, and the resume / help UX is cleaner for non-interactive flows. |
| 42 | 49 | ||
| 43 | ### Daemon lifecycle | 50 | ### Daemon lifecycle |
| 51 | |||
| 44 | ```bash | 52 | ```bash |
| 45 | executor daemon run | 53 | executor daemon run |
| 46 | executor daemon status | 54 | executor daemon status |
| @@ -53,16 +61,20 @@ executor daemon restart · 6 unmodified lines | |||
| 53 | `executor daemon run` now backgrounds by default. Pass `--foreground` to keep it attached for log inspection. | 61 | `executor daemon run` now backgrounds by default. Pass `--foreground` to keep it attached for log inspection. |
| 54 | 62 | ||
| 55 | ### OpenTelemetry everywhere | 63 | ### OpenTelemetry everywhere |
| 64 | |||
| 56 | Tool dispatch, plugins, storage, schema, and transport are now fully instrumented with OTEL spans, and the runtime is threaded through dispatch so spans actually export in all runtimes. | 65 | Tool dispatch, plugins, storage, schema, and transport are now fully instrumented with OTEL spans, and the runtime is threaded through dispatch so spans actually export in all runtimes. |
| 57 | 66 | ||
| 58 | ## New presets | 67 | ## New presets |
| 68 | |||
| 59 | - **Notion** is now a featured MCP preset. | 69 | - **Notion** is now a featured MCP preset. |
| 60 | 70 | ||
| 61 | ## Performance | 71 | ## Performance |
| 72 | |||
| 62 | - `buildExecuteDescription` no longer calls `executor.tools.list`, making tool-description generation measurably faster on large workspaces. | 73 | - `buildExecuteDescription` no longer calls `executor.tools.list`, making tool-description generation measurably faster on large workspaces. |
| 63 | - Per-scope blob and secret lookups now use a single `IN` query instead of N per-scope round-trips. | 74 | - Per-scope blob and secret lookups now use a single `IN` query instead of N per-scope round-trips. |
| 64 | 75 | ||
| 65 | ## Fixes | 76 | ## Fixes |
| 77 | |||
| 66 | - Upgrade: preserve legacy OAuth connection backfills after the `connection.kind` column is removed. | 78 | - Upgrade: preserve legacy OAuth connection backfills after the `connection.kind` column is removed. |
| 67 | - OpenAPI: refreshing or editing sources with legacy inline secret/OAuth config now materializes the new source binding rows instead of dropping credentials. | 79 | - OpenAPI: refreshing or editing sources with legacy inline secret/OAuth config now materializes the new source binding rows instead of dropping credentials. |
| 68 | - Keychain: skip provider registration when the OS backend is unreachable (no more startup failure when running headless on Linux without a keyring). | 80 | - Keychain: skip provider registration when the OS backend is unreachable (no more startup failure when running headless on Linux without a keyring). |
| @@ -72,6 +84,7 @@ Tool dispatch, plugins, storage, schema, and transport are now fully instrumente · 3 unmodified lines | |||
| 72 | ## Breaking changes | 84 | ## Breaking changes |
| 73 | 85 | ||
| 74 | ### `executor call` no longer accepts inline code | 86 | ### `executor call` no longer accepts inline code |
| 87 | |||
| 75 | The old TypeScript-as-argument forms are gone: | 88 | The old TypeScript-as-argument forms are gone: |
| 76 | 89 | ||
| 77 | ```bash | 90 | ```bash |
| @@ -92,6 +105,7 @@ executor call github issues list '{"owner":"octocat","repo":"Hello-World"}' · 14 unmodified lines | |||
| 92 | `tools.discover(...)` becomes `executor tools search "<query>"`. | 105 | `tools.discover(...)` becomes `executor tools search "<query>"`. |
| 93 | 106 | ||
| 94 | ### `sources.add` CLI form simplified | 107 | ### `sources.add` CLI form simplified |
| 108 | |||
| 95 | Use the dedicated tool: | 109 | Use the dedicated tool: |
| 96 | 110 | ||
| 97 | ```bash | 111 | ```bash |
| @@ -105,4 +119,5 @@ executor call openapi addSource '{ · 7 unmodified lines | |||
| 105 | Pass `baseUrl` when the OpenAPI document has relative `servers` entries. | 119 | Pass `baseUrl` when the OpenAPI document has relative `servers` entries. |
| 106 | 120 | ||
| 107 | ### SDK: layered scope | 121 | ### SDK: layered scope |
| 122 | |||
| 108 | Every SDK write now takes an explicit scope. If you have plugins or host code calling the SDK directly, they'll need to adopt the new layered-scope API (see the in-tree plugins for reference — they've all been migrated). This does not affect users of the CLI or web UI. | 123 | Every SDK write now takes an explicit scope. If you have plugins or host code calling the SDK directly, they'll need to adopt the new layered-scope API (see the in-tree plugins for reference — they've all been migrated). This does not affect users of the CLI or web UI. |
| @@ -68,8 +68,7 @@ const platformName = (t: Target) => (t.os === "win32" ? "windows" : t.os); · 67 unmodified lines | |||
| 68 | /** Per-platform suffix used in dist directory names, npm dist-tags, and the | 68 | /** Per-platform suffix used in dist directory names, npm dist-tags, and the |
| 69 | * semver prerelease segment of variant package versions. e.g. "linux-x64", | 69 | * semver prerelease segment of variant package versions. e.g. "linux-x64", |
| 70 | * "linux-x64-musl", "darwin-arm64". */ | 70 | * "linux-x64-musl", "darwin-arm64". */ |
| 71 | const platformTag = (t: Target) => | 71 | const platformTag = (t: Target) => [platformName(t), t.arch, t.abi].filter(Boolean).join("-"); |
| 72 | [platformName(t), t.arch, t.abi].filter(Boolean).join("-"); | ||
| 73 | 72 | ||
| 74 | /** Dist directory name (e.g. dist/executor-linux-x64). Only used as a build | 73 | /** Dist directory name (e.g. dist/executor-linux-x64). Only used as a build |
| 75 | * artifact convention; the actual npm package name inside is `executor`. */ | 74 | * artifact convention; the actual npm package name inside is `executor`. */ |
| @@ -126,7 +125,10 @@ const resolveKeyringNative = (t: Target): string | null => { · 50 unmodified lines | |||
| 126 | const platformMap: Record<string, { pkg: string; node: string }> = { | 125 | const platformMap: Record<string, { pkg: string; node: string }> = { |
| 127 | "darwin-arm64": { pkg: "@napi-rs/keyring-darwin-arm64", node: "keyring.darwin-arm64.node" }, | 126 | "darwin-arm64": { pkg: "@napi-rs/keyring-darwin-arm64", node: "keyring.darwin-arm64.node" }, |
| 128 | "darwin-x64": { pkg: "@napi-rs/keyring-darwin-x64", node: "keyring.darwin-x64.node" }, | 127 | "darwin-x64": { pkg: "@napi-rs/keyring-darwin-x64", node: "keyring.darwin-x64.node" }, |
| 129 | "linux-arm64": { pkg: "@napi-rs/keyring-linux-arm64-gnu", node: "keyring.linux-arm64-gnu.node" }, | 128 | "linux-arm64": { |
| 129 | pkg: "@napi-rs/keyring-linux-arm64-gnu", | ||
| 130 | node: "keyring.linux-arm64-gnu.node", | ||
| 131 | }, | ||
| 130 | "linux-x64": { pkg: "@napi-rs/keyring-linux-x64-gnu", node: "keyring.linux-x64-gnu.node" }, | 132 | "linux-x64": { pkg: "@napi-rs/keyring-linux-x64-gnu", node: "keyring.linux-x64-gnu.node" }, |
| 131 | "linux-arm64-musl": { | 133 | "linux-arm64-musl": { |
| 132 | pkg: "@napi-rs/keyring-linux-arm64-musl", | 134 | pkg: "@napi-rs/keyring-linux-arm64-musl", |
| @@ -146,9 +148,7 @@ const resolveKeyringNative = (t: Target): string | null => { · 13 unmodified lines | |||
| 146 | const entry = platformMap[key]; | 148 | const entry = platformMap[key]; |
| 147 | if (!entry) return null; | 149 | if (!entry) return null; |
| 148 | try { | 150 | try { |
| 149 | const req = createRequire( | 151 | const req = createRequire(join(repoRoot, "node_modules", "@napi-rs/keyring", "package.json")); |
| 150 | join(repoRoot, "node_modules", "@napi-rs/keyring", "package.json"), | ||
| 151 | ); | ||
| 152 | const pkgJson = req.resolve(`${entry.pkg}/package.json`); | 152 | const pkgJson = req.resolve(`${entry.pkg}/package.json`); |
| 153 | return join(dirname(pkgJson), entry.node); | 153 | return join(dirname(pkgJson), entry.node); |
| 154 | } catch { | 154 | } catch { |
| @@ -455,7 +455,10 @@ const buildPreviewTarballs = async (binaries: Record<string, string>) => { · 300 unmodified lines | |||
| 455 | 455 | ||
| 456 | const resolveTargetsFromEnv = (env: string | undefined): Target[] => { | 456 | const resolveTargetsFromEnv = (env: string | undefined): Target[] => { |
| 457 | if (!env) throw new Error("EXECUTOR_PREVIEW_TARGETS must be set (comma-separated package names)"); | 457 | if (!env) throw new Error("EXECUTOR_PREVIEW_TARGETS must be set (comma-separated package names)"); |
| 458 | const names = env.split(",").map((s) => s.trim()).filter(Boolean); | 458 | const names = env |
| 459 | .split(",") | ||
| 460 | .map((s) => s.trim()) | ||
| 461 | .filter(Boolean); | ||
| 459 | const resolved = names.map((name) => { | 462 | const resolved = names.map((name) => { |
| 460 | const match = ALL_TARGETS.find((t) => targetPackageName(t) === name); | 463 | const match = ALL_TARGETS.find((t) => targetPackageName(t) === name); |
| 461 | if (!match) throw new Error(`Unknown preview target: ${name}`); | 464 | if (!match) throw new Error(`Unknown preview target: ${name}`); |
| @@ -482,10 +485,7 @@ const buildPreviewWrapperPackage = async (targets: Target[]) => { · 20 unmodified lines | |||
| 482 | await writeFile(join(binDir, "executor"), NODE_SHIM); | 485 | await writeFile(join(binDir, "executor"), NODE_SHIM); |
| 483 | await chmod(join(binDir, "executor"), 0o755); | 486 | await chmod(join(binDir, "executor"), 0o755); |
| 484 | 487 | ||
| 485 | const postinstall = PREVIEW_POSTINSTALL_SCRIPT.replaceAll( | 488 | const postinstall = PREVIEW_POSTINSTALL_SCRIPT.replaceAll("__CDN_BASE_URL__", `${cdnUrl}/${sha}`); |
| 486 | "__CDN_BASE_URL__", | ||
| 487 | `${cdnUrl}/${sha}`, | ||
| 488 | ); | ||
| 489 | await writeFile(join(wrapperDir, "postinstall.cjs"), postinstall); | 489 | await writeFile(join(wrapperDir, "postinstall.cjs"), postinstall); |
| 490 | 490 | ||
| 491 | // Restrict os/cpu to platforms we actually built this run so npm refuses | 491 | // Restrict os/cpu to platforms we actually built this run so npm refuses |
| @@ -565,11 +565,10 @@ const publishPackedPackage = async (pkgDir: string, channel: string) => { · 73 unmodified lines | |||
| 565 | await $`bun pm pack`.cwd(pkgDir); | 565 | await $`bun pm pack`.cwd(pkgDir); |
| 566 | 566 | ||
| 567 | const provenance = process.env.GITHUB_ACTIONS === "true" ? ["--provenance"] : []; | 567 | const provenance = process.env.GITHUB_ACTIONS === "true" ? ["--provenance"] : []; |
| 568 | const result = | 568 | const result = await $`npm publish *.tgz --access public --tag ${channel} ${provenance}` |
| 569 | await $`npm publish *.tgz --access public --tag ${channel} ${provenance}` | 569 | .cwd(pkgDir) |
| 570 | .cwd(pkgDir) | 570 | .nothrow() |
| 571 | .nothrow() | 571 | .quiet(); |
| 572 | .quiet(); | ||
| 573 | 572 | ||
| 574 | const stdout = result.stdout.toString(); | 573 | const stdout = result.stdout.toString(); |
| 575 | const stderr = result.stderr.toString(); | 574 | const stderr = result.stderr.toString(); |
| @@ -59,7 +59,8 @@ export const currentDaemonScopeId = (): string => { · 58 unmodified lines | |||
| 59 | const resolveDaemonDataDir = (path: Path.Path): string => | 59 | const resolveDaemonDataDir = (path: Path.Path): string => |
| 60 | process.env.EXECUTOR_DATA_DIR ?? path.join(homedir(), ".executor"); | 60 | process.env.EXECUTOR_DATA_DIR ?? path.join(homedir(), ".executor"); |
| 61 | 61 | ||
| 62 | const sanitizeHostForPath = (hostname: string): string => hostname.replaceAll(/[^a-z0-9.-]+/gi, "_"); | 62 | const sanitizeHostForPath = (hostname: string): string => |
| 63 | hostname.replaceAll(/[^a-z0-9.-]+/gi, "_"); | ||
| 63 | const sanitizeScopeForPath = (scopeId: string): string => scopeId.replaceAll(/[^a-z0-9.-]+/gi, "_"); | 64 | const sanitizeScopeForPath = (scopeId: string): string => scopeId.replaceAll(/[^a-z0-9.-]+/gi, "_"); |
| 64 | 65 | ||
| 65 | const daemonRecordPath = (path: Path.Path, input: { hostname: string; port: number }): string => { | 66 | const daemonRecordPath = (path: Path.Path, input: { hostname: string; port: number }): string => { |
| @@ -67,14 +68,19 @@ const daemonRecordPath = (path: Path.Path, input: { hostname: string; port: numb · 1 unmodified line | |||
| 67 | return path.join(resolveDaemonDataDir(path), `daemon-${host}-${input.port}.json`); | 68 | return path.join(resolveDaemonDataDir(path), `daemon-${host}-${input.port}.json`); |
| 68 | }; | 69 | }; |
| 69 | 70 | ||
| 70 | const daemonPointerPath = (path: Path.Path, input: { hostname: string; scopeId: string }): string => { | 71 | const daemonPointerPath = ( |
| 72 | path: Path.Path, | ||
| 73 | input: { hostname: string; scopeId: string }, | ||
| 74 | ): string => { | ||
| 71 | const host = sanitizeHostForPath(canonicalDaemonHost(input.hostname)); | 75 | const host = sanitizeHostForPath(canonicalDaemonHost(input.hostname)); |
| 72 | const scope = sanitizeScopeForPath(input.scopeId); | 76 | const scope = sanitizeScopeForPath(input.scopeId); |
| 73 | return path.join(resolveDaemonDataDir(path), `daemon-active-${host}-${scope}.json`); | 77 | return path.join(resolveDaemonDataDir(path), `daemon-active-${host}-${scope}.json`); |
| 74 | }; | 78 | }; |
| 75 | 79 | ||
| 76 | const daemonStartLockPath = (path: Path.Path, input: { hostname: string; scopeId: string }): string => | 80 | const daemonStartLockPath = ( |
| 77 | `${daemonPointerPath(path, input)}.lock`; | 81 | path: Path.Path, |
| 82 | input: { hostname: string; scopeId: string }, | ||
| 83 | ): string => `${daemonPointerPath(path, input)}.lock`; | ||
| 78 | 84 | ||
| 79 | // --------------------------------------------------------------------------- | 85 | // --------------------------------------------------------------------------- |
| 80 | // Persistence | 86 | // Persistence |
| @@ -195,9 +201,9 @@ export const readDaemonRecord = (input: { · 114 unmodified lines | |||
| 195 | Effect.gen(function* () { | 201 | Effect.gen(function* () { |
| 196 | const fs = yield* FileSystem.FileSystem; | 202 | const fs = yield* FileSystem.FileSystem; |
| 197 | const path = yield* Path.Path; | 203 | const path = yield* Path.Path; |
| 198 | const raw = yield* fs.readFileString(daemonRecordPath(path, input)).pipe( | 204 | const raw = yield* fs |
| 199 | Effect.catchCause(() => Effect.succeed(null)), | 205 | .readFileString(daemonRecordPath(path, input)) |
| 200 | ); | 206 | .pipe(Effect.catchCause(() => Effect.succeed(null))); |
| 201 | if (raw === null) return null; | 207 | if (raw === null) return null; |
| 202 | return parseRecord(raw); | 208 | return parseRecord(raw); |
| 203 | }); | 209 | }); |
| @@ -322,7 +328,9 @@ export const acquireDaemonStartLock = (input: { · 118 unmodified lines | |||
| 322 | }; | 328 | }; |
| 323 | } | 329 | } |
| 324 | 330 | ||
| 325 | const existingRaw = yield* fs.readFileString(lockPath).pipe(Effect.catchCause(() => Effect.succeed(null))); | 331 | const existingRaw = yield* fs |
| 332 | .readFileString(lockPath) | ||
| 333 | .pipe(Effect.catchCause(() => Effect.succeed(null))); | ||
| 326 | if (existingRaw !== null) { | 334 | if (existingRaw !== null) { |
| 327 | const existingPid = parseLockPid(existingRaw); | 335 | const existingPid = parseLockPid(existingRaw); |
| 328 | if (existingPid !== null && !isPidAlive(existingPid)) { | 336 | if (existingPid !== null && !isPidAlive(existingPid)) { |
| @@ -344,11 +352,9 @@ export const acquireDaemonStartLock = (input: { · 15 unmodified lines | |||
| 344 | ); | 352 | ); |
| 345 | }); | 353 | }); |
| 346 | 354 | ||
| 347 | export const releaseDaemonStartLock = (input: DaemonStartLock): Effect.Effect< | 355 | export const releaseDaemonStartLock = ( |
| 348 | void, | 356 | input: DaemonStartLock, |
| 349 | PlatformError, | 357 | ): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> => |
| 350 | FileSystem.FileSystem | Path.Path | ||
| 351 | > => | ||
| 352 | Effect.gen(function* () { | 358 | Effect.gen(function* () { |
| 353 | const fs = yield* FileSystem.FileSystem; | 359 | const fs = yield* FileSystem.FileSystem; |
| 354 | yield* fs.remove(input.path, { force: true }); | 360 | yield* fs.remove(input.path, { force: true }); |
| @@ -374,5 +380,7 @@ export const terminatePid = (pid: number): Effect.Effect<void, Error> => · 19 unmodified lines | |||
| 374 | process.kill(pid, "SIGTERM"); | 380 | process.kill(pid, "SIGTERM"); |
| 375 | }, | 381 | }, |
| 376 | catch: (cause) => | 382 | catch: (cause) => |
| 377 | cause instanceof Error ? cause : new Error(`Failed to terminate pid ${pid}: ${String(cause)}`), | 383 | cause instanceof Error |
| 384 | ? cause | ||
| 385 | : new Error(`Failed to terminate pid ${pid}: ${String(cause)}`), | ||
| 378 | }); | 386 | }); |
| @@ -167,7 +167,10 @@ const toProbeHost = (hostname: string): string => { · 166 unmodified lines | |||
| 167 | return hostname; | 167 | return hostname; |
| 168 | }; | 168 | }; |
| 169 | 169 | ||
| 170 | const isPortAvailable = (input: { hostname: string; port: number }): Effect.Effect<boolean, Error> => | 170 | const isPortAvailable = (input: { |
| 171 | hostname: string; | ||
| 172 | port: number; | ||
| 173 | }): Effect.Effect<boolean, Error> => | ||
| 171 | Effect.tryPromise({ | 174 | Effect.tryPromise({ |
| 172 | try: () => | 175 | try: () => |
| 173 | new Promise<boolean>((resolve) => { | 176 | new Promise<boolean>((resolve) => { |
| @@ -215,7 +218,9 @@ const pickEphemeralPort = (hostname: string): Effect.Effect<number, Error> => · 41 unmodified lines | |||
| 215 | server.listen({ port: 0, host: toProbeHost(hostname) }); | 218 | server.listen({ port: 0, host: toProbeHost(hostname) }); |
| 216 | }), | 219 | }), |
| 217 | catch: (cause) => | 220 | catch: (cause) => |
| 218 | cause instanceof Error ? cause : new Error(`Failed selecting ephemeral port: ${String(cause)}`), | 221 | cause instanceof Error |
| 222 | ? cause | ||
| 223 | : new Error(`Failed selecting ephemeral port: ${String(cause)}`), | ||
| 219 | }); | 224 | }); |
| 220 | 225 | ||
| 221 | export const chooseDaemonPort = (input: { | 226 | export const chooseDaemonPort = (input: { |
| @@ -33,13 +33,10 @@ if (typeof Bun !== "undefined" && (await Bun.file(wasmOnDisk).exists())) { · 32 unmodified lines | |||
| 33 | type QuickJSSyncVariant = import("quickjs-emscripten").QuickJSSyncVariant; | 33 | type QuickJSSyncVariant = import("quickjs-emscripten").QuickJSSyncVariant; |
| 34 | const wasmBinary = await Bun.file(wasmOnDisk).arrayBuffer(); | 34 | const wasmBinary = await Bun.file(wasmOnDisk).arrayBuffer(); |
| 35 | const importFFI: QuickJSSyncVariant["importFFI"] = () => | 35 | const importFFI: QuickJSSyncVariant["importFFI"] = () => |
| 36 | import("@jitl/quickjs-wasmfile-release-sync/ffi").then( | 36 | import("@jitl/quickjs-wasmfile-release-sync/ffi").then((m) => m.QuickJSFFI); |
| 37 | (m) => m.QuickJSFFI, | ||
| 38 | ); | ||
| 39 | const importModuleLoader: QuickJSSyncVariant["importModuleLoader"] = async () => { | 37 | const importModuleLoader: QuickJSSyncVariant["importModuleLoader"] = async () => { |
| 40 | const { default: original } = await import( | 38 | const { default: original } = |
| 41 | "@jitl/quickjs-wasmfile-release-sync/emscripten-module" | 39 | await import("@jitl/quickjs-wasmfile-release-sync/emscripten-module"); |
| 42 | ); | ||
| 43 | return (moduleArg = {}) => original({ ...moduleArg, wasmBinary }); | 40 | return (moduleArg = {}) => original({ ...moduleArg, wasmBinary }); |
| 44 | }; | 41 | }; |
| 45 | const variant: QuickJSSyncVariant = { | 42 | const variant: QuickJSSyncVariant = { |
| @@ -42,7 +42,10 @@ export const inspectToolPath = (input: { · 41 unmodified lines | |||
| 42 | }): ToolPathInspection => { | 42 | }): ToolPathInspection => { |
| 43 | const prefixSegments = | 43 | const prefixSegments = |
| 44 | input.rawPrefixParts.length === 0 ? [] : buildToolPath(input.rawPrefixParts).split("."); | 44 | input.rawPrefixParts.length === 0 ? [] : buildToolPath(input.rawPrefixParts).split("."); |
| 45 | const children = new Map<string, { invokable: boolean; hasChildren: boolean; toolCount: number }>(); | 45 | const children = new Map< |
| 46 | string, | ||
| 47 | { invokable: boolean; hasChildren: boolean; toolCount: number } | ||
| 48 | >(); | ||
| 46 | let exactPath: string | undefined = undefined; | 49 | let exactPath: string | undefined = undefined; |
| 47 | let matchingToolCount = 0; | 50 | let matchingToolCount = 0; |
| 48 | 51 | ||
| @@ -97,9 +100,7 @@ export const buildToolPath = (parts: ReadonlyArray<string>): string => { · 48 unmodified lines | |||
| 97 | throw new Error("Tool path must include at least one segment"); | 100 | throw new Error("Tool path must include at least one segment"); |
| 98 | } | 101 | } |
| 99 | if (segments.some((segment) => !TOOL_PATH_TOKEN.test(segment))) { | 102 | if (segments.some((segment) => !TOOL_PATH_TOKEN.test(segment))) { |
| 100 | throw new Error( | 103 | throw new Error("Tool path segments must contain only letters, numbers, '.', '_' or '-'"); |
| 101 | "Tool path segments must contain only letters, numbers, '.', '_' or '-'", | ||
| 102 | ); | ||
| 103 | } | 104 | } |
| 104 | return segments.join("."); | 105 | return segments.join("."); |
| 105 | }; | 106 | }; |
| @@ -186,9 +187,7 @@ export interface PausedInteraction { · 80 unmodified lines | |||
| 186 | readonly requestedSchema?: Record<string, unknown>; | 187 | readonly requestedSchema?: Record<string, unknown>; |
| 187 | } | 188 | } |
| 188 | 189 | ||
| 189 | export const extractPausedInteraction = ( | 190 | export const extractPausedInteraction = (structured: unknown): PausedInteraction | undefined => { |
| 190 | structured: unknown, | ||
| 191 | ): PausedInteraction | undefined => { | ||
| 192 | if (!isRecord(structured) || !isRecord(structured.interaction)) { | 191 | if (!isRecord(structured) || !isRecord(structured.interaction)) { |
| 193 | return undefined; | 192 | return undefined; |
| 194 | } | 193 | } |
| @@ -60,12 +60,8 @@ · 59 unmodified lines | |||
| 60 | "name": "memberships_account_id_accounts_id_fk", | 60 | "name": "memberships_account_id_accounts_id_fk", |
| 61 | "tableFrom": "memberships", | 61 | "tableFrom": "memberships", |
| 62 | "tableTo": "accounts", | 62 | "tableTo": "accounts", |
| 63 | "columnsFrom": [ | 63 | "columnsFrom": ["account_id"], |
| 64 | "account_id" | 64 | "columnsTo": ["id"], |
| 65 | ], | ||
| 66 | "columnsTo": [ | ||
| 67 | "id" | ||
| 68 | ], | ||
| 69 | "onDelete": "cascade", | 65 | "onDelete": "cascade", |
| 70 | "onUpdate": "no action" | 66 | "onUpdate": "no action" |
| 71 | }, | 67 | }, |
| @@ -73,12 +69,8 @@ · 1 unmodified line | |||
| 73 | "name": "memberships_organization_id_organizations_id_fk", | 69 | "name": "memberships_organization_id_organizations_id_fk", |
| 74 | "tableFrom": "memberships", | 70 | "tableFrom": "memberships", |
| 75 | "tableTo": "organizations", | 71 | "tableTo": "organizations", |
| 76 | "columnsFrom": [ | 72 | "columnsFrom": ["organization_id"], |
| 77 | "organization_id" | 73 | "columnsTo": ["id"], |
| 78 | ], | ||
| 79 | "columnsTo": [ | ||
| 80 | "id" | ||
| 81 | ], | ||
| 82 | "onDelete": "cascade", | 74 | "onDelete": "cascade", |
| 83 | "onUpdate": "no action" | 75 | "onUpdate": "no action" |
| 84 | } | 76 | } |
| @@ -86,10 +78,7 @@ · 1 unmodified line | |||
| 86 | "compositePrimaryKeys": { | 78 | "compositePrimaryKeys": { |
| 87 | "memberships_account_id_organization_id_pk": { | 79 | "memberships_account_id_organization_id_pk": { |
| 88 | "name": "memberships_account_id_organization_id_pk", | 80 | "name": "memberships_account_id_organization_id_pk", |
| 89 | "columns": [ | 81 | "columns": ["account_id", "organization_id"] |
| 90 | "account_id", | ||
| 91 | "organization_id" | ||
| 92 | ] | ||
| 93 | } | 82 | } |
| 94 | }, | 83 | }, |
| 95 | "uniqueConstraints": {}, | 84 | "uniqueConstraints": {}, |
| @@ -157,10 +146,7 @@ · 61 unmodified lines | |||
| 157 | "compositePrimaryKeys": { | 146 | "compositePrimaryKeys": { |
| 158 | "blob_namespace_key_pk": { | 147 | "blob_namespace_key_pk": { |
| 159 | "name": "blob_namespace_key_pk", | 148 | "name": "blob_namespace_key_pk", |
| 160 | "columns": [ | 149 | "columns": ["namespace", "key"] |
| 161 | "namespace", | ||
| 162 | "key" | ||
| 163 | ] | ||
| 164 | } | 150 | } |
| 165 | }, | 151 | }, |
| 166 | "uniqueConstraints": {}, | 152 | "uniqueConstraints": {}, |
| @@ -266,10 +252,7 @@ · 99 unmodified lines | |||
| 266 | "compositePrimaryKeys": { | 252 | "compositePrimaryKeys": { |
| 267 | "definition_scope_id_id_pk": { | 253 | "definition_scope_id_id_pk": { |
| 268 | "name": "definition_scope_id_id_pk", | 254 | "name": "definition_scope_id_id_pk", |
| 269 | "columns": [ | 255 | "columns": ["scope_id", "id"] |
| 270 | "scope_id", | ||
| 271 | "id" | ||
| 272 | ] | ||
| 273 | } | 256 | } |
| 274 | }, | 257 | }, |
| 275 | "uniqueConstraints": {}, | 258 | "uniqueConstraints": {}, |
| @@ -342,10 +325,7 @@ · 66 unmodified lines | |||
| 342 | "compositePrimaryKeys": { | 325 | "compositePrimaryKeys": { |
| 343 | "graphql_operation_scope_id_id_pk": { | 326 | "graphql_operation_scope_id_id_pk": { |
| 344 | "name": "graphql_operation_scope_id_id_pk", | 327 | "name": "graphql_operation_scope_id_id_pk", |
| 345 | "columns": [ | 328 | "columns": ["scope_id", "id"] |
| 346 | "scope_id", | ||
| 347 | "id" | ||
| 348 | ] | ||
| 349 | } | 329 | } |
| 350 | }, | 330 | }, |
| 351 | "uniqueConstraints": {}, | 331 | "uniqueConstraints": {}, |
| @@ -409,10 +389,7 @@ · 57 unmodified lines | |||
| 409 | "compositePrimaryKeys": { | 389 | "compositePrimaryKeys": { |
| 410 | "graphql_source_scope_id_id_pk": { | 390 | "graphql_source_scope_id_id_pk": { |
| 411 | "name": "graphql_source_scope_id_id_pk", | 391 | "name": "graphql_source_scope_id_id_pk", |
| 412 | "columns": [ | 392 | "columns": ["scope_id", "id"] |
| 413 | "scope_id", | ||
| 414 | "id" | ||
| 415 | ] | ||
| 416 | } | 393 | } |
| 417 | }, | 394 | }, |
| 418 | "uniqueConstraints": {}, | 395 | "uniqueConstraints": {}, |
| @@ -491,10 +468,7 @@ · 72 unmodified lines | |||
| 491 | "compositePrimaryKeys": { | 468 | "compositePrimaryKeys": { |
| 492 | "mcp_binding_scope_id_id_pk": { | 469 | "mcp_binding_scope_id_id_pk": { |
| 493 | "name": "mcp_binding_scope_id_id_pk", | 470 | "name": "mcp_binding_scope_id_id_pk", |
| 494 | "columns": [ | 471 | "columns": ["scope_id", "id"] |
| 495 | "scope_id", | ||
| 496 | "id" | ||
| 497 | ] | ||
| 498 | } | 472 | } |
| 499 | }, | 473 | }, |
| 500 | "uniqueConstraints": {}, | 474 | "uniqueConstraints": {}, |
| @@ -558,10 +532,7 @@ · 57 unmodified lines | |||
| 558 | "compositePrimaryKeys": { | 532 | "compositePrimaryKeys": { |
| 559 | "mcp_oauth_session_scope_id_id_pk": { | 533 | "mcp_oauth_session_scope_id_id_pk": { |
| 560 | "name": "mcp_oauth_session_scope_id_id_pk", | 534 | "name": "mcp_oauth_session_scope_id_id_pk", |
| 561 | "columns": [ | 535 | "columns": ["scope_id", "id"] |
| 562 | "scope_id", | ||
| 563 | "id" | ||
| 564 | ] | ||
| 565 | } | 536 | } |
| 566 | }, | 537 | }, |
| 567 | "uniqueConstraints": {}, | 538 | "uniqueConstraints": {}, |
| @@ -625,10 +596,7 @@ · 57 unmodified lines | |||
| 625 | "compositePrimaryKeys": { | 596 | "compositePrimaryKeys": { |
| 626 | "mcp_source_scope_id_id_pk": { | 597 | "mcp_source_scope_id_id_pk": { |
| 627 | "name": "mcp_source_scope_id_id_pk", | 598 | "name": "mcp_source_scope_id_id_pk", |
| 628 | "columns": [ | 599 | "columns": ["scope_id", "id"] |
| 629 | "scope_id", | ||
| 630 | "id" | ||
| 631 | ] | ||
| 632 | } | 600 | } |
| 633 | }, | 601 | }, |
| 634 | "uniqueConstraints": {}, | 602 | "uniqueConstraints": {}, |
| @@ -686,10 +654,7 @@ · 51 unmodified lines | |||
| 686 | "compositePrimaryKeys": { | 654 | "compositePrimaryKeys": { |
| 687 | "openapi_oauth_session_scope_id_id_pk": { | 655 | "openapi_oauth_session_scope_id_id_pk": { |
| 688 | "name": "openapi_oauth_session_scope_id_id_pk", | 656 | "name": "openapi_oauth_session_scope_id_id_pk", |
| 689 | "columns": [ | 657 | "columns": ["scope_id", "id"] |
| 690 | "scope_id", | ||
| 691 | "id" | ||
| 692 | ] | ||
| 693 | } | 658 | } |
| 694 | }, | 659 | }, |
| 695 | "uniqueConstraints": {}, | 660 | "uniqueConstraints": {}, |
| @@ -762,10 +727,7 @@ · 66 unmodified lines | |||
| 762 | "compositePrimaryKeys": { | 727 | "compositePrimaryKeys": { |
| 763 | "openapi_operation_scope_id_id_pk": { | 728 | "openapi_operation_scope_id_id_pk": { |
| 764 | "name": "openapi_operation_scope_id_id_pk", | 729 | "name": "openapi_operation_scope_id_id_pk", |
| 765 | "columns": [ | 730 | "columns": ["scope_id", "id"] |
| 766 | "scope_id", | ||
| 767 | "id" | ||
| 768 | ] | ||
| 769 | } | 731 | } |
| 770 | }, | 732 | }, |
| 771 | "uniqueConstraints": {}, | 733 | "uniqueConstraints": {}, |
| @@ -847,10 +809,7 @@ · 75 unmodified lines | |||
| 847 | "compositePrimaryKeys": { | 809 | "compositePrimaryKeys": { |
| 848 | "openapi_source_scope_id_id_pk": { | 810 | "openapi_source_scope_id_id_pk": { |
| 849 | "name": "openapi_source_scope_id_id_pk", | 811 | "name": "openapi_source_scope_id_id_pk", |
| 850 | "columns": [ | 812 | "columns": ["scope_id", "id"] |
| 851 | "scope_id", | ||
| 852 | "id" | ||
| 853 | ] | ||
| 854 | } | 813 | } |
| 855 | }, | 814 | }, |
| 856 | "uniqueConstraints": {}, | 815 | "uniqueConstraints": {}, |
| @@ -929,10 +888,7 @@ · 72 unmodified lines | |||
| 929 | "compositePrimaryKeys": { | 888 | "compositePrimaryKeys": { |
| 930 | "secret_scope_id_id_pk": { | 889 | "secret_scope_id_id_pk": { |
| 931 | "name": "secret_scope_id_id_pk", | 890 | "name": "secret_scope_id_id_pk", |
| 932 | "columns": [ | 891 | "columns": ["scope_id", "id"] |
| 933 | "scope_id", | ||
| 934 | "id" | ||
| 935 | ] | ||
| 936 | } | 892 | } |
| 937 | }, | 893 | }, |
| 938 | "uniqueConstraints": {}, | 894 | "uniqueConstraints": {}, |
| @@ -1050,10 +1006,7 @@ · 111 unmodified lines | |||
| 1050 | "compositePrimaryKeys": { | 1006 | "compositePrimaryKeys": { |
| 1051 | "source_scope_id_id_pk": { | 1007 | "source_scope_id_id_pk": { |
| 1052 | "name": "source_scope_id_id_pk", | 1008 | "name": "source_scope_id_id_pk", |
| 1053 | "columns": [ | 1009 | "columns": ["scope_id", "id"] |
| 1054 | "scope_id", | ||
| 1055 | "id" | ||
| 1056 | ] | ||
| 1057 | } | 1010 | } |
| 1058 | }, | 1011 | }, |
| 1059 | "uniqueConstraints": {}, | 1012 | "uniqueConstraints": {}, |
| @@ -1177,10 +1130,7 @@ · 117 unmodified lines | |||
| 1177 | "compositePrimaryKeys": { | 1130 | "compositePrimaryKeys": { |
| 1178 | "tool_scope_id_id_pk": { | 1131 | "tool_scope_id_id_pk": { |
| 1179 | "name": "tool_scope_id_id_pk", | 1132 | "name": "tool_scope_id_id_pk", |
| 1180 | "columns": [ | 1133 | "columns": ["scope_id", "id"] |
| 1181 | "scope_id", | ||
| 1182 | "id" | ||
| 1183 | ] | ||
| 1184 | } | 1134 | } |
| 1185 | }, | 1135 | }, |
| 1186 | "uniqueConstraints": {}, | 1136 | "uniqueConstraints": {}, |
| @@ -1244,10 +1194,7 @@ · 57 unmodified lines | |||
| 1244 | "compositePrimaryKeys": { | 1194 | "compositePrimaryKeys": { |
| 1245 | "workos_vault_metadata_scope_id_id_pk": { | 1195 | "workos_vault_metadata_scope_id_id_pk": { |
| 1246 | "name": "workos_vault_metadata_scope_id_id_pk", | 1196 | "name": "workos_vault_metadata_scope_id_id_pk", |
| 1247 | "columns": [ | 1197 | "columns": ["scope_id", "id"] |
| 1248 | "scope_id", | ||
| 1249 | "id" | ||
| 1250 | ] | ||
| 1251 | } | 1198 | } |
| 1252 | }, | 1199 | }, |
| 1253 | "uniqueConstraints": {}, | 1200 | "uniqueConstraints": {}, |
| @@ -1267,4 +1214,4 @@ · 13 unmodified lines | |||
| 1267 | "schemas": {}, | 1214 | "schemas": {}, |
| 1268 | "tables": {} | 1215 | "tables": {} |
| 1269 | } | 1216 | } |
| 1270 | } | 1217 | } |
| @@ -60,12 +60,8 @@ · 59 unmodified lines | |||
| 60 | "name": "memberships_account_id_accounts_id_fk", | 60 | "name": "memberships_account_id_accounts_id_fk", |
| 61 | "tableFrom": "memberships", | 61 | "tableFrom": "memberships", |
| 62 | "tableTo": "accounts", | 62 | "tableTo": "accounts", |
| 63 | "columnsFrom": [ | 63 | "columnsFrom": ["account_id"], |
| 64 | "account_id" | 64 | "columnsTo": ["id"], |
| 65 | ], | ||
| 66 | "columnsTo": [ | ||
| 67 | "id" | ||
| 68 | ], | ||
| 69 | "onDelete": "cascade", | 65 | "onDelete": "cascade", |
| 70 | "onUpdate": "no action" | 66 | "onUpdate": "no action" |
| 71 | }, | 67 | }, |
| @@ -73,12 +69,8 @@ · 1 unmodified line | |||
| 73 | "name": "memberships_organization_id_organizations_id_fk", | 69 | "name": "memberships_organization_id_organizations_id_fk", |
| 74 | "tableFrom": "memberships", | 70 | "tableFrom": "memberships", |
| 75 | "tableTo": "organizations", | 71 | "tableTo": "organizations", |
| 76 | "columnsFrom": [ | 72 | "columnsFrom": ["organization_id"], |
| 77 | "organization_id" | 73 | "columnsTo": ["id"], |
| 78 | ], | ||
| 79 | "columnsTo": [ | ||
| 80 | "id" | ||
| 81 | ], | ||
| 82 | "onDelete": "cascade", | 74 | "onDelete": "cascade", |
| 83 | "onUpdate": "no action" | 75 | "onUpdate": "no action" |
| 84 | } | 76 | } |
| @@ -86,10 +78,7 @@ · 1 unmodified line | |||
| 86 | "compositePrimaryKeys": { | 78 | "compositePrimaryKeys": { |
| 87 | "memberships_account_id_organization_id_pk": { | 79 | "memberships_account_id_organization_id_pk": { |
| 88 | "name": "memberships_account_id_organization_id_pk", | 80 | "name": "memberships_account_id_organization_id_pk", |
| 89 | "columns": [ | 81 | "columns": ["account_id", "organization_id"] |
| 90 | "account_id", | ||
| 91 | "organization_id" | ||
| 92 | ] | ||
| 93 | } | 82 | } |
| 94 | }, | 83 | }, |
| 95 | "uniqueConstraints": {}, | 84 | "uniqueConstraints": {}, |
| @@ -157,10 +146,7 @@ · 61 unmodified lines | |||
| 157 | "compositePrimaryKeys": { | 146 | "compositePrimaryKeys": { |
| 158 | "blob_namespace_key_pk": { | 147 | "blob_namespace_key_pk": { |
| 159 | "name": "blob_namespace_key_pk", | 148 | "name": "blob_namespace_key_pk", |
| 160 | "columns": [ | 149 | "columns": ["namespace", "key"] |
| 161 | "namespace", | ||
| 162 | "key" | ||
| 163 | ] | ||
| 164 | } | 150 | } |
| 165 | }, | 151 | }, |
| 166 | "uniqueConstraints": {}, | 152 | "uniqueConstraints": {}, |
| @@ -266,10 +252,7 @@ · 99 unmodified lines | |||
| 266 | "compositePrimaryKeys": { | 252 | "compositePrimaryKeys": { |
| 267 | "definition_scope_id_id_pk": { | 253 | "definition_scope_id_id_pk": { |
| 268 | "name": "definition_scope_id_id_pk", | 254 | "name": "definition_scope_id_id_pk", |
| 269 | "columns": [ | 255 | "columns": ["scope_id", "id"] |
| 270 | "scope_id", | ||
| 271 | "id" | ||
| 272 | ] | ||
| 273 | } | 256 | } |
| 274 | }, | 257 | }, |
| 275 | "uniqueConstraints": {}, | 258 | "uniqueConstraints": {}, |
| @@ -342,10 +325,7 @@ · 66 unmodified lines | |||
| 342 | "compositePrimaryKeys": { | 325 | "compositePrimaryKeys": { |
| 343 | "graphql_operation_scope_id_id_pk": { | 326 | "graphql_operation_scope_id_id_pk": { |
| 344 | "name": "graphql_operation_scope_id_id_pk", | 327 | "name": "graphql_operation_scope_id_id_pk", |
| 345 | "columns": [ | 328 | "columns": ["scope_id", "id"] |
| 346 | "scope_id", | ||
| 347 | "id" | ||
| 348 | ] | ||
| 349 | } | 329 | } |
| 350 | }, | 330 | }, |
| 351 | "uniqueConstraints": {}, | 331 | "uniqueConstraints": {}, |
| @@ -409,10 +389,7 @@ · 57 unmodified lines | |||
| 409 | "compositePrimaryKeys": { | 389 | "compositePrimaryKeys": { |
| 410 | "graphql_source_scope_id_id_pk": { | 390 | "graphql_source_scope_id_id_pk": { |
| 411 | "name": "graphql_source_scope_id_id_pk", | 391 | "name": "graphql_source_scope_id_id_pk", |
| 412 | "columns": [ | 392 | "columns": ["scope_id", "id"] |
| 413 | "scope_id", | ||
| 414 | "id" | ||
| 415 | ] | ||
| 416 | } | 393 | } |
| 417 | }, | 394 | }, |
| 418 | "uniqueConstraints": {}, | 395 | "uniqueConstraints": {}, |
| @@ -491,10 +468,7 @@ · 72 unmodified lines | |||
| 491 | "compositePrimaryKeys": { | 468 | "compositePrimaryKeys": { |
| 492 | "mcp_binding_scope_id_id_pk": { | 469 | "mcp_binding_scope_id_id_pk": { |
| 493 | "name": "mcp_binding_scope_id_id_pk", | 470 | "name": "mcp_binding_scope_id_id_pk", |
| 494 | "columns": [ | 471 | "columns": ["scope_id", "id"] |
| 495 | "scope_id", | ||
| 496 | "id" | ||
| 497 | ] | ||
| 498 | } | 472 | } |
| 499 | }, | 473 | }, |
| 500 | "uniqueConstraints": {}, | 474 | "uniqueConstraints": {}, |
| @@ -558,10 +532,7 @@ · 57 unmodified lines | |||
| 558 | "compositePrimaryKeys": { | 532 | "compositePrimaryKeys": { |
| 559 | "mcp_oauth_session_scope_id_id_pk": { | 533 | "mcp_oauth_session_scope_id_id_pk": { |
| 560 | "name": "mcp_oauth_session_scope_id_id_pk", | 534 | "name": "mcp_oauth_session_scope_id_id_pk", |
| 561 | "columns": [ | 535 | "columns": ["scope_id", "id"] |
| 562 | "scope_id", | ||
| 563 | "id" | ||
| 564 | ] | ||
| 565 | } | 536 | } |
| 566 | }, | 537 | }, |
| 567 | "uniqueConstraints": {}, | 538 | "uniqueConstraints": {}, |
| @@ -625,10 +596,7 @@ · 57 unmodified lines | |||
| 625 | "compositePrimaryKeys": { | 596 | "compositePrimaryKeys": { |
| 626 | "mcp_source_scope_id_id_pk": { | 597 | "mcp_source_scope_id_id_pk": { |
| 627 | "name": "mcp_source_scope_id_id_pk", | 598 | "name": "mcp_source_scope_id_id_pk", |
| 628 | "columns": [ | 599 | "columns": ["scope_id", "id"] |
| 629 | "scope_id", | ||
| 630 | "id" | ||
| 631 | ] | ||
| 632 | } | 600 | } |
| 633 | }, | 601 | }, |
| 634 | "uniqueConstraints": {}, | 602 | "uniqueConstraints": {}, |
| @@ -686,10 +654,7 @@ · 51 unmodified lines | |||
| 686 | "compositePrimaryKeys": { | 654 | "compositePrimaryKeys": { |
| 687 | "openapi_oauth_session_scope_id_id_pk": { | 655 | "openapi_oauth_session_scope_id_id_pk": { |
| 688 | "name": "openapi_oauth_session_scope_id_id_pk", | 656 | "name": "openapi_oauth_session_scope_id_id_pk", |
| 689 | "columns": [ | 657 | "columns": ["scope_id", "id"] |
| 690 | "scope_id", | ||
| 691 | "id" | ||
| 692 | ] | ||
| 693 | } | 658 | } |
| 694 | }, | 659 | }, |
| 695 | "uniqueConstraints": {}, | 660 | "uniqueConstraints": {}, |
| @@ -762,10 +727,7 @@ · 66 unmodified lines | |||
| 762 | "compositePrimaryKeys": { | 727 | "compositePrimaryKeys": { |
| 763 | "openapi_operation_scope_id_id_pk": { | 728 | "openapi_operation_scope_id_id_pk": { |
| 764 | "name": "openapi_operation_scope_id_id_pk", | 729 | "name": "openapi_operation_scope_id_id_pk", |
| 765 | "columns": [ | 730 | "columns": ["scope_id", "id"] |
| 766 | "scope_id", | ||
| 767 | "id" | ||
| 768 | ] | ||
| 769 | } | 731 | } |
| 770 | }, | 732 | }, |
| 771 | "uniqueConstraints": {}, | 733 | "uniqueConstraints": {}, |
| @@ -853,10 +815,7 @@ · 81 unmodified lines | |||
| 853 | "compositePrimaryKeys": { | 815 | "compositePrimaryKeys": { |
| 854 | "openapi_source_scope_id_id_pk": { | 816 | "openapi_source_scope_id_id_pk": { |
| 855 | "name": "openapi_source_scope_id_id_pk", | 817 | "name": "openapi_source_scope_id_id_pk", |
| 856 | "columns": [ | 818 | "columns": ["scope_id", "id"] |
| 857 | "scope_id", | ||
| 858 | "id" | ||
| 859 | ] | ||
| 860 | } | 819 | } |
| 861 | }, | 820 | }, |
| 862 | "uniqueConstraints": {}, | 821 | "uniqueConstraints": {}, |
| @@ -935,10 +894,7 @@ · 72 unmodified lines | |||
| 935 | "compositePrimaryKeys": { | 894 | "compositePrimaryKeys": { |
| 936 | "secret_scope_id_id_pk": { | 895 | "secret_scope_id_id_pk": { |
| 937 | "name": "secret_scope_id_id_pk", | 896 | "name": "secret_scope_id_id_pk", |
| 938 | "columns": [ | 897 | "columns": ["scope_id", "id"] |
| 939 | "scope_id", | ||
| 940 | "id" | ||
| 941 | ] | ||
| 942 | } | 898 | } |
| 943 | }, | 899 | }, |
| 944 | "uniqueConstraints": {}, | 900 | "uniqueConstraints": {}, |
| @@ -1056,10 +1012,7 @@ · 111 unmodified lines | |||
| 1056 | "compositePrimaryKeys": { | 1012 | "compositePrimaryKeys": { |
| 1057 | "source_scope_id_id_pk": { | 1013 | "source_scope_id_id_pk": { |
| 1058 | "name": "source_scope_id_id_pk", | 1014 | "name": "source_scope_id_id_pk", |
| 1059 | "columns": [ | 1015 | "columns": ["scope_id", "id"] |
| 1060 | "scope_id", | ||
| 1061 | "id" | ||
| 1062 | ] | ||
| 1063 | } | 1016 | } |
| 1064 | }, | 1017 | }, |
| 1065 | "uniqueConstraints": {}, | 1018 | "uniqueConstraints": {}, |
| @@ -1183,10 +1136,7 @@ · 117 unmodified lines | |||
| 1183 | "compositePrimaryKeys": { | 1136 | "compositePrimaryKeys": { |
| 1184 | "tool_scope_id_id_pk": { | 1137 | "tool_scope_id_id_pk": { |
| 1185 | "name": "tool_scope_id_id_pk", | 1138 | "name": "tool_scope_id_id_pk", |
| 1186 | "columns": [ | 1139 | "columns": ["scope_id", "id"] |
| 1187 | "scope_id", | ||
| 1188 | "id" | ||
| 1189 | ] | ||
| 1190 | } | 1140 | } |
| 1191 | }, | 1141 | }, |
| 1192 | "uniqueConstraints": {}, | 1142 | "uniqueConstraints": {}, |
| @@ -1250,10 +1200,7 @@ · 57 unmodified lines | |||
| 1250 | "compositePrimaryKeys": { | 1200 | "compositePrimaryKeys": { |
| 1251 | "workos_vault_metadata_scope_id_id_pk": { | 1201 | "workos_vault_metadata_scope_id_id_pk": { |
| 1252 | "name": "workos_vault_metadata_scope_id_id_pk", | 1202 | "name": "workos_vault_metadata_scope_id_id_pk", |
| 1253 | "columns": [ | 1203 | "columns": ["scope_id", "id"] |
| 1254 | "scope_id", | ||
| 1255 | "id" | ||
| 1256 | ] | ||
| 1257 | } | 1204 | } |
| 1258 | }, | 1205 | }, |
| 1259 | "uniqueConstraints": {}, | 1206 | "uniqueConstraints": {}, |
| @@ -1273,4 +1220,4 @@ · 13 unmodified lines | |||
| 1273 | "schemas": {}, | 1220 | "schemas": {}, |
| 1274 | "tables": {} | 1221 | "tables": {} |
| 1275 | } | 1222 | } |
| 1276 | } | 1223 | } |
| @@ -60,12 +60,8 @@ · 59 unmodified lines | |||
| 60 | "name": "memberships_account_id_accounts_id_fk", | 60 | "name": "memberships_account_id_accounts_id_fk", |
| 61 | "tableFrom": "memberships", | 61 | "tableFrom": "memberships", |
| 62 | "tableTo": "accounts", | 62 | "tableTo": "accounts", |
| 63 | "columnsFrom": [ | 63 | "columnsFrom": ["account_id"], |
| 64 | "account_id" | 64 | "columnsTo": ["id"], |
| 65 | ], | ||
| 66 | "columnsTo": [ | ||
| 67 | "id" | ||
| 68 | ], | ||
| 69 | "onDelete": "cascade", | 65 | "onDelete": "cascade", |
| 70 | "onUpdate": "no action" | 66 | "onUpdate": "no action" |
| 71 | }, | 67 | }, |
| @@ -73,12 +69,8 @@ · 1 unmodified line | |||
| 73 | "name": "memberships_organization_id_organizations_id_fk", | 69 | "name": "memberships_organization_id_organizations_id_fk", |
| 74 | "tableFrom": "memberships", | 70 | "tableFrom": "memberships", |
| 75 | "tableTo": "organizations", | 71 | "tableTo": "organizations", |
| 76 | "columnsFrom": [ | 72 | "columnsFrom": ["organization_id"], |
| 77 | "organization_id" | 73 | "columnsTo": ["id"], |
| 78 | ], | ||
| 79 | "columnsTo": [ | ||
| 80 | "id" | ||
| 81 | ], | ||
| 82 | "onDelete": "cascade", | 74 | "onDelete": "cascade", |
| 83 | "onUpdate": "no action" | 75 | "onUpdate": "no action" |
| 84 | } | 76 | } |
| @@ -86,10 +78,7 @@ · 1 unmodified line | |||
| 86 | "compositePrimaryKeys": { | 78 | "compositePrimaryKeys": { |
| 87 | "memberships_account_id_organization_id_pk": { | 79 | "memberships_account_id_organization_id_pk": { |
| 88 | "name": "memberships_account_id_organization_id_pk", | 80 | "name": "memberships_account_id_organization_id_pk", |
| 89 | "columns": [ | 81 | "columns": ["account_id", "organization_id"] |
| 90 | "account_id", | ||
| 91 | "organization_id" | ||
| 92 | ] | ||
| 93 | } | 82 | } |
| 94 | }, | 83 | }, |
| 95 | "uniqueConstraints": {}, | 84 | "uniqueConstraints": {}, |
| @@ -157,10 +146,7 @@ · 61 unmodified lines | |||
| 157 | "compositePrimaryKeys": { | 146 | "compositePrimaryKeys": { |
| 158 | "blob_namespace_key_pk": { | 147 | "blob_namespace_key_pk": { |
| 159 | "name": "blob_namespace_key_pk", | 148 | "name": "blob_namespace_key_pk", |
| 160 | "columns": [ | 149 | "columns": ["namespace", "key"] |
| 161 | "namespace", | ||
| 162 | "key" | ||
| 163 | ] | ||
| 164 | } | 150 | } |
| 165 | }, | 151 | }, |
| 166 | "uniqueConstraints": {}, | 152 | "uniqueConstraints": {}, |
| @@ -266,10 +252,7 @@ · 99 unmodified lines | |||
| 266 | "compositePrimaryKeys": { | 252 | "compositePrimaryKeys": { |
| 267 | "definition_scope_id_id_pk": { | 253 | "definition_scope_id_id_pk": { |
| 268 | "name": "definition_scope_id_id_pk", | 254 | "name": "definition_scope_id_id_pk", |
| 269 | "columns": [ | 255 | "columns": ["scope_id", "id"] |
| 270 | "scope_id", | ||
| 271 | "id" | ||
| 272 | ] | ||
| 273 | } | 256 | } |
| 274 | }, | 257 | }, |
| 275 | "uniqueConstraints": {}, | 258 | "uniqueConstraints": {}, |
| @@ -342,10 +325,7 @@ · 66 unmodified lines | |||
| 342 | "compositePrimaryKeys": { | 325 | "compositePrimaryKeys": { |
| 343 | "graphql_operation_scope_id_id_pk": { | 326 | "graphql_operation_scope_id_id_pk": { |
| 344 | "name": "graphql_operation_scope_id_id_pk", | 327 | "name": "graphql_operation_scope_id_id_pk", |
| 345 | "columns": [ | 328 | "columns": ["scope_id", "id"] |
| 346 | "scope_id", | ||
| 347 | "id" | ||
| 348 | ] | ||
| 349 | } | 329 | } |
| 350 | }, | 330 | }, |
| 351 | "uniqueConstraints": {}, | 331 | "uniqueConstraints": {}, |
| @@ -409,10 +389,7 @@ · 57 unmodified lines | |||
| 409 | "compositePrimaryKeys": { | 389 | "compositePrimaryKeys": { |
| 410 | "graphql_source_scope_id_id_pk": { | 390 | "graphql_source_scope_id_id_pk": { |
| 411 | "name": "graphql_source_scope_id_id_pk", | 391 | "name": "graphql_source_scope_id_id_pk", |
| 412 | "columns": [ | 392 | "columns": ["scope_id", "id"] |
| 413 | "scope_id", | ||
| 414 | "id" | ||
| 415 | ] | ||
| 416 | } | 393 | } |
| 417 | }, | 394 | }, |
| 418 | "uniqueConstraints": {}, | 395 | "uniqueConstraints": {}, |
| @@ -491,10 +468,7 @@ · 72 unmodified lines | |||
| 491 | "compositePrimaryKeys": { | 468 | "compositePrimaryKeys": { |
| 492 | "mcp_binding_scope_id_id_pk": { | 469 | "mcp_binding_scope_id_id_pk": { |
| 493 | "name": "mcp_binding_scope_id_id_pk", | 470 | "name": "mcp_binding_scope_id_id_pk", |
| 494 | "columns": [ | 471 | "columns": ["scope_id", "id"] |
| 495 | "scope_id", | ||
| 496 | "id" | ||
| 497 | ] | ||
| 498 | } | 472 | } |
| 499 | }, | 473 | }, |
| 500 | "uniqueConstraints": {}, | 474 | "uniqueConstraints": {}, |
| @@ -558,10 +532,7 @@ · 57 unmodified lines | |||
| 558 | "compositePrimaryKeys": { | 532 | "compositePrimaryKeys": { |
| 559 | "mcp_oauth_session_scope_id_id_pk": { | 533 | "mcp_oauth_session_scope_id_id_pk": { |
| 560 | "name": "mcp_oauth_session_scope_id_id_pk", | 534 | "name": "mcp_oauth_session_scope_id_id_pk", |
| 561 | "columns": [ | 535 | "columns": ["scope_id", "id"] |
| 562 | "scope_id", | ||
| 563 | "id" | ||
| 564 | ] | ||
| 565 | } | 536 | } |
| 566 | }, | 537 | }, |
| 567 | "uniqueConstraints": {}, | 538 | "uniqueConstraints": {}, |
| @@ -625,10 +596,7 @@ · 57 unmodified lines | |||
| 625 | "compositePrimaryKeys": { | 596 | "compositePrimaryKeys": { |
| 626 | "mcp_source_scope_id_id_pk": { | 597 | "mcp_source_scope_id_id_pk": { |
| 627 | "name": "mcp_source_scope_id_id_pk", | 598 | "name": "mcp_source_scope_id_id_pk", |
| 628 | "columns": [ | 599 | "columns": ["scope_id", "id"] |
| 629 | "scope_id", | ||
| 630 | "id" | ||
| 631 | ] | ||
| 632 | } | 600 | } |
| 633 | }, | 601 | }, |
| 634 | "uniqueConstraints": {}, | 602 | "uniqueConstraints": {}, |
| @@ -686,10 +654,7 @@ · 51 unmodified lines | |||
| 686 | "compositePrimaryKeys": { | 654 | "compositePrimaryKeys": { |
| 687 | "openapi_oauth_session_scope_id_id_pk": { | 655 | "openapi_oauth_session_scope_id_id_pk": { |
| 688 | "name": "openapi_oauth_session_scope_id_id_pk", | 656 | "name": "openapi_oauth_session_scope_id_id_pk", |
| 689 | "columns": [ | 657 | "columns": ["scope_id", "id"] |
| 690 | "scope_id", | ||
| 691 | "id" | ||
| 692 | ] | ||
| 693 | } | 658 | } |
| 694 | }, | 659 | }, |
| 695 | "uniqueConstraints": {}, | 660 | "uniqueConstraints": {}, |
| @@ -762,10 +727,7 @@ · 66 unmodified lines | |||
| 762 | "compositePrimaryKeys": { | 727 | "compositePrimaryKeys": { |
| 763 | "openapi_operation_scope_id_id_pk": { | 728 | "openapi_operation_scope_id_id_pk": { |
| 764 | "name": "openapi_operation_scope_id_id_pk", | 729 | "name": "openapi_operation_scope_id_id_pk", |
| 765 | "columns": [ | 730 | "columns": ["scope_id", "id"] |
| 766 | "scope_id", | ||
| 767 | "id" | ||
| 768 | ] | ||
| 769 | } | 731 | } |
| 770 | }, | 732 | }, |
| 771 | "uniqueConstraints": {}, | 733 | "uniqueConstraints": {}, |
| @@ -853,10 +815,7 @@ · 81 unmodified lines | |||
| 853 | "compositePrimaryKeys": { | 815 | "compositePrimaryKeys": { |
| 854 | "openapi_source_scope_id_id_pk": { | 816 | "openapi_source_scope_id_id_pk": { |
| 855 | "name": "openapi_source_scope_id_id_pk", | 817 | "name": "openapi_source_scope_id_id_pk", |
| 856 | "columns": [ | 818 | "columns": ["scope_id", "id"] |
| 857 | "scope_id", | ||
| 858 | "id" | ||
| 859 | ] | ||
| 860 | } | 819 | } |
| 861 | }, | 820 | }, |
| 862 | "uniqueConstraints": {}, | 821 | "uniqueConstraints": {}, |
| @@ -935,10 +894,7 @@ · 72 unmodified lines | |||
| 935 | "compositePrimaryKeys": { | 894 | "compositePrimaryKeys": { |
| 936 | "secret_scope_id_id_pk": { | 895 | "secret_scope_id_id_pk": { |
| 937 | "name": "secret_scope_id_id_pk", | 896 | "name": "secret_scope_id_id_pk", |
| 938 | "columns": [ | 897 | "columns": ["scope_id", "id"] |
| 939 | "scope_id", | ||
| 940 | "id" | ||
| 941 | ] | ||
| 942 | } | 898 | } |
| 943 | }, | 899 | }, |
| 944 | "uniqueConstraints": {}, | 900 | "uniqueConstraints": {}, |
| @@ -1056,10 +1012,7 @@ · 111 unmodified lines | |||
| 1056 | "compositePrimaryKeys": { | 1012 | "compositePrimaryKeys": { |
| 1057 | "source_scope_id_id_pk": { | 1013 | "source_scope_id_id_pk": { |
| 1058 | "name": "source_scope_id_id_pk", | 1014 | "name": "source_scope_id_id_pk", |
| 1059 | "columns": [ | 1015 | "columns": ["scope_id", "id"] |
| 1060 | "scope_id", | ||
| 1061 | "id" | ||
| 1062 | ] | ||
| 1063 | } | 1016 | } |
| 1064 | }, | 1017 | }, |
| 1065 | "uniqueConstraints": {}, | 1018 | "uniqueConstraints": {}, |
| @@ -1183,10 +1136,7 @@ · 117 unmodified lines | |||
| 1183 | "compositePrimaryKeys": { | 1136 | "compositePrimaryKeys": { |
| 1184 | "tool_scope_id_id_pk": { | 1137 | "tool_scope_id_id_pk": { |
| 1185 | "name": "tool_scope_id_id_pk", | 1138 | "name": "tool_scope_id_id_pk", |
| 1186 | "columns": [ | 1139 | "columns": ["scope_id", "id"] |
| 1187 | "scope_id", | ||
| 1188 | "id" | ||
| 1189 | ] | ||
| 1190 | } | 1140 | } |
| 1191 | }, | 1141 | }, |
| 1192 | "uniqueConstraints": {}, | 1142 | "uniqueConstraints": {}, |
| @@ -1250,10 +1200,7 @@ · 57 unmodified lines | |||
| 1250 | "compositePrimaryKeys": { | 1200 | "compositePrimaryKeys": { |
| 1251 | "workos_vault_metadata_scope_id_id_pk": { | 1201 | "workos_vault_metadata_scope_id_id_pk": { |
| 1252 | "name": "workos_vault_metadata_scope_id_id_pk", | 1202 | "name": "workos_vault_metadata_scope_id_id_pk", |
| 1253 | "columns": [ | 1203 | "columns": ["scope_id", "id"] |
| 1254 | "scope_id", | ||
| 1255 | "id" | ||
| 1256 | ] | ||
| 1257 | } | 1204 | } |
| 1258 | }, | 1205 | }, |
| 1259 | "uniqueConstraints": {}, | 1206 | "uniqueConstraints": {}, |
| @@ -1273,4 +1220,4 @@ · 13 unmodified lines | |||
| 1273 | "schemas": {}, | 1220 | "schemas": {}, |
| 1274 | "tables": {} | 1221 | "tables": {} |
| 1275 | } | 1222 | } |
| 1276 | } | 1223 | } |
| @@ -60,12 +60,8 @@ · 59 unmodified lines | |||
| 60 | "name": "memberships_account_id_accounts_id_fk", | 60 | "name": "memberships_account_id_accounts_id_fk", |
| 61 | "tableFrom": "memberships", | 61 | "tableFrom": "memberships", |
| 62 | "tableTo": "accounts", | 62 | "tableTo": "accounts", |
| 63 | "columnsFrom": [ | 63 | "columnsFrom": ["account_id"], |
| 64 | "account_id" | 64 | "columnsTo": ["id"], |
| 65 | ], | ||
| 66 | "columnsTo": [ | ||
| 67 | "id" | ||
| 68 | ], | ||
| 69 | "onDelete": "cascade", | 65 | "onDelete": "cascade", |
| 70 | "onUpdate": "no action" | 66 | "onUpdate": "no action" |
| 71 | }, | 67 | }, |
| @@ -73,12 +69,8 @@ · 1 unmodified line | |||
| 73 | "name": "memberships_organization_id_organizations_id_fk", | 69 | "name": "memberships_organization_id_organizations_id_fk", |
| 74 | "tableFrom": "memberships", | 70 | "tableFrom": "memberships", |
| 75 | "tableTo": "organizations", | 71 | "tableTo": "organizations", |
| 76 | "columnsFrom": [ | 72 | "columnsFrom": ["organization_id"], |
| 77 | "organization_id" | 73 | "columnsTo": ["id"], |
| 78 | ], | ||
| 79 | "columnsTo": [ | ||
| 80 | "id" | ||
| 81 | ], | ||
| 82 | "onDelete": "cascade", | 74 | "onDelete": "cascade", |
| 83 | "onUpdate": "no action" | 75 | "onUpdate": "no action" |
| 84 | } | 76 | } |
| @@ -86,10 +78,7 @@ · 1 unmodified line | |||
| 86 | "compositePrimaryKeys": { | 78 | "compositePrimaryKeys": { |
| 87 | "memberships_account_id_organization_id_pk": { | 79 | "memberships_account_id_organization_id_pk": { |
| 88 | "name": "memberships_account_id_organization_id_pk", | 80 | "name": "memberships_account_id_organization_id_pk", |
| 89 | "columns": [ | 81 | "columns": ["account_id", "organization_id"] |
| 90 | "account_id", | ||
| 91 | "organization_id" | ||
| 92 | ] | ||
| 93 | } | 82 | } |
| 94 | }, | 83 | }, |
| 95 | "uniqueConstraints": {}, | 84 | "uniqueConstraints": {}, |
| @@ -157,10 +146,7 @@ · 61 unmodified lines | |||
| 157 | "compositePrimaryKeys": { | 146 | "compositePrimaryKeys": { |
| 158 | "blob_namespace_key_pk": { | 147 | "blob_namespace_key_pk": { |
| 159 | "name": "blob_namespace_key_pk", | 148 | "name": "blob_namespace_key_pk", |
| 160 | "columns": [ | 149 | "columns": ["namespace", "key"] |
| 161 | "namespace", | ||
| 162 | "key" | ||
| 163 | ] | ||
| 164 | } | 150 | } |
| 165 | }, | 151 | }, |
| 166 | "uniqueConstraints": {}, | 152 | "uniqueConstraints": {}, |
| @@ -281,10 +267,7 @@ · 114 unmodified lines | |||
| 281 | "compositePrimaryKeys": { | 267 | "compositePrimaryKeys": { |
| 282 | "connection_scope_id_id_pk": { | 268 | "connection_scope_id_id_pk": { |
| 283 | "name": "connection_scope_id_id_pk", | 269 | "name": "connection_scope_id_id_pk", |
| 284 | "columns": [ | 270 | "columns": ["scope_id", "id"] |
| 285 | "scope_id", | ||
| 286 | "id" | ||
| 287 | ] | ||
| 288 | } | 271 | } |
| 289 | }, | 272 | }, |
| 290 | "uniqueConstraints": {}, | 273 | "uniqueConstraints": {}, |
| @@ -390,10 +373,7 @@ · 99 unmodified lines | |||
| 390 | "compositePrimaryKeys": { | 373 | "compositePrimaryKeys": { |
| 391 | "definition_scope_id_id_pk": { | 374 | "definition_scope_id_id_pk": { |
| 392 | "name": "definition_scope_id_id_pk", | 375 | "name": "definition_scope_id_id_pk", |
| 393 | "columns": [ | 376 | "columns": ["scope_id", "id"] |
| 394 | "scope_id", | ||
| 395 | "id" | ||
| 396 | ] | ||
| 397 | } | 377 | } |
| 398 | }, | 378 | }, |
| 399 | "uniqueConstraints": {}, | 379 | "uniqueConstraints": {}, |
| @@ -466,10 +446,7 @@ · 66 unmodified lines | |||
| 466 | "compositePrimaryKeys": { | 446 | "compositePrimaryKeys": { |
| 467 | "graphql_operation_scope_id_id_pk": { | 447 | "graphql_operation_scope_id_id_pk": { |
| 468 | "name": "graphql_operation_scope_id_id_pk", | 448 | "name": "graphql_operation_scope_id_id_pk", |
| 469 | "columns": [ | 449 | "columns": ["scope_id", "id"] |
| 470 | "scope_id", | ||
| 471 | "id" | ||
| 472 | ] | ||
| 473 | } | 450 | } |
| 474 | }, | 451 | }, |
| 475 | "uniqueConstraints": {}, | 452 | "uniqueConstraints": {}, |
| @@ -533,10 +510,7 @@ · 57 unmodified lines | |||
| 533 | "compositePrimaryKeys": { | 510 | "compositePrimaryKeys": { |
| 534 | "graphql_source_scope_id_id_pk": { | 511 | "graphql_source_scope_id_id_pk": { |
| 535 | "name": "graphql_source_scope_id_id_pk", | 512 | "name": "graphql_source_scope_id_id_pk", |
| 536 | "columns": [ | 513 | "columns": ["scope_id", "id"] |
| 537 | "scope_id", | ||
| 538 | "id" | ||
| 539 | ] | ||
| 540 | } | 514 | } |
| 541 | }, | 515 | }, |
| 542 | "uniqueConstraints": {}, | 516 | "uniqueConstraints": {}, |
| @@ -615,10 +589,7 @@ · 72 unmodified lines | |||
| 615 | "compositePrimaryKeys": { | 589 | "compositePrimaryKeys": { |
| 616 | "mcp_binding_scope_id_id_pk": { | 590 | "mcp_binding_scope_id_id_pk": { |
| 617 | "name": "mcp_binding_scope_id_id_pk", | 591 | "name": "mcp_binding_scope_id_id_pk", |
| 618 | "columns": [ | 592 | "columns": ["scope_id", "id"] |
| 619 | "scope_id", | ||
| 620 | "id" | ||
| 621 | ] | ||
| 622 | } | 593 | } |
| 623 | }, | 594 | }, |
| 624 | "uniqueConstraints": {}, | 595 | "uniqueConstraints": {}, |
| @@ -682,10 +653,7 @@ · 57 unmodified lines | |||
| 682 | "compositePrimaryKeys": { | 653 | "compositePrimaryKeys": { |
| 683 | "mcp_oauth_session_scope_id_id_pk": { | 654 | "mcp_oauth_session_scope_id_id_pk": { |
| 684 | "name": "mcp_oauth_session_scope_id_id_pk", | 655 | "name": "mcp_oauth_session_scope_id_id_pk", |
| 685 | "columns": [ | 656 | "columns": ["scope_id", "id"] |
| 686 | "scope_id", | ||
| 687 | "id" | ||
| 688 | ] | ||
| 689 | } | 657 | } |
| 690 | }, | 658 | }, |
| 691 | "uniqueConstraints": {}, | 659 | "uniqueConstraints": {}, |
| @@ -749,10 +717,7 @@ · 57 unmodified lines | |||
| 749 | "compositePrimaryKeys": { | 717 | "compositePrimaryKeys": { |
| 750 | "mcp_source_scope_id_id_pk": { | 718 | "mcp_source_scope_id_id_pk": { |
| 751 | "name": "mcp_source_scope_id_id_pk", | 719 | "name": "mcp_source_scope_id_id_pk", |
| 752 | "columns": [ | 720 | "columns": ["scope_id", "id"] |
| 753 | "scope_id", | ||
| 754 | "id" | ||
| 755 | ] | ||
| 756 | } | 721 | } |
| 757 | }, | 722 | }, |
| 758 | "uniqueConstraints": {}, | 723 | "uniqueConstraints": {}, |
| @@ -810,10 +775,7 @@ · 51 unmodified lines | |||
| 810 | "compositePrimaryKeys": { | 775 | "compositePrimaryKeys": { |
| 811 | "openapi_oauth_session_scope_id_id_pk": { | 776 | "openapi_oauth_session_scope_id_id_pk": { |
| 812 | "name": "openapi_oauth_session_scope_id_id_pk", | 777 | "name": "openapi_oauth_session_scope_id_id_pk", |
| 813 | "columns": [ | 778 | "columns": ["scope_id", "id"] |
| 814 | "scope_id", | ||
| 815 | "id" | ||
| 816 | ] | ||
| 817 | } | 779 | } |
| 818 | }, | 780 | }, |
| 819 | "uniqueConstraints": {}, | 781 | "uniqueConstraints": {}, |
| @@ -886,10 +848,7 @@ · 66 unmodified lines | |||
| 886 | "compositePrimaryKeys": { | 848 | "compositePrimaryKeys": { |
| 887 | "openapi_operation_scope_id_id_pk": { | 849 | "openapi_operation_scope_id_id_pk": { |
| 888 | "name": "openapi_operation_scope_id_id_pk", | 850 | "name": "openapi_operation_scope_id_id_pk", |
| 889 | "columns": [ | 851 | "columns": ["scope_id", "id"] |
| 890 | "scope_id", | ||
| 891 | "id" | ||
| 892 | ] | ||
| 893 | } | 852 | } |
| 894 | }, | 853 | }, |
| 895 | "uniqueConstraints": {}, | 854 | "uniqueConstraints": {}, |
| @@ -977,10 +936,7 @@ · 81 unmodified lines | |||
| 977 | "compositePrimaryKeys": { | 936 | "compositePrimaryKeys": { |
| 978 | "openapi_source_scope_id_id_pk": { | 937 | "openapi_source_scope_id_id_pk": { |
| 979 | "name": "openapi_source_scope_id_id_pk", | 938 | "name": "openapi_source_scope_id_id_pk", |
| 980 | "columns": [ | 939 | "columns": ["scope_id", "id"] |
| 981 | "scope_id", | ||
| 982 | "id" | ||
| 983 | ] | ||
| 984 | } | 940 | } |
| 985 | }, | 941 | }, |
| 986 | "uniqueConstraints": {}, | 942 | "uniqueConstraints": {}, |
| @@ -1080,10 +1036,7 @@ · 93 unmodified lines | |||
| 1080 | "compositePrimaryKeys": { | 1036 | "compositePrimaryKeys": { |
| 1081 | "secret_scope_id_id_pk": { | 1037 | "secret_scope_id_id_pk": { |
| 1082 | "name": "secret_scope_id_id_pk", | 1038 | "name": "secret_scope_id_id_pk", |
| 1083 | "columns": [ | 1039 | "columns": ["scope_id", "id"] |
| 1084 | "scope_id", | ||
| 1085 | "id" | ||
| 1086 | ] | ||
| 1087 | } | 1040 | } |
| 1088 | }, | 1041 | }, |
| 1089 | "uniqueConstraints": {}, | 1042 | "uniqueConstraints": {}, |
| @@ -1201,10 +1154,7 @@ · 111 unmodified lines | |||
| 1201 | "compositePrimaryKeys": { | 1154 | "compositePrimaryKeys": { |
| 1202 | "source_scope_id_id_pk": { | 1155 | "source_scope_id_id_pk": { |
| 1203 | "name": "source_scope_id_id_pk", | 1156 | "name": "source_scope_id_id_pk", |
| 1204 | "columns": [ | 1157 | "columns": ["scope_id", "id"] |
| 1205 | "scope_id", | ||
| 1206 | "id" | ||
| 1207 | ] | ||
| 1208 | } | 1158 | } |
| 1209 | }, | 1159 | }, |
| 1210 | "uniqueConstraints": {}, | 1160 | "uniqueConstraints": {}, |
| @@ -1328,10 +1278,7 @@ · 117 unmodified lines | |||
| 1328 | "compositePrimaryKeys": { | 1278 | "compositePrimaryKeys": { |
| 1329 | "tool_scope_id_id_pk": { | 1279 | "tool_scope_id_id_pk": { |
| 1330 | "name": "tool_scope_id_id_pk", | 1280 | "name": "tool_scope_id_id_pk", |
| 1331 | "columns": [ | 1281 | "columns": ["scope_id", "id"] |
| 1332 | "scope_id", | ||
| 1333 | "id" | ||
| 1334 | ] | ||
| 1335 | } | 1282 | } |
| 1336 | }, | 1283 | }, |
| 1337 | "uniqueConstraints": {}, | 1284 | "uniqueConstraints": {}, |
| @@ -1395,10 +1342,7 @@ · 57 unmodified lines | |||
| 1395 | "compositePrimaryKeys": { | 1342 | "compositePrimaryKeys": { |
| 1396 | "workos_vault_metadata_scope_id_id_pk": { | 1343 | "workos_vault_metadata_scope_id_id_pk": { |
| 1397 | "name": "workos_vault_metadata_scope_id_id_pk", | 1344 | "name": "workos_vault_metadata_scope_id_id_pk", |
| 1398 | "columns": [ | 1345 | "columns": ["scope_id", "id"] |
| 1399 | "scope_id", | ||
| 1400 | "id" | ||
| 1401 | ] | ||
| 1402 | } | 1346 | } |
| 1403 | }, | 1347 | }, |
| 1404 | "uniqueConstraints": {}, | 1348 | "uniqueConstraints": {}, |
| @@ -1418,4 +1362,4 @@ · 13 unmodified lines | |||
| 1418 | "schemas": {}, | 1362 | "schemas": {}, |
| 1419 | "tables": {} | 1363 | "tables": {} |
| 1420 | } | 1364 | } |
| 1421 | } | 1365 | } |
| @@ -60,12 +60,8 @@ · 59 unmodified lines | |||
| 60 | "name": "memberships_account_id_accounts_id_fk", | 60 | "name": "memberships_account_id_accounts_id_fk", |
| 61 | "tableFrom": "memberships", | 61 | "tableFrom": "memberships", |
| 62 | "tableTo": "accounts", | 62 | "tableTo": "accounts", |
| 63 | "columnsFrom": [ | 63 | "columnsFrom": ["account_id"], |
| 64 | "account_id" | 64 | "columnsTo": ["id"], |
| 65 | ], | ||
| 66 | "columnsTo": [ | ||
| 67 | "id" | ||
| 68 | ], | ||
| 69 | "onDelete": "cascade", | 65 | "onDelete": "cascade", |
| 70 | "onUpdate": "no action" | 66 | "onUpdate": "no action" |
| 71 | }, | 67 | }, |
| @@ -73,12 +69,8 @@ · 1 unmodified line | |||
| 73 | "name": "memberships_organization_id_organizations_id_fk", | 69 | "name": "memberships_organization_id_organizations_id_fk", |
| 74 | "tableFrom": "memberships", | 70 | "tableFrom": "memberships", |
| 75 | "tableTo": "organizations", | 71 | "tableTo": "organizations", |
| 76 | "columnsFrom": [ | 72 | "columnsFrom": ["organization_id"], |
| 77 | "organization_id" | 73 | "columnsTo": ["id"], |
| 78 | ], | ||
| 79 | "columnsTo": [ | ||
| 80 | "id" | ||
| 81 | ], | ||
| 82 | "onDelete": "cascade", | 74 | "onDelete": "cascade", |
| 83 | "onUpdate": "no action" | 75 | "onUpdate": "no action" |
| 84 | } | 76 | } |
| @@ -86,10 +78,7 @@ · 1 unmodified line | |||
| 86 | "compositePrimaryKeys": { | 78 | "compositePrimaryKeys": { |
| 87 | "memberships_account_id_organization_id_pk": { | 79 | "memberships_account_id_organization_id_pk": { |
| 88 | "name": "memberships_account_id_organization_id_pk", | 80 | "name": "memberships_account_id_organization_id_pk", |
| 89 | "columns": [ | 81 | "columns": ["account_id", "organization_id"] |
| 90 | "account_id", | ||
| 91 | "organization_id" | ||
| 92 | ] | ||
| 93 | } | 82 | } |
| 94 | }, | 83 | }, |
| 95 | "uniqueConstraints": {}, | 84 | "uniqueConstraints": {}, |
| @@ -157,10 +146,7 @@ · 61 unmodified lines | |||
| 157 | "compositePrimaryKeys": { | 146 | "compositePrimaryKeys": { |
| 158 | "blob_namespace_key_pk": { | 147 | "blob_namespace_key_pk": { |
| 159 | "name": "blob_namespace_key_pk", | 148 | "name": "blob_namespace_key_pk", |
| 160 | "columns": [ | 149 | "columns": ["namespace", "key"] |
| 161 | "namespace", | ||
| 162 | "key" | ||
| 163 | ] | ||
| 164 | } | 150 | } |
| 165 | }, | 151 | }, |
| 166 | "uniqueConstraints": {}, | 152 | "uniqueConstraints": {}, |
| @@ -275,10 +261,7 @@ · 108 unmodified lines | |||
| 275 | "compositePrimaryKeys": { | 261 | "compositePrimaryKeys": { |
| 276 | "connection_scope_id_id_pk": { | 262 | "connection_scope_id_id_pk": { |
| 277 | "name": "connection_scope_id_id_pk", | 263 | "name": "connection_scope_id_id_pk", |
| 278 | "columns": [ | 264 | "columns": ["scope_id", "id"] |
| 279 | "scope_id", | ||
| 280 | "id" | ||
| 281 | ] | ||
| 282 | } | 265 | } |
| 283 | }, | 266 | }, |
| 284 | "uniqueConstraints": {}, | 267 | "uniqueConstraints": {}, |
| @@ -384,10 +367,7 @@ · 99 unmodified lines | |||
| 384 | "compositePrimaryKeys": { | 367 | "compositePrimaryKeys": { |
| 385 | "definition_scope_id_id_pk": { | 368 | "definition_scope_id_id_pk": { |
| 386 | "name": "definition_scope_id_id_pk", | 369 | "name": "definition_scope_id_id_pk", |
| 387 | "columns": [ | 370 | "columns": ["scope_id", "id"] |
| 388 | "scope_id", | ||
| 389 | "id" | ||
| 390 | ] | ||
| 391 | } | 371 | } |
| 392 | }, | 372 | }, |
| 393 | "uniqueConstraints": {}, | 373 | "uniqueConstraints": {}, |
| @@ -460,10 +440,7 @@ · 66 unmodified lines | |||
| 460 | "compositePrimaryKeys": { | 440 | "compositePrimaryKeys": { |
| 461 | "graphql_operation_scope_id_id_pk": { | 441 | "graphql_operation_scope_id_id_pk": { |
| 462 | "name": "graphql_operation_scope_id_id_pk", | 442 | "name": "graphql_operation_scope_id_id_pk", |
| 463 | "columns": [ | 443 | "columns": ["scope_id", "id"] |
| 464 | "scope_id", | ||
| 465 | "id" | ||
| 466 | ] | ||
| 467 | } | 444 | } |
| 468 | }, | 445 | }, |
| 469 | "uniqueConstraints": {}, | 446 | "uniqueConstraints": {}, |
| @@ -539,10 +516,7 @@ · 69 unmodified lines | |||
| 539 | "compositePrimaryKeys": { | 516 | "compositePrimaryKeys": { |
| 540 | "graphql_source_scope_id_id_pk": { | 517 | "graphql_source_scope_id_id_pk": { |
| 541 | "name": "graphql_source_scope_id_id_pk", | 518 | "name": "graphql_source_scope_id_id_pk", |
| 542 | "columns": [ | 519 | "columns": ["scope_id", "id"] |
| 543 | "scope_id", | ||
| 544 | "id" | ||
| 545 | ] | ||
| 546 | } | 520 | } |
| 547 | }, | 521 | }, |
| 548 | "uniqueConstraints": {}, | 522 | "uniqueConstraints": {}, |
| @@ -621,10 +595,7 @@ · 72 unmodified lines | |||
| 621 | "compositePrimaryKeys": { | 595 | "compositePrimaryKeys": { |
| 622 | "mcp_binding_scope_id_id_pk": { | 596 | "mcp_binding_scope_id_id_pk": { |
| 623 | "name": "mcp_binding_scope_id_id_pk", | 597 | "name": "mcp_binding_scope_id_id_pk", |
| 624 | "columns": [ | 598 | "columns": ["scope_id", "id"] |
| 625 | "scope_id", | ||
| 626 | "id" | ||
| 627 | ] | ||
| 628 | } | 599 | } |
| 629 | }, | 600 | }, |
| 630 | "uniqueConstraints": {}, | 601 | "uniqueConstraints": {}, |
| @@ -688,10 +659,7 @@ · 57 unmodified lines | |||
| 688 | "compositePrimaryKeys": { | 659 | "compositePrimaryKeys": { |
| 689 | "mcp_source_scope_id_id_pk": { | 660 | "mcp_source_scope_id_id_pk": { |
| 690 | "name": "mcp_source_scope_id_id_pk", | 661 | "name": "mcp_source_scope_id_id_pk", |
| 691 | "columns": [ | 662 | "columns": ["scope_id", "id"] |
| 692 | "scope_id", | ||
| 693 | "id" | ||
| 694 | ] | ||
| 695 | } | 663 | } |
| 696 | }, | 664 | }, |
| 697 | "uniqueConstraints": {}, | 665 | "uniqueConstraints": {}, |
| @@ -815,10 +783,7 @@ · 117 unmodified lines | |||
| 815 | "compositePrimaryKeys": { | 783 | "compositePrimaryKeys": { |
| 816 | "oauth2_session_scope_id_id_pk": { | 784 | "oauth2_session_scope_id_id_pk": { |
| 817 | "name": "oauth2_session_scope_id_id_pk", | 785 | "name": "oauth2_session_scope_id_id_pk", |
| 818 | "columns": [ | 786 | "columns": ["scope_id", "id"] |
| 819 | "scope_id", | ||
| 820 | "id" | ||
| 821 | ] | ||
| 822 | } | 787 | } |
| 823 | }, | 788 | }, |
| 824 | "uniqueConstraints": {}, | 789 | "uniqueConstraints": {}, |
| @@ -891,10 +856,7 @@ · 66 unmodified lines | |||
| 891 | "compositePrimaryKeys": { | 856 | "compositePrimaryKeys": { |
| 892 | "openapi_operation_scope_id_id_pk": { | 857 | "openapi_operation_scope_id_id_pk": { |
| 893 | "name": "openapi_operation_scope_id_id_pk", | 858 | "name": "openapi_operation_scope_id_id_pk", |
| 894 | "columns": [ | 859 | "columns": ["scope_id", "id"] |
| 895 | "scope_id", | ||
| 896 | "id" | ||
| 897 | ] | ||
| 898 | } | 860 | } |
| 899 | }, | 861 | }, |
| 900 | "uniqueConstraints": {}, | 862 | "uniqueConstraints": {}, |
| @@ -988,10 +950,7 @@ · 87 unmodified lines | |||
| 988 | "compositePrimaryKeys": { | 950 | "compositePrimaryKeys": { |
| 989 | "openapi_source_scope_id_id_pk": { | 951 | "openapi_source_scope_id_id_pk": { |
| 990 | "name": "openapi_source_scope_id_id_pk", | 952 | "name": "openapi_source_scope_id_id_pk", |
| 991 | "columns": [ | 953 | "columns": ["scope_id", "id"] |
| 992 | "scope_id", | ||
| 993 | "id" | ||
| 994 | ] | ||
| 995 | } | 954 | } |
| 996 | }, | 955 | }, |
| 997 | "uniqueConstraints": {}, | 956 | "uniqueConstraints": {}, |
| @@ -1213,10 +1172,7 @@ · 215 unmodified lines | |||
| 1213 | "compositePrimaryKeys": { | 1172 | "compositePrimaryKeys": { |
| 1214 | "secret_scope_id_id_pk": { | 1173 | "secret_scope_id_id_pk": { |
| 1215 | "name": "secret_scope_id_id_pk", | 1174 | "name": "secret_scope_id_id_pk", |
| 1216 | "columns": [ | 1175 | "columns": ["scope_id", "id"] |
| 1217 | "scope_id", | ||
| 1218 | "id" | ||
| 1219 | ] | ||
| 1220 | } | 1176 | } |
| 1221 | }, | 1177 | }, |
| 1222 | "uniqueConstraints": {}, | 1178 | "uniqueConstraints": {}, |
| @@ -1334,10 +1290,7 @@ · 111 unmodified lines | |||
| 1334 | "compositePrimaryKeys": { | 1290 | "compositePrimaryKeys": { |
| 1335 | "source_scope_id_id_pk": { | 1291 | "source_scope_id_id_pk": { |
| 1336 | "name": "source_scope_id_id_pk", | 1292 | "name": "source_scope_id_id_pk", |
| 1337 | "columns": [ | 1293 | "columns": ["scope_id", "id"] |
| 1338 | "scope_id", | ||
| 1339 | "id" | ||
| 1340 | ] | ||
| 1341 | } | 1294 | } |
| 1342 | }, | 1295 | }, |
| 1343 | "uniqueConstraints": {}, | 1296 | "uniqueConstraints": {}, |
| @@ -1461,10 +1414,7 @@ · 117 unmodified lines | |||
| 1461 | "compositePrimaryKeys": { | 1414 | "compositePrimaryKeys": { |
| 1462 | "tool_scope_id_id_pk": { | 1415 | "tool_scope_id_id_pk": { |
| 1463 | "name": "tool_scope_id_id_pk", | 1416 | "name": "tool_scope_id_id_pk", |
| 1464 | "columns": [ | 1417 | "columns": ["scope_id", "id"] |
| 1465 | "scope_id", | ||
| 1466 | "id" | ||
| 1467 | ] | ||
| 1468 | } | 1418 | } |
| 1469 | }, | 1419 | }, |
| 1470 | "uniqueConstraints": {}, | 1420 | "uniqueConstraints": {}, |
| @@ -1546,10 +1496,7 @@ · 75 unmodified lines | |||
| 1546 | "compositePrimaryKeys": { | 1496 | "compositePrimaryKeys": { |
| 1547 | "tool_policy_scope_id_id_pk": { | 1497 | "tool_policy_scope_id_id_pk": { |
| 1548 | "name": "tool_policy_scope_id_id_pk", | 1498 | "name": "tool_policy_scope_id_id_pk", |
| 1549 | "columns": [ | 1499 | "columns": ["scope_id", "id"] |
| 1550 | "scope_id", | ||
| 1551 | "id" | ||
| 1552 | ] | ||
| 1553 | } | 1500 | } |
| 1554 | }, | 1501 | }, |
| 1555 | "uniqueConstraints": {}, | 1502 | "uniqueConstraints": {}, |
| @@ -1613,10 +1560,7 @@ · 57 unmodified lines | |||
| 1613 | "compositePrimaryKeys": { | 1560 | "compositePrimaryKeys": { |
| 1614 | "workos_vault_metadata_scope_id_id_pk": { | 1561 | "workos_vault_metadata_scope_id_id_pk": { |
| 1615 | "name": "workos_vault_metadata_scope_id_id_pk", | 1562 | "name": "workos_vault_metadata_scope_id_id_pk", |
| 1616 | "columns": [ | 1563 | "columns": ["scope_id", "id"] |
| 1617 | "scope_id", | ||
| 1618 | "id" | ||
| 1619 | ] | ||
| 1620 | } | 1564 | } |
| 1621 | }, | 1565 | }, |
| 1622 | "uniqueConstraints": {}, | 1566 | "uniqueConstraints": {}, |
| @@ -1636,4 +1580,4 @@ · 13 unmodified lines | |||
| 1636 | "schemas": {}, | 1580 | "schemas": {}, |
| 1637 | "tables": {} | 1581 | "tables": {} |
| 1638 | } | 1582 | } |
| 1639 | } | 1583 | } |
| @@ -60,12 +60,8 @@ · 59 unmodified lines | |||
| 60 | "name": "memberships_account_id_accounts_id_fk", | 60 | "name": "memberships_account_id_accounts_id_fk", |
| 61 | "tableFrom": "memberships", | 61 | "tableFrom": "memberships", |
| 62 | "tableTo": "accounts", | 62 | "tableTo": "accounts", |
| 63 | "columnsFrom": [ | 63 | "columnsFrom": ["account_id"], |
| 64 | "account_id" | 64 | "columnsTo": ["id"], |
| 65 | ], | ||
| 66 | "columnsTo": [ | ||
| 67 | "id" | ||
| 68 | ], | ||
| 69 | "onDelete": "cascade", | 65 | "onDelete": "cascade", |
| 70 | "onUpdate": "no action" | 66 | "onUpdate": "no action" |
| 71 | }, | 67 | }, |
| @@ -73,12 +69,8 @@ · 1 unmodified line | |||
| 73 | "name": "memberships_organization_id_organizations_id_fk", | 69 | "name": "memberships_organization_id_organizations_id_fk", |
| 74 | "tableFrom": "memberships", | 70 | "tableFrom": "memberships", |
| 75 | "tableTo": "organizations", | 71 | "tableTo": "organizations", |
| 76 | "columnsFrom": [ | 72 | "columnsFrom": ["organization_id"], |
| 77 | "organization_id" | 73 | "columnsTo": ["id"], |
| 78 | ], | ||
| 79 | "columnsTo": [ | ||
| 80 | "id" | ||
| 81 | ], | ||
| 82 | "onDelete": "cascade", | 74 | "onDelete": "cascade", |
| 83 | "onUpdate": "no action" | 75 | "onUpdate": "no action" |
| 84 | } | 76 | } |
| @@ -86,10 +78,7 @@ · 1 unmodified line | |||
| 86 | "compositePrimaryKeys": { | 78 | "compositePrimaryKeys": { |
| 87 | "memberships_account_id_organization_id_pk": { | 79 | "memberships_account_id_organization_id_pk": { |
| 88 | "name": "memberships_account_id_organization_id_pk", | 80 | "name": "memberships_account_id_organization_id_pk", |
| 89 | "columns": [ | 81 | "columns": ["account_id", "organization_id"] |
| 90 | "account_id", | ||
| 91 | "organization_id" | ||
| 92 | ] | ||
| 93 | } | 82 | } |
| 94 | }, | 83 | }, |
| 95 | "uniqueConstraints": {}, | 84 | "uniqueConstraints": {}, |
| @@ -157,10 +146,7 @@ · 61 unmodified lines | |||
| 157 | "compositePrimaryKeys": { | 146 | "compositePrimaryKeys": { |
| 158 | "blob_namespace_key_pk": { | 147 | "blob_namespace_key_pk": { |
| 159 | "name": "blob_namespace_key_pk", | 148 | "name": "blob_namespace_key_pk", |
| 160 | "columns": [ | 149 | "columns": ["namespace", "key"] |
| 161 | "namespace", | ||
| 162 | "key" | ||
| 163 | ] | ||
| 164 | } | 150 | } |
| 165 | }, | 151 | }, |
| 166 | "uniqueConstraints": {}, | 152 | "uniqueConstraints": {}, |
| @@ -275,10 +261,7 @@ · 108 unmodified lines | |||
| 275 | "compositePrimaryKeys": { | 261 | "compositePrimaryKeys": { |
| 276 | "connection_scope_id_id_pk": { | 262 | "connection_scope_id_id_pk": { |
| 277 | "name": "connection_scope_id_id_pk", | 263 | "name": "connection_scope_id_id_pk", |
| 278 | "columns": [ | 264 | "columns": ["scope_id", "id"] |
| 279 | "scope_id", | ||
| 280 | "id" | ||
| 281 | ] | ||
| 282 | } | 265 | } |
| 283 | }, | 266 | }, |
| 284 | "uniqueConstraints": {}, | 267 | "uniqueConstraints": {}, |
| @@ -384,10 +367,7 @@ · 99 unmodified lines | |||
| 384 | "compositePrimaryKeys": { | 367 | "compositePrimaryKeys": { |
| 385 | "definition_scope_id_id_pk": { | 368 | "definition_scope_id_id_pk": { |
| 386 | "name": "definition_scope_id_id_pk", | 369 | "name": "definition_scope_id_id_pk", |
| 387 | "columns": [ | 370 | "columns": ["scope_id", "id"] |
| 388 | "scope_id", | ||
| 389 | "id" | ||
| 390 | ] | ||
| 391 | } | 371 | } |
| 392 | }, | 372 | }, |
| 393 | "uniqueConstraints": {}, | 373 | "uniqueConstraints": {}, |
| @@ -460,10 +440,7 @@ · 66 unmodified lines | |||
| 460 | "compositePrimaryKeys": { | 440 | "compositePrimaryKeys": { |
| 461 | "graphql_operation_scope_id_id_pk": { | 441 | "graphql_operation_scope_id_id_pk": { |
| 462 | "name": "graphql_operation_scope_id_id_pk", | 442 | "name": "graphql_operation_scope_id_id_pk", |
| 463 | "columns": [ | 443 | "columns": ["scope_id", "id"] |
| 464 | "scope_id", | ||
| 465 | "id" | ||
| 466 | ] | ||
| 467 | } | 444 | } |
| 468 | }, | 445 | }, |
| 469 | "uniqueConstraints": {}, | 446 | "uniqueConstraints": {}, |
| @@ -549,10 +526,7 @@ · 79 unmodified lines | |||
| 549 | "compositePrimaryKeys": { | 526 | "compositePrimaryKeys": { |
| 550 | "graphql_source_scope_id_id_pk": { | 527 | "graphql_source_scope_id_id_pk": { |
| 551 | "name": "graphql_source_scope_id_id_pk", | 528 | "name": "graphql_source_scope_id_id_pk", |
| 552 | "columns": [ | 529 | "columns": ["scope_id", "id"] |
| 553 | "scope_id", | ||
| 554 | "id" | ||
| 555 | ] | ||
| 556 | } | 530 | } |
| 557 | }, | 531 | }, |
| 558 | "uniqueConstraints": {}, | 532 | "uniqueConstraints": {}, |
| @@ -631,10 +605,7 @@ · 72 unmodified lines | |||
| 631 | "compositePrimaryKeys": { | 605 | "compositePrimaryKeys": { |
| 632 | "mcp_binding_scope_id_id_pk": { | 606 | "mcp_binding_scope_id_id_pk": { |
| 633 | "name": "mcp_binding_scope_id_id_pk", | 607 | "name": "mcp_binding_scope_id_id_pk", |
| 634 | "columns": [ | 608 | "columns": ["scope_id", "id"] |
| 635 | "scope_id", | ||
| 636 | "id" | ||
| 637 | ] | ||
| 638 | } | 609 | } |
| 639 | }, | 610 | }, |
| 640 | "uniqueConstraints": {}, | 611 | "uniqueConstraints": {}, |
| @@ -801,10 +772,7 @@ · 160 unmodified lines | |||
| 801 | "compositePrimaryKeys": { | 772 | "compositePrimaryKeys": { |
| 802 | "mcp_source_scope_id_id_pk": { | 773 | "mcp_source_scope_id_id_pk": { |
| 803 | "name": "mcp_source_scope_id_id_pk", | 774 | "name": "mcp_source_scope_id_id_pk", |
| 804 | "columns": [ | 775 | "columns": ["scope_id", "id"] |
| 805 | "scope_id", | ||
| 806 | "id" | ||
| 807 | ] | ||
| 808 | } | 776 | } |
| 809 | }, | 777 | }, |
| 810 | "uniqueConstraints": {}, | 778 | "uniqueConstraints": {}, |
| @@ -928,10 +896,7 @@ · 117 unmodified lines | |||
| 928 | "compositePrimaryKeys": { | 896 | "compositePrimaryKeys": { |
| 929 | "oauth2_session_scope_id_id_pk": { | 897 | "oauth2_session_scope_id_id_pk": { |
| 930 | "name": "oauth2_session_scope_id_id_pk", | 898 | "name": "oauth2_session_scope_id_id_pk", |
| 931 | "columns": [ | 899 | "columns": ["scope_id", "id"] |
| 932 | "scope_id", | ||
| 933 | "id" | ||
| 934 | ] | ||
| 935 | } | 900 | } |
| 936 | }, | 901 | }, |
| 937 | "uniqueConstraints": {}, | 902 | "uniqueConstraints": {}, |
| @@ -1004,10 +969,7 @@ · 66 unmodified lines | |||
| 1004 | "compositePrimaryKeys": { | 969 | "compositePrimaryKeys": { |
| 1005 | "openapi_operation_scope_id_id_pk": { | 970 | "openapi_operation_scope_id_id_pk": { |
| 1006 | "name": "openapi_operation_scope_id_id_pk", | 971 | "name": "openapi_operation_scope_id_id_pk", |
| 1007 | "columns": [ | 972 | "columns": ["scope_id", "id"] |
| 1008 | "scope_id", | ||
| 1009 | "id" | ||
| 1010 | ] | ||
| 1011 | } | 973 | } |
| 1012 | }, | 974 | }, |
| 1013 | "uniqueConstraints": {}, | 975 | "uniqueConstraints": {}, |
| @@ -1089,10 +1051,7 @@ · 75 unmodified lines | |||
| 1089 | "compositePrimaryKeys": { | 1051 | "compositePrimaryKeys": { |
| 1090 | "openapi_source_scope_id_id_pk": { | 1052 | "openapi_source_scope_id_id_pk": { |
| 1091 | "name": "openapi_source_scope_id_id_pk", | 1053 | "name": "openapi_source_scope_id_id_pk", |
| 1092 | "columns": [ | 1054 | "columns": ["scope_id", "id"] |
| 1093 | "scope_id", | ||
| 1094 | "id" | ||
| 1095 | ] | ||
| 1096 | } | 1055 | } |
| 1097 | }, | 1056 | }, |
| 1098 | "uniqueConstraints": {}, | 1057 | "uniqueConstraints": {}, |
| @@ -1363,10 +1322,7 @@ · 264 unmodified lines | |||
| 1363 | "compositePrimaryKeys": { | 1322 | "compositePrimaryKeys": { |
| 1364 | "secret_scope_id_id_pk": { | 1323 | "secret_scope_id_id_pk": { |
| 1365 | "name": "secret_scope_id_id_pk", | 1324 | "name": "secret_scope_id_id_pk", |
| 1366 | "columns": [ | 1325 | "columns": ["scope_id", "id"] |
| 1367 | "scope_id", | ||
| 1368 | "id" | ||
| 1369 | ] | ||
| 1370 | } | 1326 | } |
| 1371 | }, | 1327 | }, |
| 1372 | "uniqueConstraints": {}, | 1328 | "uniqueConstraints": {}, |
| @@ -1484,10 +1440,7 @@ · 111 unmodified lines | |||
| 1484 | "compositePrimaryKeys": { | 1440 | "compositePrimaryKeys": { |
| 1485 | "source_scope_id_id_pk": { | 1441 | "source_scope_id_id_pk": { |
| 1486 | "name": "source_scope_id_id_pk", | 1442 | "name": "source_scope_id_id_pk", |
| 1487 | "columns": [ | 1443 | "columns": ["scope_id", "id"] |
| 1488 | "scope_id", | ||
| 1489 | "id" | ||
| 1490 | ] | ||
| 1491 | } | 1444 | } |
| 1492 | }, | 1445 | }, |
| 1493 | "uniqueConstraints": {}, | 1446 | "uniqueConstraints": {}, |
| @@ -1611,10 +1564,7 @@ · 117 unmodified lines | |||
| 1611 | "compositePrimaryKeys": { | 1564 | "compositePrimaryKeys": { |
| 1612 | "tool_scope_id_id_pk": { | 1565 | "tool_scope_id_id_pk": { |
| 1613 | "name": "tool_scope_id_id_pk", | 1566 | "name": "tool_scope_id_id_pk", |
| 1614 | "columns": [ | 1567 | "columns": ["scope_id", "id"] |
| 1615 | "scope_id", | ||
| 1616 | "id" | ||
| 1617 | ] | ||
| 1618 | } | 1568 | } |
| 1619 | }, | 1569 | }, |
| 1620 | "uniqueConstraints": {}, | 1570 | "uniqueConstraints": {}, |
| @@ -1696,10 +1646,7 @@ · 75 unmodified lines | |||
| 1696 | "compositePrimaryKeys": { | 1646 | "compositePrimaryKeys": { |
| 1697 | "tool_policy_scope_id_id_pk": { | 1647 | "tool_policy_scope_id_id_pk": { |
| 1698 | "name": "tool_policy_scope_id_id_pk", | 1648 | "name": "tool_policy_scope_id_id_pk", |
| 1699 | "columns": [ | 1649 | "columns": ["scope_id", "id"] |
| 1700 | "scope_id", | ||
| 1701 | "id" | ||
| 1702 | ] | ||
| 1703 | } | 1650 | } |
| 1704 | }, | 1651 | }, |
| 1705 | "uniqueConstraints": {}, | 1652 | "uniqueConstraints": {}, |
| @@ -1763,10 +1710,7 @@ · 57 unmodified lines | |||
| 1763 | "compositePrimaryKeys": { | 1710 | "compositePrimaryKeys": { |
| 1764 | "workos_vault_metadata_scope_id_id_pk": { | 1711 | "workos_vault_metadata_scope_id_id_pk": { |
| 1765 | "name": "workos_vault_metadata_scope_id_id_pk", | 1712 | "name": "workos_vault_metadata_scope_id_id_pk", |
| 1766 | "columns": [ | 1713 | "columns": ["scope_id", "id"] |
| 1767 | "scope_id", | ||
| 1768 | "id" | ||
| 1769 | ] | ||
| 1770 | } | 1714 | } |
| 1771 | }, | 1715 | }, |
| 1772 | "uniqueConstraints": {}, | 1716 | "uniqueConstraints": {}, |
| @@ -1878,10 +1822,7 @@ · 105 unmodified lines | |||
| 1878 | "compositePrimaryKeys": { | 1822 | "compositePrimaryKeys": { |
| 1879 | "graphql_source_header_scope_id_id_pk": { | 1823 | "graphql_source_header_scope_id_id_pk": { |
| 1880 | "name": "graphql_source_header_scope_id_id_pk", | 1824 | "name": "graphql_source_header_scope_id_id_pk", |
| 1881 | "columns": [ | 1825 | "columns": ["scope_id", "id"] |
| 1882 | "scope_id", | ||
| 1883 | "id" | ||
| 1884 | ] | ||
| 1885 | } | 1826 | } |
| 1886 | }, | 1827 | }, |
| 1887 | "uniqueConstraints": {}, | 1828 | "uniqueConstraints": {}, |
| @@ -1993,10 +1934,7 @@ · 105 unmodified lines | |||
| 1993 | "compositePrimaryKeys": { | 1934 | "compositePrimaryKeys": { |
| 1994 | "graphql_source_query_param_scope_id_id_pk": { | 1935 | "graphql_source_query_param_scope_id_id_pk": { |
| 1995 | "name": "graphql_source_query_param_scope_id_id_pk", | 1936 | "name": "graphql_source_query_param_scope_id_id_pk", |
| 1996 | "columns": [ | 1937 | "columns": ["scope_id", "id"] |
| 1997 | "scope_id", | ||
| 1998 | "id" | ||
| 1999 | ] | ||
| 2000 | } | 1938 | } |
| 2001 | }, | 1939 | }, |
| 2002 | "uniqueConstraints": {}, | 1940 | "uniqueConstraints": {}, |
| @@ -2108,10 +2046,7 @@ · 105 unmodified lines | |||
| 2108 | "compositePrimaryKeys": { | 2046 | "compositePrimaryKeys": { |
| 2109 | "openapi_source_query_param_scope_id_id_pk": { | 2047 | "openapi_source_query_param_scope_id_id_pk": { |
| 2110 | "name": "openapi_source_query_param_scope_id_id_pk", | 2048 | "name": "openapi_source_query_param_scope_id_id_pk", |
| 2111 | "columns": [ | 2049 | "columns": ["scope_id", "id"] |
| 2112 | "scope_id", | ||
| 2113 | "id" | ||
| 2114 | ] | ||
| 2115 | } | 2050 | } |
| 2116 | }, | 2051 | }, |
| 2117 | "uniqueConstraints": {}, | 2052 | "uniqueConstraints": {}, |
| @@ -2223,10 +2158,7 @@ · 105 unmodified lines | |||
| 2223 | "compositePrimaryKeys": { | 2158 | "compositePrimaryKeys": { |
| 2224 | "openapi_source_spec_fetch_header_scope_id_id_pk": { | 2159 | "openapi_source_spec_fetch_header_scope_id_id_pk": { |
| 2225 | "name": "openapi_source_spec_fetch_header_scope_id_id_pk", | 2160 | "name": "openapi_source_spec_fetch_header_scope_id_id_pk", |
| 2226 | "columns": [ | 2161 | "columns": ["scope_id", "id"] |
| 2227 | "scope_id", | ||
| 2228 | "id" | ||
| 2229 | ] | ||
| 2230 | } | 2162 | } |
| 2231 | }, | 2163 | }, |
| 2232 | "uniqueConstraints": {}, | 2164 | "uniqueConstraints": {}, |
| @@ -2338,10 +2270,7 @@ · 105 unmodified lines | |||
| 2338 | "compositePrimaryKeys": { | 2270 | "compositePrimaryKeys": { |
| 2339 | "openapi_source_spec_fetch_query_param_scope_id_id_pk": { | 2271 | "openapi_source_spec_fetch_query_param_scope_id_id_pk": { |
| 2340 | "name": "openapi_source_spec_fetch_query_param_scope_id_id_pk", | 2272 | "name": "openapi_source_spec_fetch_query_param_scope_id_id_pk", |
| 2341 | "columns": [ | 2273 | "columns": ["scope_id", "id"] |
| 2342 | "scope_id", | ||
| 2343 | "id" | ||
| 2344 | ] | ||
| 2345 | } | 2274 | } |
| 2346 | }, | 2275 | }, |
| 2347 | "uniqueConstraints": {}, | 2276 | "uniqueConstraints": {}, |
| @@ -2453,10 +2382,7 @@ · 105 unmodified lines | |||
| 2453 | "compositePrimaryKeys": { | 2382 | "compositePrimaryKeys": { |
| 2454 | "mcp_source_header_scope_id_id_pk": { | 2383 | "mcp_source_header_scope_id_id_pk": { |
| 2455 | "name": "mcp_source_header_scope_id_id_pk", | 2384 | "name": "mcp_source_header_scope_id_id_pk", |
| 2456 | "columns": [ | 2385 | "columns": ["scope_id", "id"] |
| 2457 | "scope_id", | ||
| 2458 | "id" | ||
| 2459 | ] | ||
| 2460 | } | 2386 | } |
| 2461 | }, | 2387 | }, |
| 2462 | "uniqueConstraints": {}, | 2388 | "uniqueConstraints": {}, |
| @@ -2568,10 +2494,7 @@ · 105 unmodified lines | |||
| 2568 | "compositePrimaryKeys": { | 2494 | "compositePrimaryKeys": { |
| 2569 | "mcp_source_query_param_scope_id_id_pk": { | 2495 | "mcp_source_query_param_scope_id_id_pk": { |
| 2570 | "name": "mcp_source_query_param_scope_id_id_pk", | 2496 | "name": "mcp_source_query_param_scope_id_id_pk", |
| 2571 | "columns": [ | 2497 | "columns": ["scope_id", "id"] |
| 2572 | "scope_id", | ||
| 2573 | "id" | ||
| 2574 | ] | ||
| 2575 | } | 2498 | } |
| 2576 | }, | 2499 | }, |
| 2577 | "uniqueConstraints": {}, | 2500 | "uniqueConstraints": {}, |
| @@ -2,10 +2,7 @@ import { defineExecutorConfig } from "@executor-js/sdk"; · 1 unmodified line | |||
| 2 | import { openApiPlugin } from "@executor-js/plugin-openapi"; | 2 | import { openApiPlugin } from "@executor-js/plugin-openapi"; |
| 3 | import { mcpPlugin } from "@executor-js/plugin-mcp"; | 3 | import { mcpPlugin } from "@executor-js/plugin-mcp"; |
| 4 | import { graphqlPlugin } from "@executor-js/plugin-graphql"; | 4 | import { graphqlPlugin } from "@executor-js/plugin-graphql"; |
| 5 | import { | 5 | import { workosVaultPlugin, type WorkOSVaultClient } from "@executor-js/plugin-workos-vault"; |
| 6 | workosVaultPlugin, | ||
| 7 | type WorkOSVaultClient, | ||
| 8 | } from "@executor-js/plugin-workos-vault"; | ||
| 9 | 6 | ||
| 10 | // --------------------------------------------------------------------------- | 7 | // --------------------------------------------------------------------------- |
| 11 | // Single source of truth for the cloud app's plugin list. | 8 | // Single source of truth for the cloud app's plugin list. |
| @@ -29,15 +29,9 @@ import { RequestScopedServicesLive } from "./api/layers"; · 28 unmodified lines | |||
| 29 | import { requestScopedMiddleware } from "./api/request-scoped"; | 29 | import { requestScopedMiddleware } from "./api/request-scoped"; |
| 30 | import { makeApiLive } from "./api/router"; | 30 | import { makeApiLive } from "./api/router"; |
| 31 | 31 | ||
| 32 | class Counter extends Context.Service< | 32 | class Counter extends Context.Service<Counter, { readonly id: number }>()("test/Counter") {} |
| 33 | Counter, | 33 | |
| 34 | { readonly id: number } | 34 | const makeCounterLive = (counts: { acquires: number; releases: number }, acquireDelayMs = 0) => |
| 35 | >()("test/Counter") {} | ||
| 36 | |||
| 37 | const makeCounterLive = ( | ||
| 38 | counts: { acquires: number; releases: number }, | ||
| 39 | acquireDelayMs = 0, | ||
| 40 | ) => | ||
| 41 | Layer.effect(Counter)( | 35 | Layer.effect(Counter)( |
| 42 | Effect.acquireRelease( | 36 | Effect.acquireRelease( |
| 43 | Effect.gen(function* () { | 37 | Effect.gen(function* () { |
| @@ -72,8 +66,7 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 28 unmodified lines | |||
| 72 | Layer.provideMerge(makeCounterLive(counts)), | 66 | Layer.provideMerge(makeCounterLive(counts)), |
| 73 | Layer.provideMerge(HttpServer.layerServices), | 67 | Layer.provideMerge(HttpServer.layerServices), |
| 74 | ); | 68 | ); |
| 75 | const handler = HttpRouter.toWebHandler(App, { disableLogger: true }) | 69 | const handler = HttpRouter.toWebHandler(App, { disableLogger: true }).handler; |
| 76 | .handler; | ||
| 77 | 70 | ||
| 78 | const a = await handler(new Request("http://test.local/")); | 71 | const a = await handler(new Request("http://test.local/")); |
| 79 | const b = await handler(new Request("http://test.local/")); | 72 | const b = await handler(new Request("http://test.local/")); |
| @@ -91,8 +84,7 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 11 unmodified lines | |||
| 91 | HttpRouter.provideRequest(makeCounterLive(counts)), | 84 | HttpRouter.provideRequest(makeCounterLive(counts)), |
| 92 | Layer.provideMerge(HttpServer.layerServices), | 85 | Layer.provideMerge(HttpServer.layerServices), |
| 93 | ); | 86 | ); |
| 94 | const handler = HttpRouter.toWebHandler(App, { disableLogger: true }) | 87 | const handler = HttpRouter.toWebHandler(App, { disableLogger: true }).handler; |
| 95 | .handler; | ||
| 96 | 88 | ||
| 97 | const a = await handler(new Request("http://test.local/")); | 89 | const a = await handler(new Request("http://test.local/")); |
| 98 | const b = await handler(new Request("http://test.local/")); | 90 | const b = await handler(new Request("http://test.local/")); |
| @@ -111,8 +103,7 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 12 unmodified lines | |||
| 111 | Layer.provide(requestScopedMiddleware(makeCounterLive(counts)).layer), | 103 | Layer.provide(requestScopedMiddleware(makeCounterLive(counts)).layer), |
| 112 | Layer.provideMerge(HttpServer.layerServices), | 104 | Layer.provideMerge(HttpServer.layerServices), |
| 113 | ); | 105 | ); |
| 114 | const handler = HttpRouter.toWebHandler(App, { disableLogger: true }) | 106 | const handler = HttpRouter.toWebHandler(App, { disableLogger: true }).handler; |
| 115 | .handler; | ||
| 116 | 107 | ||
| 117 | const a = await handler(new Request("http://test.local/")); | 108 | const a = await handler(new Request("http://test.local/")); |
| 118 | const b = await handler(new Request("http://test.local/")); | 109 | const b = await handler(new Request("http://test.local/")); |
| @@ -144,8 +135,7 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 25 unmodified lines | |||
| 144 | Layer.provide(requestScopedMiddleware(makeCounterLive(counts, 5)).layer), | 135 | Layer.provide(requestScopedMiddleware(makeCounterLive(counts, 5)).layer), |
| 145 | Layer.provideMerge(HttpServer.layerServices), | 136 | Layer.provideMerge(HttpServer.layerServices), |
| 146 | ); | 137 | ); |
| 147 | const handler = HttpRouter.toWebHandler(App, { disableLogger: true }) | 138 | const handler = HttpRouter.toWebHandler(App, { disableLogger: true }).handler; |
| 148 | .handler; | ||
| 149 | 139 | ||
| 150 | const [a, b] = await Promise.all([ | 140 | const [a, b] = await Promise.all([ |
| 151 | handler(new Request("http://test.local/")), | 141 | handler(new Request("http://test.local/")), |
| @@ -93,9 +93,7 @@ const TestProtectedGate = HttpRouter.middleware()((httpEffect) => · 92 unmodified lines | |||
| 93 | ); | 93 | ); |
| 94 | } | 94 | } |
| 95 | if (testState.mode === "error") { | 95 | if (testState.mode === "error") { |
| 96 | return Effect.succeed( | 96 | return Effect.succeed(HttpServerResponse.jsonUnsafe({ error: "boom" }, { status: 500 })); |
| 97 | HttpServerResponse.jsonUnsafe({ error: "boom" }, { status: 500 }), | ||
| 98 | ); | ||
| 99 | } | 97 | } |
| 100 | if (testState.mode === "bad-status") { | 98 | if (testState.mode === "bad-status") { |
| 101 | return Effect.succeed( | 99 | return Effect.succeed( |
| @@ -130,10 +128,7 @@ const TestApiLive = Layer.mergeAll( · 28 unmodified lines | |||
| 130 | AuthTestLive, | 128 | AuthTestLive, |
| 131 | ProtectedTestLive, | 129 | ProtectedTestLive, |
| 132 | AutumnTestRoutesLive, | 130 | AutumnTestRoutesLive, |
| 133 | ).pipe( | 131 | ).pipe(Layer.provideMerge(RouterConfig), Layer.provideMerge(HttpServer.layerServices)); |
| 134 | Layer.provideMerge(RouterConfig), | ||
| 135 | Layer.provideMerge(HttpServer.layerServices), | ||
| 136 | ); | ||
| 137 | 132 | ||
| 138 | const requestHandler = HttpRouter.toWebHandler(TestApiLive, { disableLogger: true }).handler; | 133 | const requestHandler = HttpRouter.toWebHandler(TestApiLive, { disableLogger: true }).handler; |
| 139 | 134 | ||
| @@ -1,18 +1,10 @@ | |||
| 1 | import { env } from "cloudflare:workers"; | 1 | import { env } from "cloudflare:workers"; |
| 2 | import { Cause, Effect } from "effect"; | 2 | import { Cause, Effect } from "effect"; |
| 3 | import { | 3 | import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; |
| 4 | HttpRouter, | ||
| 5 | HttpServerRequest, | ||
| 6 | HttpServerResponse, | ||
| 7 | } from "effect/unstable/http"; | ||
| 8 | import { autumnHandler } from "autumn-js/backend"; | 4 | import { autumnHandler } from "autumn-js/backend"; |
| 9 | 5 | ||
| 10 | import { WorkOSAuth } from "../auth/workos"; | 6 | import { WorkOSAuth } from "../auth/workos"; |
| 11 | import { | 7 | import { HttpResponseError, isServerError, toErrorServerResponse } from "./error-response"; |
| 12 | HttpResponseError, | ||
| 13 | isServerError, | ||
| 14 | toErrorServerResponse, | ||
| 15 | } from "./error-response"; | ||
| 16 | 8 | ||
| 17 | const handler = Effect.gen(function* () { | 9 | const handler = Effect.gen(function* () { |
| 18 | const request = yield* HttpServerRequest.HttpServerRequest; | 10 | const request = yield* HttpServerRequest.HttpServerRequest; |
| @@ -1,8 +1,5 @@ | |||
| 1 | import { Cause, Data, Effect, Predicate, Result } from "effect"; | 1 | import { Cause, Data, Effect, Predicate, Result } from "effect"; |
| 2 | import { | 2 | import { HttpServerRespondable, HttpServerResponse } from "effect/unstable/http"; |
| 3 | HttpServerRespondable, | ||
| 4 | HttpServerResponse, | ||
| 5 | } from "effect/unstable/http"; | ||
| 6 | 3 | ||
| 7 | import { captureCause } from "../observability"; | 4 | import { captureCause } from "../observability"; |
| 8 | 5 | ||
| @@ -18,11 +18,7 @@ import { CoreSharedServices } from "./core-shared-services"; · 17 unmodified lines | |||
| 18 | import { ProtectedCloudApi, RouterConfig } from "./protected-layers"; | 18 | import { ProtectedCloudApi, RouterConfig } from "./protected-layers"; |
| 19 | import { requestScopedMiddleware } from "./request-scoped"; | 19 | import { requestScopedMiddleware } from "./request-scoped"; |
| 20 | 20 | ||
| 21 | export { | 21 | export { CoreSharedServices, ProtectedCloudApi, RouterConfig }; |
| 22 | CoreSharedServices, | ||
| 23 | ProtectedCloudApi, | ||
| 24 | RouterConfig, | ||
| 25 | }; | ||
| 26 | 22 | ||
| 27 | const DbLive = DbService.Live; | 23 | const DbLive = DbService.Live; |
| 28 | const UserStoreLive = UserStoreService.Live.pipe(Layer.provide(DbLive)); | 24 | const UserStoreLive = UserStoreService.Live.pipe(Layer.provide(DbLive)); |
| @@ -51,9 +47,7 @@ export const BootSharedServices = Layer.mergeAll( · 22 unmodified lines | |||
| 51 | // without per-request scoping the postgres.js socket pins to the worker's | 47 | // without per-request scoping the postgres.js socket pins to the worker's |
| 52 | // boot scope and Cloudflare Workers' I/O isolation kills the second | 48 | // boot scope and Cloudflare Workers' I/O isolation kills the second |
| 53 | // request. | 49 | // request. |
| 54 | export const makeNonProtectedApiLive = ( | 50 | export const makeNonProtectedApiLive = (rsLive: Layer.Layer<DbService | UserStoreService>) => |
| 55 | rsLive: Layer.Layer<DbService | UserStoreService>, | ||
| 56 | ) => | ||
| 57 | HttpApiBuilder.layer(NonProtectedApi).pipe( | 51 | HttpApiBuilder.layer(NonProtectedApi).pipe( |
| 58 | Layer.provide(Layer.mergeAll(CloudAuthPublicHandlers, CloudSessionAuthHandlers)), | 52 | Layer.provide(Layer.mergeAll(CloudAuthPublicHandlers, CloudSessionAuthHandlers)), |
| 59 | Layer.provide(requestScopedMiddleware(rsLive).layer), | 53 | Layer.provide(requestScopedMiddleware(rsLive).layer), |
| @@ -62,9 +56,7 @@ export const makeNonProtectedApiLive = ( · 2 unmodified lines | |||
| 62 | 56 | ||
| 63 | // Routes scoped to a specific org (membership management, switching, etc.). | 57 | // Routes scoped to a specific org (membership management, switching, etc.). |
| 64 | // Auth is enforced by `OrgAuth` middleware declared on `OrgHttpApi`. | 58 | // Auth is enforced by `OrgAuth` middleware declared on `OrgHttpApi`. |
| 65 | export const makeOrgApiLive = ( | 59 | export const makeOrgApiLive = (rsLive: Layer.Layer<DbService | UserStoreService>) => |
| 66 | rsLive: Layer.Layer<DbService | UserStoreService>, | ||
| 67 | ) => | ||
| 68 | HttpApiBuilder.layer(OrgHttpApi).pipe( | 60 | HttpApiBuilder.layer(OrgHttpApi).pipe( |
| 69 | Layer.provide(OrgHandlers), | 61 | Layer.provide(OrgHandlers), |
| 70 | Layer.provide(requestScopedMiddleware(rsLive).layer), | 62 | Layer.provide(requestScopedMiddleware(rsLive).layer), |
| @@ -74,7 +66,5 @@ export const makeOrgApiLive = ( · 3 unmodified lines | |||
| 74 | // Default exports use the production per-request layer. Existing callers | 66 | // Default exports use the production per-request layer. Existing callers |
| 75 | // that import `NonProtectedApiLive`/`OrgApiLive` continue to work; the | 67 | // that import `NonProtectedApiLive`/`OrgApiLive` continue to work; the |
| 76 | // `make*` factories exist for tests that need to swap in a fake. | 68 | // `make*` factories exist for tests that need to swap in a fake. |
| 77 | export const NonProtectedApiLive = makeNonProtectedApiLive( | 69 | export const NonProtectedApiLive = makeNonProtectedApiLive(RequestScopedServicesLive); |
| 78 | RequestScopedServicesLive, | ||
| 79 | ); | ||
| 80 | export const OrgApiLive = makeOrgApiLive(RequestScopedServicesLive); | 70 | export const OrgApiLive = makeOrgApiLive(RequestScopedServicesLive); |
| @@ -8,11 +8,7 @@ import { HttpRouter, HttpServer } from "effect/unstable/http"; · 7 unmodified lines | |||
| 8 | import { Layer } from "effect"; | 8 | import { Layer } from "effect"; |
| 9 | 9 | ||
| 10 | import { observabilityMiddleware } from "@executor-js/api"; | 10 | import { observabilityMiddleware } from "@executor-js/api"; |
| 11 | import { | 11 | import { CoreHandlers, composePluginApi, composePluginHandlerLayer } from "@executor-js/api/server"; |
| 12 | CoreHandlers, | ||
| 13 | composePluginApi, | ||
| 14 | composePluginHandlerLayer, | ||
| 15 | } from "@executor-js/api/server"; | ||
| 16 | 12 | ||
| 17 | import { cloudPlugins } from "./cloud-plugins"; | 13 | import { cloudPlugins } from "./cloud-plugins"; |
| 18 | import { UserStoreService } from "../auth/context"; | 14 | import { UserStoreService } from "../auth/context"; |
| @@ -3,10 +3,7 @@ · 2 unmodified lines | |||
| 3 | // harness can't load in the workerd test runtime. | 3 | // harness can't load in the workerd test runtime. |
| 4 | 4 | ||
| 5 | import { HttpApiSwagger } from "effect/unstable/httpapi"; | 5 | import { HttpApiSwagger } from "effect/unstable/httpapi"; |
| 6 | import { | 6 | import { HttpRouter, HttpServerRequest } from "effect/unstable/http"; |
| 7 | HttpRouter, | ||
| 8 | HttpServerRequest, | ||
| 9 | } from "effect/unstable/http"; | ||
| 10 | import { Effect, Layer } from "effect"; | 7 | import { Effect, Layer } from "effect"; |
| 11 | 8 | ||
| 12 | import { | 9 | import { |
| @@ -26,11 +23,7 @@ import { DbService } from "../services/db"; · 13 unmodified lines | |||
| 26 | import { makeExecutionStack } from "../services/execution-stack"; | 23 | import { makeExecutionStack } from "../services/execution-stack"; |
| 27 | import { HttpResponseError } from "./error-response"; | 24 | import { HttpResponseError } from "./error-response"; |
| 28 | import { RequestScopedServicesLive } from "./layers"; | 25 | import { RequestScopedServicesLive } from "./layers"; |
| 29 | import { | 26 | import { ProtectedCloudApi, ProtectedCloudApiLive, RouterConfig } from "./protected-layers"; |
| 30 | ProtectedCloudApi, | ||
| 31 | ProtectedCloudApiLive, | ||
| 32 | RouterConfig, | ||
| 33 | } from "./protected-layers"; | ||
| 34 | import { requestScopedMiddleware } from "./request-scoped"; | 27 | import { requestScopedMiddleware } from "./request-scoped"; |
| 35 | 28 | ||
| 36 | // Pre-compute the per-plugin `Effect.provideService(extensionService, | 29 | // Pre-compute the per-plugin `Effect.provideService(extensionService, |
| @@ -119,9 +112,7 @@ const ExecutionStackMiddleware = HttpRouter.middleware<{ · 82 unmodified lines | |||
| 119 | // the layer rebuilds per HTTP request, satisfying Cloudflare Workers' | 112 | // the layer rebuilds per HTTP request, satisfying Cloudflare Workers' |
| 120 | // I/O isolation. Exposed as a factory so tests can swap in a counting | 113 | // I/O isolation. Exposed as a factory so tests can swap in a counting |
| 121 | // fake — see `apps/cloud/src/api.request-scope.node.test.ts`. | 114 | // fake — see `apps/cloud/src/api.request-scope.node.test.ts`. |
| 122 | export const makeProtectedApiLive = ( | 115 | export const makeProtectedApiLive = (rsLive: Layer.Layer<DbService | UserStoreService>) => { |
| 123 | rsLive: Layer.Layer<DbService | UserStoreService>, | ||
| 124 | ) => { | ||
| 125 | const protectedMiddleware = ExecutionStackMiddleware.combine( | 116 | const protectedMiddleware = ExecutionStackMiddleware.combine( |
| 126 | requestScopedMiddleware(rsLive), | 117 | requestScopedMiddleware(rsLive), |
| 127 | ).layer; | 118 | ).layer; |
| @@ -25,18 +25,13 @@ import { SlackContactRoutesLive } from "./slack"; · 24 unmodified lines | |||
| 25 | // so tests can substitute a counting fake for `DbService.Live` and | 25 | // so tests can substitute a counting fake for `DbService.Live` and |
| 26 | // assert per-request semantics — see | 26 | // assert per-request semantics — see |
| 27 | // `apps/cloud/src/api.request-scope.node.test.ts`. | 27 | // `apps/cloud/src/api.request-scope.node.test.ts`. |
| 28 | export const makeApiLive = ( | 28 | export const makeApiLive = (requestScopedLive: Layer.Layer<DbService | UserStoreService>) => |
| 29 | requestScopedLive: Layer.Layer<DbService | UserStoreService>, | ||
| 30 | ) => | ||
| 31 | Layer.mergeAll( | 29 | Layer.mergeAll( |
| 32 | makeNonProtectedApiLive(requestScopedLive), | 30 | makeNonProtectedApiLive(requestScopedLive), |
| 33 | makeOrgApiLive(requestScopedLive), | 31 | makeOrgApiLive(requestScopedLive), |
| 34 | makeProtectedApiLive(requestScopedLive), | 32 | makeProtectedApiLive(requestScopedLive), |
| 35 | AutumnRoutesLive, | 33 | AutumnRoutesLive, |
| 36 | SlackContactRoutesLive, | 34 | SlackContactRoutesLive, |
| 37 | ).pipe( | 35 | ).pipe(Layer.provideMerge(RouterConfig), Layer.provideMerge(BootSharedServices)); |
| 38 | Layer.provideMerge(RouterConfig), | ||
| 39 | Layer.provideMerge(BootSharedServices), | ||
| 40 | ); | ||
| 41 | 36 | ||
| 42 | export const ApiLive = makeApiLive(RequestScopedServicesLive); | 37 | export const ApiLive = makeApiLive(RequestScopedServicesLive); |
| @@ -3,11 +3,7 @@ import { Cause, Effect } from "effect"; · 2 unmodified lines | |||
| 3 | import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; | 3 | import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; |
| 4 | 4 | ||
| 5 | import { SlackService } from "../services/slack"; | 5 | import { SlackService } from "../services/slack"; |
| 6 | import { | 6 | import { HttpResponseError, isServerError, toErrorServerResponse } from "./error-response"; |
| 7 | HttpResponseError, | ||
| 8 | isServerError, | ||
| 9 | toErrorServerResponse, | ||
| 10 | } from "./error-response"; | ||
| 11 | 7 | ||
| 12 | const isValidEmail = (s: string): boolean => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s); | 8 | const isValidEmail = (s: string): boolean => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s); |
| 13 | 9 | ||
| @@ -23,10 +23,9 @@ const makeService = (store: RawStore) => ({ · 22 unmodified lines | |||
| 23 | 23 | ||
| 24 | type UserStoreServiceType = ReturnType<typeof makeService>; | 24 | type UserStoreServiceType = ReturnType<typeof makeService>; |
| 25 | 25 | ||
| 26 | export class UserStoreService extends Context.Service< | 26 | export class UserStoreService extends Context.Service<UserStoreService, UserStoreServiceType>()( |
| 27 | UserStoreService, | 27 | "@executor-js/cloud/UserStoreService", |
| 28 | UserStoreServiceType | 28 | ) { |
| 29 | >()("@executor-js/cloud/UserStoreService") { | ||
| 30 | static Live = Layer.effect(this)( | 29 | static Live = Layer.effect(this)( |
| 31 | Effect.map(DbService.asEffect(), ({ db }) => makeService(makeUserStore(db))), | 30 | Effect.map(DbService.asEffect(), ({ db }) => makeService(makeUserStore(db))), |
| 32 | ); | 31 | ); |
| @@ -304,8 +304,7 @@ export const CloudSessionAuthHandlers = HttpApiBuilder.group( · 303 unmodified lines | |||
| 304 | ? yield* workos.getUser(inv.inviterUserId).pipe( | 304 | ? yield* workos.getUser(inv.inviterUserId).pipe( |
| 305 | Effect.map((u) => ({ | 305 | Effect.map((u) => ({ |
| 306 | email: u.email, | 306 | email: u.email, |
| 307 | name: | 307 | name: [u.firstName, u.lastName].filter(Boolean).join(" ") || null, |
| 308 | [u.firstName, u.lastName].filter(Boolean).join(" ") || null, | ||
| 309 | })), | 308 | })), |
| 310 | Effect.orElseSucceed(() => null), | 309 | Effect.orElseSucceed(() => null), |
| 311 | ) | 310 | ) |
| @@ -354,20 +353,15 @@ export const CloudSessionAuthHandlers = HttpApiBuilder.group( · 42 unmodified lines | |||
| 354 | // through login again. The acceptance has already succeeded | 353 | // through login again. The acceptance has already succeeded |
| 355 | // server-side, so the next login will pick up the membership. | 354 | // server-side, so the next login will pick up the membership. |
| 356 | const refreshed = yield* workos.refreshSession(session.sealedSession, org.id); | 355 | const refreshed = yield* workos.refreshSession(session.sealedSession, org.id); |
| 357 | const verified = refreshed | 356 | const verified = refreshed ? yield* workos.authenticateSealedSession(refreshed) : null; |
| 358 | ? yield* workos.authenticateSealedSession(refreshed) | ||
| 359 | : null; | ||
| 360 | 357 | ||
| 361 | if (!refreshed || !verified || verified.organizationId !== org.id) { | 358 | if (!refreshed || !verified || verified.organizationId !== org.id) { |
| 362 | yield* Effect.logWarning( | 359 | yield* Effect.logWarning("acceptInvitation: unable to attach org to current session", { |
| 363 | "acceptInvitation: unable to attach org to current session", | 360 | userId: session.accountId, |
| 364 | { | 361 | orgId: org.id, |
| 365 | userId: session.accountId, | 362 | refreshReturnedSession: refreshed != null, |
| 366 | orgId: org.id, | 363 | verifiedOrgId: verified?.organizationId ?? null, |
| 367 | refreshReturnedSession: refreshed != null, | 364 | }); |
| 368 | verifiedOrgId: verified?.organizationId ?? null, | ||
| 369 | }, | ||
| 370 | ); | ||
| 371 | deleteCookie("wos-session", { path: "/" }); | 365 | deleteCookie("wos-session", { path: "/" }); |
| 372 | return yield* new WorkOSError(); | 366 | return yield* new WorkOSError(); |
| 373 | } | 367 | } |
| @@ -23,8 +23,9 @@ export type Session = { · 22 unmodified lines | |||
| 23 | readonly refreshedSession: string | null; | 23 | readonly refreshedSession: string | null; |
| 24 | }; | 24 | }; |
| 25 | 25 | ||
| 26 | export class SessionContext extends Context.Service<SessionContext, Session | 26 | export class SessionContext extends Context.Service<SessionContext, Session>()( |
| 27 | >()("@executor-js/cloud/Session") {} | 27 | "@executor-js/cloud/Session", |
| 28 | ) {} | ||
| 28 | 29 | ||
| 29 | // --------------------------------------------------------------------------- | 30 | // --------------------------------------------------------------------------- |
| 30 | // Errors | 31 | // Errors |
| @@ -71,12 +72,12 @@ export class AuthContext extends Context.Service< · 40 unmodified lines | |||
| 71 | } | 72 | } |
| 72 | >()("@executor-js/cloud/AuthContext") {} | 73 | >()("@executor-js/cloud/AuthContext") {} |
| 73 | 74 | ||
| 74 | export class OrgAuth extends HttpApiMiddleware.Service< | 75 | export class OrgAuth extends HttpApiMiddleware.Service<OrgAuth, { provides: AuthContext }>()( |
| 75 | OrgAuth, | 76 | "OrgAuth", |
| 76 | { provides: AuthContext } | 77 | { |
| 77 | >()("OrgAuth", { | 78 | error: [Unauthorized, NoOrganization], |
| 78 | error: [Unauthorized, NoOrganization], | 79 | security: { |
| 79 | security: { | 80 | cookie: HttpApiSecurity.apiKey({ in: "cookie", key: "wos-session" }), |
| 80 | cookie: HttpApiSecurity.apiKey({ in: "cookie", key: "wos-session" }), | 81 | }, |
| 81 | }, | 82 | }, |
| 82 | }) {} | 83 | ) {} |
| @@ -24,7 +24,5 @@ export const resolveOrganization = (organizationId: string) => · 23 unmodified lines | |||
| 24 | 24 | ||
| 25 | const workos = yield* WorkOSAuth; | 25 | const workos = yield* WorkOSAuth; |
| 26 | const fresh = yield* workos.getOrganization(organizationId); | 26 | const fresh = yield* workos.getOrganization(organizationId); |
| 27 | return yield* users.use((s) => | 27 | return yield* users.use((s) => s.upsertOrganization({ id: fresh.id, name: fresh.name })); |
| 28 | s.upsertOrganization({ id: fresh.id, name: fresh.name }), | ||
| 29 | ); | ||
| 30 | }); | 28 | }); |
| @@ -8,8 +8,7 @@ import { GeneratePortalLinkIntent, WorkOS } from "@workos-inc/node/worker"; · 7 unmodified lines | |||
| 8 | import { WorkOSError, tryPromiseService, withServiceLogging } from "./errors"; | 8 | import { WorkOSError, tryPromiseService, withServiceLogging } from "./errors"; |
| 9 | 9 | ||
| 10 | const COOKIE_NAME = "wos-session"; | 10 | const COOKIE_NAME = "wos-session"; |
| 11 | const INVALID_COOKIE_PASSWORD_MESSAGE = | 11 | const INVALID_COOKIE_PASSWORD_MESSAGE = "WORKOS_COOKIE_PASSWORD must be at least 32 characters"; |
| 12 | "WORKOS_COOKIE_PASSWORD must be at least 32 characters"; | ||
| 13 | 12 | ||
| 14 | class WorkOSAuthConfigurationError extends Data.TaggedError("WorkOSAuthConfigurationError")<{ | 13 | class WorkOSAuthConfigurationError extends Data.TaggedError("WorkOSAuthConfigurationError")<{ |
| 15 | readonly message: string; | 14 | readonly message: string; |
| @@ -247,10 +246,9 @@ const make = Effect.gen(function* () { · 231 unmodified lines | |||
| 247 | 246 | ||
| 248 | export type WorkOSAuthService = Effect.Success<typeof make>; | 247 | export type WorkOSAuthService = Effect.Success<typeof make>; |
| 249 | 248 | ||
| 250 | export class WorkOSAuth extends Context.Service< | 249 | export class WorkOSAuth extends Context.Service<WorkOSAuth, WorkOSAuthService>()( |
| 251 | WorkOSAuth, | 250 | "@executor-js/cloud/WorkOSAuth", |
| 252 | WorkOSAuthService | 251 | ) { |
| 253 | >()("@executor-js/cloud/WorkOSAuth") { | ||
| 254 | static Default = Layer.effect(this)(make).pipe( | 252 | static Default = Layer.effect(this)(make).pipe( |
| 255 | Layer.withSpan("WorkOSAuth", { attributes: { module: "WorkOSAuth" } }), | 253 | Layer.withSpan("WorkOSAuth", { attributes: { module: "WorkOSAuth" } }), |
| 256 | ); | 254 | ); |
| @@ -102,9 +102,7 @@ describe("createCachedRemoteJWKSet", () => { · 101 unmodified lines | |||
| 102 | const jwks = createCachedRemoteJWKSet(jwksUrl, { fetch }); | 102 | const jwks = createCachedRemoteJWKSet(jwksUrl, { fetch }); |
| 103 | const token = await sign(kp); | 103 | const token = await sign(kp); |
| 104 | 104 | ||
| 105 | const verifies = Array.from({ length: 10 }, () => | 105 | const verifies = Array.from({ length: 10 }, () => jwtVerify(token, jwks, { issuer, audience })); |
| 106 | jwtVerify(token, jwks, { issuer, audience }), | ||
| 107 | ); | ||
| 108 | // Let microtasks settle so all 10 calls hit the cache miss path. | 106 | // Let microtasks settle so all 10 calls hit the cache miss path. |
| 109 | await new Promise((r) => setTimeout(r, 10)); | 107 | await new Promise((r) => setTimeout(r, 10)); |
| 110 | resolveFetch(); | 108 | resolveFetch(); |
| @@ -36,10 +36,12 @@ describe("MCP AuthKit token verification", () => { · 35 unmodified lines | |||
| 36 | const { jwks, sign } = yield* Effect.promise(() => makeVerifier()); | 36 | const { jwks, sign } = yield* Effect.promise(() => makeVerifier()); |
| 37 | const token = yield* Effect.promise(() => sign({ aud: otherResource })); | 37 | const token = yield* Effect.promise(() => sign({ aud: otherResource })); |
| 38 | 38 | ||
| 39 | const error = yield* Effect.flip(verifyMcpAccessToken(token, jwks, { | 39 | const error = yield* Effect.flip( |
| 40 | issuer, | 40 | verifyMcpAccessToken(token, jwks, { |
| 41 | audience: resource, | 41 | issuer, |
| 42 | })); | 42 | audience: resource, |
| 43 | }), | ||
| 44 | ); | ||
| 43 | 45 | ||
| 44 | expect(error).toBeInstanceOf(McpJwtVerificationError); | 46 | expect(error).toBeInstanceOf(McpJwtVerificationError); |
| 45 | }), | 47 | }), |
| @@ -45,10 +45,11 @@ const SESSION_TIMEOUT_MS = 5 * 60 * 1000; · 44 unmodified lines | |||
| 45 | const SESSION_META_KEY = "session-meta"; | 45 | const SESSION_META_KEY = "session-meta"; |
| 46 | const LAST_ACTIVITY_KEY = "last-activity-ms"; | 46 | const LAST_ACTIVITY_KEY = "last-activity-ms"; |
| 47 | 47 | ||
| 48 | const doRuntimeControls = (instance: unknown): { | 48 | const doRuntimeControls = ( |
| 49 | instance: unknown, | ||
| 50 | ): { | ||
| 49 | closeRuntime: () => Effect.Effect<void>; | 51 | closeRuntime: () => Effect.Effect<void>; |
| 50 | } => | 52 | } => instance as { closeRuntime: () => Effect.Effect<void> }; |
| 51 | instance as { closeRuntime: () => Effect.Effect<void> }; | ||
| 52 | 53 | ||
| 53 | const doActivityState = (instance: unknown): { lastActivityMs: number } => | 54 | const doActivityState = (instance: unknown): { lastActivityMs: number } => |
| 54 | instance as { lastActivityMs: number }; | 55 | instance as { lastActivityMs: number }; |
| @@ -340,9 +341,7 @@ describe("/mcp session restore", () => { · 285 unmodified lines | |||
| 340 | const ns = env.MCP_SESSION; | 341 | const ns = env.MCP_SESSION; |
| 341 | const stub = ns.get(ns.idFromString(sessionId!)); | 342 | const stub = ns.get(ns.idFromString(sessionId!)); |
| 342 | await runInDurableObject(stub, async (instance) => { | 343 | await runInDurableObject(stub, async (instance) => { |
| 343 | await Effect.runPromise( | 344 | await Effect.runPromise(doRuntimeControls(instance).closeRuntime()); |
| 344 | doRuntimeControls(instance).closeRuntime(), | ||
| 345 | ); | ||
| 346 | }); | 345 | }); |
| 347 | 346 | ||
| 348 | const response = await mcpPost({ | 347 | const response = await mcpPost({ |
| @@ -376,9 +375,7 @@ describe("/mcp session restore", () => { · 27 unmodified lines | |||
| 376 | const ns = env.MCP_SESSION; | 375 | const ns = env.MCP_SESSION; |
| 377 | const stub = ns.get(ns.idFromString(sessionId!)); | 376 | const stub = ns.get(ns.idFromString(sessionId!)); |
| 378 | await runInDurableObject(stub, async (instance) => { | 377 | await runInDurableObject(stub, async (instance) => { |
| 379 | await Effect.runPromise( | 378 | await Effect.runPromise(doRuntimeControls(instance).closeRuntime()); |
| 380 | doRuntimeControls(instance).closeRuntime(), | ||
| 381 | ); | ||
| 382 | }); | 379 | }); |
| 383 | 380 | ||
| 384 | const getResponse = await mcpGet({ bearer, sessionId: sessionId! }); | 381 | const getResponse = await mcpGet({ bearer, sessionId: sessionId! }); |
| @@ -438,9 +435,7 @@ describe("/mcp session restore", () => { · 53 unmodified lines | |||
| 438 | const ns = env.MCP_SESSION; | 435 | const ns = env.MCP_SESSION; |
| 439 | const stub = ns.get(ns.idFromString(sessionId!)); | 436 | const stub = ns.get(ns.idFromString(sessionId!)); |
| 440 | await runInDurableObject(stub, async (instance) => { | 437 | await runInDurableObject(stub, async (instance) => { |
| 441 | await Effect.runPromise( | 438 | await Effect.runPromise(doRuntimeControls(instance).closeRuntime()); |
| 442 | doRuntimeControls(instance).closeRuntime(), | ||
| 443 | ); | ||
| 444 | }); | 439 | }); |
| 445 | 440 | ||
| 446 | const response = await mcpPost({ | 441 | const response = await mcpPost({ |
| @@ -33,10 +33,7 @@ import { · 32 unmodified lines | |||
| 33 | createExecutor, | 33 | createExecutor, |
| 34 | definePlugin, | 34 | definePlugin, |
| 35 | } from "@executor-js/sdk"; | 35 | } from "@executor-js/sdk"; |
| 36 | import { | 36 | import { makePostgresAdapter, makePostgresBlobStore } from "@executor-js/storage-postgres"; |
| 37 | makePostgresAdapter, | ||
| 38 | makePostgresBlobStore, | ||
| 39 | } from "@executor-js/storage-postgres"; | ||
| 40 | import { makeTestWorkOSVaultClient } from "@executor-js/plugin-workos-vault/testing"; | 37 | import { makeTestWorkOSVaultClient } from "@executor-js/plugin-workos-vault/testing"; |
| 41 | import executorConfig from "../executor.config"; | 38 | import executorConfig from "../executor.config"; |
| 42 | import { DbService } from "./services/db"; | 39 | import { DbService } from "./services/db"; |
| @@ -65,7 +62,11 @@ const elicitingTestPlugin = definePlugin(() => ({ · 22 unmodified lines | |||
| 65 | name: "needsApproval", | 62 | name: "needsApproval", |
| 66 | description: "Tool that asks the caller to approve before returning.", | 63 | description: "Tool that asks the caller to approve before returning.", |
| 67 | inputSchema: EMPTY_INPUT_SCHEMA, | 64 | inputSchema: EMPTY_INPUT_SCHEMA, |
| 68 | handler: ({ elicit }: { elicit: (r: FormElicitation) => Effect.Effect<typeof ElicitationResponse.Type, unknown> }) => | 65 | handler: ({ |
| 66 | elicit, | ||
| 67 | }: { | ||
| 68 | elicit: (r: FormElicitation) => Effect.Effect<typeof ElicitationResponse.Type, unknown>; | ||
| 69 | }) => | ||
| 69 | Effect.gen(function* () { | 70 | Effect.gen(function* () { |
| 70 | const response = yield* elicit( | 71 | const response = yield* elicit( |
| 71 | new FormElicitation({ | 72 | new FormElicitation({ |
| @@ -94,11 +95,7 @@ const ELICITATION_CAPS: ClientCapabilities = { · 22 unmodified lines | |||
| 94 | 95 | ||
| 95 | type BuildOptions = { readonly withElicitingPlugin?: boolean }; | 96 | type BuildOptions = { readonly withElicitingPlugin?: boolean }; |
| 96 | 97 | ||
| 97 | const buildScopedExecutor = ( | 98 | const buildScopedExecutor = (scopeId: string, scopeName: string, options: BuildOptions = {}) => |
| 98 | scopeId: string, | ||
| 99 | scopeName: string, | ||
| 100 | options: BuildOptions = {}, | ||
| 101 | ) => | ||
| 102 | Effect.gen(function* () { | 99 | Effect.gen(function* () { |
| 103 | const { db } = yield* DbService; | 100 | const { db } = yield* DbService; |
| 104 | const basePlugins = executorConfig.plugins({ | 101 | const basePlugins = executorConfig.plugins({ |
| @@ -381,69 +381,69 @@ function SlackContactCta() { · 380 unmodified lines | |||
| 381 | <span aria-hidden>→</span> | 381 | <span aria-hidden>→</span> |
| 382 | </Button> | 382 | </Button> |
| 383 | </DialogTrigger> | 383 | </DialogTrigger> |
| 384 | <DialogContent> | 384 | <DialogContent> |
| 385 | {inviteUrl ? ( | 385 | {inviteUrl ? ( |
| 386 | <> | 386 | <> |
| 387 | <DialogHeader> | 387 | <DialogHeader> |
| 388 | <DialogTitle>Check your inbox</DialogTitle> | 388 | <DialogTitle>Check your inbox</DialogTitle> |
| 389 | <DialogDescription> | 389 | <DialogDescription> |
| 390 | We've created a private Slack channel and emailed you an invite. You can also | 390 | We've created a private Slack channel and emailed you an invite. You can also |
| 391 | open it directly: | 391 | open it directly: |
| 392 | </DialogDescription> | 392 | </DialogDescription> |
| 393 | </DialogHeader> | 393 | </DialogHeader> |
| 394 | <a | 394 | <a |
| 395 | href={inviteUrl} | 395 | href={inviteUrl} |
| 396 | target="_blank" | 396 | target="_blank" |
| 397 | rel="noreferrer" | 397 | rel="noreferrer" |
| 398 | className="inline-flex h-9 w-full items-center justify-center gap-2 rounded-md bg-primary text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90" | 398 | className="inline-flex h-9 w-full items-center justify-center gap-2 rounded-md bg-primary text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90" |
| 399 | > | 399 | > |
| 400 | <SlackMark className="size-4" /> | 400 | <SlackMark className="size-4" /> |
| 401 | Open Slack invite | 401 | Open Slack invite |
| 402 | </a> | 402 | </a> |
| 403 | <DialogFooter> | 403 | <DialogFooter> |
| 404 | <DialogClose asChild> | 404 | <DialogClose asChild> |
| 405 | <Button type="button" variant="outline"> | 405 | <Button type="button" variant="outline"> |
| 406 | Done | 406 | Done |
| 407 | </Button> | 407 | </Button> |
| 408 | </DialogClose> | 408 | </DialogClose> |
| 409 | </DialogFooter> | 409 | </DialogFooter> |
| 410 | </> | 410 | </> |
| 411 | ) : ( | 411 | ) : ( |
| 412 | <form onSubmit={onSubmit}> | 412 | <form onSubmit={onSubmit}> |
| 413 | <DialogHeader> | 413 | <DialogHeader> |
| 414 | <DialogTitle>Get in touch on Slack</DialogTitle> | 414 | <DialogTitle>Get in touch on Slack</DialogTitle> |
| 415 | <DialogDescription> | 415 | <DialogDescription> |
| 416 | We'll create a private Slack Connect channel between you and the Executor team. | 416 | We'll create a private Slack Connect channel between you and the Executor team. |
| 417 | </DialogDescription> | 417 | </DialogDescription> |
| 418 | </DialogHeader> | 418 | </DialogHeader> |
| 419 | <div className="grid gap-4 py-4"> | 419 | <div className="grid gap-4 py-4"> |
| 420 | <div className="grid gap-2"> | 420 | <div className="grid gap-2"> |
| 421 | <Label htmlFor="slack-contact-email">Work email</Label> | 421 | <Label htmlFor="slack-contact-email">Work email</Label> |
| 422 | <Input | 422 | <Input |
| 423 | id="slack-contact-email" | 423 | id="slack-contact-email" |
| 424 | type="email" | 424 | type="email" |
| 425 | autoComplete="email" | 425 | autoComplete="email" |
| 426 | required | 426 | required |
| 427 | value={email} | 427 | value={email} |
| 428 | onChange={(e) => setEmail(e.currentTarget.value)} | 428 | onChange={(e) => setEmail(e.currentTarget.value)} |
| 429 | placeholder="you@company.com" | 429 | placeholder="you@company.com" |
| 430 | /> | 430 | /> |
| 431 | </div> | ||
| 432 | {error && <p className="text-sm text-destructive">{error}</p>} | ||
| 431 | </div> | 433 | </div> |
| 432 | {error && <p className="text-sm text-destructive">{error}</p>} | 434 | <DialogFooter> |
| 433 | </div> | 435 | <DialogClose asChild> |
| 434 | <DialogFooter> | 436 | <Button type="button" variant="outline" disabled={submitting}> |
| 435 | <DialogClose asChild> | 437 | Cancel |
| 436 | <Button type="button" variant="outline" disabled={submitting}> | 438 | </Button> |
| 437 | Cancel | 439 | </DialogClose> |
| 440 | <Button type="submit" disabled={submitting || !email}> | ||
| 441 | {submitting ? "Sending…" : "Send invite"} | ||
| 438 | </Button> | 442 | </Button> |
| 439 | </DialogClose> | 443 | </DialogFooter> |
| 440 | <Button type="submit" disabled={submitting || !email}> | 444 | </form> |
| 441 | {submitting ? "Sending…" : "Send invite"} | 445 | )} |
| 442 | </Button> | 446 | </DialogContent> |
| 443 | </DialogFooter> | ||
| 444 | </form> | ||
| 445 | )} | ||
| 446 | </DialogContent> | ||
| 447 | </Dialog> | 447 | </Dialog> |
| 448 | <span className="text-muted-foreground/60" aria-hidden> | 448 | <span className="text-muted-foreground/60" aria-hidden> |
| 449 | · | 449 | · |
| @@ -463,8 +463,19 @@ function SlackContactCta() { · 13 unmodified lines | |||
| 463 | 463 | ||
| 464 | function MailIcon({ className }: { className?: string }) { | 464 | function MailIcon({ className }: { className?: string }) { |
| 465 | return ( | 465 | return ( |
| 466 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className={className} aria-hidden> | 466 | <svg |
| 467 | <path strokeLinecap="round" strokeLinejoin="round" d="M3 7l9 6 9-6M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /> | 467 | viewBox="0 0 24 24" |
| 468 | fill="none" | ||
| 469 | stroke="currentColor" | ||
| 470 | strokeWidth="1.8" | ||
| 471 | className={className} | ||
| 472 | aria-hidden | ||
| 473 | > | ||
| 474 | <path | ||
| 475 | strokeLinecap="round" | ||
| 476 | strokeLinejoin="round" | ||
| 477 | d="M3 7l9 6 9-6M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" | ||
| 478 | /> | ||
| 468 | </svg> | 479 | </svg> |
| 469 | ); | 480 | ); |
| 470 | } | 481 | } |
| @@ -15,13 +15,6 @@ export const Route = createFileRoute("/sources/add/$pluginKey")({ · 14 unmodified lines | |||
| 15 | component: () => { | 15 | component: () => { |
| 16 | const { pluginKey } = Route.useParams(); | 16 | const { pluginKey } = Route.useParams(); |
| 17 | const { url, preset, namespace } = Route.useSearch(); | 17 | const { url, preset, namespace } = Route.useSearch(); |
| 18 | return ( | 18 | return <SourcesAddPage pluginKey={pluginKey} url={url} preset={preset} namespace={namespace} />; |
| 19 | <SourcesAddPage | ||
| 20 | pluginKey={pluginKey} | ||
| 21 | url={url} | ||
| 22 | preset={preset} | ||
| 23 | namespace={namespace} | ||
| 24 | /> | ||
| 25 | ); | ||
| 26 | }, | 19 | }, |
| 27 | }); | 20 | }); |
| @@ -31,192 +31,180 @@ import { Effect, Result } from "effect"; · 30 unmodified lines | |||
| 31 | 31 | ||
| 32 | import { ScopeId, SecretId } from "@executor-js/sdk"; | 32 | import { ScopeId, SecretId } from "@executor-js/sdk"; |
| 33 | 33 | ||
| 34 | import { | 34 | import { asUser, testUserOrgScopeId } from "./services/__test-harness__/api-harness"; |
| 35 | asUser, | ||
| 36 | testUserOrgScopeId, | ||
| 37 | } from "./services/__test-harness__/api-harness"; | ||
| 38 | 35 | ||
| 39 | const uniq = () => crypto.randomUUID().slice(0, 8); | 36 | const uniq = () => crypto.randomUUID().slice(0, 8); |
| 40 | const nextOrgId = () => `org_iso_${uniq()}`; | 37 | const nextOrgId = () => `org_iso_${uniq()}`; |
| 41 | const nextUserId = () => `user_iso_${uniq()}`; | 38 | const nextUserId = () => `user_iso_${uniq()}`; |
| 42 | 39 | ||
| 43 | describe("cloud secret isolation (HTTP, user-org scope stack)", () => { | 40 | describe("cloud secret isolation (HTTP, user-org scope stack)", () => { |
| 44 | it.effect( | 41 | it.effect("users in different orgs cannot read each other's org-scoped secrets", () => |
| 45 | "users in different orgs cannot read each other's org-scoped secrets", | 42 | Effect.gen(function* () { |
| 46 | () => | 43 | const orgA = nextOrgId(); |
| 47 | Effect.gen(function* () { | 44 | const orgB = nextOrgId(); |
| 48 | const orgA = nextOrgId(); | 45 | const alice = nextUserId(); |
| 49 | const orgB = nextOrgId(); | 46 | const charlie = nextUserId(); |
| 50 | const alice = nextUserId(); | 47 | const id = `sec_${uniq()}`; |
| 51 | const charlie = nextUserId(); | 48 | |
| 52 | const id = `sec_${uniq()}`; | 49 | yield* asUser(alice, orgA, (client) => |
| 53 | 50 | client.secrets.set({ | |
| 54 | yield* asUser(alice, orgA, (client) => | 51 | params: { scopeId: ScopeId.make(orgA) }, |
| 55 | client.secrets.set({ | 52 | payload: { |
| 56 | params: { scopeId: ScopeId.make(orgA) }, | 53 | id: SecretId.make(id), |
| 57 | payload: { | 54 | name: "Shared", |
| 58 | id: SecretId.make(id), | 55 | value: "alice-org-secret", |
| 59 | name: "Shared", | 56 | }, |
| 60 | value: "alice-org-secret", | 57 | }), |
| 61 | }, | 58 | ); |
| 62 | }), | ||
| 63 | ); | ||
| 64 | |||
| 65 | const charlieStatus = yield* asUser(charlie, orgB, (client) => | ||
| 66 | client.secrets.status({ | ||
| 67 | params: { scopeId: ScopeId.make(orgB), secretId: SecretId.make(id) }, | ||
| 68 | }), | ||
| 69 | ); | ||
| 70 | expect(charlieStatus.status).toBe("missing"); | ||
| 71 | 59 | ||
| 72 | const charlieList = yield* asUser(charlie, orgB, (client) => | 60 | const charlieStatus = yield* asUser(charlie, orgB, (client) => |
| 73 | client.secrets.list({ params: { scopeId: ScopeId.make(orgB) } }), | 61 | client.secrets.status({ |
| 74 | ); | 62 | params: { scopeId: ScopeId.make(orgB), secretId: SecretId.make(id) }, |
| 75 | expect(charlieList.map((s) => s.id)).not.toContain(id); | 63 | }), |
| 64 | ); | ||
| 65 | expect(charlieStatus.status).toBe("missing"); | ||
| 76 | 66 | ||
| 77 | }), | 67 | const charlieList = yield* asUser(charlie, orgB, (client) => |
| 68 | client.secrets.list({ params: { scopeId: ScopeId.make(orgB) } }), | ||
| 69 | ); | ||
| 70 | expect(charlieList.map((s) => s.id)).not.toContain(id); | ||
| 71 | }), | ||
| 78 | ); | 72 | ); |
| 79 | 73 | ||
| 80 | it.effect( | 74 | it.effect("users in same org cannot read each other's user-scoped secrets", () => |
| 81 | "users in same org cannot read each other's user-scoped secrets", | 75 | Effect.gen(function* () { |
| 82 | () => | 76 | const orgId = nextOrgId(); |
| 83 | Effect.gen(function* () { | 77 | const aliceId = nextUserId(); |
| 84 | const orgId = nextOrgId(); | 78 | const bobId = nextUserId(); |
| 85 | const aliceId = nextUserId(); | 79 | const id = `sec_${uniq()}`; |
| 86 | const bobId = nextUserId(); | 80 | |
| 87 | const id = `sec_${uniq()}`; | 81 | // Alice writes at her per-user scope — where OAuth tokens land. |
| 88 | 82 | yield* asUser(aliceId, orgId, (client) => | |
| 89 | // Alice writes at her per-user scope — where OAuth tokens land. | 83 | client.secrets.set({ |
| 90 | yield* asUser(aliceId, orgId, (client) => | 84 | params: { scopeId: ScopeId.make(testUserOrgScopeId(aliceId, orgId)) }, |
| 91 | client.secrets.set({ | 85 | payload: { |
| 92 | params: { scopeId: ScopeId.make(testUserOrgScopeId(aliceId, orgId)) }, | 86 | id: SecretId.make(id), |
| 93 | payload: { | 87 | name: "Alice's token", |
| 94 | id: SecretId.make(id), | 88 | value: "alice-token-value", |
| 95 | name: "Alice's token", | 89 | }, |
| 96 | value: "alice-token-value", | 90 | }), |
| 97 | }, | 91 | ); |
| 98 | }), | ||
| 99 | ); | ||
| 100 | 92 | ||
| 101 | // Bob is in the same org — his user-org scope differs. He should | 93 | // Bob is in the same org — his user-org scope differs. He should |
| 102 | // not see the token in a list. | 94 | // not see the token in a list. |
| 103 | const bobList = yield* asUser(bobId, orgId, (client) => | 95 | const bobList = yield* asUser(bobId, orgId, (client) => |
| 104 | client.secrets.list({ | 96 | client.secrets.list({ |
| 105 | params: { scopeId: ScopeId.make(testUserOrgScopeId(bobId, orgId)) }, | 97 | params: { scopeId: ScopeId.make(testUserOrgScopeId(bobId, orgId)) }, |
| 106 | }), | 98 | }), |
| 107 | ); | 99 | ); |
| 108 | expect(bobList.map((s) => s.id)).not.toContain(id); | 100 | expect(bobList.map((s) => s.id)).not.toContain(id); |
| 109 | 101 | ||
| 110 | const bobStatus = yield* asUser(bobId, orgId, (client) => | 102 | const bobStatus = yield* asUser(bobId, orgId, (client) => |
| 111 | client.secrets.status({ | 103 | client.secrets.status({ |
| 112 | params: { | 104 | params: { |
| 113 | scopeId: ScopeId.make(testUserOrgScopeId(bobId, orgId)), | 105 | scopeId: ScopeId.make(testUserOrgScopeId(bobId, orgId)), |
| 114 | secretId: SecretId.make(id), | 106 | secretId: SecretId.make(id), |
| 115 | }, | 107 | }, |
| 116 | }), | 108 | }), |
| 117 | ); | 109 | ); |
| 118 | expect(bobStatus.status).toBe("missing"); | 110 | expect(bobStatus.status).toBe("missing"); |
| 119 | 111 | ||
| 120 | // And Alice still sees her own token metadata. | 112 | // And Alice still sees her own token metadata. |
| 121 | const aliceStatus = yield* asUser(aliceId, orgId, (client) => | 113 | const aliceStatus = yield* asUser(aliceId, orgId, (client) => |
| 122 | client.secrets.status({ | 114 | client.secrets.status({ |
| 123 | params: { | 115 | params: { |
| 124 | scopeId: ScopeId.make(testUserOrgScopeId(aliceId, orgId)), | 116 | scopeId: ScopeId.make(testUserOrgScopeId(aliceId, orgId)), |
| 125 | secretId: SecretId.make(id), | 117 | secretId: SecretId.make(id), |
| 126 | }, | 118 | }, |
| 127 | }), | 119 | }), |
| 128 | ); | 120 | ); |
| 129 | expect(aliceStatus.status).toBe("resolved"); | 121 | expect(aliceStatus.status).toBe("resolved"); |
| 130 | }), | 122 | }), |
| 131 | ); | 123 | ); |
| 132 | 124 | ||
| 133 | it.effect( | 125 | it.effect("org-scoped secrets are visible to every user in that org", () => |
| 134 | "org-scoped secrets are visible to every user in that org", | 126 | Effect.gen(function* () { |
| 135 | () => | 127 | const orgId = nextOrgId(); |
| 136 | Effect.gen(function* () { | 128 | const adminId = nextUserId(); |
| 137 | const orgId = nextOrgId(); | 129 | const memberId = nextUserId(); |
| 138 | const adminId = nextUserId(); | 130 | const id = `sec_${uniq()}`; |
| 139 | const memberId = nextUserId(); | 131 | |
| 140 | const id = `sec_${uniq()}`; | 132 | yield* asUser(adminId, orgId, (client) => |
| 141 | 133 | client.secrets.set({ | |
| 142 | yield* asUser(adminId, orgId, (client) => | 134 | params: { scopeId: ScopeId.make(orgId) }, |
| 143 | client.secrets.set({ | 135 | payload: { |
| 144 | params: { scopeId: ScopeId.make(orgId) }, | 136 | id: SecretId.make(id), |
| 145 | payload: { | 137 | name: "Org API Key", |
| 146 | id: SecretId.make(id), | 138 | value: "shared-org-key", |
| 147 | name: "Org API Key", | 139 | }, |
| 148 | value: "shared-org-key", | 140 | }), |
| 149 | }, | 141 | ); |
| 150 | }), | ||
| 151 | ); | ||
| 152 | 142 | ||
| 153 | const adminStatus = yield* asUser(adminId, orgId, (client) => | 143 | const adminStatus = yield* asUser(adminId, orgId, (client) => |
| 154 | client.secrets.status({ | 144 | client.secrets.status({ |
| 155 | params: { scopeId: ScopeId.make(orgId), secretId: SecretId.make(id) }, | 145 | params: { scopeId: ScopeId.make(orgId), secretId: SecretId.make(id) }, |
| 156 | }), | 146 | }), |
| 157 | ); | 147 | ); |
| 158 | const memberStatus = yield* asUser(memberId, orgId, (client) => | 148 | const memberStatus = yield* asUser(memberId, orgId, (client) => |
| 159 | client.secrets.status({ | 149 | client.secrets.status({ |
| 160 | params: { scopeId: ScopeId.make(orgId), secretId: SecretId.make(id) }, | 150 | params: { scopeId: ScopeId.make(orgId), secretId: SecretId.make(id) }, |
| 161 | }), | 151 | }), |
| 162 | ); | 152 | ); |
| 163 | expect(adminStatus.status).toBe("resolved"); | 153 | expect(adminStatus.status).toBe("resolved"); |
| 164 | expect(memberStatus.status).toBe("resolved"); | 154 | expect(memberStatus.status).toBe("resolved"); |
| 165 | }), | 155 | }), |
| 166 | ); | 156 | ); |
| 167 | 157 | ||
| 168 | it.effect( | 158 | it.effect("same userId in different orgs gets distinct per-user scopes", () => |
| 169 | "same userId in different orgs gets distinct per-user scopes", | 159 | Effect.gen(function* () { |
| 170 | () => | 160 | const userId = nextUserId(); |
| 171 | Effect.gen(function* () { | 161 | const orgA = nextOrgId(); |
| 172 | const userId = nextUserId(); | 162 | const orgB = nextOrgId(); |
| 173 | const orgA = nextOrgId(); | 163 | const id = `sec_${uniq()}`; |
| 174 | const orgB = nextOrgId(); | 164 | |
| 175 | const id = `sec_${uniq()}`; | 165 | yield* asUser(userId, orgA, (client) => |
| 176 | 166 | client.secrets.set({ | |
| 177 | yield* asUser(userId, orgA, (client) => | 167 | params: { scopeId: ScopeId.make(testUserOrgScopeId(userId, orgA)) }, |
| 178 | client.secrets.set({ | 168 | payload: { |
| 179 | params: { scopeId: ScopeId.make(testUserOrgScopeId(userId, orgA)) }, | 169 | id: SecretId.make(id), |
| 180 | payload: { | 170 | name: "A token", |
| 181 | id: SecretId.make(id), | 171 | value: "value-in-a", |
| 182 | name: "A token", | 172 | }, |
| 183 | value: "value-in-a", | 173 | }), |
| 184 | }, | 174 | ); |
| 185 | }), | ||
| 186 | ); | ||
| 187 | 175 | ||
| 188 | // Same user id, different org → distinct user-org scope. The | 176 | // Same user id, different org → distinct user-org scope. The |
| 189 | // secret written in org A must not be visible when the same user | 177 | // secret written in org A must not be visible when the same user |
| 190 | // logs into org B. | 178 | // logs into org B. |
| 191 | const listInB = yield* asUser(userId, orgB, (client) => | 179 | const listInB = yield* asUser(userId, orgB, (client) => |
| 192 | client.secrets.list({ | 180 | client.secrets.list({ |
| 193 | params: { scopeId: ScopeId.make(testUserOrgScopeId(userId, orgB)) }, | 181 | params: { scopeId: ScopeId.make(testUserOrgScopeId(userId, orgB)) }, |
| 194 | }), | 182 | }), |
| 195 | ); | 183 | ); |
| 196 | expect(listInB.map((s) => s.id)).not.toContain(id); | 184 | expect(listInB.map((s) => s.id)).not.toContain(id); |
| 197 | 185 | ||
| 198 | const statusInB = yield* asUser(userId, orgB, (client) => | 186 | const statusInB = yield* asUser(userId, orgB, (client) => |
| 199 | client.secrets.status({ | 187 | client.secrets.status({ |
| 200 | params: { | 188 | params: { |
| 201 | scopeId: ScopeId.make(testUserOrgScopeId(userId, orgB)), | 189 | scopeId: ScopeId.make(testUserOrgScopeId(userId, orgB)), |
| 202 | secretId: SecretId.make(id), | 190 | secretId: SecretId.make(id), |
| 203 | }, | 191 | }, |
| 204 | }), | 192 | }), |
| 205 | ); | 193 | ); |
| 206 | expect(statusInB.status).toBe("missing"); | 194 | expect(statusInB.status).toBe("missing"); |
| 207 | 195 | ||
| 208 | // Sanity: the original write is still visible under the org-A | 196 | // Sanity: the original write is still visible under the org-A |
| 209 | // user-org scope. | 197 | // user-org scope. |
| 210 | const statusInA = yield* asUser(userId, orgA, (client) => | 198 | const statusInA = yield* asUser(userId, orgA, (client) => |
| 211 | client.secrets.status({ | 199 | client.secrets.status({ |
| 212 | params: { | 200 | params: { |
| 213 | scopeId: ScopeId.make(testUserOrgScopeId(userId, orgA)), | 201 | scopeId: ScopeId.make(testUserOrgScopeId(userId, orgA)), |
| 214 | secretId: SecretId.make(id), | 202 | secretId: SecretId.make(id), |
| 215 | }, | 203 | }, |
| 216 | }), | 204 | }), |
| 217 | ); | 205 | ); |
| 218 | expect(statusInA.status).toBe("resolved"); | 206 | expect(statusInA.status).toBe("resolved"); |
| 219 | }), | 207 | }), |
| 220 | ); | 208 | ); |
| 221 | 209 | ||
| 222 | it.effect("secrets.set rejects a scope outside the executor's stack", () => | 210 | it.effect("secrets.set rejects a scope outside the executor's stack", () => |
| @@ -16,12 +16,7 @@ · 15 unmodified lines | |||
| 16 | 16 | ||
| 17 | import { Effect, Layer } from "effect"; | 17 | import { Effect, Layer } from "effect"; |
| 18 | import { HttpApiBuilder, HttpApiClient, HttpApiSwagger } from "effect/unstable/httpapi"; | 18 | import { HttpApiBuilder, HttpApiClient, HttpApiSwagger } from "effect/unstable/httpapi"; |
| 19 | import { | 19 | import { FetchHttpClient, HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http"; |
| 20 | FetchHttpClient, | ||
| 21 | HttpRouter, | ||
| 22 | HttpServer, | ||
| 23 | HttpServerRequest, | ||
| 24 | } from "effect/unstable/http"; | ||
| 25 | 20 | ||
| 26 | import { | 21 | import { |
| 27 | ExecutionEngineService, | 22 | ExecutionEngineService, |
| @@ -31,16 +26,8 @@ import { · 3 unmodified lines | |||
| 31 | } from "@executor-js/api/server"; | 26 | } from "@executor-js/api/server"; |
| 32 | import { createExecutionEngine } from "@executor-js/execution"; | 27 | import { createExecutionEngine } from "@executor-js/execution"; |
| 33 | import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs"; | 28 | import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs"; |
| 34 | import { | 29 | import { Scope, ScopeId, collectSchemas, createExecutor } from "@executor-js/sdk"; |
| 35 | Scope, | 30 | import { makePostgresAdapter, makePostgresBlobStore } from "@executor-js/storage-postgres"; |
| 36 | ScopeId, | ||
| 37 | collectSchemas, | ||
| 38 | createExecutor, | ||
| 39 | } from "@executor-js/sdk"; | ||
| 40 | import { | ||
| 41 | makePostgresAdapter, | ||
| 42 | makePostgresBlobStore, | ||
| 43 | } from "@executor-js/storage-postgres"; | ||
| 44 | import { makeTestWorkOSVaultClient } from "@executor-js/plugin-workos-vault/testing"; | 31 | import { makeTestWorkOSVaultClient } from "@executor-js/plugin-workos-vault/testing"; |
| 45 | 32 | ||
| 46 | import executorConfig from "../../../executor.config"; | 33 | import executorConfig from "../../../executor.config"; |
| @@ -59,8 +46,7 @@ export const TEST_USER_HEADER = "x-test-user-id"; · 12 unmodified lines | |||
| 59 | // Mirrors apps/cloud/src/services/executor.ts#createScopedExecutor — the | 46 | // Mirrors apps/cloud/src/services/executor.ts#createScopedExecutor — the |
| 60 | // per-user scope id bakes in the org so the same user id in a different | 47 | // per-user scope id bakes in the org so the same user id in a different |
| 61 | // org gets a distinct scope row. | 48 | // org gets a distinct scope row. |
| 62 | const userOrgScopeId = (userId: string, orgId: string) => | 49 | const userOrgScopeId = (userId: string, orgId: string) => `user-org:${userId}:${orgId}`; |
| 63 | `user-org:${userId}:${orgId}`; | ||
| 64 | 50 | ||
| 65 | // `asOrg(orgId, …)` callers don't care which specific user they are, only | 51 | // `asOrg(orgId, …)` callers don't care which specific user they are, only |
| 66 | // that the executor has a valid user-org scope. We give each org a stable | 52 | // that the executor has a valid user-org scope. We give each org a stable |
| @@ -77,11 +63,7 @@ const defaultUserFor = (orgId: string) => `default_user_${orgId}`; · 10 unmodified lines | |||
| 77 | const fakeVault = makeTestWorkOSVaultClient(); | 63 | const fakeVault = makeTestWorkOSVaultClient(); |
| 78 | const testPlugins = executorConfig.plugins({ workosVaultClient: fakeVault }); | 64 | const testPlugins = executorConfig.plugins({ workosVaultClient: fakeVault }); |
| 79 | 65 | ||
| 80 | const createTestScopedExecutor = ( | 66 | const createTestScopedExecutor = (userId: string, orgId: string, orgName: string) => |
| 81 | userId: string, | ||
| 82 | orgId: string, | ||
| 83 | orgName: string, | ||
| 84 | ) => | ||
| 85 | Effect.gen(function* () { | 67 | Effect.gen(function* () { |
| 86 | const { db } = yield* DbService; | 68 | const { db } = yield* DbService; |
| 87 | const plugins = testPlugins; | 69 | const plugins = testPlugins; |
| @@ -188,10 +170,7 @@ export const fetchForOrg = (orgId: string): typeof globalThis.fetch => · 100 unmodified lines | |||
| 188 | return handler(req); | 170 | return handler(req); |
| 189 | }) as typeof globalThis.fetch; | 171 | }) as typeof globalThis.fetch; |
| 190 | 172 | ||
| 191 | export const fetchForUser = ( | 173 | export const fetchForUser = (userId: string, orgId: string): typeof globalThis.fetch => |
| 192 | userId: string, | ||
| 193 | orgId: string, | ||
| 194 | ): typeof globalThis.fetch => | ||
| 195 | ((input: RequestInfo | URL, init?: RequestInit) => { | 174 | ((input: RequestInfo | URL, init?: RequestInit) => { |
| 196 | const base = input instanceof Request ? input : new Request(input, init); | 175 | const base = input instanceof Request ? input : new Request(input, init); |
| 197 | const req = new Request(base, { | 176 | const req = new Request(base, { |
| @@ -211,9 +190,7 @@ export const clientLayerForOrg = (orgId: string) => · 13 unmodified lines | |||
| 211 | 190 | ||
| 212 | export const clientLayerForUser = (userId: string, orgId: string) => | 191 | export const clientLayerForUser = (userId: string, orgId: string) => |
| 213 | FetchHttpClient.layer.pipe( | 192 | FetchHttpClient.layer.pipe( |
| 214 | Layer.provide( | 193 | Layer.provide(Layer.succeed(FetchHttpClient.Fetch)(fetchForUser(userId, orgId))), |
| 215 | Layer.succeed(FetchHttpClient.Fetch)(fetchForUser(userId, orgId)), | ||
| 216 | ), | ||
| 217 | ); | 194 | ); |
| 218 | 195 | ||
| 219 | // Constructs an HttpApiClient bound to the given org, hands it to `body`, | 196 | // Constructs an HttpApiClient bound to the given org, hands it to `body`, |
| @@ -242,14 +219,11 @@ export const asUser = <A, E>( · 22 unmodified lines | |||
| 242 | Effect.gen(function* () { | 219 | Effect.gen(function* () { |
| 243 | const client = yield* HttpApiClient.make(ProtectedCloudApi, { baseUrl: TEST_BASE_URL }); | 220 | const client = yield* HttpApiClient.make(ProtectedCloudApi, { baseUrl: TEST_BASE_URL }); |
| 244 | return yield* body(client); | 221 | return yield* body(client); |
| 245 | }).pipe( | 222 | }).pipe(Effect.provide(clientLayerForUser(userId, orgId))) as Effect.Effect<A, E>; |
| 246 | Effect.provide(clientLayerForUser(userId, orgId)), | ||
| 247 | ) as Effect.Effect<A, E>; | ||
| 248 | 223 | ||
| 249 | // Exposed so tests can build the same user-org scope id the harness uses | 224 | // Exposed so tests can build the same user-org scope id the harness uses |
| 250 | // when writing at a specific user's scope. | 225 | // when writing at a specific user's scope. |
| 251 | export const testUserOrgScopeId = (userId: string, orgId: string) => | 226 | export const testUserOrgScopeId = (userId: string, orgId: string) => userOrgScopeId(userId, orgId); |
| 252 | userOrgScopeId(userId, orgId); | ||
| 253 | 227 | ||
| 254 | // Re-exports so call sites don't need a second import. | 228 | // Re-exports so call sites don't need a second import. |
| 255 | export { ProtectedCloudApi }; | 229 | export { ProtectedCloudApi }; |
| @@ -24,10 +24,7 @@ import { combinedSchema } from "./db"; · 23 unmodified lines | |||
| 24 | 24 | ||
| 25 | describe("combinedSchema", () => { | 25 | describe("combinedSchema", () => { |
| 26 | it("spreads every cloud + executor schema export", () => { | 26 | it("spreads every cloud + executor schema export", () => { |
| 27 | const expected = new Set([ | 27 | const expected = new Set([...Object.keys(cloudSchema), ...Object.keys(executorSchema)]); |
| 28 | ...Object.keys(cloudSchema), | ||
| 29 | ...Object.keys(executorSchema), | ||
| 30 | ]); | ||
| 31 | for (const key of expected) { | 28 | for (const key of expected) { |
| 32 | expect(combinedSchema, `combinedSchema missing "${key}"`).toHaveProperty(key); | 29 | expect(combinedSchema, `combinedSchema missing "${key}"`).toHaveProperty(key); |
| 33 | } | 30 | } |
| @@ -60,11 +60,11 @@ const makeSql = (): Sql => · 59 unmodified lines | |||
| 60 | onnotice: () => undefined, | 60 | onnotice: () => undefined, |
| 61 | }); | 61 | }); |
| 62 | 62 | ||
| 63 | export class DbService extends Context.Service< | 63 | export class DbService extends Context.Service<DbService, DbServiceShape>()( |
| 64 | DbService, | 64 | "@executor-js/cloud/DbService", |
| 65 | DbServiceShape | 65 | ) { |
| 66 | >()("@executor-js/cloud/DbService") { | 66 | static Live = Layer.effect(this)( |
| 67 | static Live = Layer.effect(this)(Effect.acquireRelease( | 67 | Effect.acquireRelease( |
| 68 | Effect.sync((): DbServiceShape => { | 68 | Effect.sync((): DbServiceShape => { |
| 69 | const sql = makeSql(); | 69 | const sql = makeSql(); |
| 70 | return { sql, db: drizzle(sql, { schema: combinedSchema }) as DrizzleDb }; | 70 | return { sql, db: drizzle(sql, { schema: combinedSchema }) as DrizzleDb }; |
| @@ -20,11 +20,9 @@ export const makeExecutionStack = ( · 19 unmodified lines | |||
| 20 | organizationName: string, | 20 | organizationName: string, |
| 21 | ) => | 21 | ) => |
| 22 | Effect.gen(function* () { | 22 | Effect.gen(function* () { |
| 23 | const executor = yield* createScopedExecutor( | 23 | const executor = yield* createScopedExecutor(userId, organizationId, organizationName).pipe( |
| 24 | userId, | 24 | Effect.withSpan("McpSessionDO.createScopedExecutor"), |
| 25 | organizationId, | 25 | ); |
| 26 | organizationName, | ||
| 27 | ).pipe(Effect.withSpan("McpSessionDO.createScopedExecutor")); | ||
| 28 | const codeExecutor = makeDynamicWorkerExecutor({ loader: env.LOADER }); | 26 | const codeExecutor = makeDynamicWorkerExecutor({ loader: env.LOADER }); |
| 29 | const autumn = yield* AutumnService; | 27 | const autumn = yield* AutumnService; |
| 30 | const engine = withExecutionUsageTracking( | 28 | const engine = withExecutionUsageTracking( |
| @@ -1,350 +1,453 @@ | |||
| 1 | import { pgTable, text, boolean, timestamp, bigint, jsonb, index, primaryKey } from "drizzle-orm/pg-core"; | 1 | import { |
| 2 | pgTable, | ||
| 3 | text, | ||
| 4 | boolean, | ||
| 5 | timestamp, | ||
| 6 | bigint, | ||
| 7 | jsonb, | ||
| 8 | index, | ||
| 9 | primaryKey, | ||
| 10 | } from "drizzle-orm/pg-core"; | ||
| 2 | 11 | ||
| 3 | export const source = pgTable("source", { | 12 | export const source = pgTable( |
| 4 | id: text('id').notNull(), | 13 | "source", |
| 5 | scope_id: text('scope_id').notNull(), | 14 | { |
| 6 | plugin_id: text('plugin_id').notNull(), | 15 | id: text("id").notNull(), |
| 7 | kind: text('kind').notNull(), | 16 | scope_id: text("scope_id").notNull(), |
| 8 | name: text('name').notNull(), | 17 | plugin_id: text("plugin_id").notNull(), |
| 9 | url: text('url'), | 18 | kind: text("kind").notNull(), |
| 10 | can_remove: boolean('can_remove').default(true).notNull(), | 19 | name: text("name").notNull(), |
| 11 | can_refresh: boolean('can_refresh').default(false).notNull(), | 20 | url: text("url"), |
| 12 | can_edit: boolean('can_edit').default(false).notNull(), | 21 | can_remove: boolean("can_remove").default(true).notNull(), |
| 13 | created_at: timestamp('created_at').notNull(), | 22 | can_refresh: boolean("can_refresh").default(false).notNull(), |
| 14 | updated_at: timestamp('updated_at').notNull() | 23 | can_edit: boolean("can_edit").default(false).notNull(), |
| 15 | }, (table) => [ | 24 | created_at: timestamp("created_at").notNull(), |
| 16 | primaryKey({ columns: [table.scope_id, table.id] }), | 25 | updated_at: timestamp("updated_at").notNull(), |
| 17 | index("source_scope_id_idx").on(table.scope_id), | 26 | }, |
| 18 | index("source_plugin_id_idx").on(table.plugin_id), | 27 | (table) => [ |
| 19 | ]); | 28 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 29 | index("source_scope_id_idx").on(table.scope_id), | ||
| 30 | index("source_plugin_id_idx").on(table.plugin_id), | ||
| 31 | ], | ||
| 32 | ); | ||
| 20 | 33 | ||
| 21 | export const tool = pgTable("tool", { | 34 | export const tool = pgTable( |
| 22 | id: text('id').notNull(), | 35 | "tool", |
| 23 | scope_id: text('scope_id').notNull(), | 36 | { |
| 24 | source_id: text('source_id').notNull(), | 37 | id: text("id").notNull(), |
| 25 | plugin_id: text('plugin_id').notNull(), | 38 | scope_id: text("scope_id").notNull(), |
| 26 | name: text('name').notNull(), | 39 | source_id: text("source_id").notNull(), |
| 27 | description: text('description').notNull(), | 40 | plugin_id: text("plugin_id").notNull(), |
| 28 | input_schema: jsonb('input_schema'), | 41 | name: text("name").notNull(), |
| 29 | output_schema: jsonb('output_schema'), | 42 | description: text("description").notNull(), |
| 30 | created_at: timestamp('created_at').notNull(), | 43 | input_schema: jsonb("input_schema"), |
| 31 | updated_at: timestamp('updated_at').notNull() | 44 | output_schema: jsonb("output_schema"), |
| 32 | }, (table) => [ | 45 | created_at: timestamp("created_at").notNull(), |
| 33 | primaryKey({ columns: [table.scope_id, table.id] }), | 46 | updated_at: timestamp("updated_at").notNull(), |
| 34 | index("tool_scope_id_idx").on(table.scope_id), | 47 | }, |
| 35 | index("tool_source_id_idx").on(table.source_id), | 48 | (table) => [ |
| 36 | index("tool_plugin_id_idx").on(table.plugin_id), | 49 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 37 | ]); | 50 | index("tool_scope_id_idx").on(table.scope_id), |
| 51 | index("tool_source_id_idx").on(table.source_id), | ||
| 52 | index("tool_plugin_id_idx").on(table.plugin_id), | ||
| 53 | ], | ||
| 54 | ); | ||
| 38 | 55 | ||
| 39 | export const definition = pgTable("definition", { | 56 | export const definition = pgTable( |
| 40 | id: text('id').notNull(), | 57 | "definition", |
| 41 | scope_id: text('scope_id').notNull(), | 58 | { |
| 42 | source_id: text('source_id').notNull(), | 59 | id: text("id").notNull(), |
| 43 | plugin_id: text('plugin_id').notNull(), | 60 | scope_id: text("scope_id").notNull(), |
| 44 | name: text('name').notNull(), | 61 | source_id: text("source_id").notNull(), |
| 45 | schema: jsonb('schema').notNull(), | 62 | plugin_id: text("plugin_id").notNull(), |
| 46 | created_at: timestamp('created_at').notNull() | 63 | name: text("name").notNull(), |
| 47 | }, (table) => [ | 64 | schema: jsonb("schema").notNull(), |
| 48 | primaryKey({ columns: [table.scope_id, table.id] }), | 65 | created_at: timestamp("created_at").notNull(), |
| 49 | index("definition_scope_id_idx").on(table.scope_id), | 66 | }, |
| 50 | index("definition_source_id_idx").on(table.source_id), | 67 | (table) => [ |
| 51 | index("definition_plugin_id_idx").on(table.plugin_id), | 68 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 52 | ]); | 69 | index("definition_scope_id_idx").on(table.scope_id), |
| 70 | index("definition_source_id_idx").on(table.source_id), | ||
| 71 | index("definition_plugin_id_idx").on(table.plugin_id), | ||
| 72 | ], | ||
| 73 | ); | ||
| 53 | 74 | ||
| 54 | export const secret = pgTable("secret", { | 75 | export const secret = pgTable( |
| 55 | id: text('id').notNull(), | 76 | "secret", |
| 56 | scope_id: text('scope_id').notNull(), | 77 | { |
| 57 | name: text('name').notNull(), | 78 | id: text("id").notNull(), |
| 58 | provider: text('provider').notNull(), | 79 | scope_id: text("scope_id").notNull(), |
| 59 | owned_by_connection_id: text('owned_by_connection_id'), | 80 | name: text("name").notNull(), |
| 60 | created_at: timestamp('created_at').notNull() | 81 | provider: text("provider").notNull(), |
| 61 | }, (table) => [ | 82 | owned_by_connection_id: text("owned_by_connection_id"), |
| 62 | primaryKey({ columns: [table.scope_id, table.id] }), | 83 | created_at: timestamp("created_at").notNull(), |
| 63 | index("secret_scope_id_idx").on(table.scope_id), | 84 | }, |
| 64 | index("secret_provider_idx").on(table.provider), | 85 | (table) => [ |
| 65 | index("secret_owned_by_connection_id_idx").on(table.owned_by_connection_id), | 86 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 66 | ]); | 87 | index("secret_scope_id_idx").on(table.scope_id), |
| 88 | index("secret_provider_idx").on(table.provider), | ||
| 89 | index("secret_owned_by_connection_id_idx").on(table.owned_by_connection_id), | ||
| 90 | ], | ||
| 91 | ); | ||
| 67 | 92 | ||
| 68 | export const connection = pgTable("connection", { | 93 | export const connection = pgTable( |
| 69 | id: text('id').notNull(), | 94 | "connection", |
| 70 | scope_id: text('scope_id').notNull(), | 95 | { |
| 71 | provider: text('provider').notNull(), | 96 | id: text("id").notNull(), |
| 72 | identity_label: text('identity_label'), | 97 | scope_id: text("scope_id").notNull(), |
| 73 | access_token_secret_id: text('access_token_secret_id').notNull(), | 98 | provider: text("provider").notNull(), |
| 74 | refresh_token_secret_id: text('refresh_token_secret_id'), | 99 | identity_label: text("identity_label"), |
| 75 | expires_at: bigint('expires_at', { mode: 'number' }), | 100 | access_token_secret_id: text("access_token_secret_id").notNull(), |
| 76 | scope: text('scope'), | 101 | refresh_token_secret_id: text("refresh_token_secret_id"), |
| 77 | provider_state: jsonb('provider_state'), | 102 | expires_at: bigint("expires_at", { mode: "number" }), |
| 78 | created_at: timestamp('created_at').notNull(), | 103 | scope: text("scope"), |
| 79 | updated_at: timestamp('updated_at').notNull() | 104 | provider_state: jsonb("provider_state"), |
| 80 | }, (table) => [ | 105 | created_at: timestamp("created_at").notNull(), |
| 81 | primaryKey({ columns: [table.scope_id, table.id] }), | 106 | updated_at: timestamp("updated_at").notNull(), |
| 82 | index("connection_scope_id_idx").on(table.scope_id), | 107 | }, |
| 83 | index("connection_provider_idx").on(table.provider), | 108 | (table) => [ |
| 84 | ]); | 109 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 110 | index("connection_scope_id_idx").on(table.scope_id), | ||
| 111 | index("connection_provider_idx").on(table.provider), | ||
| 112 | ], | ||
| 113 | ); | ||
| 85 | 114 | ||
| 86 | export const oauth2_session = pgTable("oauth2_session", { | 115 | export const oauth2_session = pgTable( |
| 87 | id: text('id').notNull(), | 116 | "oauth2_session", |
| 88 | scope_id: text('scope_id').notNull(), | 117 | { |
| 89 | plugin_id: text('plugin_id').notNull(), | 118 | id: text("id").notNull(), |
| 90 | strategy: text('strategy').notNull(), | 119 | scope_id: text("scope_id").notNull(), |
| 91 | connection_id: text('connection_id').notNull(), | 120 | plugin_id: text("plugin_id").notNull(), |
| 92 | token_scope: text('token_scope').notNull(), | 121 | strategy: text("strategy").notNull(), |
| 93 | redirect_url: text('redirect_url').notNull(), | 122 | connection_id: text("connection_id").notNull(), |
| 94 | payload: jsonb('payload').notNull(), | 123 | token_scope: text("token_scope").notNull(), |
| 95 | expires_at: bigint('expires_at', { mode: 'number' }).notNull(), | 124 | redirect_url: text("redirect_url").notNull(), |
| 96 | created_at: timestamp('created_at').notNull() | 125 | payload: jsonb("payload").notNull(), |
| 97 | }, (table) => [ | 126 | expires_at: bigint("expires_at", { mode: "number" }).notNull(), |
| 98 | primaryKey({ columns: [table.scope_id, table.id] }), | 127 | created_at: timestamp("created_at").notNull(), |
| 99 | index("oauth2_session_scope_id_idx").on(table.scope_id), | 128 | }, |
| 100 | index("oauth2_session_plugin_id_idx").on(table.plugin_id), | 129 | (table) => [ |
| 101 | index("oauth2_session_connection_id_idx").on(table.connection_id), | 130 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 102 | ]); | 131 | index("oauth2_session_scope_id_idx").on(table.scope_id), |
| 132 | index("oauth2_session_plugin_id_idx").on(table.plugin_id), | ||
| 133 | index("oauth2_session_connection_id_idx").on(table.connection_id), | ||
| 134 | ], | ||
| 135 | ); | ||
| 103 | 136 | ||
| 104 | export const tool_policy = pgTable("tool_policy", { | 137 | export const tool_policy = pgTable( |
| 105 | id: text('id').notNull(), | 138 | "tool_policy", |
| 106 | scope_id: text('scope_id').notNull(), | 139 | { |
| 107 | pattern: text('pattern').notNull(), | 140 | id: text("id").notNull(), |
| 108 | action: text('action').notNull(), | 141 | scope_id: text("scope_id").notNull(), |
| 109 | position: text('position').notNull(), | 142 | pattern: text("pattern").notNull(), |
| 110 | created_at: timestamp('created_at').notNull(), | 143 | action: text("action").notNull(), |
| 111 | updated_at: timestamp('updated_at').notNull() | 144 | position: text("position").notNull(), |
| 112 | }, (table) => [ | 145 | created_at: timestamp("created_at").notNull(), |
| 113 | primaryKey({ columns: [table.scope_id, table.id] }), | 146 | updated_at: timestamp("updated_at").notNull(), |
| 114 | index("tool_policy_scope_id_position_idx").on(table.scope_id, table.position), | 147 | }, |
| 115 | ]); | 148 | (table) => [ |
| 149 | primaryKey({ columns: [table.scope_id, table.id] }), | ||
| 150 | index("tool_policy_scope_id_position_idx").on(table.scope_id, table.position), | ||
| 151 | ], | ||
| 152 | ); | ||
| 116 | 153 | ||
| 117 | export const openapi_source = pgTable("openapi_source", { | 154 | export const openapi_source = pgTable( |
| 118 | id: text('id').notNull(), | 155 | "openapi_source", |
| 119 | scope_id: text('scope_id').notNull(), | 156 | { |
| 120 | name: text('name').notNull(), | 157 | id: text("id").notNull(), |
| 121 | spec: text('spec').notNull(), | 158 | scope_id: text("scope_id").notNull(), |
| 122 | source_url: text('source_url'), | 159 | name: text("name").notNull(), |
| 123 | base_url: text('base_url'), | 160 | spec: text("spec").notNull(), |
| 124 | headers: jsonb('headers'), | 161 | source_url: text("source_url"), |
| 125 | oauth2: jsonb('oauth2') | 162 | base_url: text("base_url"), |
| 126 | }, (table) => [ | 163 | headers: jsonb("headers"), |
| 127 | primaryKey({ columns: [table.scope_id, table.id] }), | 164 | oauth2: jsonb("oauth2"), |
| 128 | index("openapi_source_scope_id_idx").on(table.scope_id), | 165 | }, |
| 129 | ]); | 166 | (table) => [ |
| 167 | primaryKey({ columns: [table.scope_id, table.id] }), | ||
| 168 | index("openapi_source_scope_id_idx").on(table.scope_id), | ||
| 169 | ], | ||
| 170 | ); | ||
| 130 | 171 | ||
| 131 | export const openapi_operation = pgTable("openapi_operation", { | 172 | export const openapi_operation = pgTable( |
| 132 | id: text('id').notNull(), | 173 | "openapi_operation", |
| 133 | scope_id: text('scope_id').notNull(), | 174 | { |
| 134 | source_id: text('source_id').notNull(), | 175 | id: text("id").notNull(), |
| 135 | binding: jsonb('binding').notNull() | 176 | scope_id: text("scope_id").notNull(), |
| 136 | }, (table) => [ | 177 | source_id: text("source_id").notNull(), |
| 137 | primaryKey({ columns: [table.scope_id, table.id] }), | 178 | binding: jsonb("binding").notNull(), |
| 138 | index("openapi_operation_scope_id_idx").on(table.scope_id), | 179 | }, |
| 139 | index("openapi_operation_source_id_idx").on(table.source_id), | 180 | (table) => [ |
| 140 | ]); | 181 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 182 | index("openapi_operation_scope_id_idx").on(table.scope_id), | ||
| 183 | index("openapi_operation_source_id_idx").on(table.source_id), | ||
| 184 | ], | ||
| 185 | ); | ||
| 141 | 186 | ||
| 142 | export const openapi_source_binding = pgTable("openapi_source_binding", { | 187 | export const openapi_source_binding = pgTable( |
| 143 | id: text('id').primaryKey(), | 188 | "openapi_source_binding", |
| 144 | source_id: text('source_id').notNull(), | 189 | { |
| 145 | source_scope_id: text('source_scope_id').notNull(), | 190 | id: text("id").primaryKey(), |
| 146 | target_scope_id: text('target_scope_id').notNull(), | 191 | source_id: text("source_id").notNull(), |
| 147 | slot: text('slot').notNull(), | 192 | source_scope_id: text("source_scope_id").notNull(), |
| 148 | kind: text('kind', { enum: ['secret', 'connection', 'text'] }).notNull(), | 193 | target_scope_id: text("target_scope_id").notNull(), |
| 149 | secret_id: text('secret_id'), | 194 | slot: text("slot").notNull(), |
| 150 | connection_id: text('connection_id'), | 195 | kind: text("kind", { enum: ["secret", "connection", "text"] }).notNull(), |
| 151 | text_value: text('text_value'), | 196 | secret_id: text("secret_id"), |
| 152 | created_at: timestamp('created_at').notNull(), | 197 | connection_id: text("connection_id"), |
| 153 | updated_at: timestamp('updated_at').notNull() | 198 | text_value: text("text_value"), |
| 154 | }, (table) => [ | 199 | created_at: timestamp("created_at").notNull(), |
| 155 | index("openapi_source_binding_source_id_idx").on(table.source_id), | 200 | updated_at: timestamp("updated_at").notNull(), |
| 156 | index("openapi_source_binding_source_scope_id_idx").on(table.source_scope_id), | 201 | }, |
| 157 | index("openapi_source_binding_target_scope_id_idx").on(table.target_scope_id), | 202 | (table) => [ |
| 158 | index("openapi_source_binding_slot_idx").on(table.slot), | 203 | index("openapi_source_binding_source_id_idx").on(table.source_id), |
| 159 | index("openapi_source_binding_secret_id_idx").on(table.secret_id), | 204 | index("openapi_source_binding_source_scope_id_idx").on(table.source_scope_id), |
| 160 | index("openapi_source_binding_connection_id_idx").on(table.connection_id), | 205 | index("openapi_source_binding_target_scope_id_idx").on(table.target_scope_id), |
| 161 | ]); | 206 | index("openapi_source_binding_slot_idx").on(table.slot), |
| 207 | index("openapi_source_binding_secret_id_idx").on(table.secret_id), | ||
| 208 | index("openapi_source_binding_connection_id_idx").on(table.connection_id), | ||
| 209 | ], | ||
| 210 | ); | ||
| 162 | 211 | ||
| 163 | export const openapi_source_query_param = pgTable("openapi_source_query_param", { | 212 | export const openapi_source_query_param = pgTable( |
| 164 | id: text('id').notNull(), | 213 | "openapi_source_query_param", |
| 165 | scope_id: text('scope_id').notNull(), | 214 | { |
| 166 | source_id: text('source_id').notNull(), | 215 | id: text("id").notNull(), |
| 167 | name: text('name').notNull(), | 216 | scope_id: text("scope_id").notNull(), |
| 168 | kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | 217 | source_id: text("source_id").notNull(), |
| 169 | text_value: text('text_value'), | 218 | name: text("name").notNull(), |
| 170 | secret_id: text('secret_id'), | 219 | kind: text("kind", { enum: ["text", "secret"] }).notNull(), |
| 171 | secret_prefix: text('secret_prefix') | 220 | text_value: text("text_value"), |
| 172 | }, (table) => [ | 221 | secret_id: text("secret_id"), |
| 173 | primaryKey({ columns: [table.scope_id, table.id] }), | 222 | secret_prefix: text("secret_prefix"), |
| 174 | index("openapi_source_query_param_scope_id_idx").on(table.scope_id), | 223 | }, |
| 175 | index("openapi_source_query_param_source_id_idx").on(table.source_id), | 224 | (table) => [ |
| 176 | index("openapi_source_query_param_secret_id_idx").on(table.secret_id), | 225 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 177 | ]); | 226 | index("openapi_source_query_param_scope_id_idx").on(table.scope_id), |
| 227 | index("openapi_source_query_param_source_id_idx").on(table.source_id), | ||
| 228 | index("openapi_source_query_param_secret_id_idx").on(table.secret_id), | ||
| 229 | ], | ||
| 230 | ); | ||
| 178 | 231 | ||
| 179 | export const openapi_source_spec_fetch_header = pgTable("openapi_source_spec_fetch_header", { | 232 | export const openapi_source_spec_fetch_header = pgTable( |
| 180 | id: text('id').notNull(), | 233 | "openapi_source_spec_fetch_header", |
| 181 | scope_id: text('scope_id').notNull(), | 234 | { |
| 182 | source_id: text('source_id').notNull(), | 235 | id: text("id").notNull(), |
| 183 | name: text('name').notNull(), | 236 | scope_id: text("scope_id").notNull(), |
| 184 | kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | 237 | source_id: text("source_id").notNull(), |
| 185 | text_value: text('text_value'), | 238 | name: text("name").notNull(), |
| 186 | secret_id: text('secret_id'), | 239 | kind: text("kind", { enum: ["text", "secret"] }).notNull(), |
| 187 | secret_prefix: text('secret_prefix') | 240 | text_value: text("text_value"), |
| 188 | }, (table) => [ | 241 | secret_id: text("secret_id"), |
| 189 | primaryKey({ columns: [table.scope_id, table.id] }), | 242 | secret_prefix: text("secret_prefix"), |
| 190 | index("openapi_source_spec_fetch_header_scope_id_idx").on(table.scope_id), | 243 | }, |
| 191 | index("openapi_source_spec_fetch_header_source_id_idx").on(table.source_id), | 244 | (table) => [ |
| 192 | index("openapi_source_spec_fetch_header_secret_id_idx").on(table.secret_id), | 245 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 193 | ]); | 246 | index("openapi_source_spec_fetch_header_scope_id_idx").on(table.scope_id), |
| 247 | index("openapi_source_spec_fetch_header_source_id_idx").on(table.source_id), | ||
| 248 | index("openapi_source_spec_fetch_header_secret_id_idx").on(table.secret_id), | ||
| 249 | ], | ||
| 250 | ); | ||
| 194 | 251 | ||
| 195 | export const openapi_source_spec_fetch_query_param = pgTable("openapi_source_spec_fetch_query_param", { | 252 | export const openapi_source_spec_fetch_query_param = pgTable( |
| 196 | id: text('id').notNull(), | 253 | "openapi_source_spec_fetch_query_param", |
| 197 | scope_id: text('scope_id').notNull(), | 254 | { |
| 198 | source_id: text('source_id').notNull(), | 255 | id: text("id").notNull(), |
| 199 | name: text('name').notNull(), | 256 | scope_id: text("scope_id").notNull(), |
| 200 | kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | 257 | source_id: text("source_id").notNull(), |
| 201 | text_value: text('text_value'), | 258 | name: text("name").notNull(), |
| 202 | secret_id: text('secret_id'), | 259 | kind: text("kind", { enum: ["text", "secret"] }).notNull(), |
| 203 | secret_prefix: text('secret_prefix') | 260 | text_value: text("text_value"), |
| 204 | }, (table) => [ | 261 | secret_id: text("secret_id"), |
| 205 | primaryKey({ columns: [table.scope_id, table.id] }), | 262 | secret_prefix: text("secret_prefix"), |
| 206 | index("openapi_source_spec_fetch_query_param_scope_id_idx").on(table.scope_id), | 263 | }, |
| 207 | index("openapi_source_spec_fetch_query_param_source_id_idx").on(table.source_id), | 264 | (table) => [ |
| 208 | index("openapi_source_spec_fetch_query_param_secret_id_idx").on(table.secret_id), | 265 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 209 | ]); | 266 | index("openapi_source_spec_fetch_query_param_scope_id_idx").on(table.scope_id), |
| 267 | index("openapi_source_spec_fetch_query_param_source_id_idx").on(table.source_id), | ||
| 268 | index("openapi_source_spec_fetch_query_param_secret_id_idx").on(table.secret_id), | ||
| 269 | ], | ||
| 270 | ); | ||
| 210 | 271 | ||
| 211 | export const mcp_source = pgTable("mcp_source", { | 272 | export const mcp_source = pgTable( |
| 212 | id: text('id').notNull(), | 273 | "mcp_source", |
| 213 | scope_id: text('scope_id').notNull(), | 274 | { |
| 214 | name: text('name').notNull(), | 275 | id: text("id").notNull(), |
| 215 | config: jsonb('config').notNull(), | 276 | scope_id: text("scope_id").notNull(), |
| 216 | auth_kind: text('auth_kind', { enum: ['none', 'header', 'oauth2'] }).default("none").notNull(), | 277 | name: text("name").notNull(), |
| 217 | auth_header_name: text('auth_header_name'), | 278 | config: jsonb("config").notNull(), |
| 218 | auth_secret_id: text('auth_secret_id'), | 279 | auth_kind: text("auth_kind", { enum: ["none", "header", "oauth2"] }) |
| 219 | auth_secret_prefix: text('auth_secret_prefix'), | 280 | .default("none") |
| 220 | auth_connection_id: text('auth_connection_id'), | 281 | .notNull(), |
| 221 | auth_client_id_secret_id: text('auth_client_id_secret_id'), | 282 | auth_header_name: text("auth_header_name"), |
| 222 | auth_client_secret_secret_id: text('auth_client_secret_secret_id'), | 283 | auth_secret_id: text("auth_secret_id"), |
| 223 | created_at: timestamp('created_at').notNull() | 284 | auth_secret_prefix: text("auth_secret_prefix"), |
| 224 | }, (table) => [ | 285 | auth_connection_id: text("auth_connection_id"), |
| 225 | primaryKey({ columns: [table.scope_id, table.id] }), | 286 | auth_client_id_secret_id: text("auth_client_id_secret_id"), |
| 226 | index("mcp_source_scope_id_idx").on(table.scope_id), | 287 | auth_client_secret_secret_id: text("auth_client_secret_secret_id"), |
| 227 | index("mcp_source_auth_secret_id_idx").on(table.auth_secret_id), | 288 | created_at: timestamp("created_at").notNull(), |
| 228 | index("mcp_source_auth_connection_id_idx").on(table.auth_connection_id), | 289 | }, |
| 229 | index("mcp_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id), | 290 | (table) => [ |
| 230 | index("mcp_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id), | 291 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 231 | ]); | 292 | index("mcp_source_scope_id_idx").on(table.scope_id), |
| 293 | index("mcp_source_auth_secret_id_idx").on(table.auth_secret_id), | ||
| 294 | index("mcp_source_auth_connection_id_idx").on(table.auth_connection_id), | ||
| 295 | index("mcp_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id), | ||
| 296 | index("mcp_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id), | ||
| 297 | ], | ||
| 298 | ); | ||
| 232 | 299 | ||
| 233 | export const mcp_source_header = pgTable("mcp_source_header", { | 300 | export const mcp_source_header = pgTable( |
| 234 | id: text('id').notNull(), | 301 | "mcp_source_header", |
| 235 | scope_id: text('scope_id').notNull(), | 302 | { |
| 236 | source_id: text('source_id').notNull(), | 303 | id: text("id").notNull(), |
| 237 | name: text('name').notNull(), | 304 | scope_id: text("scope_id").notNull(), |
| 238 | kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | 305 | source_id: text("source_id").notNull(), |
| 239 | text_value: text('text_value'), | 306 | name: text("name").notNull(), |
| 240 | secret_id: text('secret_id'), | 307 | kind: text("kind", { enum: ["text", "secret"] }).notNull(), |
| 241 | secret_prefix: text('secret_prefix') | 308 | text_value: text("text_value"), |
| 242 | }, (table) => [ | 309 | secret_id: text("secret_id"), |
| 243 | primaryKey({ columns: [table.scope_id, table.id] }), | 310 | secret_prefix: text("secret_prefix"), |
| 244 | index("mcp_source_header_scope_id_idx").on(table.scope_id), | 311 | }, |
| 245 | index("mcp_source_header_source_id_idx").on(table.source_id), | 312 | (table) => [ |
| 246 | index("mcp_source_header_secret_id_idx").on(table.secret_id), | 313 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 247 | ]); | 314 | index("mcp_source_header_scope_id_idx").on(table.scope_id), |
| 315 | index("mcp_source_header_source_id_idx").on(table.source_id), | ||
| 316 | index("mcp_source_header_secret_id_idx").on(table.secret_id), | ||
| 317 | ], | ||
| 318 | ); | ||
| 248 | 319 | ||
| 249 | export const mcp_source_query_param = pgTable("mcp_source_query_param", { | 320 | export const mcp_source_query_param = pgTable( |
| 250 | id: text('id').notNull(), | 321 | "mcp_source_query_param", |
| 251 | scope_id: text('scope_id').notNull(), | 322 | { |
| 252 | source_id: text('source_id').notNull(), | 323 | id: text("id").notNull(), |
| 253 | name: text('name').notNull(), | 324 | scope_id: text("scope_id").notNull(), |
| 254 | kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | 325 | source_id: text("source_id").notNull(), |
| 255 | text_value: text('text_value'), | 326 | name: text("name").notNull(), |
| 256 | secret_id: text('secret_id'), | 327 | kind: text("kind", { enum: ["text", "secret"] }).notNull(), |
| 257 | secret_prefix: text('secret_prefix') | 328 | text_value: text("text_value"), |
| 258 | }, (table) => [ | 329 | secret_id: text("secret_id"), |
| 259 | primaryKey({ columns: [table.scope_id, table.id] }), | 330 | secret_prefix: text("secret_prefix"), |
| 260 | index("mcp_source_query_param_scope_id_idx").on(table.scope_id), | 331 | }, |
| 261 | index("mcp_source_query_param_source_id_idx").on(table.source_id), | 332 | (table) => [ |
| 262 | index("mcp_source_query_param_secret_id_idx").on(table.secret_id), | 333 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 263 | ]); | 334 | index("mcp_source_query_param_scope_id_idx").on(table.scope_id), |
| 335 | index("mcp_source_query_param_source_id_idx").on(table.source_id), | ||
| 336 | index("mcp_source_query_param_secret_id_idx").on(table.secret_id), | ||
| 337 | ], | ||
| 338 | ); | ||
| 264 | 339 | ||
| 265 | export const mcp_binding = pgTable("mcp_binding", { | 340 | export const mcp_binding = pgTable( |
| 266 | id: text('id').notNull(), | 341 | "mcp_binding", |
| 267 | scope_id: text('scope_id').notNull(), | 342 | { |
| 268 | source_id: text('source_id').notNull(), | 343 | id: text("id").notNull(), |
| 269 | binding: jsonb('binding').notNull(), | 344 | scope_id: text("scope_id").notNull(), |
| 270 | created_at: timestamp('created_at').notNull() | 345 | source_id: text("source_id").notNull(), |
| 271 | }, (table) => [ | 346 | binding: jsonb("binding").notNull(), |
| 272 | primaryKey({ columns: [table.scope_id, table.id] }), | 347 | created_at: timestamp("created_at").notNull(), |
| 273 | index("mcp_binding_scope_id_idx").on(table.scope_id), | 348 | }, |
| 274 | index("mcp_binding_source_id_idx").on(table.source_id), | 349 | (table) => [ |
| 275 | ]); | 350 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 351 | index("mcp_binding_scope_id_idx").on(table.scope_id), | ||
| 352 | index("mcp_binding_source_id_idx").on(table.source_id), | ||
| 353 | ], | ||
| 354 | ); | ||
| 276 | 355 | ||
| 277 | export const graphql_source = pgTable("graphql_source", { | 356 | export const graphql_source = pgTable( |
| 278 | id: text('id').notNull(), | 357 | "graphql_source", |
| 279 | scope_id: text('scope_id').notNull(), | 358 | { |
| 280 | name: text('name').notNull(), | 359 | id: text("id").notNull(), |
| 281 | endpoint: text('endpoint').notNull(), | 360 | scope_id: text("scope_id").notNull(), |
| 282 | auth_kind: text('auth_kind', { enum: ['none', 'oauth2'] }).default("none").notNull(), | 361 | name: text("name").notNull(), |
| 283 | auth_connection_id: text('auth_connection_id') | 362 | endpoint: text("endpoint").notNull(), |
| 284 | }, (table) => [ | 363 | auth_kind: text("auth_kind", { enum: ["none", "oauth2"] }) |
| 285 | primaryKey({ columns: [table.scope_id, table.id] }), | 364 | .default("none") |
| 286 | index("graphql_source_scope_id_idx").on(table.scope_id), | 365 | .notNull(), |
| 287 | index("graphql_source_auth_connection_id_idx").on(table.auth_connection_id), | 366 | auth_connection_id: text("auth_connection_id"), |
| 288 | ]); | 367 | }, |
| 368 | (table) => [ | ||
| 369 | primaryKey({ columns: [table.scope_id, table.id] }), | ||
| 370 | index("graphql_source_scope_id_idx").on(table.scope_id), | ||
| 371 | index("graphql_source_auth_connection_id_idx").on(table.auth_connection_id), | ||
| 372 | ], | ||
| 373 | ); | ||
| 289 | 374 | ||
| 290 | export const graphql_source_header = pgTable("graphql_source_header", { | 375 | export const graphql_source_header = pgTable( |
| 291 | id: text('id').notNull(), | 376 | "graphql_source_header", |
| 292 | scope_id: text('scope_id').notNull(), | 377 | { |
| 293 | source_id: text('source_id').notNull(), | 378 | id: text("id").notNull(), |
| 294 | name: text('name').notNull(), | 379 | scope_id: text("scope_id").notNull(), |
| 295 | kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | 380 | source_id: text("source_id").notNull(), |
| 296 | text_value: text('text_value'), | 381 | name: text("name").notNull(), |
| 297 | secret_id: text('secret_id'), | 382 | kind: text("kind", { enum: ["text", "secret"] }).notNull(), |
| 298 | secret_prefix: text('secret_prefix') | 383 | text_value: text("text_value"), |
| 299 | }, (table) => [ | 384 | secret_id: text("secret_id"), |
| 300 | primaryKey({ columns: [table.scope_id, table.id] }), | 385 | secret_prefix: text("secret_prefix"), |
| 301 | index("graphql_source_header_scope_id_idx").on(table.scope_id), | 386 | }, |
| 302 | index("graphql_source_header_source_id_idx").on(table.source_id), | 387 | (table) => [ |
| 303 | index("graphql_source_header_secret_id_idx").on(table.secret_id), | 388 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 304 | ]); | 389 | index("graphql_source_header_scope_id_idx").on(table.scope_id), |
| 390 | index("graphql_source_header_source_id_idx").on(table.source_id), | ||
| 391 | index("graphql_source_header_secret_id_idx").on(table.secret_id), | ||
| 392 | ], | ||
| 393 | ); | ||
| 305 | 394 | ||
| 306 | export const graphql_source_query_param = pgTable("graphql_source_query_param", { | 395 | export const graphql_source_query_param = pgTable( |
| 307 | id: text('id').notNull(), | 396 | "graphql_source_query_param", |
| 308 | scope_id: text('scope_id').notNull(), | 397 | { |
| 309 | source_id: text('source_id').notNull(), | 398 | id: text("id").notNull(), |
| 310 | name: text('name').notNull(), | 399 | scope_id: text("scope_id").notNull(), |
| 311 | kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | 400 | source_id: text("source_id").notNull(), |
| 312 | text_value: text('text_value'), | 401 | name: text("name").notNull(), |
| 313 | secret_id: text('secret_id'), | 402 | kind: text("kind", { enum: ["text", "secret"] }).notNull(), |
| 314 | secret_prefix: text('secret_prefix') | 403 | text_value: text("text_value"), |
| 315 | }, (table) => [ | 404 | secret_id: text("secret_id"), |
| 316 | primaryKey({ columns: [table.scope_id, table.id] }), | 405 | secret_prefix: text("secret_prefix"), |
| 317 | index("graphql_source_query_param_scope_id_idx").on(table.scope_id), | 406 | }, |
| 318 | index("graphql_source_query_param_source_id_idx").on(table.source_id), | 407 | (table) => [ |
| 319 | index("graphql_source_query_param_secret_id_idx").on(table.secret_id), | 408 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 320 | ]); | 409 | index("graphql_source_query_param_scope_id_idx").on(table.scope_id), |
| 410 | index("graphql_source_query_param_source_id_idx").on(table.source_id), | ||
| 411 | index("graphql_source_query_param_secret_id_idx").on(table.secret_id), | ||
| 412 | ], | ||
| 413 | ); | ||
| 321 | 414 | ||
| 322 | export const graphql_operation = pgTable("graphql_operation", { | 415 | export const graphql_operation = pgTable( |
| 323 | id: text('id').notNull(), | 416 | "graphql_operation", |
| 324 | scope_id: text('scope_id').notNull(), | 417 | { |
| 325 | source_id: text('source_id').notNull(), | 418 | id: text("id").notNull(), |
| 326 | binding: jsonb('binding').notNull() | 419 | scope_id: text("scope_id").notNull(), |
| 327 | }, (table) => [ | 420 | source_id: text("source_id").notNull(), |
| 328 | primaryKey({ columns: [table.scope_id, table.id] }), | 421 | binding: jsonb("binding").notNull(), |
| 329 | index("graphql_operation_scope_id_idx").on(table.scope_id), | 422 | }, |
| 330 | index("graphql_operation_source_id_idx").on(table.source_id), | 423 | (table) => [ |
| 331 | ]); | 424 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 425 | index("graphql_operation_scope_id_idx").on(table.scope_id), | ||
| 426 | index("graphql_operation_source_id_idx").on(table.source_id), | ||
| 427 | ], | ||
| 428 | ); | ||
| 332 | 429 | ||
| 333 | export const workos_vault_metadata = pgTable("workos_vault_metadata", { | 430 | export const workos_vault_metadata = pgTable( |
| 334 | id: text('id').notNull(), | 431 | "workos_vault_metadata", |
| 335 | scope_id: text('scope_id').notNull(), | 432 | { |
| 336 | name: text('name').notNull(), | 433 | id: text("id").notNull(), |
| 337 | purpose: text('purpose'), | 434 | scope_id: text("scope_id").notNull(), |
| 338 | created_at: timestamp('created_at').notNull() | 435 | name: text("name").notNull(), |
| 339 | }, (table) => [ | 436 | purpose: text("purpose"), |
| 340 | primaryKey({ columns: [table.scope_id, table.id] }), | 437 | created_at: timestamp("created_at").notNull(), |
| 341 | index("workos_vault_metadata_scope_id_idx").on(table.scope_id), | 438 | }, |
| 342 | ]); | 439 | (table) => [ |
| 440 | primaryKey({ columns: [table.scope_id, table.id] }), | ||
| 441 | index("workos_vault_metadata_scope_id_idx").on(table.scope_id), | ||
| 442 | ], | ||
| 443 | ); | ||
| 343 | 444 | ||
| 344 | export const blob = pgTable("blob", { | 445 | export const blob = pgTable( |
| 345 | namespace: text('namespace').notNull(), | 446 | "blob", |
| 346 | key: text('key').notNull(), | 447 | { |
| 347 | value: text('value').notNull() | 448 | namespace: text("namespace").notNull(), |
| 348 | }, (table) => [ | 449 | key: text("key").notNull(), |
| 349 | primaryKey({ columns: [table.namespace, table.key] }), | 450 | value: text("value").notNull(), |
| 350 | ]); | 451 | }, |
| 452 | (table) => [primaryKey({ columns: [table.namespace, table.key] })], | ||
| 453 | ); | ||
| @@ -10,16 +10,8 @@ · 9 unmodified lines | |||
| 10 | 10 | ||
| 11 | import { Effect } from "effect"; | 11 | import { Effect } from "effect"; |
| 12 | 12 | ||
| 13 | import { | 13 | import { Scope, ScopeId, collectSchemas, createExecutor } from "@executor-js/sdk"; |
| 14 | Scope, | 14 | import { makePostgresAdapter, makePostgresBlobStore } from "@executor-js/storage-postgres"; |
| 15 | ScopeId, | ||
| 16 | collectSchemas, | ||
| 17 | createExecutor, | ||
| 18 | } from "@executor-js/sdk"; | ||
| 19 | import { | ||
| 20 | makePostgresAdapter, | ||
| 21 | makePostgresBlobStore, | ||
| 22 | } from "@executor-js/storage-postgres"; | ||
| 23 | 15 | ||
| 24 | import { env } from "cloudflare:workers"; | 16 | import { env } from "cloudflare:workers"; |
| 25 | import executorConfig from "../../executor.config"; | 17 | import executorConfig from "../../executor.config"; |
| @@ -48,21 +48,15 @@ const RegistrationBody = Schema.Struct({ · 47 unmodified lines | |||
| 48 | grant_types: Schema.optional(Schema.Array(Schema.String)), | 48 | grant_types: Schema.optional(Schema.Array(Schema.String)), |
| 49 | response_types: Schema.optional(Schema.Array(Schema.String)), | 49 | response_types: Schema.optional(Schema.Array(Schema.String)), |
| 50 | }); | 50 | }); |
| 51 | const decodeRegistrationBody = Schema.decodeUnknownOption( | 51 | const decodeRegistrationBody = Schema.decodeUnknownOption(Schema.fromJsonString(RegistrationBody)); |
| 52 | Schema.fromJsonString(RegistrationBody), | ||
| 53 | ); | ||
| 54 | 52 | ||
| 55 | const startFakeServer = async (): Promise<FakeServer> => { | 53 | const startFakeServer = async (): Promise<FakeServer> => { |
| 56 | const clients = new Map<string, { redirect_uris: readonly string[] }>(); | 54 | const clients = new Map<string, { redirect_uris: readonly string[] }>(); |
| 57 | const codes = new Map< | 55 | const codes = new Map<string, { readonly clientId: string; readonly codeChallenge: string }>(); |
| 58 | string, | ||
| 59 | { readonly clientId: string; readonly codeChallenge: string } | ||
| 60 | >(); | ||
| 61 | const accessTokens = new Map<string, { readonly refresh: string }>(); | 56 | const accessTokens = new Map<string, { readonly refresh: string }>(); |
| 62 | const refreshTokens = new Map<string, string>(); | 57 | const refreshTokens = new Map<string, string>(); |
| 63 | let seq = 0; | 58 | let seq = 0; |
| 64 | const next = (p: string) => | 59 | const next = (p: string) => `${p}_${++seq}_${randomBytes(6).toString("hex")}`; |
| 65 | `${p}_${++seq}_${randomBytes(6).toString("hex")}`; | ||
| 66 | let registrations = 0; | 60 | let registrations = 0; |
| 67 | let tokenCalls = 0; | 61 | let tokenCalls = 0; |
| 68 | 62 | ||
| @@ -76,16 +70,10 @@ const startFakeServer = async (): Promise<FakeServer> => { · 7 unmodified lines | |||
| 76 | 70 | ||
| 77 | const server: Server = createServer(async (req, res) => { | 71 | const server: Server = createServer(async (req, res) => { |
| 78 | const url = new URL(req.url!, `http://${req.headers.host}`); | 72 | const url = new URL(req.url!, `http://${req.headers.host}`); |
| 79 | const send = ( | 73 | const send = (status: number, body: unknown, headers: Record<string, string> = {}) => { |
| 80 | status: number, | 74 | const payload = typeof body === "string" ? body : JSON.stringify(body); |
| 81 | body: unknown, | ||
| 82 | headers: Record<string, string> = {}, | ||
| 83 | ) => { | ||
| 84 | const payload = | ||
| 85 | typeof body === "string" ? body : JSON.stringify(body); | ||
| 86 | res.writeHead(status, { | 75 | res.writeHead(status, { |
| 87 | "content-type": | 76 | "content-type": typeof body === "string" ? "text/plain" : "application/json", |
| 88 | typeof body === "string" ? "text/plain" : "application/json", | ||
| 89 | ...headers, | 77 | ...headers, |
| 90 | }); | 78 | }); |
| 91 | res.end(payload); | 79 | res.end(payload); |
| @@ -130,10 +118,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 38 unmodified lines | |||
| 130 | client_id: clientId, | 118 | client_id: clientId, |
| 131 | client_id_issued_at: Math.floor(Date.now() / 1000), | 119 | client_id_issued_at: Math.floor(Date.now() / 1000), |
| 132 | redirect_uris: parsed.redirect_uris ?? [], | 120 | redirect_uris: parsed.redirect_uris ?? [], |
| 133 | grant_types: parsed.grant_types ?? [ | 121 | grant_types: parsed.grant_types ?? ["authorization_code", "refresh_token"], |
| 134 | "authorization_code", | ||
| 135 | "refresh_token", | ||
| 136 | ], | ||
| 137 | response_types: parsed.response_types ?? ["code"], | 122 | response_types: parsed.response_types ?? ["code"], |
| 138 | token_endpoint_auth_method: "none", | 123 | token_endpoint_auth_method: "none", |
| 139 | }); | 124 | }); |
| @@ -171,9 +156,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 31 unmodified lines | |||
| 171 | const record = codes.get(code); | 156 | const record = codes.get(code); |
| 172 | if (!record) return send(400, { error: "invalid_grant" }); | 157 | if (!record) return send(400, { error: "invalid_grant" }); |
| 173 | codes.delete(code); | 158 | codes.delete(code); |
| 174 | const computed = createHash("sha256") | 159 | const computed = createHash("sha256").update(verifier).digest("base64url"); |
| 175 | .update(verifier) | ||
| 176 | .digest("base64url"); | ||
| 177 | if (computed !== record.codeChallenge) { | 160 | if (computed !== record.codeChallenge) { |
| 178 | return send(400, { error: "invalid_grant" }); | 161 | return send(400, { error: "invalid_grant" }); |
| 179 | } | 162 | } |
| @@ -229,9 +212,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 49 unmodified lines | |||
| 229 | } | 212 | } |
| 230 | }); | 213 | }); |
| 231 | 214 | ||
| 232 | await new Promise<void>((resolve) => | 215 | await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", () => resolve())); |
| 233 | server.listen(0, "127.0.0.1", () => resolve()), | ||
| 234 | ); | ||
| 235 | const address = server.address() as AddressInfo; | 216 | const address = server.address() as AddressInfo; |
| 236 | 217 | ||
| 237 | return { | 218 | return { |
| @@ -352,9 +333,7 @@ describe("mcp oauth end-to-end (node pool, real OAuth + MCP server)", () => { · 114 unmodified lines | |||
| 352 | }, | 333 | }, |
| 353 | }), | 334 | }), |
| 354 | ); | 335 | ); |
| 355 | const redirA = yield* Effect.promise(() => | 336 | const redirA = yield* Effect.promise(() => followAuthorize(startedA.authorizationUrl!)); |
| 356 | followAuthorize(startedA.authorizationUrl!), | ||
| 357 | ); | ||
| 358 | const completedA = yield* asUser(userA, orgId, (client) => | 337 | const completedA = yield* asUser(userA, orgId, (client) => |
| 359 | client.oauth.complete({ | 338 | client.oauth.complete({ |
| 360 | params: { scopeId: scopeA }, | 339 | params: { scopeId: scopeA }, |
| @@ -377,9 +356,7 @@ describe("mcp oauth end-to-end (node pool, real OAuth + MCP server)", () => { · 16 unmodified lines | |||
| 377 | }, | 356 | }, |
| 378 | }), | 357 | }), |
| 379 | ); | 358 | ); |
| 380 | const redirB = yield* Effect.promise(() => | 359 | const redirB = yield* Effect.promise(() => followAuthorize(startedB.authorizationUrl!)); |
| 381 | followAuthorize(startedB.authorizationUrl!), | ||
| 382 | ); | ||
| 383 | const completedB = yield* asUser(userB, orgId, (client) => | 360 | const completedB = yield* asUser(userB, orgId, (client) => |
| 384 | client.oauth.complete({ | 361 | client.oauth.complete({ |
| 385 | params: { scopeId: scopeB }, | 362 | params: { scopeId: scopeB }, |
| @@ -58,9 +58,7 @@ describe("JsonRpcRequestIdQueue", () => { · 57 unmodified lines | |||
| 58 | queue | 58 | queue |
| 59 | .run(jsonRpcRequest({ jsonrpc: "2.0", id: 2, method: "tools/call" }), async () => "done") | 59 | .run(jsonRpcRequest({ jsonrpc: "2.0", id: 2, method: "tools/call" }), async () => "done") |
| 60 | .then((v) => ({ kind: "settled" as const, v })), | 60 | .then((v) => ({ kind: "settled" as const, v })), |
| 61 | new Promise<{ kind: "blocked" }>((r) => | 61 | new Promise<{ kind: "blocked" }>((r) => setTimeout(() => r({ kind: "blocked" }), 200)), |
| 62 | setTimeout(() => r({ kind: "blocked" }), 200), | ||
| 63 | ), | ||
| 64 | ]); | 62 | ]); |
| 65 | 63 | ||
| 66 | expect(otherDone.kind).toBe("settled"); | 64 | expect(otherDone.kind).toBe("settled"); |
| @@ -64,9 +64,7 @@ const extractJsonRpcRequestIdKeys = async (request: Request): Promise<ReadonlyAr · 63 unmodified lines | |||
| 64 | const contentType = request.headers.get("content-type") ?? ""; | 64 | const contentType = request.headers.get("content-type") ?? ""; |
| 65 | if (!contentType.includes("application/json")) return []; | 65 | if (!contentType.includes("application/json")) return []; |
| 66 | 66 | ||
| 67 | const parsed = await Effect.runPromiseExit( | 67 | const parsed = await Effect.runPromiseExit(Effect.tryPromise(() => request.clone().json())); |
| 68 | Effect.tryPromise(() => request.clone().json()), | ||
| 69 | ); | ||
| 70 | if (Exit.isFailure(parsed)) { | 68 | if (Exit.isFailure(parsed)) { |
| 71 | return []; | 69 | return []; |
| 72 | } | 70 | } |
| @@ -192,8 +190,6 @@ export const makeMcpWorkerTransport = ( · 119 unmodified lines | |||
| 192 | try: () => transport.close(), | 190 | try: () => transport.close(), |
| 193 | catch: (cause) => new McpWorkerTransportError({ cause }), | 191 | catch: (cause) => new McpWorkerTransportError({ cause }), |
| 194 | }), | 192 | }), |
| 195 | ).pipe( | 193 | ).pipe(Effect.withSpan("mcp.worker_transport.close")), |
| 196 | Effect.withSpan("mcp.worker_transport.close"), | ||
| 197 | ), | ||
| 198 | } satisfies McpWorkerTransport; | 194 | } satisfies McpWorkerTransport; |
| 199 | }).pipe(Effect.withSpan("mcp.worker_transport.make")); | 195 | }).pipe(Effect.withSpan("mcp.worker_transport.make")); |
| @@ -14,10 +14,7 @@ export class SlackError extends Data.TaggedError("SlackError")<{ · 13 unmodified lines | |||
| 14 | }> {} | 14 | }> {} |
| 15 | 15 | ||
| 16 | export type ISlackService = Readonly<{ | 16 | export type ISlackService = Readonly<{ |
| 17 | createConnectInvite: (input: { | 17 | createConnectInvite: (input: { email: string; organization?: string }) => Effect.Effect< |
| 18 | email: string; | ||
| 19 | organization?: string; | ||
| 20 | }) => Effect.Effect< | ||
| 21 | { | 18 | { |
| 22 | channel: { id: string; name: string }; | 19 | channel: { id: string; name: string }; |
| 23 | invite: { invite_id: string; url: string }; | 20 | invite: { invite_id: string; url: string }; |
| @@ -46,9 +43,7 @@ const make = Effect.sync(() => { · 22 unmodified lines | |||
| 46 | 43 | ||
| 47 | if (!token) { | 44 | if (!token) { |
| 48 | const notConfigured = (method: string) => | 45 | const notConfigured = (method: string) => |
| 49 | Effect.fail( | 46 | Effect.fail(new SlackError({ method, error: "SLACK_BOT_TOKEN is not configured" })); |
| 50 | new SlackError({ method, error: "SLACK_BOT_TOKEN is not configured" }), | ||
| 51 | ); | ||
| 52 | return { | 47 | return { |
| 53 | createConnectInvite: () => notConfigured("createConnectInvite"), | 48 | createConnectInvite: () => notConfigured("createConnectInvite"), |
| 54 | } satisfies ISlackService; | 49 | } satisfies ISlackService; |
| @@ -83,20 +78,17 @@ const make = Effect.sync(() => { · 28 unmodified lines | |||
| 83 | Effect.withSpan(`slack.${method}`), | 78 | Effect.withSpan(`slack.${method}`), |
| 84 | ); | 79 | ); |
| 85 | 80 | ||
| 86 | const createConnectInvite: ISlackService["createConnectInvite"] = ({ | 81 | const createConnectInvite: ISlackService["createConnectInvite"] = ({ email, organization }) => |
| 87 | email, | ||
| 88 | organization, | ||
| 89 | }) => | ||
| 90 | Effect.gen(function* () { | 82 | Effect.gen(function* () { |
| 91 | // Slack channel names: lowercase, no spaces, max 80 chars, unique per | 83 | // Slack channel names: lowercase, no spaces, max 80 chars, unique per |
| 92 | // workspace. Use the org name when available; fall back to the email | 84 | // workspace. Use the org name when available; fall back to the email |
| 93 | // local-part for unauthenticated submissions. | 85 | // local-part for unauthenticated submissions. |
| 94 | const baseName = slugify(organization ?? email).slice(0, 80) || "contact"; | 86 | const baseName = slugify(organization ?? email).slice(0, 80) || "contact"; |
| 95 | const tryCreate = (n: string) => | 87 | const tryCreate = (n: string) => |
| 96 | call<SlackResponse & { channel: { id: string; name: string } }>( | 88 | call<SlackResponse & { channel: { id: string; name: string } }>("conversations.create", { |
| 97 | "conversations.create", | 89 | name: n, |
| 98 | { name: n, is_private: false }, | 90 | is_private: false, |
| 99 | ); | 91 | }); |
| 100 | 92 | ||
| 101 | const created = yield* tryCreate(baseName).pipe( | 93 | const created = yield* tryCreate(baseName).pipe( |
| 102 | Effect.catchTag("SlackError", (err) => | 94 | Effect.catchTag("SlackError", (err) => |
| @@ -125,9 +117,7 @@ const make = Effect.sync(() => { · 22 unmodified lines | |||
| 125 | channel: { id: channel.id, name: channel.name }, | 117 | channel: { id: channel.id, name: channel.name }, |
| 126 | invite: { invite_id: invite.invite_id, url: invite.url }, | 118 | invite: { invite_id: invite.invite_id, url: invite.url }, |
| 127 | }; | 119 | }; |
| 128 | }).pipe( | 120 | }).pipe(Effect.withSpan("slack.createConnectInvite", { attributes: { "slack.email": email } })); |
| 129 | Effect.withSpan("slack.createConnectInvite", { attributes: { "slack.email": email } }), | ||
| 130 | ); | ||
| 131 | 121 | ||
| 132 | return { createConnectInvite } satisfies ISlackService; | 122 | return { createConnectInvite } satisfies ISlackService; |
| 133 | }); | 123 | }); |
| @@ -14,11 +14,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/ · 13 unmodified lines | |||
| 14 | 14 | ||
| 15 | import { ScopeId, SecretId } from "@executor-js/sdk"; | 15 | import { ScopeId, SecretId } from "@executor-js/sdk"; |
| 16 | 16 | ||
| 17 | import { | 17 | import { asOrg, asUser, testUserOrgScopeId } from "./__test-harness__/api-harness"; |
| 18 | asOrg, | ||
| 19 | asUser, | ||
| 20 | testUserOrgScopeId, | ||
| 21 | } from "./__test-harness__/api-harness"; | ||
| 22 | 18 | ||
| 23 | const MINIMAL_OPENAPI_SPEC = JSON.stringify({ | 19 | const MINIMAL_OPENAPI_SPEC = JSON.stringify({ |
| 24 | openapi: "3.0.0", | 20 | openapi: "3.0.0", |
| @@ -108,7 +104,10 @@ const startEchoServer = () => { · 83 unmodified lines | |||
| 108 | 104 | ||
| 109 | return new Promise<{ | 105 | return new Promise<{ |
| 110 | readonly baseUrl: string; | 106 | readonly baseUrl: string; |
| 111 | readonly requests: () => ReadonlyArray<{ readonly path: string; readonly suffix: string | null }>; | 107 | readonly requests: () => ReadonlyArray<{ |
| 108 | readonly path: string; | ||
| 109 | readonly suffix: string | null; | ||
| 110 | }>; | ||
| 112 | readonly close: () => Promise<void>; | 111 | readonly close: () => Promise<void>; |
| 113 | }>((resolveServer) => { | 112 | }>((resolveServer) => { |
| 114 | server.listen(0, "127.0.0.1", () => { | 113 | server.listen(0, "127.0.0.1", () => { |
| @@ -225,10 +224,7 @@ const startGraphqlServer = () => { · 110 unmodified lines | |||
| 225 | }; | 224 | }; |
| 226 | 225 | ||
| 227 | const createCloudMcpServer = () => { | 226 | const createCloudMcpServer = () => { |
| 228 | const server = new McpServer( | 227 | const server = new McpServer({ name: "cloud-e2e-mcp", version: "1.0.0" }, { capabilities: {} }); |
| 229 | { name: "cloud-e2e-mcp", version: "1.0.0" }, | ||
| 230 | { capabilities: {} }, | ||
| 231 | ); | ||
| 232 | 228 | ||
| 233 | server.registerTool( | 229 | server.registerTool( |
| 234 | "simple_echo", | 230 | "simple_echo", |
| @@ -831,9 +827,7 @@ describe("sources api (HTTP)", () => { · 596 unmodified lines | |||
| 831 | const sources = yield* asOrg(orgId, (client) => | 827 | const sources = yield* asOrg(orgId, (client) => |
| 832 | client.sources.list({ params: { scopeId: ScopeId.make(orgId) } }), | 828 | client.sources.list({ params: { scopeId: ScopeId.make(orgId) } }), |
| 833 | ); | 829 | ); |
| 834 | expect(sources.find((source) => source.id === namespace)?.scopeId).toBe( | 830 | expect(sources.find((source) => source.id === namespace)?.scopeId).toBe(ScopeId.make(orgId)); |
| 835 | ScopeId.make(orgId), | ||
| 836 | ); | ||
| 837 | }), | 831 | }), |
| 838 | ); | 832 | ); |
| 839 | 833 | ||
| @@ -49,10 +49,7 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http/build · 48 unmodified lines | |||
| 49 | import { resourceFromAttributes } from "@opentelemetry/resources"; | 49 | import { resourceFromAttributes } from "@opentelemetry/resources"; |
| 50 | import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base"; | 50 | import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base"; |
| 51 | import { WebTracerProvider } from "@opentelemetry/sdk-trace-web"; | 51 | import { WebTracerProvider } from "@opentelemetry/sdk-trace-web"; |
| 52 | import { | 52 | import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions"; |
| 53 | ATTR_SERVICE_NAME, | ||
| 54 | ATTR_SERVICE_VERSION, | ||
| 55 | } from "@opentelemetry/semantic-conventions"; | ||
| 56 | import { env } from "cloudflare:workers"; | 53 | import { env } from "cloudflare:workers"; |
| 57 | import { Effect, Layer } from "effect"; | 54 | import { Effect, Layer } from "effect"; |
| 58 | 55 | ||
| @@ -53,8 +53,7 @@ const TestMcpAuthLive = Layer.succeed(McpAuth)({ · 52 unmodified lines | |||
| 53 | }); | 53 | }); |
| 54 | 54 | ||
| 55 | const TestMcpOrganizationAuthLive = Layer.succeed(McpOrganizationAuth)({ | 55 | const TestMcpOrganizationAuthLive = Layer.succeed(McpOrganizationAuth)({ |
| 56 | authorize: (_accountId, organizationId) => | 56 | authorize: (_accountId, organizationId) => Effect.succeed(!organizationId.startsWith("revoked_")), |
| 57 | Effect.succeed(!organizationId.startsWith("revoked_")), | ||
| 58 | }); | 57 | }); |
| 59 | 58 | ||
| 60 | // --------------------------------------------------------------------------- | 59 | // --------------------------------------------------------------------------- |
| @@ -112,14 +111,14 @@ const handleSeedOrg = async ( · 51 unmodified lines | |||
| 112 | // OTLPTraceExporter — not Durable-Object-specific) to stand in. | 111 | // OTLPTraceExporter — not Durable-Object-specific) to stand in. |
| 113 | const testMcpFetch = HttpEffect.toWebHandler( | 112 | const testMcpFetch = HttpEffect.toWebHandler( |
| 114 | mcpApp.pipe( | 113 | mcpApp.pipe( |
| 115 | Effect.provide( | 114 | Effect.provide(Layer.mergeAll(TestMcpAuthLive, TestMcpOrganizationAuthLive, DoTelemetryLive)), |
| 116 | Layer.mergeAll(TestMcpAuthLive, TestMcpOrganizationAuthLive, DoTelemetryLive), | ||
| 117 | ), | ||
| 118 | ), | 115 | ), |
| 119 | ); | 116 | ); |
| 120 | 117 | ||
| 121 | const realAuthMcpFetch = HttpEffect.toWebHandler( | 118 | const realAuthMcpFetch = HttpEffect.toWebHandler( |
| 122 | mcpApp.pipe(Effect.provide(Layer.mergeAll(McpAuthLive, McpOrganizationAuthLive, DoTelemetryLive))), | 119 | mcpApp.pipe( |
| 120 | Effect.provide(Layer.mergeAll(McpAuthLive, McpOrganizationAuthLive, DoTelemetryLive)), | ||
| 121 | ), | ||
| 123 | ); | 122 | ); |
| 124 | 123 | ||
| 125 | export default { | 124 | export default { |
| @@ -69,9 +69,7 @@ export const OnboardingPage = () => { · 68 unmodified lines | |||
| 69 | }; | 69 | }; |
| 70 | 70 | ||
| 71 | const suggestedName = | 71 | const suggestedName = |
| 72 | auth.status === "authenticated" && | 72 | auth.status === "authenticated" && auth.user.name != null && auth.user.name.trim() !== "" |
| 73 | auth.user.name != null && | ||
| 74 | auth.user.name.trim() !== "" | ||
| 75 | ? `${auth.user.name}'s Organization` | 73 | ? `${auth.user.name}'s Organization` |
| 76 | : ""; | 74 | : ""; |
| 77 | 75 | ||
| @@ -80,7 +78,8 @@ export const OnboardingPage = () => { · 2 unmodified lines | |||
| 80 | onSuccess: () => {}, | 78 | onSuccess: () => {}, |
| 81 | }); | 79 | }); |
| 82 | 80 | ||
| 83 | const isLoading = AsyncResult.isInitial(invitationsResult) || AsyncResult.isWaiting(invitationsResult); | 81 | const isLoading = |
| 82 | AsyncResult.isInitial(invitationsResult) || AsyncResult.isWaiting(invitationsResult); | ||
| 84 | const invitations = AsyncResult.match(invitationsResult, { | 83 | const invitations = AsyncResult.match(invitationsResult, { |
| 85 | onInitial: () => [] as readonly PendingInvitation[], | 84 | onInitial: () => [] as readonly PendingInvitation[], |
| 86 | onFailure: () => [] as readonly PendingInvitation[], | 85 | onFailure: () => [] as readonly PendingInvitation[], |
| @@ -130,10 +129,7 @@ export const OnboardingPage = () => { · 43 unmodified lines | |||
| 130 | )} | 129 | )} |
| 131 | 130 | ||
| 132 | {!isLoading && (count === 0 || sole || count > 1) && ( | 131 | {!isLoading && (count === 0 || sole || count > 1) && ( |
| 133 | <CreateOrgSection | 132 | <CreateOrgSection isPrimary={count === 0} form={form} /> |
| 134 | isPrimary={count === 0} | ||
| 135 | form={form} | ||
| 136 | /> | ||
| 137 | )} | 133 | )} |
| 138 | 134 | ||
| 139 | <footer className="flex items-center justify-center"> | 135 | <footer className="flex items-center justify-center"> |
| @@ -19,10 +19,7 @@ const devCrashGuard = (): Plugin => { · 18 unmodified lines | |||
| 19 | if (installed) return; | 19 | if (installed) return; |
| 20 | installed = true; | 20 | installed = true; |
| 21 | process.on("uncaughtException", (err, origin) => { | 21 | process.on("uncaughtException", (err, origin) => { |
| 22 | console.error( | 22 | console.error(`[dev-crash-guard] uncaughtException (origin=${origin}):`, err); |
| 23 | `[dev-crash-guard] uncaughtException (origin=${origin}):`, | ||
| 24 | err, | ||
| 25 | ); | ||
| 26 | }); | 23 | }); |
| 27 | process.on("unhandledRejection", (reason, promise) => { | 24 | process.on("unhandledRejection", (reason, promise) => { |
| 28 | console.error("[dev-crash-guard] unhandledRejection:", reason, promise); | 25 | console.error("[dev-crash-guard] unhandledRejection:", reason, promise); |
| @@ -16,11 +16,11 @@ · 15 unmodified lines | |||
| 16 | "WORKOS_COOKIE_PASSWORD": "test_cookie_password_at_least_32_chars!", | 16 | "WORKOS_COOKIE_PASSWORD": "test_cookie_password_at_least_32_chars!", |
| 17 | "MCP_AUTHKIT_DOMAIN": "https://test-authkit.example.com", | 17 | "MCP_AUTHKIT_DOMAIN": "https://test-authkit.example.com", |
| 18 | "MCP_RESOURCE_ORIGIN": "https://test-resource.example.com", | 18 | "MCP_RESOURCE_ORIGIN": "https://test-resource.example.com", |
| 19 | "NODE_ENV": "test" | 19 | "NODE_ENV": "test", |
| 20 | }, | 20 | }, |
| 21 | "durable_objects": { | 21 | "durable_objects": { |
| 22 | "bindings": [{"name": "MCP_SESSION", "class_name": "McpSessionDO"}] | 22 | "bindings": [{ "name": "MCP_SESSION", "class_name": "McpSessionDO" }], |
| 23 | }, | 23 | }, |
| 24 | "worker_loaders": [{"binding": "LOADER"}], | 24 | "worker_loaders": [{ "binding": "LOADER" }], |
| 25 | "migrations": [{"tag": "v1", "new_classes": ["McpSessionDO"]}] | 25 | "migrations": [{ "tag": "v1", "new_classes": ["McpSessionDO"] }], |
| 26 | } | 26 | } |
| @@ -12,11 +12,11 @@ · 11 unmodified lines | |||
| 12 | "WORKOS_COOKIE_PASSWORD": "test_cookie_password_at_least_32_chars!", | 12 | "WORKOS_COOKIE_PASSWORD": "test_cookie_password_at_least_32_chars!", |
| 13 | "MCP_AUTHKIT_DOMAIN": "https://test-authkit.example.com", | 13 | "MCP_AUTHKIT_DOMAIN": "https://test-authkit.example.com", |
| 14 | "MCP_RESOURCE_ORIGIN": "https://test-resource.example.com", | 14 | "MCP_RESOURCE_ORIGIN": "https://test-resource.example.com", |
| 15 | "NODE_ENV": "test" | 15 | "NODE_ENV": "test", |
| 16 | }, | 16 | }, |
| 17 | "durable_objects": { | 17 | "durable_objects": { |
| 18 | "bindings": [{"name": "MCP_SESSION", "class_name": "McpSessionDO"}] | 18 | "bindings": [{ "name": "MCP_SESSION", "class_name": "McpSessionDO" }], |
| 19 | }, | 19 | }, |
| 20 | "worker_loaders": [{"binding": "LOADER"}], | 20 | "worker_loaders": [{ "binding": "LOADER" }], |
| 21 | "migrations": [{"tag": "v1", "new_classes": ["McpSessionDO"]}] | 21 | "migrations": [{ "tag": "v1", "new_classes": ["McpSessionDO"] }], |
| 22 | } | 22 | } |
| @@ -99,11 +99,7 @@ const installCli = (): void => { · 98 unmodified lines | |||
| 99 | // Patch shell profiles with PATH | 99 | // Patch shell profiles with PATH |
| 100 | if (process.platform === "win32") { | 100 | if (process.platform === "win32") { |
| 101 | // Add bin dir to the user PATH via registry so new terminals pick it up | 101 | // Add bin dir to the user PATH via registry so new terminals pick it up |
| 102 | const result = spawn( | 102 | const result = spawn("reg", ["query", "HKCU\\Environment", "/v", "Path"], { stdio: "pipe" }); |
| 103 | "reg", | ||
| 104 | ["query", "HKCU\\Environment", "/v", "Path"], | ||
| 105 | { stdio: "pipe" }, | ||
| 106 | ); | ||
| 107 | let out = ""; | 103 | let out = ""; |
| 108 | result.stdout?.on("data", (d: Buffer) => (out += d.toString())); | 104 | result.stdout?.on("data", (d: Buffer) => (out += d.toString())); |
| 109 | result.on("close", (code) => { | 105 | result.on("close", (code) => { |
| @@ -128,8 +124,15 @@ const installCli = (): void => { · 18 unmodified lines | |||
| 128 | if (!current.toLowerCase().includes(CLI_BIN_DIR.toLowerCase())) { | 124 | if (!current.toLowerCase().includes(CLI_BIN_DIR.toLowerCase())) { |
| 129 | const updated = current ? `${current};${CLI_BIN_DIR}` : CLI_BIN_DIR; | 125 | const updated = current ? `${current};${CLI_BIN_DIR}` : CLI_BIN_DIR; |
| 130 | spawn("reg", [ | 126 | spawn("reg", [ |
| 131 | "add", "HKCU\\Environment", "/v", "Path", | 127 | "add", |
| 132 | "/t", "REG_EXPAND_SZ", "/d", updated, "/f", | 128 | "HKCU\\Environment", |
| 129 | "/v", | ||
| 130 | "Path", | ||
| 131 | "/t", | ||
| 132 | "REG_EXPAND_SZ", | ||
| 133 | "/d", | ||
| 134 | updated, | ||
| 135 | "/f", | ||
| 133 | ]); | 136 | ]); |
| 134 | } | 137 | } |
| 135 | }); | 138 | }); |
| @@ -33,10 +33,7 @@ · 32 unmodified lines | |||
| 33 | "foreignKeys": {}, | 33 | "foreignKeys": {}, |
| 34 | "compositePrimaryKeys": { | 34 | "compositePrimaryKeys": { |
| 35 | "blob_namespace_key_pk": { | 35 | "blob_namespace_key_pk": { |
| 36 | "columns": [ | 36 | "columns": ["namespace", "key"], |
| 37 | "namespace", | ||
| 38 | "key" | ||
| 39 | ], | ||
| 40 | "name": "blob_namespace_key_pk" | 37 | "name": "blob_namespace_key_pk" |
| 41 | } | 38 | } |
| 42 | }, | 39 | }, |
| @@ -99,33 +96,24 @@ · 56 unmodified lines | |||
| 99 | "indexes": { | 96 | "indexes": { |
| 100 | "definition_scope_id_idx": { | 97 | "definition_scope_id_idx": { |
| 101 | "name": "definition_scope_id_idx", | 98 | "name": "definition_scope_id_idx", |
| 102 | "columns": [ | 99 | "columns": ["scope_id"], |
| 103 | "scope_id" | ||
| 104 | ], | ||
| 105 | "isUnique": false | 100 | "isUnique": false |
| 106 | }, | 101 | }, |
| 107 | "definition_source_id_idx": { | 102 | "definition_source_id_idx": { |
| 108 | "name": "definition_source_id_idx", | 103 | "name": "definition_source_id_idx", |
| 109 | "columns": [ | 104 | "columns": ["source_id"], |
| 110 | "source_id" | ||
| 111 | ], | ||
| 112 | "isUnique": false | 105 | "isUnique": false |
| 113 | }, | 106 | }, |
| 114 | "definition_plugin_id_idx": { | 107 | "definition_plugin_id_idx": { |
| 115 | "name": "definition_plugin_id_idx", | 108 | "name": "definition_plugin_id_idx", |
| 116 | "columns": [ | 109 | "columns": ["plugin_id"], |
| 117 | "plugin_id" | ||
| 118 | ], | ||
| 119 | "isUnique": false | 110 | "isUnique": false |
| 120 | } | 111 | } |
| 121 | }, | 112 | }, |
| 122 | "foreignKeys": {}, | 113 | "foreignKeys": {}, |
| 123 | "compositePrimaryKeys": { | 114 | "compositePrimaryKeys": { |
| 124 | "definition_scope_id_id_pk": { | 115 | "definition_scope_id_id_pk": { |
| 125 | "columns": [ | 116 | "columns": ["scope_id", "id"], |
| 126 | "scope_id", | ||
| 127 | "id" | ||
| 128 | ], | ||
| 129 | "name": "definition_scope_id_id_pk" | 117 | "name": "definition_scope_id_id_pk" |
| 130 | } | 118 | } |
| 131 | }, | 119 | }, |
| @@ -174,26 +162,19 @@ · 42 unmodified lines | |||
| 174 | "indexes": { | 162 | "indexes": { |
| 175 | "google_discovery_binding_scope_id_idx": { | 163 | "google_discovery_binding_scope_id_idx": { |
| 176 | "name": "google_discovery_binding_scope_id_idx", | 164 | "name": "google_discovery_binding_scope_id_idx", |
| 177 | "columns": [ | 165 | "columns": ["scope_id"], |
| 178 | "scope_id" | ||
| 179 | ], | ||
| 180 | "isUnique": false | 166 | "isUnique": false |
| 181 | }, | 167 | }, |
| 182 | "google_discovery_binding_source_id_idx": { | 168 | "google_discovery_binding_source_id_idx": { |
| 183 | "name": "google_discovery_binding_source_id_idx", | 169 | "name": "google_discovery_binding_source_id_idx", |
| 184 | "columns": [ | 170 | "columns": ["source_id"], |
| 185 | "source_id" | ||
| 186 | ], | ||
| 187 | "isUnique": false | 171 | "isUnique": false |
| 188 | } | 172 | } |
| 189 | }, | 173 | }, |
| 190 | "foreignKeys": {}, | 174 | "foreignKeys": {}, |
| 191 | "compositePrimaryKeys": { | 175 | "compositePrimaryKeys": { |
| 192 | "google_discovery_binding_scope_id_id_pk": { | 176 | "google_discovery_binding_scope_id_id_pk": { |
| 193 | "columns": [ | 177 | "columns": ["scope_id", "id"], |
| 194 | "scope_id", | ||
| 195 | "id" | ||
| 196 | ], | ||
| 197 | "name": "google_discovery_binding_scope_id_id_pk" | 178 | "name": "google_discovery_binding_scope_id_id_pk" |
| 198 | } | 179 | } |
| 199 | }, | 180 | }, |
| @@ -235,19 +216,14 @@ · 35 unmodified lines | |||
| 235 | "indexes": { | 216 | "indexes": { |
| 236 | "google_discovery_oauth_session_scope_id_idx": { | 217 | "google_discovery_oauth_session_scope_id_idx": { |
| 237 | "name": "google_discovery_oauth_session_scope_id_idx", | 218 | "name": "google_discovery_oauth_session_scope_id_idx", |
| 238 | "columns": [ | 219 | "columns": ["scope_id"], |
| 239 | "scope_id" | ||
| 240 | ], | ||
| 241 | "isUnique": false | 220 | "isUnique": false |
| 242 | } | 221 | } |
| 243 | }, | 222 | }, |
| 244 | "foreignKeys": {}, | 223 | "foreignKeys": {}, |
| 245 | "compositePrimaryKeys": { | 224 | "compositePrimaryKeys": { |
| 246 | "google_discovery_oauth_session_scope_id_id_pk": { | 225 | "google_discovery_oauth_session_scope_id_id_pk": { |
| 247 | "columns": [ | 226 | "columns": ["scope_id", "id"], |
| 248 | "scope_id", | ||
| 249 | "id" | ||
| 250 | ], | ||
| 251 | "name": "google_discovery_oauth_session_scope_id_id_pk" | 227 | "name": "google_discovery_oauth_session_scope_id_id_pk" |
| 252 | } | 228 | } |
| 253 | }, | 229 | }, |
| @@ -303,19 +279,14 @@ · 49 unmodified lines | |||
| 303 | "indexes": { | 279 | "indexes": { |
| 304 | "google_discovery_source_scope_id_idx": { | 280 | "google_discovery_source_scope_id_idx": { |
| 305 | "name": "google_discovery_source_scope_id_idx", | 281 | "name": "google_discovery_source_scope_id_idx", |
| 306 | "columns": [ | 282 | "columns": ["scope_id"], |
| 307 | "scope_id" | ||
| 308 | ], | ||
| 309 | "isUnique": false | 283 | "isUnique": false |
| 310 | } | 284 | } |
| 311 | }, | 285 | }, |
| 312 | "foreignKeys": {}, | 286 | "foreignKeys": {}, |
| 313 | "compositePrimaryKeys": { | 287 | "compositePrimaryKeys": { |
| 314 | "google_discovery_source_scope_id_id_pk": { | 288 | "google_discovery_source_scope_id_id_pk": { |
| 315 | "columns": [ | 289 | "columns": ["scope_id", "id"], |
| 316 | "scope_id", | ||
| 317 | "id" | ||
| 318 | ], | ||
| 319 | "name": "google_discovery_source_scope_id_id_pk" | 290 | "name": "google_discovery_source_scope_id_id_pk" |
| 320 | } | 291 | } |
| 321 | }, | 292 | }, |
| @@ -357,26 +328,19 @@ · 35 unmodified lines | |||
| 357 | "indexes": { | 328 | "indexes": { |
| 358 | "graphql_operation_scope_id_idx": { | 329 | "graphql_operation_scope_id_idx": { |
| 359 | "name": "graphql_operation_scope_id_idx", | 330 | "name": "graphql_operation_scope_id_idx", |
| 360 | "columns": [ | 331 | "columns": ["scope_id"], |
| 361 | "scope_id" | ||
| 362 | ], | ||
| 363 | "isUnique": false | 332 | "isUnique": false |
| 364 | }, | 333 | }, |
| 365 | "graphql_operation_source_id_idx": { | 334 | "graphql_operation_source_id_idx": { |
| 366 | "name": "graphql_operation_source_id_idx", | 335 | "name": "graphql_operation_source_id_idx", |
| 367 | "columns": [ | 336 | "columns": ["source_id"], |
| 368 | "source_id" | ||
| 369 | ], | ||
| 370 | "isUnique": false | 337 | "isUnique": false |
| 371 | } | 338 | } |
| 372 | }, | 339 | }, |
| 373 | "foreignKeys": {}, | 340 | "foreignKeys": {}, |
| 374 | "compositePrimaryKeys": { | 341 | "compositePrimaryKeys": { |
| 375 | "graphql_operation_scope_id_id_pk": { | 342 | "graphql_operation_scope_id_id_pk": { |
| 376 | "columns": [ | 343 | "columns": ["scope_id", "id"], |
| 377 | "scope_id", | ||
| 378 | "id" | ||
| 379 | ], | ||
| 380 | "name": "graphql_operation_scope_id_id_pk" | 344 | "name": "graphql_operation_scope_id_id_pk" |
| 381 | } | 345 | } |
| 382 | }, | 346 | }, |
| @@ -425,19 +389,14 @@ · 42 unmodified lines | |||
| 425 | "indexes": { | 389 | "indexes": { |
| 426 | "graphql_source_scope_id_idx": { | 390 | "graphql_source_scope_id_idx": { |
| 427 | "name": "graphql_source_scope_id_idx", | 391 | "name": "graphql_source_scope_id_idx", |
| 428 | "columns": [ | 392 | "columns": ["scope_id"], |
| 429 | "scope_id" | ||
| 430 | ], | ||
| 431 | "isUnique": false | 393 | "isUnique": false |
| 432 | } | 394 | } |
| 433 | }, | 395 | }, |
| 434 | "foreignKeys": {}, | 396 | "foreignKeys": {}, |
| 435 | "compositePrimaryKeys": { | 397 | "compositePrimaryKeys": { |
| 436 | "graphql_source_scope_id_id_pk": { | 398 | "graphql_source_scope_id_id_pk": { |
| 437 | "columns": [ | 399 | "columns": ["scope_id", "id"], |
| 438 | "scope_id", | ||
| 439 | "id" | ||
| 440 | ], | ||
| 441 | "name": "graphql_source_scope_id_id_pk" | 400 | "name": "graphql_source_scope_id_id_pk" |
| 442 | } | 401 | } |
| 443 | }, | 402 | }, |
| @@ -486,26 +445,19 @@ · 42 unmodified lines | |||
| 486 | "indexes": { | 445 | "indexes": { |
| 487 | "mcp_binding_scope_id_idx": { | 446 | "mcp_binding_scope_id_idx": { |
| 488 | "name": "mcp_binding_scope_id_idx", | 447 | "name": "mcp_binding_scope_id_idx", |
| 489 | "columns": [ | 448 | "columns": ["scope_id"], |
| 490 | "scope_id" | ||
| 491 | ], | ||
| 492 | "isUnique": false | 449 | "isUnique": false |
| 493 | }, | 450 | }, |
| 494 | "mcp_binding_source_id_idx": { | 451 | "mcp_binding_source_id_idx": { |
| 495 | "name": "mcp_binding_source_id_idx", | 452 | "name": "mcp_binding_source_id_idx", |
| 496 | "columns": [ | 453 | "columns": ["source_id"], |
| 497 | "source_id" | ||
| 498 | ], | ||
| 499 | "isUnique": false | 454 | "isUnique": false |
| 500 | } | 455 | } |
| 501 | }, | 456 | }, |
| 502 | "foreignKeys": {}, | 457 | "foreignKeys": {}, |
| 503 | "compositePrimaryKeys": { | 458 | "compositePrimaryKeys": { |
| 504 | "mcp_binding_scope_id_id_pk": { | 459 | "mcp_binding_scope_id_id_pk": { |
| 505 | "columns": [ | 460 | "columns": ["scope_id", "id"], |
| 506 | "scope_id", | ||
| 507 | "id" | ||
| 508 | ], | ||
| 509 | "name": "mcp_binding_scope_id_id_pk" | 461 | "name": "mcp_binding_scope_id_id_pk" |
| 510 | } | 462 | } |
| 511 | }, | 463 | }, |
| @@ -554,19 +506,14 @@ · 42 unmodified lines | |||
| 554 | "indexes": { | 506 | "indexes": { |
| 555 | "mcp_oauth_session_scope_id_idx": { | 507 | "mcp_oauth_session_scope_id_idx": { |
| 556 | "name": "mcp_oauth_session_scope_id_idx", | 508 | "name": "mcp_oauth_session_scope_id_idx", |
| 557 | "columns": [ | 509 | "columns": ["scope_id"], |
| 558 | "scope_id" | ||
| 559 | ], | ||
| 560 | "isUnique": false | 510 | "isUnique": false |
| 561 | } | 511 | } |
| 562 | }, | 512 | }, |
| 563 | "foreignKeys": {}, | 513 | "foreignKeys": {}, |
| 564 | "compositePrimaryKeys": { | 514 | "compositePrimaryKeys": { |
| 565 | "mcp_oauth_session_scope_id_id_pk": { | 515 | "mcp_oauth_session_scope_id_id_pk": { |
| 566 | "columns": [ | 516 | "columns": ["scope_id", "id"], |
| 567 | "scope_id", | ||
| 568 | "id" | ||
| 569 | ], | ||
| 570 | "name": "mcp_oauth_session_scope_id_id_pk" | 517 | "name": "mcp_oauth_session_scope_id_id_pk" |
| 571 | } | 518 | } |
| 572 | }, | 519 | }, |
| @@ -615,19 +562,14 @@ · 42 unmodified lines | |||
| 615 | "indexes": { | 562 | "indexes": { |
| 616 | "mcp_source_scope_id_idx": { | 563 | "mcp_source_scope_id_idx": { |
| 617 | "name": "mcp_source_scope_id_idx", | 564 | "name": "mcp_source_scope_id_idx", |
| 618 | "columns": [ | 565 | "columns": ["scope_id"], |
| 619 | "scope_id" | ||
| 620 | ], | ||
| 621 | "isUnique": false | 566 | "isUnique": false |
| 622 | } | 567 | } |
| 623 | }, | 568 | }, |
| 624 | "foreignKeys": {}, | 569 | "foreignKeys": {}, |
| 625 | "compositePrimaryKeys": { | 570 | "compositePrimaryKeys": { |
| 626 | "mcp_source_scope_id_id_pk": { | 571 | "mcp_source_scope_id_id_pk": { |
| 627 | "columns": [ | 572 | "columns": ["scope_id", "id"], |
| 628 | "scope_id", | ||
| 629 | "id" | ||
| 630 | ], | ||
| 631 | "name": "mcp_source_scope_id_id_pk" | 573 | "name": "mcp_source_scope_id_id_pk" |
| 632 | } | 574 | } |
| 633 | }, | 575 | }, |
| @@ -669,19 +611,14 @@ · 35 unmodified lines | |||
| 669 | "indexes": { | 611 | "indexes": { |
| 670 | "openapi_oauth_session_scope_id_idx": { | 612 | "openapi_oauth_session_scope_id_idx": { |
| 671 | "name": "openapi_oauth_session_scope_id_idx", | 613 | "name": "openapi_oauth_session_scope_id_idx", |
| 672 | "columns": [ | 614 | "columns": ["scope_id"], |
| 673 | "scope_id" | ||
| 674 | ], | ||
| 675 | "isUnique": false | 615 | "isUnique": false |
| 676 | } | 616 | } |
| 677 | }, | 617 | }, |
| 678 | "foreignKeys": {}, | 618 | "foreignKeys": {}, |
| 679 | "compositePrimaryKeys": { | 619 | "compositePrimaryKeys": { |
| 680 | "openapi_oauth_session_scope_id_id_pk": { | 620 | "openapi_oauth_session_scope_id_id_pk": { |
| 681 | "columns": [ | 621 | "columns": ["scope_id", "id"], |
| 682 | "scope_id", | ||
| 683 | "id" | ||
| 684 | ], | ||
| 685 | "name": "openapi_oauth_session_scope_id_id_pk" | 622 | "name": "openapi_oauth_session_scope_id_id_pk" |
| 686 | } | 623 | } |
| 687 | }, | 624 | }, |
| @@ -723,26 +660,19 @@ · 35 unmodified lines | |||
| 723 | "indexes": { | 660 | "indexes": { |
| 724 | "openapi_operation_scope_id_idx": { | 661 | "openapi_operation_scope_id_idx": { |
| 725 | "name": "openapi_operation_scope_id_idx", | 662 | "name": "openapi_operation_scope_id_idx", |
| 726 | "columns": [ | 663 | "columns": ["scope_id"], |
| 727 | "scope_id" | ||
| 728 | ], | ||
| 729 | "isUnique": false | 664 | "isUnique": false |
| 730 | }, | 665 | }, |
| 731 | "openapi_operation_source_id_idx": { | 666 | "openapi_operation_source_id_idx": { |
| 732 | "name": "openapi_operation_source_id_idx", | 667 | "name": "openapi_operation_source_id_idx", |
| 733 | "columns": [ | 668 | "columns": ["source_id"], |
| 734 | "source_id" | ||
| 735 | ], | ||
| 736 | "isUnique": false | 669 | "isUnique": false |
| 737 | } | 670 | } |
| 738 | }, | 671 | }, |
| 739 | "foreignKeys": {}, | 672 | "foreignKeys": {}, |
| 740 | "compositePrimaryKeys": { | 673 | "compositePrimaryKeys": { |
| 741 | "openapi_operation_scope_id_id_pk": { | 674 | "openapi_operation_scope_id_id_pk": { |
| 742 | "columns": [ | 675 | "columns": ["scope_id", "id"], |
| 743 | "scope_id", | ||
| 744 | "id" | ||
| 745 | ], | ||
| 746 | "name": "openapi_operation_scope_id_id_pk" | 676 | "name": "openapi_operation_scope_id_id_pk" |
| 747 | } | 677 | } |
| 748 | }, | 678 | }, |
| @@ -812,19 +742,14 @@ · 63 unmodified lines | |||
| 812 | "indexes": { | 742 | "indexes": { |
| 813 | "openapi_source_scope_id_idx": { | 743 | "openapi_source_scope_id_idx": { |
| 814 | "name": "openapi_source_scope_id_idx", | 744 | "name": "openapi_source_scope_id_idx", |
| 815 | "columns": [ | 745 | "columns": ["scope_id"], |
| 816 | "scope_id" | ||
| 817 | ], | ||
| 818 | "isUnique": false | 746 | "isUnique": false |
| 819 | } | 747 | } |
| 820 | }, | 748 | }, |
| 821 | "foreignKeys": {}, | 749 | "foreignKeys": {}, |
| 822 | "compositePrimaryKeys": { | 750 | "compositePrimaryKeys": { |
| 823 | "openapi_source_scope_id_id_pk": { | 751 | "openapi_source_scope_id_id_pk": { |
| 824 | "columns": [ | 752 | "columns": ["scope_id", "id"], |
| 825 | "scope_id", | ||
| 826 | "id" | ||
| 827 | ], | ||
| 828 | "name": "openapi_source_scope_id_id_pk" | 753 | "name": "openapi_source_scope_id_id_pk" |
| 829 | } | 754 | } |
| 830 | }, | 755 | }, |
| @@ -873,26 +798,19 @@ · 42 unmodified lines | |||
| 873 | "indexes": { | 798 | "indexes": { |
| 874 | "secret_scope_id_idx": { | 799 | "secret_scope_id_idx": { |
| 875 | "name": "secret_scope_id_idx", | 800 | "name": "secret_scope_id_idx", |
| 876 | "columns": [ | 801 | "columns": ["scope_id"], |
| 877 | "scope_id" | ||
| 878 | ], | ||
| 879 | "isUnique": false | 802 | "isUnique": false |
| 880 | }, | 803 | }, |
| 881 | "secret_provider_idx": { | 804 | "secret_provider_idx": { |
| 882 | "name": "secret_provider_idx", | 805 | "name": "secret_provider_idx", |
| 883 | "columns": [ | 806 | "columns": ["provider"], |
| 884 | "provider" | ||
| 885 | ], | ||
| 886 | "isUnique": false | 807 | "isUnique": false |
| 887 | } | 808 | } |
| 888 | }, | 809 | }, |
| 889 | "foreignKeys": {}, | 810 | "foreignKeys": {}, |
| 890 | "compositePrimaryKeys": { | 811 | "compositePrimaryKeys": { |
| 891 | "secret_scope_id_id_pk": { | 812 | "secret_scope_id_id_pk": { |
| 892 | "columns": [ | 813 | "columns": ["scope_id", "id"], |
| 893 | "scope_id", | ||
| 894 | "id" | ||
| 895 | ], | ||
| 896 | "name": "secret_scope_id_id_pk" | 814 | "name": "secret_scope_id_id_pk" |
| 897 | } | 815 | } |
| 898 | }, | 816 | }, |
| @@ -986,26 +904,19 @@ · 87 unmodified lines | |||
| 986 | "indexes": { | 904 | "indexes": { |
| 987 | "source_scope_id_idx": { | 905 | "source_scope_id_idx": { |
| 988 | "name": "source_scope_id_idx", | 906 | "name": "source_scope_id_idx", |
| 989 | "columns": [ | 907 | "columns": ["scope_id"], |
| 990 | "scope_id" | ||
| 991 | ], | ||
| 992 | "isUnique": false | 908 | "isUnique": false |
| 993 | }, | 909 | }, |
| 994 | "source_plugin_id_idx": { | 910 | "source_plugin_id_idx": { |
| 995 | "name": "source_plugin_id_idx", | 911 | "name": "source_plugin_id_idx", |
| 996 | "columns": [ | 912 | "columns": ["plugin_id"], |
| 997 | "plugin_id" | ||
| 998 | ], | ||
| 999 | "isUnique": false | 913 | "isUnique": false |
| 1000 | } | 914 | } |
| 1001 | }, | 915 | }, |
| 1002 | "foreignKeys": {}, | 916 | "foreignKeys": {}, |
| 1003 | "compositePrimaryKeys": { | 917 | "compositePrimaryKeys": { |
| 1004 | "source_scope_id_id_pk": { | 918 | "source_scope_id_id_pk": { |
| 1005 | "columns": [ | 919 | "columns": ["scope_id", "id"], |
| 1006 | "scope_id", | ||
| 1007 | "id" | ||
| 1008 | ], | ||
| 1009 | "name": "source_scope_id_id_pk" | 920 | "name": "source_scope_id_id_pk" |
| 1010 | } | 921 | } |
| 1011 | }, | 922 | }, |
| @@ -1089,33 +1000,24 @@ · 77 unmodified lines | |||
| 1089 | "indexes": { | 1000 | "indexes": { |
| 1090 | "tool_scope_id_idx": { | 1001 | "tool_scope_id_idx": { |
| 1091 | "name": "tool_scope_id_idx", | 1002 | "name": "tool_scope_id_idx", |
| 1092 | "columns": [ | 1003 | "columns": ["scope_id"], |
| 1093 | "scope_id" | ||
| 1094 | ], | ||
| 1095 | "isUnique": false | 1004 | "isUnique": false |
| 1096 | }, | 1005 | }, |
| 1097 | "tool_source_id_idx": { | 1006 | "tool_source_id_idx": { |
| 1098 | "name": "tool_source_id_idx", | 1007 | "name": "tool_source_id_idx", |
| 1099 | "columns": [ | 1008 | "columns": ["source_id"], |
| 1100 | "source_id" | ||
| 1101 | ], | ||
| 1102 | "isUnique": false | 1009 | "isUnique": false |
| 1103 | }, | 1010 | }, |
| 1104 | "tool_plugin_id_idx": { | 1011 | "tool_plugin_id_idx": { |
| 1105 | "name": "tool_plugin_id_idx", | 1012 | "name": "tool_plugin_id_idx", |
| 1106 | "columns": [ | 1013 | "columns": ["plugin_id"], |
| 1107 | "plugin_id" | ||
| 1108 | ], | ||
| 1109 | "isUnique": false | 1014 | "isUnique": false |
| 1110 | } | 1015 | } |
| 1111 | }, | 1016 | }, |
| 1112 | "foreignKeys": {}, | 1017 | "foreignKeys": {}, |
| 1113 | "compositePrimaryKeys": { | 1018 | "compositePrimaryKeys": { |
| 1114 | "tool_scope_id_id_pk": { | 1019 | "tool_scope_id_id_pk": { |
| 1115 | "columns": [ | 1020 | "columns": ["scope_id", "id"], |
| 1116 | "scope_id", | ||
| 1117 | "id" | ||
| 1118 | ], | ||
| 1119 | "name": "tool_scope_id_id_pk" | 1021 | "name": "tool_scope_id_id_pk" |
| 1120 | } | 1022 | } |
| 1121 | }, | 1023 | }, |
| @@ -1133,4 +1035,4 @@ · 11 unmodified lines | |||
| 1133 | "internal": { | 1035 | "internal": { |
| 1134 | "indexes": {} | 1036 | "indexes": {} |
| 1135 | } | 1037 | } |
| 1136 | } | 1038 | } |
| @@ -33,10 +33,7 @@ · 32 unmodified lines | |||
| 33 | "foreignKeys": {}, | 33 | "foreignKeys": {}, |
| 34 | "compositePrimaryKeys": { | 34 | "compositePrimaryKeys": { |
| 35 | "blob_namespace_key_pk": { | 35 | "blob_namespace_key_pk": { |
| 36 | "columns": [ | 36 | "columns": ["namespace", "key"], |
| 37 | "namespace", | ||
| 38 | "key" | ||
| 39 | ], | ||
| 40 | "name": "blob_namespace_key_pk" | 37 | "name": "blob_namespace_key_pk" |
| 41 | } | 38 | } |
| 42 | }, | 39 | }, |
| @@ -99,33 +96,24 @@ · 56 unmodified lines | |||
| 99 | "indexes": { | 96 | "indexes": { |
| 100 | "definition_scope_id_idx": { | 97 | "definition_scope_id_idx": { |
| 101 | "name": "definition_scope_id_idx", | 98 | "name": "definition_scope_id_idx", |
| 102 | "columns": [ | 99 | "columns": ["scope_id"], |
| 103 | "scope_id" | ||
| 104 | ], | ||
| 105 | "isUnique": false | 100 | "isUnique": false |
| 106 | }, | 101 | }, |
| 107 | "definition_source_id_idx": { | 102 | "definition_source_id_idx": { |
| 108 | "name": "definition_source_id_idx", | 103 | "name": "definition_source_id_idx", |
| 109 | "columns": [ | 104 | "columns": ["source_id"], |
| 110 | "source_id" | ||
| 111 | ], | ||
| 112 | "isUnique": false | 105 | "isUnique": false |
| 113 | }, | 106 | }, |
| 114 | "definition_plugin_id_idx": { | 107 | "definition_plugin_id_idx": { |
| 115 | "name": "definition_plugin_id_idx", | 108 | "name": "definition_plugin_id_idx", |
| 116 | "columns": [ | 109 | "columns": ["plugin_id"], |
| 117 | "plugin_id" | ||
| 118 | ], | ||
| 119 | "isUnique": false | 110 | "isUnique": false |
| 120 | } | 111 | } |
| 121 | }, | 112 | }, |
| 122 | "foreignKeys": {}, | 113 | "foreignKeys": {}, |
| 123 | "compositePrimaryKeys": { | 114 | "compositePrimaryKeys": { |
| 124 | "definition_scope_id_id_pk": { | 115 | "definition_scope_id_id_pk": { |
| 125 | "columns": [ | 116 | "columns": ["scope_id", "id"], |
| 126 | "scope_id", | ||
| 127 | "id" | ||
| 128 | ], | ||
| 129 | "name": "definition_scope_id_id_pk" | 117 | "name": "definition_scope_id_id_pk" |
| 130 | } | 118 | } |
| 131 | }, | 119 | }, |
| @@ -174,26 +162,19 @@ · 42 unmodified lines | |||
| 174 | "indexes": { | 162 | "indexes": { |
| 175 | "google_discovery_binding_scope_id_idx": { | 163 | "google_discovery_binding_scope_id_idx": { |
| 176 | "name": "google_discovery_binding_scope_id_idx", | 164 | "name": "google_discovery_binding_scope_id_idx", |
| 177 | "columns": [ | 165 | "columns": ["scope_id"], |
| 178 | "scope_id" | ||
| 179 | ], | ||
| 180 | "isUnique": false | 166 | "isUnique": false |
| 181 | }, | 167 | }, |
| 182 | "google_discovery_binding_source_id_idx": { | 168 | "google_discovery_binding_source_id_idx": { |
| 183 | "name": "google_discovery_binding_source_id_idx", | 169 | "name": "google_discovery_binding_source_id_idx", |
| 184 | "columns": [ | 170 | "columns": ["source_id"], |
| 185 | "source_id" | ||
| 186 | ], | ||
| 187 | "isUnique": false | 171 | "isUnique": false |
| 188 | } | 172 | } |
| 189 | }, | 173 | }, |
| 190 | "foreignKeys": {}, | 174 | "foreignKeys": {}, |
| 191 | "compositePrimaryKeys": { | 175 | "compositePrimaryKeys": { |
| 192 | "google_discovery_binding_scope_id_id_pk": { | 176 | "google_discovery_binding_scope_id_id_pk": { |
| 193 | "columns": [ | 177 | "columns": ["scope_id", "id"], |
| 194 | "scope_id", | ||
| 195 | "id" | ||
| 196 | ], | ||
| 197 | "name": "google_discovery_binding_scope_id_id_pk" | 178 | "name": "google_discovery_binding_scope_id_id_pk" |
| 198 | } | 179 | } |
| 199 | }, | 180 | }, |
| @@ -235,19 +216,14 @@ · 35 unmodified lines | |||
| 235 | "indexes": { | 216 | "indexes": { |
| 236 | "google_discovery_oauth_session_scope_id_idx": { | 217 | "google_discovery_oauth_session_scope_id_idx": { |
| 237 | "name": "google_discovery_oauth_session_scope_id_idx", | 218 | "name": "google_discovery_oauth_session_scope_id_idx", |
| 238 | "columns": [ | 219 | "columns": ["scope_id"], |
| 239 | "scope_id" | ||
| 240 | ], | ||
| 241 | "isUnique": false | 220 | "isUnique": false |
| 242 | } | 221 | } |
| 243 | }, | 222 | }, |
| 244 | "foreignKeys": {}, | 223 | "foreignKeys": {}, |
| 245 | "compositePrimaryKeys": { | 224 | "compositePrimaryKeys": { |
| 246 | "google_discovery_oauth_session_scope_id_id_pk": { | 225 | "google_discovery_oauth_session_scope_id_id_pk": { |
| 247 | "columns": [ | 226 | "columns": ["scope_id", "id"], |
| 248 | "scope_id", | ||
| 249 | "id" | ||
| 250 | ], | ||
| 251 | "name": "google_discovery_oauth_session_scope_id_id_pk" | 227 | "name": "google_discovery_oauth_session_scope_id_id_pk" |
| 252 | } | 228 | } |
| 253 | }, | 229 | }, |
| @@ -303,19 +279,14 @@ · 49 unmodified lines | |||
| 303 | "indexes": { | 279 | "indexes": { |
| 304 | "google_discovery_source_scope_id_idx": { | 280 | "google_discovery_source_scope_id_idx": { |
| 305 | "name": "google_discovery_source_scope_id_idx", | 281 | "name": "google_discovery_source_scope_id_idx", |
| 306 | "columns": [ | 282 | "columns": ["scope_id"], |
| 307 | "scope_id" | ||
| 308 | ], | ||
| 309 | "isUnique": false | 283 | "isUnique": false |
| 310 | } | 284 | } |
| 311 | }, | 285 | }, |
| 312 | "foreignKeys": {}, | 286 | "foreignKeys": {}, |
| 313 | "compositePrimaryKeys": { | 287 | "compositePrimaryKeys": { |
| 314 | "google_discovery_source_scope_id_id_pk": { | 288 | "google_discovery_source_scope_id_id_pk": { |
| 315 | "columns": [ | 289 | "columns": ["scope_id", "id"], |
| 316 | "scope_id", | ||
| 317 | "id" | ||
| 318 | ], | ||
| 319 | "name": "google_discovery_source_scope_id_id_pk" | 290 | "name": "google_discovery_source_scope_id_id_pk" |
| 320 | } | 291 | } |
| 321 | }, | 292 | }, |
| @@ -357,26 +328,19 @@ · 35 unmodified lines | |||
| 357 | "indexes": { | 328 | "indexes": { |
| 358 | "graphql_operation_scope_id_idx": { | 329 | "graphql_operation_scope_id_idx": { |
| 359 | "name": "graphql_operation_scope_id_idx", | 330 | "name": "graphql_operation_scope_id_idx", |
| 360 | "columns": [ | 331 | "columns": ["scope_id"], |
| 361 | "scope_id" | ||
| 362 | ], | ||
| 363 | "isUnique": false | 332 | "isUnique": false |
| 364 | }, | 333 | }, |
| 365 | "graphql_operation_source_id_idx": { | 334 | "graphql_operation_source_id_idx": { |
| 366 | "name": "graphql_operation_source_id_idx", | 335 | "name": "graphql_operation_source_id_idx", |
| 367 | "columns": [ | 336 | "columns": ["source_id"], |
| 368 | "source_id" | ||
| 369 | ], | ||
| 370 | "isUnique": false | 337 | "isUnique": false |
| 371 | } | 338 | } |
| 372 | }, | 339 | }, |
| 373 | "foreignKeys": {}, | 340 | "foreignKeys": {}, |
| 374 | "compositePrimaryKeys": { | 341 | "compositePrimaryKeys": { |
| 375 | "graphql_operation_scope_id_id_pk": { | 342 | "graphql_operation_scope_id_id_pk": { |
| 376 | "columns": [ | 343 | "columns": ["scope_id", "id"], |
| 377 | "scope_id", | ||
| 378 | "id" | ||
| 379 | ], | ||
| 380 | "name": "graphql_operation_scope_id_id_pk" | 344 | "name": "graphql_operation_scope_id_id_pk" |
| 381 | } | 345 | } |
| 382 | }, | 346 | }, |
| @@ -425,19 +389,14 @@ · 42 unmodified lines | |||
| 425 | "indexes": { | 389 | "indexes": { |
| 426 | "graphql_source_scope_id_idx": { | 390 | "graphql_source_scope_id_idx": { |
| 427 | "name": "graphql_source_scope_id_idx", | 391 | "name": "graphql_source_scope_id_idx", |
| 428 | "columns": [ | 392 | "columns": ["scope_id"], |
| 429 | "scope_id" | ||
| 430 | ], | ||
| 431 | "isUnique": false | 393 | "isUnique": false |
| 432 | } | 394 | } |
| 433 | }, | 395 | }, |
| 434 | "foreignKeys": {}, | 396 | "foreignKeys": {}, |
| 435 | "compositePrimaryKeys": { | 397 | "compositePrimaryKeys": { |
| 436 | "graphql_source_scope_id_id_pk": { | 398 | "graphql_source_scope_id_id_pk": { |
| 437 | "columns": [ | 399 | "columns": ["scope_id", "id"], |
| 438 | "scope_id", | ||
| 439 | "id" | ||
| 440 | ], | ||
| 441 | "name": "graphql_source_scope_id_id_pk" | 400 | "name": "graphql_source_scope_id_id_pk" |
| 442 | } | 401 | } |
| 443 | }, | 402 | }, |
| @@ -486,26 +445,19 @@ · 42 unmodified lines | |||
| 486 | "indexes": { | 445 | "indexes": { |
| 487 | "mcp_binding_scope_id_idx": { | 446 | "mcp_binding_scope_id_idx": { |
| 488 | "name": "mcp_binding_scope_id_idx", | 447 | "name": "mcp_binding_scope_id_idx", |
| 489 | "columns": [ | 448 | "columns": ["scope_id"], |
| 490 | "scope_id" | ||
| 491 | ], | ||
| 492 | "isUnique": false | 449 | "isUnique": false |
| 493 | }, | 450 | }, |
| 494 | "mcp_binding_source_id_idx": { | 451 | "mcp_binding_source_id_idx": { |
| 495 | "name": "mcp_binding_source_id_idx", | 452 | "name": "mcp_binding_source_id_idx", |
| 496 | "columns": [ | 453 | "columns": ["source_id"], |
| 497 | "source_id" | ||
| 498 | ], | ||
| 499 | "isUnique": false | 454 | "isUnique": false |
| 500 | } | 455 | } |
| 501 | }, | 456 | }, |
| 502 | "foreignKeys": {}, | 457 | "foreignKeys": {}, |
| 503 | "compositePrimaryKeys": { | 458 | "compositePrimaryKeys": { |
| 504 | "mcp_binding_scope_id_id_pk": { | 459 | "mcp_binding_scope_id_id_pk": { |
| 505 | "columns": [ | 460 | "columns": ["scope_id", "id"], |
| 506 | "scope_id", | ||
| 507 | "id" | ||
| 508 | ], | ||
| 509 | "name": "mcp_binding_scope_id_id_pk" | 461 | "name": "mcp_binding_scope_id_id_pk" |
| 510 | } | 462 | } |
| 511 | }, | 463 | }, |
| @@ -554,19 +506,14 @@ · 42 unmodified lines | |||
| 554 | "indexes": { | 506 | "indexes": { |
| 555 | "mcp_oauth_session_scope_id_idx": { | 507 | "mcp_oauth_session_scope_id_idx": { |
| 556 | "name": "mcp_oauth_session_scope_id_idx", | 508 | "name": "mcp_oauth_session_scope_id_idx", |
| 557 | "columns": [ | 509 | "columns": ["scope_id"], |
| 558 | "scope_id" | ||
| 559 | ], | ||
| 560 | "isUnique": false | 510 | "isUnique": false |
| 561 | } | 511 | } |
| 562 | }, | 512 | }, |
| 563 | "foreignKeys": {}, | 513 | "foreignKeys": {}, |
| 564 | "compositePrimaryKeys": { | 514 | "compositePrimaryKeys": { |
| 565 | "mcp_oauth_session_scope_id_id_pk": { | 515 | "mcp_oauth_session_scope_id_id_pk": { |
| 566 | "columns": [ | 516 | "columns": ["scope_id", "id"], |
| 567 | "scope_id", | ||
| 568 | "id" | ||
| 569 | ], | ||
| 570 | "name": "mcp_oauth_session_scope_id_id_pk" | 517 | "name": "mcp_oauth_session_scope_id_id_pk" |
| 571 | } | 518 | } |
| 572 | }, | 519 | }, |
| @@ -615,19 +562,14 @@ · 42 unmodified lines | |||
| 615 | "indexes": { | 562 | "indexes": { |
| 616 | "mcp_source_scope_id_idx": { | 563 | "mcp_source_scope_id_idx": { |
| 617 | "name": "mcp_source_scope_id_idx", | 564 | "name": "mcp_source_scope_id_idx", |
| 618 | "columns": [ | 565 | "columns": ["scope_id"], |
| 619 | "scope_id" | ||
| 620 | ], | ||
| 621 | "isUnique": false | 566 | "isUnique": false |
| 622 | } | 567 | } |
| 623 | }, | 568 | }, |
| 624 | "foreignKeys": {}, | 569 | "foreignKeys": {}, |
| 625 | "compositePrimaryKeys": { | 570 | "compositePrimaryKeys": { |
| 626 | "mcp_source_scope_id_id_pk": { | 571 | "mcp_source_scope_id_id_pk": { |
| 627 | "columns": [ | 572 | "columns": ["scope_id", "id"], |
| 628 | "scope_id", | ||
| 629 | "id" | ||
| 630 | ], | ||
| 631 | "name": "mcp_source_scope_id_id_pk" | 573 | "name": "mcp_source_scope_id_id_pk" |
| 632 | } | 574 | } |
| 633 | }, | 575 | }, |
| @@ -669,19 +611,14 @@ · 35 unmodified lines | |||
| 669 | "indexes": { | 611 | "indexes": { |
| 670 | "openapi_oauth_session_scope_id_idx": { | 612 | "openapi_oauth_session_scope_id_idx": { |
| 671 | "name": "openapi_oauth_session_scope_id_idx", | 613 | "name": "openapi_oauth_session_scope_id_idx", |
| 672 | "columns": [ | 614 | "columns": ["scope_id"], |
| 673 | "scope_id" | ||
| 674 | ], | ||
| 675 | "isUnique": false | 615 | "isUnique": false |
| 676 | } | 616 | } |
| 677 | }, | 617 | }, |
| 678 | "foreignKeys": {}, | 618 | "foreignKeys": {}, |
| 679 | "compositePrimaryKeys": { | 619 | "compositePrimaryKeys": { |
| 680 | "openapi_oauth_session_scope_id_id_pk": { | 620 | "openapi_oauth_session_scope_id_id_pk": { |
| 681 | "columns": [ | 621 | "columns": ["scope_id", "id"], |
| 682 | "scope_id", | ||
| 683 | "id" | ||
| 684 | ], | ||
| 685 | "name": "openapi_oauth_session_scope_id_id_pk" | 622 | "name": "openapi_oauth_session_scope_id_id_pk" |
| 686 | } | 623 | } |
| 687 | }, | 624 | }, |
| @@ -723,26 +660,19 @@ · 35 unmodified lines | |||
| 723 | "indexes": { | 660 | "indexes": { |
| 724 | "openapi_operation_scope_id_idx": { | 661 | "openapi_operation_scope_id_idx": { |
| 725 | "name": "openapi_operation_scope_id_idx", | 662 | "name": "openapi_operation_scope_id_idx", |
| 726 | "columns": [ | 663 | "columns": ["scope_id"], |
| 727 | "scope_id" | ||
| 728 | ], | ||
| 729 | "isUnique": false | 664 | "isUnique": false |
| 730 | }, | 665 | }, |
| 731 | "openapi_operation_source_id_idx": { | 666 | "openapi_operation_source_id_idx": { |
| 732 | "name": "openapi_operation_source_id_idx", | 667 | "name": "openapi_operation_source_id_idx", |
| 733 | "columns": [ | 668 | "columns": ["source_id"], |
| 734 | "source_id" | ||
| 735 | ], | ||
| 736 | "isUnique": false | 669 | "isUnique": false |
| 737 | } | 670 | } |
| 738 | }, | 671 | }, |
| 739 | "foreignKeys": {}, | 672 | "foreignKeys": {}, |
| 740 | "compositePrimaryKeys": { | 673 | "compositePrimaryKeys": { |
| 741 | "openapi_operation_scope_id_id_pk": { | 674 | "openapi_operation_scope_id_id_pk": { |
| 742 | "columns": [ | 675 | "columns": ["scope_id", "id"], |
| 743 | "scope_id", | ||
| 744 | "id" | ||
| 745 | ], | ||
| 746 | "name": "openapi_operation_scope_id_id_pk" | 676 | "name": "openapi_operation_scope_id_id_pk" |
| 747 | } | 677 | } |
| 748 | }, | 678 | }, |
| @@ -819,19 +749,14 @@ · 70 unmodified lines | |||
| 819 | "indexes": { | 749 | "indexes": { |
| 820 | "openapi_source_scope_id_idx": { | 750 | "openapi_source_scope_id_idx": { |
| 821 | "name": "openapi_source_scope_id_idx", | 751 | "name": "openapi_source_scope_id_idx", |
| 822 | "columns": [ | 752 | "columns": ["scope_id"], |
| 823 | "scope_id" | ||
| 824 | ], | ||
| 825 | "isUnique": false | 753 | "isUnique": false |
| 826 | } | 754 | } |
| 827 | }, | 755 | }, |
| 828 | "foreignKeys": {}, | 756 | "foreignKeys": {}, |
| 829 | "compositePrimaryKeys": { | 757 | "compositePrimaryKeys": { |
| 830 | "openapi_source_scope_id_id_pk": { | 758 | "openapi_source_scope_id_id_pk": { |
| 831 | "columns": [ | 759 | "columns": ["scope_id", "id"], |
| 832 | "scope_id", | ||
| 833 | "id" | ||
| 834 | ], | ||
| 835 | "name": "openapi_source_scope_id_id_pk" | 760 | "name": "openapi_source_scope_id_id_pk" |
| 836 | } | 761 | } |
| 837 | }, | 762 | }, |
| @@ -880,26 +805,19 @@ · 42 unmodified lines | |||
| 880 | "indexes": { | 805 | "indexes": { |
| 881 | "secret_scope_id_idx": { | 806 | "secret_scope_id_idx": { |
| 882 | "name": "secret_scope_id_idx", | 807 | "name": "secret_scope_id_idx", |
| 883 | "columns": [ | 808 | "columns": ["scope_id"], |
| 884 | "scope_id" | ||
| 885 | ], | ||
| 886 | "isUnique": false | 809 | "isUnique": false |
| 887 | }, | 810 | }, |
| 888 | "secret_provider_idx": { | 811 | "secret_provider_idx": { |
| 889 | "name": "secret_provider_idx", | 812 | "name": "secret_provider_idx", |
| 890 | "columns": [ | 813 | "columns": ["provider"], |
| 891 | "provider" | ||
| 892 | ], | ||
| 893 | "isUnique": false | 814 | "isUnique": false |
| 894 | } | 815 | } |
| 895 | }, | 816 | }, |
| 896 | "foreignKeys": {}, | 817 | "foreignKeys": {}, |
| 897 | "compositePrimaryKeys": { | 818 | "compositePrimaryKeys": { |
| 898 | "secret_scope_id_id_pk": { | 819 | "secret_scope_id_id_pk": { |
| 899 | "columns": [ | 820 | "columns": ["scope_id", "id"], |
| 900 | "scope_id", | ||
| 901 | "id" | ||
| 902 | ], | ||
| 903 | "name": "secret_scope_id_id_pk" | 821 | "name": "secret_scope_id_id_pk" |
| 904 | } | 822 | } |
| 905 | }, | 823 | }, |
| @@ -993,26 +911,19 @@ · 87 unmodified lines | |||
| 993 | "indexes": { | 911 | "indexes": { |
| 994 | "source_scope_id_idx": { | 912 | "source_scope_id_idx": { |
| 995 | "name": "source_scope_id_idx", | 913 | "name": "source_scope_id_idx", |
| 996 | "columns": [ | 914 | "columns": ["scope_id"], |
| 997 | "scope_id" | ||
| 998 | ], | ||
| 999 | "isUnique": false | 915 | "isUnique": false |
| 1000 | }, | 916 | }, |
| 1001 | "source_plugin_id_idx": { | 917 | "source_plugin_id_idx": { |
| 1002 | "name": "source_plugin_id_idx", | 918 | "name": "source_plugin_id_idx", |
| 1003 | "columns": [ | 919 | "columns": ["plugin_id"], |
| 1004 | "plugin_id" | ||
| 1005 | ], | ||
| 1006 | "isUnique": false | 920 | "isUnique": false |
| 1007 | } | 921 | } |
| 1008 | }, | 922 | }, |
| 1009 | "foreignKeys": {}, | 923 | "foreignKeys": {}, |
| 1010 | "compositePrimaryKeys": { | 924 | "compositePrimaryKeys": { |
| 1011 | "source_scope_id_id_pk": { | 925 | "source_scope_id_id_pk": { |
| 1012 | "columns": [ | 926 | "columns": ["scope_id", "id"], |
| 1013 | "scope_id", | ||
| 1014 | "id" | ||
| 1015 | ], | ||
| 1016 | "name": "source_scope_id_id_pk" | 927 | "name": "source_scope_id_id_pk" |
| 1017 | } | 928 | } |
| 1018 | }, | 929 | }, |
| @@ -1096,33 +1007,24 @@ · 77 unmodified lines | |||
| 1096 | "indexes": { | 1007 | "indexes": { |
| 1097 | "tool_scope_id_idx": { | 1008 | "tool_scope_id_idx": { |
| 1098 | "name": "tool_scope_id_idx", | 1009 | "name": "tool_scope_id_idx", |
| 1099 | "columns": [ | 1010 | "columns": ["scope_id"], |
| 1100 | "scope_id" | ||
| 1101 | ], | ||
| 1102 | "isUnique": false | 1011 | "isUnique": false |
| 1103 | }, | 1012 | }, |
| 1104 | "tool_source_id_idx": { | 1013 | "tool_source_id_idx": { |
| 1105 | "name": "tool_source_id_idx", | 1014 | "name": "tool_source_id_idx", |
| 1106 | "columns": [ | 1015 | "columns": ["source_id"], |
| 1107 | "source_id" | ||
| 1108 | ], | ||
| 1109 | "isUnique": false | 1016 | "isUnique": false |
| 1110 | }, | 1017 | }, |
| 1111 | "tool_plugin_id_idx": { | 1018 | "tool_plugin_id_idx": { |
| 1112 | "name": "tool_plugin_id_idx", | 1019 | "name": "tool_plugin_id_idx", |
| 1113 | "columns": [ | 1020 | "columns": ["plugin_id"], |
| 1114 | "plugin_id" | ||
| 1115 | ], | ||
| 1116 | "isUnique": false | 1021 | "isUnique": false |
| 1117 | } | 1022 | } |
| 1118 | }, | 1023 | }, |
| 1119 | "foreignKeys": {}, | 1024 | "foreignKeys": {}, |
| 1120 | "compositePrimaryKeys": { | 1025 | "compositePrimaryKeys": { |
| 1121 | "tool_scope_id_id_pk": { | 1026 | "tool_scope_id_id_pk": { |
| 1122 | "columns": [ | 1027 | "columns": ["scope_id", "id"], |
| 1123 | "scope_id", | ||
| 1124 | "id" | ||
| 1125 | ], | ||
| 1126 | "name": "tool_scope_id_id_pk" | 1028 | "name": "tool_scope_id_id_pk" |
| 1127 | } | 1029 | } |
| 1128 | }, | 1030 | }, |
| @@ -1140,4 +1042,4 @@ · 11 unmodified lines | |||
| 1140 | "internal": { | 1042 | "internal": { |
| 1141 | "indexes": {} | 1043 | "indexes": {} |
| 1142 | } | 1044 | } |
| 1143 | } | 1045 | } |
| @@ -33,10 +33,7 @@ · 32 unmodified lines | |||
| 33 | "foreignKeys": {}, | 33 | "foreignKeys": {}, |
| 34 | "compositePrimaryKeys": { | 34 | "compositePrimaryKeys": { |
| 35 | "blob_namespace_key_pk": { | 35 | "blob_namespace_key_pk": { |
| 36 | "columns": [ | 36 | "columns": ["namespace", "key"], |
| 37 | "namespace", | ||
| 38 | "key" | ||
| 39 | ], | ||
| 40 | "name": "blob_namespace_key_pk" | 37 | "name": "blob_namespace_key_pk" |
| 41 | } | 38 | } |
| 42 | }, | 39 | }, |
| @@ -134,26 +131,19 @@ · 91 unmodified lines | |||
| 134 | "indexes": { | 131 | "indexes": { |
| 135 | "connection_scope_id_idx": { | 132 | "connection_scope_id_idx": { |
| 136 | "name": "connection_scope_id_idx", | 133 | "name": "connection_scope_id_idx", |
| 137 | "columns": [ | 134 | "columns": ["scope_id"], |
| 138 | "scope_id" | ||
| 139 | ], | ||
| 140 | "isUnique": false | 135 | "isUnique": false |
| 141 | }, | 136 | }, |
| 142 | "connection_provider_idx": { | 137 | "connection_provider_idx": { |
| 143 | "name": "connection_provider_idx", | 138 | "name": "connection_provider_idx", |
| 144 | "columns": [ | 139 | "columns": ["provider"], |
| 145 | "provider" | ||
| 146 | ], | ||
| 147 | "isUnique": false | 140 | "isUnique": false |
| 148 | } | 141 | } |
| 149 | }, | 142 | }, |
| 150 | "foreignKeys": {}, | 143 | "foreignKeys": {}, |
| 151 | "compositePrimaryKeys": { | 144 | "compositePrimaryKeys": { |
| 152 | "connection_scope_id_id_pk": { | 145 | "connection_scope_id_id_pk": { |
| 153 | "columns": [ | 146 | "columns": ["scope_id", "id"], |
| 154 | "scope_id", | ||
| 155 | "id" | ||
| 156 | ], | ||
| 157 | "name": "connection_scope_id_id_pk" | 147 | "name": "connection_scope_id_id_pk" |
| 158 | } | 148 | } |
| 159 | }, | 149 | }, |
| @@ -216,33 +206,24 @@ · 56 unmodified lines | |||
| 216 | "indexes": { | 206 | "indexes": { |
| 217 | "definition_scope_id_idx": { | 207 | "definition_scope_id_idx": { |
| 218 | "name": "definition_scope_id_idx", | 208 | "name": "definition_scope_id_idx", |
| 219 | "columns": [ | 209 | "columns": ["scope_id"], |
| 220 | "scope_id" | ||
| 221 | ], | ||
| 222 | "isUnique": false | 210 | "isUnique": false |
| 223 | }, | 211 | }, |
| 224 | "definition_source_id_idx": { | 212 | "definition_source_id_idx": { |
| 225 | "name": "definition_source_id_idx", | 213 | "name": "definition_source_id_idx", |
| 226 | "columns": [ | 214 | "columns": ["source_id"], |
| 227 | "source_id" | ||
| 228 | ], | ||
| 229 | "isUnique": false | 215 | "isUnique": false |
| 230 | }, | 216 | }, |
| 231 | "definition_plugin_id_idx": { | 217 | "definition_plugin_id_idx": { |
| 232 | "name": "definition_plugin_id_idx", | 218 | "name": "definition_plugin_id_idx", |
| 233 | "columns": [ | 219 | "columns": ["plugin_id"], |
| 234 | "plugin_id" | ||
| 235 | ], | ||
| 236 | "isUnique": false | 220 | "isUnique": false |
| 237 | } | 221 | } |
| 238 | }, | 222 | }, |
| 239 | "foreignKeys": {}, | 223 | "foreignKeys": {}, |
| 240 | "compositePrimaryKeys": { | 224 | "compositePrimaryKeys": { |
| 241 | "definition_scope_id_id_pk": { | 225 | "definition_scope_id_id_pk": { |
| 242 | "columns": [ | 226 | "columns": ["scope_id", "id"], |
| 243 | "scope_id", | ||
| 244 | "id" | ||
| 245 | ], | ||
| 246 | "name": "definition_scope_id_id_pk" | 227 | "name": "definition_scope_id_id_pk" |
| 247 | } | 228 | } |
| 248 | }, | 229 | }, |
| @@ -291,26 +272,19 @@ · 42 unmodified lines | |||
| 291 | "indexes": { | 272 | "indexes": { |
| 292 | "google_discovery_binding_scope_id_idx": { | 273 | "google_discovery_binding_scope_id_idx": { |
| 293 | "name": "google_discovery_binding_scope_id_idx", | 274 | "name": "google_discovery_binding_scope_id_idx", |
| 294 | "columns": [ | 275 | "columns": ["scope_id"], |
| 295 | "scope_id" | ||
| 296 | ], | ||
| 297 | "isUnique": false | 276 | "isUnique": false |
| 298 | }, | 277 | }, |
| 299 | "google_discovery_binding_source_id_idx": { | 278 | "google_discovery_binding_source_id_idx": { |
| 300 | "name": "google_discovery_binding_source_id_idx", | 279 | "name": "google_discovery_binding_source_id_idx", |
| 301 | "columns": [ | 280 | "columns": ["source_id"], |
| 302 | "source_id" | ||
| 303 | ], | ||
| 304 | "isUnique": false | 281 | "isUnique": false |
| 305 | } | 282 | } |
| 306 | }, | 283 | }, |
| 307 | "foreignKeys": {}, | 284 | "foreignKeys": {}, |
| 308 | "compositePrimaryKeys": { | 285 | "compositePrimaryKeys": { |
| 309 | "google_discovery_binding_scope_id_id_pk": { | 286 | "google_discovery_binding_scope_id_id_pk": { |
| 310 | "columns": [ | 287 | "columns": ["scope_id", "id"], |
| 311 | "scope_id", | ||
| 312 | "id" | ||
| 313 | ], | ||
| 314 | "name": "google_discovery_binding_scope_id_id_pk" | 288 | "name": "google_discovery_binding_scope_id_id_pk" |
| 315 | } | 289 | } |
| 316 | }, | 290 | }, |
| @@ -352,19 +326,14 @@ · 35 unmodified lines | |||
| 352 | "indexes": { | 326 | "indexes": { |
| 353 | "google_discovery_oauth_session_scope_id_idx": { | 327 | "google_discovery_oauth_session_scope_id_idx": { |
| 354 | "name": "google_discovery_oauth_session_scope_id_idx", | 328 | "name": "google_discovery_oauth_session_scope_id_idx", |
| 355 | "columns": [ | 329 | "columns": ["scope_id"], |
| 356 | "scope_id" | ||
| 357 | ], | ||
| 358 | "isUnique": false | 330 | "isUnique": false |
| 359 | } | 331 | } |
| 360 | }, | 332 | }, |
| 361 | "foreignKeys": {}, | 333 | "foreignKeys": {}, |
| 362 | "compositePrimaryKeys": { | 334 | "compositePrimaryKeys": { |
| 363 | "google_discovery_oauth_session_scope_id_id_pk": { | 335 | "google_discovery_oauth_session_scope_id_id_pk": { |
| 364 | "columns": [ | 336 | "columns": ["scope_id", "id"], |
| 365 | "scope_id", | ||
| 366 | "id" | ||
| 367 | ], | ||
| 368 | "name": "google_discovery_oauth_session_scope_id_id_pk" | 337 | "name": "google_discovery_oauth_session_scope_id_id_pk" |
| 369 | } | 338 | } |
| 370 | }, | 339 | }, |
| @@ -420,19 +389,14 @@ · 49 unmodified lines | |||
| 420 | "indexes": { | 389 | "indexes": { |
| 421 | "google_discovery_source_scope_id_idx": { | 390 | "google_discovery_source_scope_id_idx": { |
| 422 | "name": "google_discovery_source_scope_id_idx", | 391 | "name": "google_discovery_source_scope_id_idx", |
| 423 | "columns": [ | 392 | "columns": ["scope_id"], |
| 424 | "scope_id" | ||
| 425 | ], | ||
| 426 | "isUnique": false | 393 | "isUnique": false |
| 427 | } | 394 | } |
| 428 | }, | 395 | }, |
| 429 | "foreignKeys": {}, | 396 | "foreignKeys": {}, |
| 430 | "compositePrimaryKeys": { | 397 | "compositePrimaryKeys": { |
| 431 | "google_discovery_source_scope_id_id_pk": { | 398 | "google_discovery_source_scope_id_id_pk": { |
| 432 | "columns": [ | 399 | "columns": ["scope_id", "id"], |
| 433 | "scope_id", | ||
| 434 | "id" | ||
| 435 | ], | ||
| 436 | "name": "google_discovery_source_scope_id_id_pk" | 400 | "name": "google_discovery_source_scope_id_id_pk" |
| 437 | } | 401 | } |
| 438 | }, | 402 | }, |
| @@ -474,26 +438,19 @@ · 35 unmodified lines | |||
| 474 | "indexes": { | 438 | "indexes": { |
| 475 | "graphql_operation_scope_id_idx": { | 439 | "graphql_operation_scope_id_idx": { |
| 476 | "name": "graphql_operation_scope_id_idx", | 440 | "name": "graphql_operation_scope_id_idx", |
| 477 | "columns": [ | 441 | "columns": ["scope_id"], |
| 478 | "scope_id" | ||
| 479 | ], | ||
| 480 | "isUnique": false | 442 | "isUnique": false |
| 481 | }, | 443 | }, |
| 482 | "graphql_operation_source_id_idx": { | 444 | "graphql_operation_source_id_idx": { |
| 483 | "name": "graphql_operation_source_id_idx", | 445 | "name": "graphql_operation_source_id_idx", |
| 484 | "columns": [ | 446 | "columns": ["source_id"], |
| 485 | "source_id" | ||
| 486 | ], | ||
| 487 | "isUnique": false | 447 | "isUnique": false |
| 488 | } | 448 | } |
| 489 | }, | 449 | }, |
| 490 | "foreignKeys": {}, | 450 | "foreignKeys": {}, |
| 491 | "compositePrimaryKeys": { | 451 | "compositePrimaryKeys": { |
| 492 | "graphql_operation_scope_id_id_pk": { | 452 | "graphql_operation_scope_id_id_pk": { |
| 493 | "columns": [ | 453 | "columns": ["scope_id", "id"], |
| 494 | "scope_id", | ||
| 495 | "id" | ||
| 496 | ], | ||
| 497 | "name": "graphql_operation_scope_id_id_pk" | 454 | "name": "graphql_operation_scope_id_id_pk" |
| 498 | } | 455 | } |
| 499 | }, | 456 | }, |
| @@ -542,19 +499,14 @@ · 42 unmodified lines | |||
| 542 | "indexes": { | 499 | "indexes": { |
| 543 | "graphql_source_scope_id_idx": { | 500 | "graphql_source_scope_id_idx": { |
| 544 | "name": "graphql_source_scope_id_idx", | 501 | "name": "graphql_source_scope_id_idx", |
| 545 | "columns": [ | 502 | "columns": ["scope_id"], |
| 546 | "scope_id" | ||
| 547 | ], | ||
| 548 | "isUnique": false | 503 | "isUnique": false |
| 549 | } | 504 | } |
| 550 | }, | 505 | }, |
| 551 | "foreignKeys": {}, | 506 | "foreignKeys": {}, |
| 552 | "compositePrimaryKeys": { | 507 | "compositePrimaryKeys": { |
| 553 | "graphql_source_scope_id_id_pk": { | 508 | "graphql_source_scope_id_id_pk": { |
| 554 | "columns": [ | 509 | "columns": ["scope_id", "id"], |
| 555 | "scope_id", | ||
| 556 | "id" | ||
| 557 | ], | ||
| 558 | "name": "graphql_source_scope_id_id_pk" | 510 | "name": "graphql_source_scope_id_id_pk" |
| 559 | } | 511 | } |
| 560 | }, | 512 | }, |
| @@ -603,26 +555,19 @@ · 42 unmodified lines | |||
| 603 | "indexes": { | 555 | "indexes": { |
| 604 | "mcp_binding_scope_id_idx": { | 556 | "mcp_binding_scope_id_idx": { |
| 605 | "name": "mcp_binding_scope_id_idx", | 557 | "name": "mcp_binding_scope_id_idx", |
| 606 | "columns": [ | 558 | "columns": ["scope_id"], |
| 607 | "scope_id" | ||
| 608 | ], | ||
| 609 | "isUnique": false | 559 | "isUnique": false |
| 610 | }, | 560 | }, |
| 611 | "mcp_binding_source_id_idx": { | 561 | "mcp_binding_source_id_idx": { |
| 612 | "name": "mcp_binding_source_id_idx", | 562 | "name": "mcp_binding_source_id_idx", |
| 613 | "columns": [ | 563 | "columns": ["source_id"], |
| 614 | "source_id" | ||
| 615 | ], | ||
| 616 | "isUnique": false | 564 | "isUnique": false |
| 617 | } | 565 | } |
| 618 | }, | 566 | }, |
| 619 | "foreignKeys": {}, | 567 | "foreignKeys": {}, |
| 620 | "compositePrimaryKeys": { | 568 | "compositePrimaryKeys": { |
| 621 | "mcp_binding_scope_id_id_pk": { | 569 | "mcp_binding_scope_id_id_pk": { |
| 622 | "columns": [ | 570 | "columns": ["scope_id", "id"], |
| 623 | "scope_id", | ||
| 624 | "id" | ||
| 625 | ], | ||
| 626 | "name": "mcp_binding_scope_id_id_pk" | 571 | "name": "mcp_binding_scope_id_id_pk" |
| 627 | } | 572 | } |
| 628 | }, | 573 | }, |
| @@ -671,19 +616,14 @@ · 42 unmodified lines | |||
| 671 | "indexes": { | 616 | "indexes": { |
| 672 | "mcp_oauth_session_scope_id_idx": { | 617 | "mcp_oauth_session_scope_id_idx": { |
| 673 | "name": "mcp_oauth_session_scope_id_idx", | 618 | "name": "mcp_oauth_session_scope_id_idx", |
| 674 | "columns": [ | 619 | "columns": ["scope_id"], |
| 675 | "scope_id" | ||
| 676 | ], | ||
| 677 | "isUnique": false | 620 | "isUnique": false |
| 678 | } | 621 | } |
| 679 | }, | 622 | }, |
| 680 | "foreignKeys": {}, | 623 | "foreignKeys": {}, |
| 681 | "compositePrimaryKeys": { | 624 | "compositePrimaryKeys": { |
| 682 | "mcp_oauth_session_scope_id_id_pk": { | 625 | "mcp_oauth_session_scope_id_id_pk": { |
| 683 | "columns": [ | 626 | "columns": ["scope_id", "id"], |
| 684 | "scope_id", | ||
| 685 | "id" | ||
| 686 | ], | ||
| 687 | "name": "mcp_oauth_session_scope_id_id_pk" | 627 | "name": "mcp_oauth_session_scope_id_id_pk" |
| 688 | } | 628 | } |
| 689 | }, | 629 | }, |
| @@ -732,19 +672,14 @@ · 42 unmodified lines | |||
| 732 | "indexes": { | 672 | "indexes": { |
| 733 | "mcp_source_scope_id_idx": { | 673 | "mcp_source_scope_id_idx": { |
| 734 | "name": "mcp_source_scope_id_idx", | 674 | "name": "mcp_source_scope_id_idx", |
| 735 | "columns": [ | 675 | "columns": ["scope_id"], |
| 736 | "scope_id" | ||
| 737 | ], | ||
| 738 | "isUnique": false | 676 | "isUnique": false |
| 739 | } | 677 | } |
| 740 | }, | 678 | }, |
| 741 | "foreignKeys": {}, | 679 | "foreignKeys": {}, |
| 742 | "compositePrimaryKeys": { | 680 | "compositePrimaryKeys": { |
| 743 | "mcp_source_scope_id_id_pk": { | 681 | "mcp_source_scope_id_id_pk": { |
| 744 | "columns": [ | 682 | "columns": ["scope_id", "id"], |
| 745 | "scope_id", | ||
| 746 | "id" | ||
| 747 | ], | ||
| 748 | "name": "mcp_source_scope_id_id_pk" | 683 | "name": "mcp_source_scope_id_id_pk" |
| 749 | } | 684 | } |
| 750 | }, | 685 | }, |
| @@ -786,19 +721,14 @@ · 35 unmodified lines | |||
| 786 | "indexes": { | 721 | "indexes": { |
| 787 | "openapi_oauth_session_scope_id_idx": { | 722 | "openapi_oauth_session_scope_id_idx": { |
| 788 | "name": "openapi_oauth_session_scope_id_idx", | 723 | "name": "openapi_oauth_session_scope_id_idx", |
| 789 | "columns": [ | 724 | "columns": ["scope_id"], |
| 790 | "scope_id" | ||
| 791 | ], | ||
| 792 | "isUnique": false | 725 | "isUnique": false |
| 793 | } | 726 | } |
| 794 | }, | 727 | }, |
| 795 | "foreignKeys": {}, | 728 | "foreignKeys": {}, |
| 796 | "compositePrimaryKeys": { | 729 | "compositePrimaryKeys": { |
| 797 | "openapi_oauth_session_scope_id_id_pk": { | 730 | "openapi_oauth_session_scope_id_id_pk": { |
| 798 | "columns": [ | 731 | "columns": ["scope_id", "id"], |
| 799 | "scope_id", | ||
| 800 | "id" | ||
| 801 | ], | ||
| 802 | "name": "openapi_oauth_session_scope_id_id_pk" | 732 | "name": "openapi_oauth_session_scope_id_id_pk" |
| 803 | } | 733 | } |
| 804 | }, | 734 | }, |
| @@ -840,26 +770,19 @@ · 35 unmodified lines | |||
| 840 | "indexes": { | 770 | "indexes": { |
| 841 | "openapi_operation_scope_id_idx": { | 771 | "openapi_operation_scope_id_idx": { |
| 842 | "name": "openapi_operation_scope_id_idx", | 772 | "name": "openapi_operation_scope_id_idx", |
| 843 | "columns": [ | 773 | "columns": ["scope_id"], |
| 844 | "scope_id" | ||
| 845 | ], | ||
| 846 | "isUnique": false | 774 | "isUnique": false |
| 847 | }, | 775 | }, |
| 848 | "openapi_operation_source_id_idx": { | 776 | "openapi_operation_source_id_idx": { |
| 849 | "name": "openapi_operation_source_id_idx", | 777 | "name": "openapi_operation_source_id_idx", |
| 850 | "columns": [ | 778 | "columns": ["source_id"], |
| 851 | "source_id" | ||
| 852 | ], | ||
| 853 | "isUnique": false | 779 | "isUnique": false |
| 854 | } | 780 | } |
| 855 | }, | 781 | }, |
| 856 | "foreignKeys": {}, | 782 | "foreignKeys": {}, |
| 857 | "compositePrimaryKeys": { | 783 | "compositePrimaryKeys": { |
| 858 | "openapi_operation_scope_id_id_pk": { | 784 | "openapi_operation_scope_id_id_pk": { |
| 859 | "columns": [ | 785 | "columns": ["scope_id", "id"], |
| 860 | "scope_id", | ||
| 861 | "id" | ||
| 862 | ], | ||
| 863 | "name": "openapi_operation_scope_id_id_pk" | 786 | "name": "openapi_operation_scope_id_id_pk" |
| 864 | } | 787 | } |
| 865 | }, | 788 | }, |
| @@ -936,19 +859,14 @@ · 70 unmodified lines | |||
| 936 | "indexes": { | 859 | "indexes": { |
| 937 | "openapi_source_scope_id_idx": { | 860 | "openapi_source_scope_id_idx": { |
| 938 | "name": "openapi_source_scope_id_idx", | 861 | "name": "openapi_source_scope_id_idx", |
| 939 | "columns": [ | 862 | "columns": ["scope_id"], |
| 940 | "scope_id" | ||
| 941 | ], | ||
| 942 | "isUnique": false | 863 | "isUnique": false |
| 943 | } | 864 | } |
| 944 | }, | 865 | }, |
| 945 | "foreignKeys": {}, | 866 | "foreignKeys": {}, |
| 946 | "compositePrimaryKeys": { | 867 | "compositePrimaryKeys": { |
| 947 | "openapi_source_scope_id_id_pk": { | 868 | "openapi_source_scope_id_id_pk": { |
| 948 | "columns": [ | 869 | "columns": ["scope_id", "id"], |
| 949 | "scope_id", | ||
| 950 | "id" | ||
| 951 | ], | ||
| 952 | "name": "openapi_source_scope_id_id_pk" | 870 | "name": "openapi_source_scope_id_id_pk" |
| 953 | } | 871 | } |
| 954 | }, | 872 | }, |
| @@ -1004,33 +922,24 @@ · 49 unmodified lines | |||
| 1004 | "indexes": { | 922 | "indexes": { |
| 1005 | "secret_scope_id_idx": { | 923 | "secret_scope_id_idx": { |
| 1006 | "name": "secret_scope_id_idx", | 924 | "name": "secret_scope_id_idx", |
| 1007 | "columns": [ | 925 | "columns": ["scope_id"], |
| 1008 | "scope_id" | ||
| 1009 | ], | ||
| 1010 | "isUnique": false | 926 | "isUnique": false |
| 1011 | }, | 927 | }, |
| 1012 | "secret_provider_idx": { | 928 | "secret_provider_idx": { |
| 1013 | "name": "secret_provider_idx", | 929 | "name": "secret_provider_idx", |
| 1014 | "columns": [ | 930 | "columns": ["provider"], |
| 1015 | "provider" | ||
| 1016 | ], | ||
| 1017 | "isUnique": false | 931 | "isUnique": false |
| 1018 | }, | 932 | }, |
| 1019 | "secret_owned_by_connection_id_idx": { | 933 | "secret_owned_by_connection_id_idx": { |
| 1020 | "name": "secret_owned_by_connection_id_idx", | 934 | "name": "secret_owned_by_connection_id_idx", |
| 1021 | "columns": [ | 935 | "columns": ["owned_by_connection_id"], |
| 1022 | "owned_by_connection_id" | ||
| 1023 | ], | ||
| 1024 | "isUnique": false | 936 | "isUnique": false |
| 1025 | } | 937 | } |
| 1026 | }, | 938 | }, |
| 1027 | "foreignKeys": {}, | 939 | "foreignKeys": {}, |
| 1028 | "compositePrimaryKeys": { | 940 | "compositePrimaryKeys": { |
| 1029 | "secret_scope_id_id_pk": { | 941 | "secret_scope_id_id_pk": { |
| 1030 | "columns": [ | 942 | "columns": ["scope_id", "id"], |
| 1031 | "scope_id", | ||
| 1032 | "id" | ||
| 1033 | ], | ||
| 1034 | "name": "secret_scope_id_id_pk" | 943 | "name": "secret_scope_id_id_pk" |
| 1035 | } | 944 | } |
| 1036 | }, | 945 | }, |
| @@ -1124,26 +1033,19 @@ · 87 unmodified lines | |||
| 1124 | "indexes": { | 1033 | "indexes": { |
| 1125 | "source_scope_id_idx": { | 1034 | "source_scope_id_idx": { |
| 1126 | "name": "source_scope_id_idx", | 1035 | "name": "source_scope_id_idx", |
| 1127 | "columns": [ | 1036 | "columns": ["scope_id"], |
| 1128 | "scope_id" | ||
| 1129 | ], | ||
| 1130 | "isUnique": false | 1037 | "isUnique": false |
| 1131 | }, | 1038 | }, |
| 1132 | "source_plugin_id_idx": { | 1039 | "source_plugin_id_idx": { |
| 1133 | "name": "source_plugin_id_idx", | 1040 | "name": "source_plugin_id_idx", |
| 1134 | "columns": [ | 1041 | "columns": ["plugin_id"], |
| 1135 | "plugin_id" | ||
| 1136 | ], | ||
| 1137 | "isUnique": false | 1042 | "isUnique": false |
| 1138 | } | 1043 | } |
| 1139 | }, | 1044 | }, |
| 1140 | "foreignKeys": {}, | 1045 | "foreignKeys": {}, |
| 1141 | "compositePrimaryKeys": { | 1046 | "compositePrimaryKeys": { |
| 1142 | "source_scope_id_id_pk": { | 1047 | "source_scope_id_id_pk": { |
| 1143 | "columns": [ | 1048 | "columns": ["scope_id", "id"], |
| 1144 | "scope_id", | ||
| 1145 | "id" | ||
| 1146 | ], | ||
| 1147 | "name": "source_scope_id_id_pk" | 1049 | "name": "source_scope_id_id_pk" |
| 1148 | } | 1050 | } |
| 1149 | }, | 1051 | }, |
| @@ -1227,33 +1129,24 @@ · 77 unmodified lines | |||
| 1227 | "indexes": { | 1129 | "indexes": { |
| 1228 | "tool_scope_id_idx": { | 1130 | "tool_scope_id_idx": { |
| 1229 | "name": "tool_scope_id_idx", | 1131 | "name": "tool_scope_id_idx", |
| 1230 | "columns": [ | 1132 | "columns": ["scope_id"], |
| 1231 | "scope_id" | ||
| 1232 | ], | ||
| 1233 | "isUnique": false | 1133 | "isUnique": false |
| 1234 | }, | 1134 | }, |
| 1235 | "tool_source_id_idx": { | 1135 | "tool_source_id_idx": { |
| 1236 | "name": "tool_source_id_idx", | 1136 | "name": "tool_source_id_idx", |
| 1237 | "columns": [ | 1137 | "columns": ["source_id"], |
| 1238 | "source_id" | ||
| 1239 | ], | ||
| 1240 | "isUnique": false | 1138 | "isUnique": false |
| 1241 | }, | 1139 | }, |
| 1242 | "tool_plugin_id_idx": { | 1140 | "tool_plugin_id_idx": { |
| 1243 | "name": "tool_plugin_id_idx", | 1141 | "name": "tool_plugin_id_idx", |
| 1244 | "columns": [ | 1142 | "columns": ["plugin_id"], |
| 1245 | "plugin_id" | ||
| 1246 | ], | ||
| 1247 | "isUnique": false | 1143 | "isUnique": false |
| 1248 | } | 1144 | } |
| 1249 | }, | 1145 | }, |
| 1250 | "foreignKeys": {}, | 1146 | "foreignKeys": {}, |
| 1251 | "compositePrimaryKeys": { | 1147 | "compositePrimaryKeys": { |
| 1252 | "tool_scope_id_id_pk": { | 1148 | "tool_scope_id_id_pk": { |
| 1253 | "columns": [ | 1149 | "columns": ["scope_id", "id"], |
| 1254 | "scope_id", | ||
| 1255 | "id" | ||
| 1256 | ], | ||
| 1257 | "name": "tool_scope_id_id_pk" | 1150 | "name": "tool_scope_id_id_pk" |
| 1258 | } | 1151 | } |
| 1259 | }, | 1152 | }, |
| @@ -1271,4 +1164,4 @@ · 11 unmodified lines | |||
| 1271 | "internal": { | 1164 | "internal": { |
| 1272 | "indexes": {} | 1165 | "indexes": {} |
| 1273 | } | 1166 | } |
| 1274 | } | 1167 | } |
| @@ -88,26 +88,19 @@ · 87 unmodified lines | |||
| 88 | "indexes": { | 88 | "indexes": { |
| 89 | "connection_scope_id_idx": { | 89 | "connection_scope_id_idx": { |
| 90 | "name": "connection_scope_id_idx", | 90 | "name": "connection_scope_id_idx", |
| 91 | "columns": [ | 91 | "columns": ["scope_id"], |
| 92 | "scope_id" | ||
| 93 | ], | ||
| 94 | "isUnique": false | 92 | "isUnique": false |
| 95 | }, | 93 | }, |
| 96 | "connection_provider_idx": { | 94 | "connection_provider_idx": { |
| 97 | "name": "connection_provider_idx", | 95 | "name": "connection_provider_idx", |
| 98 | "columns": [ | 96 | "columns": ["provider"], |
| 99 | "provider" | ||
| 100 | ], | ||
| 101 | "isUnique": false | 97 | "isUnique": false |
| 102 | } | 98 | } |
| 103 | }, | 99 | }, |
| 104 | "foreignKeys": {}, | 100 | "foreignKeys": {}, |
| 105 | "compositePrimaryKeys": { | 101 | "compositePrimaryKeys": { |
| 106 | "connection_scope_id_id_pk": { | 102 | "connection_scope_id_id_pk": { |
| 107 | "columns": [ | 103 | "columns": ["scope_id", "id"], |
| 108 | "scope_id", | ||
| 109 | "id" | ||
| 110 | ], | ||
| 111 | "name": "connection_scope_id_id_pk" | 104 | "name": "connection_scope_id_id_pk" |
| 112 | } | 105 | } |
| 113 | }, | 106 | }, |
| @@ -170,33 +163,24 @@ · 56 unmodified lines | |||
| 170 | "indexes": { | 163 | "indexes": { |
| 171 | "definition_scope_id_idx": { | 164 | "definition_scope_id_idx": { |
| 172 | "name": "definition_scope_id_idx", | 165 | "name": "definition_scope_id_idx", |
| 173 | "columns": [ | 166 | "columns": ["scope_id"], |
| 174 | "scope_id" | ||
| 175 | ], | ||
| 176 | "isUnique": false | 167 | "isUnique": false |
| 177 | }, | 168 | }, |
| 178 | "definition_source_id_idx": { | 169 | "definition_source_id_idx": { |
| 179 | "name": "definition_source_id_idx", | 170 | "name": "definition_source_id_idx", |
| 180 | "columns": [ | 171 | "columns": ["source_id"], |
| 181 | "source_id" | ||
| 182 | ], | ||
| 183 | "isUnique": false | 172 | "isUnique": false |
| 184 | }, | 173 | }, |
| 185 | "definition_plugin_id_idx": { | 174 | "definition_plugin_id_idx": { |
| 186 | "name": "definition_plugin_id_idx", | 175 | "name": "definition_plugin_id_idx", |
| 187 | "columns": [ | 176 | "columns": ["plugin_id"], |
| 188 | "plugin_id" | ||
| 189 | ], | ||
| 190 | "isUnique": false | 177 | "isUnique": false |
| 191 | } | 178 | } |
| 192 | }, | 179 | }, |
| 193 | "foreignKeys": {}, | 180 | "foreignKeys": {}, |
| 194 | "compositePrimaryKeys": { | 181 | "compositePrimaryKeys": { |
| 195 | "definition_scope_id_id_pk": { | 182 | "definition_scope_id_id_pk": { |
| 196 | "columns": [ | 183 | "columns": ["scope_id", "id"], |
| 197 | "scope_id", | ||
| 198 | "id" | ||
| 199 | ], | ||
| 200 | "name": "definition_scope_id_id_pk" | 184 | "name": "definition_scope_id_id_pk" |
| 201 | } | 185 | } |
| 202 | }, | 186 | }, |
| @@ -245,26 +229,19 @@ · 42 unmodified lines | |||
| 245 | "indexes": { | 229 | "indexes": { |
| 246 | "google_discovery_binding_scope_id_idx": { | 230 | "google_discovery_binding_scope_id_idx": { |
| 247 | "name": "google_discovery_binding_scope_id_idx", | 231 | "name": "google_discovery_binding_scope_id_idx", |
| 248 | "columns": [ | 232 | "columns": ["scope_id"], |
| 249 | "scope_id" | ||
| 250 | ], | ||
| 251 | "isUnique": false | 233 | "isUnique": false |
| 252 | }, | 234 | }, |
| 253 | "google_discovery_binding_source_id_idx": { | 235 | "google_discovery_binding_source_id_idx": { |
| 254 | "name": "google_discovery_binding_source_id_idx", | 236 | "name": "google_discovery_binding_source_id_idx", |
| 255 | "columns": [ | 237 | "columns": ["source_id"], |
| 256 | "source_id" | ||
| 257 | ], | ||
| 258 | "isUnique": false | 238 | "isUnique": false |
| 259 | } | 239 | } |
| 260 | }, | 240 | }, |
| 261 | "foreignKeys": {}, | 241 | "foreignKeys": {}, |
| 262 | "compositePrimaryKeys": { | 242 | "compositePrimaryKeys": { |
| 263 | "google_discovery_binding_scope_id_id_pk": { | 243 | "google_discovery_binding_scope_id_id_pk": { |
| 264 | "columns": [ | 244 | "columns": ["scope_id", "id"], |
| 265 | "scope_id", | ||
| 266 | "id" | ||
| 267 | ], | ||
| 268 | "name": "google_discovery_binding_scope_id_id_pk" | 245 | "name": "google_discovery_binding_scope_id_id_pk" |
| 269 | } | 246 | } |
| 270 | }, | 247 | }, |
| @@ -306,19 +283,14 @@ · 35 unmodified lines | |||
| 306 | "indexes": { | 283 | "indexes": { |
| 307 | "google_discovery_oauth_session_scope_id_idx": { | 284 | "google_discovery_oauth_session_scope_id_idx": { |
| 308 | "name": "google_discovery_oauth_session_scope_id_idx", | 285 | "name": "google_discovery_oauth_session_scope_id_idx", |
| 309 | "columns": [ | 286 | "columns": ["scope_id"], |
| 310 | "scope_id" | ||
| 311 | ], | ||
| 312 | "isUnique": false | 287 | "isUnique": false |
| 313 | } | 288 | } |
| 314 | }, | 289 | }, |
| 315 | "foreignKeys": {}, | 290 | "foreignKeys": {}, |
| 316 | "compositePrimaryKeys": { | 291 | "compositePrimaryKeys": { |
| 317 | "google_discovery_oauth_session_scope_id_id_pk": { | 292 | "google_discovery_oauth_session_scope_id_id_pk": { |
| 318 | "columns": [ | 293 | "columns": ["scope_id", "id"], |
| 319 | "scope_id", | ||
| 320 | "id" | ||
| 321 | ], | ||
| 322 | "name": "google_discovery_oauth_session_scope_id_id_pk" | 294 | "name": "google_discovery_oauth_session_scope_id_id_pk" |
| 323 | } | 295 | } |
| 324 | }, | 296 | }, |
| @@ -374,19 +346,14 @@ · 49 unmodified lines | |||
| 374 | "indexes": { | 346 | "indexes": { |
| 375 | "google_discovery_source_scope_id_idx": { | 347 | "google_discovery_source_scope_id_idx": { |
| 376 | "name": "google_discovery_source_scope_id_idx", | 348 | "name": "google_discovery_source_scope_id_idx", |
| 377 | "columns": [ | 349 | "columns": ["scope_id"], |
| 378 | "scope_id" | ||
| 379 | ], | ||
| 380 | "isUnique": false | 350 | "isUnique": false |
| 381 | } | 351 | } |
| 382 | }, | 352 | }, |
| 383 | "foreignKeys": {}, | 353 | "foreignKeys": {}, |
| 384 | "compositePrimaryKeys": { | 354 | "compositePrimaryKeys": { |
| 385 | "google_discovery_source_scope_id_id_pk": { | 355 | "google_discovery_source_scope_id_id_pk": { |
| 386 | "columns": [ | 356 | "columns": ["scope_id", "id"], |
| 387 | "scope_id", | ||
| 388 | "id" | ||
| 389 | ], | ||
| 390 | "name": "google_discovery_source_scope_id_id_pk" | 357 | "name": "google_discovery_source_scope_id_id_pk" |
| 391 | } | 358 | } |
| 392 | }, | 359 | }, |
| @@ -428,26 +395,19 @@ · 35 unmodified lines | |||
| 428 | "indexes": { | 395 | "indexes": { |
| 429 | "graphql_operation_scope_id_idx": { | 396 | "graphql_operation_scope_id_idx": { |
| 430 | "name": "graphql_operation_scope_id_idx", | 397 | "name": "graphql_operation_scope_id_idx", |
| 431 | "columns": [ | 398 | "columns": ["scope_id"], |
| 432 | "scope_id" | ||
| 433 | ], | ||
| 434 | "isUnique": false | 399 | "isUnique": false |
| 435 | }, | 400 | }, |
| 436 | "graphql_operation_source_id_idx": { | 401 | "graphql_operation_source_id_idx": { |
| 437 | "name": "graphql_operation_source_id_idx", | 402 | "name": "graphql_operation_source_id_idx", |
| 438 | "columns": [ | 403 | "columns": ["source_id"], |
| 439 | "source_id" | ||
| 440 | ], | ||
| 441 | "isUnique": false | 404 | "isUnique": false |
| 442 | } | 405 | } |
| 443 | }, | 406 | }, |
| 444 | "foreignKeys": {}, | 407 | "foreignKeys": {}, |
| 445 | "compositePrimaryKeys": { | 408 | "compositePrimaryKeys": { |
| 446 | "graphql_operation_scope_id_id_pk": { | 409 | "graphql_operation_scope_id_id_pk": { |
| 447 | "columns": [ | 410 | "columns": ["scope_id", "id"], |
| 448 | "scope_id", | ||
| 449 | "id" | ||
| 450 | ], | ||
| 451 | "name": "graphql_operation_scope_id_id_pk" | 411 | "name": "graphql_operation_scope_id_id_pk" |
| 452 | } | 412 | } |
| 453 | }, | 413 | }, |
| @@ -496,19 +456,14 @@ · 42 unmodified lines | |||
| 496 | "indexes": { | 456 | "indexes": { |
| 497 | "graphql_source_scope_id_idx": { | 457 | "graphql_source_scope_id_idx": { |
| 498 | "name": "graphql_source_scope_id_idx", | 458 | "name": "graphql_source_scope_id_idx", |
| 499 | "columns": [ | 459 | "columns": ["scope_id"], |
| 500 | "scope_id" | ||
| 501 | ], | ||
| 502 | "isUnique": false | 460 | "isUnique": false |
| 503 | } | 461 | } |
| 504 | }, | 462 | }, |
| 505 | "foreignKeys": {}, | 463 | "foreignKeys": {}, |
| 506 | "compositePrimaryKeys": { | 464 | "compositePrimaryKeys": { |
| 507 | "graphql_source_scope_id_id_pk": { | 465 | "graphql_source_scope_id_id_pk": { |
| 508 | "columns": [ | 466 | "columns": ["scope_id", "id"], |
| 509 | "scope_id", | ||
| 510 | "id" | ||
| 511 | ], | ||
| 512 | "name": "graphql_source_scope_id_id_pk" | 467 | "name": "graphql_source_scope_id_id_pk" |
| 513 | } | 468 | } |
| 514 | }, | 469 | }, |
| @@ -557,26 +512,19 @@ · 42 unmodified lines | |||
| 557 | "indexes": { | 512 | "indexes": { |
| 558 | "mcp_binding_scope_id_idx": { | 513 | "mcp_binding_scope_id_idx": { |
| 559 | "name": "mcp_binding_scope_id_idx", | 514 | "name": "mcp_binding_scope_id_idx", |
| 560 | "columns": [ | 515 | "columns": ["scope_id"], |
| 561 | "scope_id" | ||
| 562 | ], | ||
| 563 | "isUnique": false | 516 | "isUnique": false |
| 564 | }, | 517 | }, |
| 565 | "mcp_binding_source_id_idx": { | 518 | "mcp_binding_source_id_idx": { |
| 566 | "name": "mcp_binding_source_id_idx", | 519 | "name": "mcp_binding_source_id_idx", |
| 567 | "columns": [ | 520 | "columns": ["source_id"], |
| 568 | "source_id" | ||
| 569 | ], | ||
| 570 | "isUnique": false | 521 | "isUnique": false |
| 571 | } | 522 | } |
| 572 | }, | 523 | }, |
| 573 | "foreignKeys": {}, | 524 | "foreignKeys": {}, |
| 574 | "compositePrimaryKeys": { | 525 | "compositePrimaryKeys": { |
| 575 | "mcp_binding_scope_id_id_pk": { | 526 | "mcp_binding_scope_id_id_pk": { |
| 576 | "columns": [ | 527 | "columns": ["scope_id", "id"], |
| 577 | "scope_id", | ||
| 578 | "id" | ||
| 579 | ], | ||
| 580 | "name": "mcp_binding_scope_id_id_pk" | 528 | "name": "mcp_binding_scope_id_id_pk" |
| 581 | } | 529 | } |
| 582 | }, | 530 | }, |
| @@ -625,19 +573,14 @@ · 42 unmodified lines | |||
| 625 | "indexes": { | 573 | "indexes": { |
| 626 | "mcp_oauth_session_scope_id_idx": { | 574 | "mcp_oauth_session_scope_id_idx": { |
| 627 | "name": "mcp_oauth_session_scope_id_idx", | 575 | "name": "mcp_oauth_session_scope_id_idx", |
| 628 | "columns": [ | 576 | "columns": ["scope_id"], |
| 629 | "scope_id" | ||
| 630 | ], | ||
| 631 | "isUnique": false | 577 | "isUnique": false |
| 632 | } | 578 | } |
| 633 | }, | 579 | }, |
| 634 | "foreignKeys": {}, | 580 | "foreignKeys": {}, |
| 635 | "compositePrimaryKeys": { | 581 | "compositePrimaryKeys": { |
| 636 | "mcp_oauth_session_scope_id_id_pk": { | 582 | "mcp_oauth_session_scope_id_id_pk": { |
| 637 | "columns": [ | 583 | "columns": ["scope_id", "id"], |
| 638 | "scope_id", | ||
| 639 | "id" | ||
| 640 | ], | ||
| 641 | "name": "mcp_oauth_session_scope_id_id_pk" | 584 | "name": "mcp_oauth_session_scope_id_id_pk" |
| 642 | } | 585 | } |
| 643 | }, | 586 | }, |
| @@ -686,19 +629,14 @@ · 42 unmodified lines | |||
| 686 | "indexes": { | 629 | "indexes": { |
| 687 | "mcp_source_scope_id_idx": { | 630 | "mcp_source_scope_id_idx": { |
| 688 | "name": "mcp_source_scope_id_idx", | 631 | "name": "mcp_source_scope_id_idx", |
| 689 | "columns": [ | 632 | "columns": ["scope_id"], |
| 690 | "scope_id" | ||
| 691 | ], | ||
| 692 | "isUnique": false | 633 | "isUnique": false |
| 693 | } | 634 | } |
| 694 | }, | 635 | }, |
| 695 | "foreignKeys": {}, | 636 | "foreignKeys": {}, |
| 696 | "compositePrimaryKeys": { | 637 | "compositePrimaryKeys": { |
| 697 | "mcp_source_scope_id_id_pk": { | 638 | "mcp_source_scope_id_id_pk": { |
| 698 | "columns": [ | 639 | "columns": ["scope_id", "id"], |
| 699 | "scope_id", | ||
| 700 | "id" | ||
| 701 | ], | ||
| 702 | "name": "mcp_source_scope_id_id_pk" | 640 | "name": "mcp_source_scope_id_id_pk" |
| 703 | } | 641 | } |
| 704 | }, | 642 | }, |
| @@ -740,19 +678,14 @@ · 35 unmodified lines | |||
| 740 | "indexes": { | 678 | "indexes": { |
| 741 | "openapi_oauth_session_scope_id_idx": { | 679 | "openapi_oauth_session_scope_id_idx": { |
| 742 | "name": "openapi_oauth_session_scope_id_idx", | 680 | "name": "openapi_oauth_session_scope_id_idx", |
| 743 | "columns": [ | 681 | "columns": ["scope_id"], |
| 744 | "scope_id" | ||
| 745 | ], | ||
| 746 | "isUnique": false | 682 | "isUnique": false |
| 747 | } | 683 | } |
| 748 | }, | 684 | }, |
| 749 | "foreignKeys": {}, | 685 | "foreignKeys": {}, |
| 750 | "compositePrimaryKeys": { | 686 | "compositePrimaryKeys": { |
| 751 | "openapi_oauth_session_scope_id_id_pk": { | 687 | "openapi_oauth_session_scope_id_id_pk": { |
| 752 | "columns": [ | 688 | "columns": ["scope_id", "id"], |
| 753 | "scope_id", | ||
| 754 | "id" | ||
| 755 | ], | ||
| 756 | "name": "openapi_oauth_session_scope_id_id_pk" | 689 | "name": "openapi_oauth_session_scope_id_id_pk" |
| 757 | } | 690 | } |
| 758 | }, | 691 | }, |
| @@ -794,26 +727,19 @@ · 35 unmodified lines | |||
| 794 | "indexes": { | 727 | "indexes": { |
| 795 | "openapi_operation_scope_id_idx": { | 728 | "openapi_operation_scope_id_idx": { |
| 796 | "name": "openapi_operation_scope_id_idx", | 729 | "name": "openapi_operation_scope_id_idx", |
| 797 | "columns": [ | 730 | "columns": ["scope_id"], |
| 798 | "scope_id" | ||
| 799 | ], | ||
| 800 | "isUnique": false | 731 | "isUnique": false |
| 801 | }, | 732 | }, |
| 802 | "openapi_operation_source_id_idx": { | 733 | "openapi_operation_source_id_idx": { |
| 803 | "name": "openapi_operation_source_id_idx", | 734 | "name": "openapi_operation_source_id_idx", |
| 804 | "columns": [ | 735 | "columns": ["source_id"], |
| 805 | "source_id" | ||
| 806 | ], | ||
| 807 | "isUnique": false | 736 | "isUnique": false |
| 808 | } | 737 | } |
| 809 | }, | 738 | }, |
| 810 | "foreignKeys": {}, | 739 | "foreignKeys": {}, |
| 811 | "compositePrimaryKeys": { | 740 | "compositePrimaryKeys": { |
| 812 | "openapi_operation_scope_id_id_pk": { | 741 | "openapi_operation_scope_id_id_pk": { |
| 813 | "columns": [ | 742 | "columns": ["scope_id", "id"], |
| 814 | "scope_id", | ||
| 815 | "id" | ||
| 816 | ], | ||
| 817 | "name": "openapi_operation_scope_id_id_pk" | 743 | "name": "openapi_operation_scope_id_id_pk" |
| 818 | } | 744 | } |
| 819 | }, | 745 | }, |
| @@ -890,19 +816,14 @@ · 70 unmodified lines | |||
| 890 | "indexes": { | 816 | "indexes": { |
| 891 | "openapi_source_scope_id_idx": { | 817 | "openapi_source_scope_id_idx": { |
| 892 | "name": "openapi_source_scope_id_idx", | 818 | "name": "openapi_source_scope_id_idx", |
| 893 | "columns": [ | 819 | "columns": ["scope_id"], |
| 894 | "scope_id" | ||
| 895 | ], | ||
| 896 | "isUnique": false | 820 | "isUnique": false |
| 897 | } | 821 | } |
| 898 | }, | 822 | }, |
| 899 | "foreignKeys": {}, | 823 | "foreignKeys": {}, |
| 900 | "compositePrimaryKeys": { | 824 | "compositePrimaryKeys": { |
| 901 | "openapi_source_scope_id_id_pk": { | 825 | "openapi_source_scope_id_id_pk": { |
| 902 | "columns": [ | 826 | "columns": ["scope_id", "id"], |
| 903 | "scope_id", | ||
| 904 | "id" | ||
| 905 | ], | ||
| 906 | "name": "openapi_source_scope_id_id_pk" | 827 | "name": "openapi_source_scope_id_id_pk" |
| 907 | } | 828 | } |
| 908 | }, | 829 | }, |
| @@ -972,30 +893,22 @@ · 63 unmodified lines | |||
| 972 | "indexes": { | 893 | "indexes": { |
| 973 | "openapi_source_binding_source_id_idx": { | 894 | "openapi_source_binding_source_id_idx": { |
| 974 | "name": "openapi_source_binding_source_id_idx", | 895 | "name": "openapi_source_binding_source_id_idx", |
| 975 | "columns": [ | 896 | "columns": ["source_id"], |
| 976 | "source_id" | ||
| 977 | ], | ||
| 978 | "isUnique": false | 897 | "isUnique": false |
| 979 | }, | 898 | }, |
| 980 | "openapi_source_binding_source_scope_id_idx": { | 899 | "openapi_source_binding_source_scope_id_idx": { |
| 981 | "name": "openapi_source_binding_source_scope_id_idx", | 900 | "name": "openapi_source_binding_source_scope_id_idx", |
| 982 | "columns": [ | 901 | "columns": ["source_scope_id"], |
| 983 | "source_scope_id" | ||
| 984 | ], | ||
| 985 | "isUnique": false | 902 | "isUnique": false |
| 986 | }, | 903 | }, |
| 987 | "openapi_source_binding_target_scope_id_idx": { | 904 | "openapi_source_binding_target_scope_id_idx": { |
| 988 | "name": "openapi_source_binding_target_scope_id_idx", | 905 | "name": "openapi_source_binding_target_scope_id_idx", |
| 989 | "columns": [ | 906 | "columns": ["target_scope_id"], |
| 990 | "target_scope_id" | ||
| 991 | ], | ||
| 992 | "isUnique": false | 907 | "isUnique": false |
| 993 | }, | 908 | }, |
| 994 | "openapi_source_binding_slot_idx": { | 909 | "openapi_source_binding_slot_idx": { |
| 995 | "name": "openapi_source_binding_slot_idx", | 910 | "name": "openapi_source_binding_slot_idx", |
| 996 | "columns": [ | 911 | "columns": ["slot"], |
| 997 | "slot" | ||
| 998 | ], | ||
| 999 | "isUnique": false | 912 | "isUnique": false |
| 1000 | } | 913 | } |
| 1001 | }, | 914 | }, |
| @@ -1053,33 +966,24 @@ · 51 unmodified lines | |||
| 1053 | "indexes": { | 966 | "indexes": { |
| 1054 | "secret_scope_id_idx": { | 967 | "secret_scope_id_idx": { |
| 1055 | "name": "secret_scope_id_idx", | 968 | "name": "secret_scope_id_idx", |
| 1056 | "columns": [ | 969 | "columns": ["scope_id"], |
| 1057 | "scope_id" | ||
| 1058 | ], | ||
| 1059 | "isUnique": false | 970 | "isUnique": false |
| 1060 | }, | 971 | }, |
| 1061 | "secret_provider_idx": { | 972 | "secret_provider_idx": { |
| 1062 | "name": "secret_provider_idx", | 973 | "name": "secret_provider_idx", |
| 1063 | "columns": [ | 974 | "columns": ["provider"], |
| 1064 | "provider" | ||
| 1065 | ], | ||
| 1066 | "isUnique": false | 975 | "isUnique": false |
| 1067 | }, | 976 | }, |
| 1068 | "secret_owned_by_connection_id_idx": { | 977 | "secret_owned_by_connection_id_idx": { |
| 1069 | "name": "secret_owned_by_connection_id_idx", | 978 | "name": "secret_owned_by_connection_id_idx", |
| 1070 | "columns": [ | 979 | "columns": ["owned_by_connection_id"], |
| 1071 | "owned_by_connection_id" | ||
| 1072 | ], | ||
| 1073 | "isUnique": false | 980 | "isUnique": false |
| 1074 | } | 981 | } |
| 1075 | }, | 982 | }, |
| 1076 | "foreignKeys": {}, | 983 | "foreignKeys": {}, |
| 1077 | "compositePrimaryKeys": { | 984 | "compositePrimaryKeys": { |
| 1078 | "secret_scope_id_id_pk": { | 985 | "secret_scope_id_id_pk": { |
| 1079 | "columns": [ | 986 | "columns": ["scope_id", "id"], |
| 1080 | "scope_id", | ||
| 1081 | "id" | ||
| 1082 | ], | ||
| 1083 | "name": "secret_scope_id_id_pk" | 987 | "name": "secret_scope_id_id_pk" |
| 1084 | } | 988 | } |
| 1085 | }, | 989 | }, |
| @@ -1173,26 +1077,19 @@ · 87 unmodified lines | |||
| 1173 | "indexes": { | 1077 | "indexes": { |
| 1174 | "source_scope_id_idx": { | 1078 | "source_scope_id_idx": { |
| 1175 | "name": "source_scope_id_idx", | 1079 | "name": "source_scope_id_idx", |
| 1176 | "columns": [ | 1080 | "columns": ["scope_id"], |
| 1177 | "scope_id" | ||
| 1178 | ], | ||
| 1179 | "isUnique": false | 1081 | "isUnique": false |
| 1180 | }, | 1082 | }, |
| 1181 | "source_plugin_id_idx": { | 1083 | "source_plugin_id_idx": { |
| 1182 | "name": "source_plugin_id_idx", | 1084 | "name": "source_plugin_id_idx", |
| 1183 | "columns": [ | 1085 | "columns": ["plugin_id"], |
| 1184 | "plugin_id" | ||
| 1185 | ], | ||
| 1186 | "isUnique": false | 1086 | "isUnique": false |
| 1187 | } | 1087 | } |
| 1188 | }, | 1088 | }, |
| 1189 | "foreignKeys": {}, | 1089 | "foreignKeys": {}, |
| 1190 | "compositePrimaryKeys": { | 1090 | "compositePrimaryKeys": { |
| 1191 | "source_scope_id_id_pk": { | 1091 | "source_scope_id_id_pk": { |
| 1192 | "columns": [ | 1092 | "columns": ["scope_id", "id"], |
| 1193 | "scope_id", | ||
| 1194 | "id" | ||
| 1195 | ], | ||
| 1196 | "name": "source_scope_id_id_pk" | 1093 | "name": "source_scope_id_id_pk" |
| 1197 | } | 1094 | } |
| 1198 | }, | 1095 | }, |
| @@ -1276,33 +1173,24 @@ · 77 unmodified lines | |||
| 1276 | "indexes": { | 1173 | "indexes": { |
| 1277 | "tool_scope_id_idx": { | 1174 | "tool_scope_id_idx": { |
| 1278 | "name": "tool_scope_id_idx", | 1175 | "name": "tool_scope_id_idx", |
| 1279 | "columns": [ | 1176 | "columns": ["scope_id"], |
| 1280 | "scope_id" | ||
| 1281 | ], | ||
| 1282 | "isUnique": false | 1177 | "isUnique": false |
| 1283 | }, | 1178 | }, |
| 1284 | "tool_source_id_idx": { | 1179 | "tool_source_id_idx": { |
| 1285 | "name": "tool_source_id_idx", | 1180 | "name": "tool_source_id_idx", |
| 1286 | "columns": [ | 1181 | "columns": ["source_id"], |
| 1287 | "source_id" | ||
| 1288 | ], | ||
| 1289 | "isUnique": false | 1182 | "isUnique": false |
| 1290 | }, | 1183 | }, |
| 1291 | "tool_plugin_id_idx": { | 1184 | "tool_plugin_id_idx": { |
| 1292 | "name": "tool_plugin_id_idx", | 1185 | "name": "tool_plugin_id_idx", |
| 1293 | "columns": [ | 1186 | "columns": ["plugin_id"], |
| 1294 | "plugin_id" | ||
| 1295 | ], | ||
| 1296 | "isUnique": false | 1187 | "isUnique": false |
| 1297 | } | 1188 | } |
| 1298 | }, | 1189 | }, |
| 1299 | "foreignKeys": {}, | 1190 | "foreignKeys": {}, |
| 1300 | "compositePrimaryKeys": { | 1191 | "compositePrimaryKeys": { |
| 1301 | "tool_scope_id_id_pk": { | 1192 | "tool_scope_id_id_pk": { |
| 1302 | "columns": [ | 1193 | "columns": ["scope_id", "id"], |
| 1303 | "scope_id", | ||
| 1304 | "id" | ||
| 1305 | ], | ||
| 1306 | "name": "tool_scope_id_id_pk" | 1194 | "name": "tool_scope_id_id_pk" |
| 1307 | } | 1195 | } |
| 1308 | }, | 1196 | }, |
| @@ -1338,10 +1226,7 @@ · 29 unmodified lines | |||
| 1338 | "foreignKeys": {}, | 1226 | "foreignKeys": {}, |
| 1339 | "compositePrimaryKeys": { | 1227 | "compositePrimaryKeys": { |
| 1340 | "blob_namespace_key_pk": { | 1228 | "blob_namespace_key_pk": { |
| 1341 | "columns": [ | 1229 | "columns": ["namespace", "key"], |
| 1342 | "namespace", | ||
| 1343 | "key" | ||
| 1344 | ], | ||
| 1345 | "name": "blob_namespace_key_pk" | 1230 | "name": "blob_namespace_key_pk" |
| 1346 | } | 1231 | } |
| 1347 | }, | 1232 | }, |
| @@ -88,26 +88,19 @@ · 87 unmodified lines | |||
| 88 | "indexes": { | 88 | "indexes": { |
| 89 | "connection_scope_id_idx": { | 89 | "connection_scope_id_idx": { |
| 90 | "name": "connection_scope_id_idx", | 90 | "name": "connection_scope_id_idx", |
| 91 | "columns": [ | 91 | "columns": ["scope_id"], |
| 92 | "scope_id" | ||
| 93 | ], | ||
| 94 | "isUnique": false | 92 | "isUnique": false |
| 95 | }, | 93 | }, |
| 96 | "connection_provider_idx": { | 94 | "connection_provider_idx": { |
| 97 | "name": "connection_provider_idx", | 95 | "name": "connection_provider_idx", |
| 98 | "columns": [ | 96 | "columns": ["provider"], |
| 99 | "provider" | ||
| 100 | ], | ||
| 101 | "isUnique": false | 97 | "isUnique": false |
| 102 | } | 98 | } |
| 103 | }, | 99 | }, |
| 104 | "foreignKeys": {}, | 100 | "foreignKeys": {}, |
| 105 | "compositePrimaryKeys": { | 101 | "compositePrimaryKeys": { |
| 106 | "connection_scope_id_id_pk": { | 102 | "connection_scope_id_id_pk": { |
| 107 | "columns": [ | 103 | "columns": ["scope_id", "id"], |
| 108 | "scope_id", | ||
| 109 | "id" | ||
| 110 | ], | ||
| 111 | "name": "connection_scope_id_id_pk" | 104 | "name": "connection_scope_id_id_pk" |
| 112 | } | 105 | } |
| 113 | }, | 106 | }, |
| @@ -170,33 +163,24 @@ · 56 unmodified lines | |||
| 170 | "indexes": { | 163 | "indexes": { |
| 171 | "definition_scope_id_idx": { | 164 | "definition_scope_id_idx": { |
| 172 | "name": "definition_scope_id_idx", | 165 | "name": "definition_scope_id_idx", |
| 173 | "columns": [ | 166 | "columns": ["scope_id"], |
| 174 | "scope_id" | ||
| 175 | ], | ||
| 176 | "isUnique": false | 167 | "isUnique": false |
| 177 | }, | 168 | }, |
| 178 | "definition_source_id_idx": { | 169 | "definition_source_id_idx": { |
| 179 | "name": "definition_source_id_idx", | 170 | "name": "definition_source_id_idx", |
| 180 | "columns": [ | 171 | "columns": ["source_id"], |
| 181 | "source_id" | ||
| 182 | ], | ||
| 183 | "isUnique": false | 172 | "isUnique": false |
| 184 | }, | 173 | }, |
| 185 | "definition_plugin_id_idx": { | 174 | "definition_plugin_id_idx": { |
| 186 | "name": "definition_plugin_id_idx", | 175 | "name": "definition_plugin_id_idx", |
| 187 | "columns": [ | 176 | "columns": ["plugin_id"], |
| 188 | "plugin_id" | ||
| 189 | ], | ||
| 190 | "isUnique": false | 177 | "isUnique": false |
| 191 | } | 178 | } |
| 192 | }, | 179 | }, |
| 193 | "foreignKeys": {}, | 180 | "foreignKeys": {}, |
| 194 | "compositePrimaryKeys": { | 181 | "compositePrimaryKeys": { |
| 195 | "definition_scope_id_id_pk": { | 182 | "definition_scope_id_id_pk": { |
| 196 | "columns": [ | 183 | "columns": ["scope_id", "id"], |
| 197 | "scope_id", | ||
| 198 | "id" | ||
| 199 | ], | ||
| 200 | "name": "definition_scope_id_id_pk" | 184 | "name": "definition_scope_id_id_pk" |
| 201 | } | 185 | } |
| 202 | }, | 186 | }, |
| @@ -245,26 +229,19 @@ · 42 unmodified lines | |||
| 245 | "indexes": { | 229 | "indexes": { |
| 246 | "google_discovery_binding_scope_id_idx": { | 230 | "google_discovery_binding_scope_id_idx": { |
| 247 | "name": "google_discovery_binding_scope_id_idx", | 231 | "name": "google_discovery_binding_scope_id_idx", |
| 248 | "columns": [ | 232 | "columns": ["scope_id"], |
| 249 | "scope_id" | ||
| 250 | ], | ||
| 251 | "isUnique": false | 233 | "isUnique": false |
| 252 | }, | 234 | }, |
| 253 | "google_discovery_binding_source_id_idx": { | 235 | "google_discovery_binding_source_id_idx": { |
| 254 | "name": "google_discovery_binding_source_id_idx", | 236 | "name": "google_discovery_binding_source_id_idx", |
| 255 | "columns": [ | 237 | "columns": ["source_id"], |
| 256 | "source_id" | ||
| 257 | ], | ||
| 258 | "isUnique": false | 238 | "isUnique": false |
| 259 | } | 239 | } |
| 260 | }, | 240 | }, |
| 261 | "foreignKeys": {}, | 241 | "foreignKeys": {}, |
| 262 | "compositePrimaryKeys": { | 242 | "compositePrimaryKeys": { |
| 263 | "google_discovery_binding_scope_id_id_pk": { | 243 | "google_discovery_binding_scope_id_id_pk": { |
| 264 | "columns": [ | 244 | "columns": ["scope_id", "id"], |
| 265 | "scope_id", | ||
| 266 | "id" | ||
| 267 | ], | ||
| 268 | "name": "google_discovery_binding_scope_id_id_pk" | 245 | "name": "google_discovery_binding_scope_id_id_pk" |
| 269 | } | 246 | } |
| 270 | }, | 247 | }, |
| @@ -306,19 +283,14 @@ · 35 unmodified lines | |||
| 306 | "indexes": { | 283 | "indexes": { |
| 307 | "google_discovery_oauth_session_scope_id_idx": { | 284 | "google_discovery_oauth_session_scope_id_idx": { |
| 308 | "name": "google_discovery_oauth_session_scope_id_idx", | 285 | "name": "google_discovery_oauth_session_scope_id_idx", |
| 309 | "columns": [ | 286 | "columns": ["scope_id"], |
| 310 | "scope_id" | ||
| 311 | ], | ||
| 312 | "isUnique": false | 287 | "isUnique": false |
| 313 | } | 288 | } |
| 314 | }, | 289 | }, |
| 315 | "foreignKeys": {}, | 290 | "foreignKeys": {}, |
| 316 | "compositePrimaryKeys": { | 291 | "compositePrimaryKeys": { |
| 317 | "google_discovery_oauth_session_scope_id_id_pk": { | 292 | "google_discovery_oauth_session_scope_id_id_pk": { |
| 318 | "columns": [ | 293 | "columns": ["scope_id", "id"], |
| 319 | "scope_id", | ||
| 320 | "id" | ||
| 321 | ], | ||
| 322 | "name": "google_discovery_oauth_session_scope_id_id_pk" | 294 | "name": "google_discovery_oauth_session_scope_id_id_pk" |
| 323 | } | 295 | } |
| 324 | }, | 296 | }, |
| @@ -374,19 +346,14 @@ · 49 unmodified lines | |||
| 374 | "indexes": { | 346 | "indexes": { |
| 375 | "google_discovery_source_scope_id_idx": { | 347 | "google_discovery_source_scope_id_idx": { |
| 376 | "name": "google_discovery_source_scope_id_idx", | 348 | "name": "google_discovery_source_scope_id_idx", |
| 377 | "columns": [ | 349 | "columns": ["scope_id"], |
| 378 | "scope_id" | ||
| 379 | ], | ||
| 380 | "isUnique": false | 350 | "isUnique": false |
| 381 | } | 351 | } |
| 382 | }, | 352 | }, |
| 383 | "foreignKeys": {}, | 353 | "foreignKeys": {}, |
| 384 | "compositePrimaryKeys": { | 354 | "compositePrimaryKeys": { |
| 385 | "google_discovery_source_scope_id_id_pk": { | 355 | "google_discovery_source_scope_id_id_pk": { |
| 386 | "columns": [ | 356 | "columns": ["scope_id", "id"], |
| 387 | "scope_id", | ||
| 388 | "id" | ||
| 389 | ], | ||
| 390 | "name": "google_discovery_source_scope_id_id_pk" | 357 | "name": "google_discovery_source_scope_id_id_pk" |
| 391 | } | 358 | } |
| 392 | }, | 359 | }, |
| @@ -428,26 +395,19 @@ · 35 unmodified lines | |||
| 428 | "indexes": { | 395 | "indexes": { |
| 429 | "graphql_operation_scope_id_idx": { | 396 | "graphql_operation_scope_id_idx": { |
| 430 | "name": "graphql_operation_scope_id_idx", | 397 | "name": "graphql_operation_scope_id_idx", |
| 431 | "columns": [ | 398 | "columns": ["scope_id"], |
| 432 | "scope_id" | ||
| 433 | ], | ||
| 434 | "isUnique": false | 399 | "isUnique": false |
| 435 | }, | 400 | }, |
| 436 | "graphql_operation_source_id_idx": { | 401 | "graphql_operation_source_id_idx": { |
| 437 | "name": "graphql_operation_source_id_idx", | 402 | "name": "graphql_operation_source_id_idx", |
| 438 | "columns": [ | 403 | "columns": ["source_id"], |
| 439 | "source_id" | ||
| 440 | ], | ||
| 441 | "isUnique": false | 404 | "isUnique": false |
| 442 | } | 405 | } |
| 443 | }, | 406 | }, |
| 444 | "foreignKeys": {}, | 407 | "foreignKeys": {}, |
| 445 | "compositePrimaryKeys": { | 408 | "compositePrimaryKeys": { |
| 446 | "graphql_operation_scope_id_id_pk": { | 409 | "graphql_operation_scope_id_id_pk": { |
| 447 | "columns": [ | 410 | "columns": ["scope_id", "id"], |
| 448 | "scope_id", | ||
| 449 | "id" | ||
| 450 | ], | ||
| 451 | "name": "graphql_operation_scope_id_id_pk" | 411 | "name": "graphql_operation_scope_id_id_pk" |
| 452 | } | 412 | } |
| 453 | }, | 413 | }, |
| @@ -496,19 +456,14 @@ · 42 unmodified lines | |||
| 496 | "indexes": { | 456 | "indexes": { |
| 497 | "graphql_source_scope_id_idx": { | 457 | "graphql_source_scope_id_idx": { |
| 498 | "name": "graphql_source_scope_id_idx", | 458 | "name": "graphql_source_scope_id_idx", |
| 499 | "columns": [ | 459 | "columns": ["scope_id"], |
| 500 | "scope_id" | ||
| 501 | ], | ||
| 502 | "isUnique": false | 460 | "isUnique": false |
| 503 | } | 461 | } |
| 504 | }, | 462 | }, |
| 505 | "foreignKeys": {}, | 463 | "foreignKeys": {}, |
| 506 | "compositePrimaryKeys": { | 464 | "compositePrimaryKeys": { |
| 507 | "graphql_source_scope_id_id_pk": { | 465 | "graphql_source_scope_id_id_pk": { |
| 508 | "columns": [ | 466 | "columns": ["scope_id", "id"], |
| 509 | "scope_id", | ||
| 510 | "id" | ||
| 511 | ], | ||
| 512 | "name": "graphql_source_scope_id_id_pk" | 467 | "name": "graphql_source_scope_id_id_pk" |
| 513 | } | 468 | } |
| 514 | }, | 469 | }, |
| @@ -557,26 +512,19 @@ · 42 unmodified lines | |||
| 557 | "indexes": { | 512 | "indexes": { |
| 558 | "mcp_binding_scope_id_idx": { | 513 | "mcp_binding_scope_id_idx": { |
| 559 | "name": "mcp_binding_scope_id_idx", | 514 | "name": "mcp_binding_scope_id_idx", |
| 560 | "columns": [ | 515 | "columns": ["scope_id"], |
| 561 | "scope_id" | ||
| 562 | ], | ||
| 563 | "isUnique": false | 516 | "isUnique": false |
| 564 | }, | 517 | }, |
| 565 | "mcp_binding_source_id_idx": { | 518 | "mcp_binding_source_id_idx": { |
| 566 | "name": "mcp_binding_source_id_idx", | 519 | "name": "mcp_binding_source_id_idx", |
| 567 | "columns": [ | 520 | "columns": ["source_id"], |
| 568 | "source_id" | ||
| 569 | ], | ||
| 570 | "isUnique": false | 521 | "isUnique": false |
| 571 | } | 522 | } |
| 572 | }, | 523 | }, |
| 573 | "foreignKeys": {}, | 524 | "foreignKeys": {}, |
| 574 | "compositePrimaryKeys": { | 525 | "compositePrimaryKeys": { |
| 575 | "mcp_binding_scope_id_id_pk": { | 526 | "mcp_binding_scope_id_id_pk": { |
| 576 | "columns": [ | 527 | "columns": ["scope_id", "id"], |
| 577 | "scope_id", | ||
| 578 | "id" | ||
| 579 | ], | ||
| 580 | "name": "mcp_binding_scope_id_id_pk" | 528 | "name": "mcp_binding_scope_id_id_pk" |
| 581 | } | 529 | } |
| 582 | }, | 530 | }, |
| @@ -625,19 +573,14 @@ · 42 unmodified lines | |||
| 625 | "indexes": { | 573 | "indexes": { |
| 626 | "mcp_oauth_session_scope_id_idx": { | 574 | "mcp_oauth_session_scope_id_idx": { |
| 627 | "name": "mcp_oauth_session_scope_id_idx", | 575 | "name": "mcp_oauth_session_scope_id_idx", |
| 628 | "columns": [ | 576 | "columns": ["scope_id"], |
| 629 | "scope_id" | ||
| 630 | ], | ||
| 631 | "isUnique": false | 577 | "isUnique": false |
| 632 | } | 578 | } |
| 633 | }, | 579 | }, |
| 634 | "foreignKeys": {}, | 580 | "foreignKeys": {}, |
| 635 | "compositePrimaryKeys": { | 581 | "compositePrimaryKeys": { |
| 636 | "mcp_oauth_session_scope_id_id_pk": { | 582 | "mcp_oauth_session_scope_id_id_pk": { |
| 637 | "columns": [ | 583 | "columns": ["scope_id", "id"], |
| 638 | "scope_id", | ||
| 639 | "id" | ||
| 640 | ], | ||
| 641 | "name": "mcp_oauth_session_scope_id_id_pk" | 584 | "name": "mcp_oauth_session_scope_id_id_pk" |
| 642 | } | 585 | } |
| 643 | }, | 586 | }, |
| @@ -686,19 +629,14 @@ · 42 unmodified lines | |||
| 686 | "indexes": { | 629 | "indexes": { |
| 687 | "mcp_source_scope_id_idx": { | 630 | "mcp_source_scope_id_idx": { |
| 688 | "name": "mcp_source_scope_id_idx", | 631 | "name": "mcp_source_scope_id_idx", |
| 689 | "columns": [ | 632 | "columns": ["scope_id"], |
| 690 | "scope_id" | ||
| 691 | ], | ||
| 692 | "isUnique": false | 633 | "isUnique": false |
| 693 | } | 634 | } |
| 694 | }, | 635 | }, |
| 695 | "foreignKeys": {}, | 636 | "foreignKeys": {}, |
| 696 | "compositePrimaryKeys": { | 637 | "compositePrimaryKeys": { |
| 697 | "mcp_source_scope_id_id_pk": { | 638 | "mcp_source_scope_id_id_pk": { |
| 698 | "columns": [ | 639 | "columns": ["scope_id", "id"], |
| 699 | "scope_id", | ||
| 700 | "id" | ||
| 701 | ], | ||
| 702 | "name": "mcp_source_scope_id_id_pk" | 640 | "name": "mcp_source_scope_id_id_pk" |
| 703 | } | 641 | } |
| 704 | }, | 642 | }, |
| @@ -740,19 +678,14 @@ · 35 unmodified lines | |||
| 740 | "indexes": { | 678 | "indexes": { |
| 741 | "openapi_oauth_session_scope_id_idx": { | 679 | "openapi_oauth_session_scope_id_idx": { |
| 742 | "name": "openapi_oauth_session_scope_id_idx", | 680 | "name": "openapi_oauth_session_scope_id_idx", |
| 743 | "columns": [ | 681 | "columns": ["scope_id"], |
| 744 | "scope_id" | ||
| 745 | ], | ||
| 746 | "isUnique": false | 682 | "isUnique": false |
| 747 | } | 683 | } |
| 748 | }, | 684 | }, |
| 749 | "foreignKeys": {}, | 685 | "foreignKeys": {}, |
| 750 | "compositePrimaryKeys": { | 686 | "compositePrimaryKeys": { |
| 751 | "openapi_oauth_session_scope_id_id_pk": { | 687 | "openapi_oauth_session_scope_id_id_pk": { |
| 752 | "columns": [ | 688 | "columns": ["scope_id", "id"], |
| 753 | "scope_id", | ||
| 754 | "id" | ||
| 755 | ], | ||
| 756 | "name": "openapi_oauth_session_scope_id_id_pk" | 689 | "name": "openapi_oauth_session_scope_id_id_pk" |
| 757 | } | 690 | } |
| 758 | }, | 691 | }, |
| @@ -794,26 +727,19 @@ · 35 unmodified lines | |||
| 794 | "indexes": { | 727 | "indexes": { |
| 795 | "openapi_operation_scope_id_idx": { | 728 | "openapi_operation_scope_id_idx": { |
| 796 | "name": "openapi_operation_scope_id_idx", | 729 | "name": "openapi_operation_scope_id_idx", |
| 797 | "columns": [ | 730 | "columns": ["scope_id"], |
| 798 | "scope_id" | ||
| 799 | ], | ||
| 800 | "isUnique": false | 731 | "isUnique": false |
| 801 | }, | 732 | }, |
| 802 | "openapi_operation_source_id_idx": { | 733 | "openapi_operation_source_id_idx": { |
| 803 | "name": "openapi_operation_source_id_idx", | 734 | "name": "openapi_operation_source_id_idx", |
| 804 | "columns": [ | 735 | "columns": ["source_id"], |
| 805 | "source_id" | ||
| 806 | ], | ||
| 807 | "isUnique": false | 736 | "isUnique": false |
| 808 | } | 737 | } |
| 809 | }, | 738 | }, |
| 810 | "foreignKeys": {}, | 739 | "foreignKeys": {}, |
| 811 | "compositePrimaryKeys": { | 740 | "compositePrimaryKeys": { |
| 812 | "openapi_operation_scope_id_id_pk": { | 741 | "openapi_operation_scope_id_id_pk": { |
| 813 | "columns": [ | 742 | "columns": ["scope_id", "id"], |
| 814 | "scope_id", | ||
| 815 | "id" | ||
| 816 | ], | ||
| 817 | "name": "openapi_operation_scope_id_id_pk" | 743 | "name": "openapi_operation_scope_id_id_pk" |
| 818 | } | 744 | } |
| 819 | }, | 745 | }, |
| @@ -890,19 +816,14 @@ · 70 unmodified lines | |||
| 890 | "indexes": { | 816 | "indexes": { |
| 891 | "openapi_source_scope_id_idx": { | 817 | "openapi_source_scope_id_idx": { |
| 892 | "name": "openapi_source_scope_id_idx", | 818 | "name": "openapi_source_scope_id_idx", |
| 893 | "columns": [ | 819 | "columns": ["scope_id"], |
| 894 | "scope_id" | ||
| 895 | ], | ||
| 896 | "isUnique": false | 820 | "isUnique": false |
| 897 | } | 821 | } |
| 898 | }, | 822 | }, |
| 899 | "foreignKeys": {}, | 823 | "foreignKeys": {}, |
| 900 | "compositePrimaryKeys": { | 824 | "compositePrimaryKeys": { |
| 901 | "openapi_source_scope_id_id_pk": { | 825 | "openapi_source_scope_id_id_pk": { |
| 902 | "columns": [ | 826 | "columns": ["scope_id", "id"], |
| 903 | "scope_id", | ||
| 904 | "id" | ||
| 905 | ], | ||
| 906 | "name": "openapi_source_scope_id_id_pk" | 827 | "name": "openapi_source_scope_id_id_pk" |
| 907 | } | 828 | } |
| 908 | }, | 829 | }, |
| @@ -972,30 +893,22 @@ · 63 unmodified lines | |||
| 972 | "indexes": { | 893 | "indexes": { |
| 973 | "openapi_source_binding_source_id_idx": { | 894 | "openapi_source_binding_source_id_idx": { |
| 974 | "name": "openapi_source_binding_source_id_idx", | 895 | "name": "openapi_source_binding_source_id_idx", |
| 975 | "columns": [ | 896 | "columns": ["source_id"], |
| 976 | "source_id" | ||
| 977 | ], | ||
| 978 | "isUnique": false | 897 | "isUnique": false |
| 979 | }, | 898 | }, |
| 980 | "openapi_source_binding_source_scope_id_idx": { | 899 | "openapi_source_binding_source_scope_id_idx": { |
| 981 | "name": "openapi_source_binding_source_scope_id_idx", | 900 | "name": "openapi_source_binding_source_scope_id_idx", |
| 982 | "columns": [ | 901 | "columns": ["source_scope_id"], |
| 983 | "source_scope_id" | ||
| 984 | ], | ||
| 985 | "isUnique": false | 902 | "isUnique": false |
| 986 | }, | 903 | }, |
| 987 | "openapi_source_binding_target_scope_id_idx": { | 904 | "openapi_source_binding_target_scope_id_idx": { |
| 988 | "name": "openapi_source_binding_target_scope_id_idx", | 905 | "name": "openapi_source_binding_target_scope_id_idx", |
| 989 | "columns": [ | 906 | "columns": ["target_scope_id"], |
| 990 | "target_scope_id" | ||
| 991 | ], | ||
| 992 | "isUnique": false | 907 | "isUnique": false |
| 993 | }, | 908 | }, |
| 994 | "openapi_source_binding_slot_idx": { | 909 | "openapi_source_binding_slot_idx": { |
| 995 | "name": "openapi_source_binding_slot_idx", | 910 | "name": "openapi_source_binding_slot_idx", |
| 996 | "columns": [ | 911 | "columns": ["slot"], |
| 997 | "slot" | ||
| 998 | ], | ||
| 999 | "isUnique": false | 912 | "isUnique": false |
| 1000 | } | 913 | } |
| 1001 | }, | 914 | }, |
| @@ -1053,33 +966,24 @@ · 51 unmodified lines | |||
| 1053 | "indexes": { | 966 | "indexes": { |
| 1054 | "secret_scope_id_idx": { | 967 | "secret_scope_id_idx": { |
| 1055 | "name": "secret_scope_id_idx", | 968 | "name": "secret_scope_id_idx", |
| 1056 | "columns": [ | 969 | "columns": ["scope_id"], |
| 1057 | "scope_id" | ||
| 1058 | ], | ||
| 1059 | "isUnique": false | 970 | "isUnique": false |
| 1060 | }, | 971 | }, |
| 1061 | "secret_provider_idx": { | 972 | "secret_provider_idx": { |
| 1062 | "name": "secret_provider_idx", | 973 | "name": "secret_provider_idx", |
| 1063 | "columns": [ | 974 | "columns": ["provider"], |
| 1064 | "provider" | ||
| 1065 | ], | ||
| 1066 | "isUnique": false | 975 | "isUnique": false |
| 1067 | }, | 976 | }, |
| 1068 | "secret_owned_by_connection_id_idx": { | 977 | "secret_owned_by_connection_id_idx": { |
| 1069 | "name": "secret_owned_by_connection_id_idx", | 978 | "name": "secret_owned_by_connection_id_idx", |
| 1070 | "columns": [ | 979 | "columns": ["owned_by_connection_id"], |
| 1071 | "owned_by_connection_id" | ||
| 1072 | ], | ||
| 1073 | "isUnique": false | 980 | "isUnique": false |
| 1074 | } | 981 | } |
| 1075 | }, | 982 | }, |
| 1076 | "foreignKeys": {}, | 983 | "foreignKeys": {}, |
| 1077 | "compositePrimaryKeys": { | 984 | "compositePrimaryKeys": { |
| 1078 | "secret_scope_id_id_pk": { | 985 | "secret_scope_id_id_pk": { |
| 1079 | "columns": [ | 986 | "columns": ["scope_id", "id"], |
| 1080 | "scope_id", | ||
| 1081 | "id" | ||
| 1082 | ], | ||
| 1083 | "name": "secret_scope_id_id_pk" | 987 | "name": "secret_scope_id_id_pk" |
| 1084 | } | 988 | } |
| 1085 | }, | 989 | }, |
| @@ -1173,26 +1077,19 @@ · 87 unmodified lines | |||
| 1173 | "indexes": { | 1077 | "indexes": { |
| 1174 | "source_scope_id_idx": { | 1078 | "source_scope_id_idx": { |
| 1175 | "name": "source_scope_id_idx", | 1079 | "name": "source_scope_id_idx", |
| 1176 | "columns": [ | 1080 | "columns": ["scope_id"], |
| 1177 | "scope_id" | ||
| 1178 | ], | ||
| 1179 | "isUnique": false | 1081 | "isUnique": false |
| 1180 | }, | 1082 | }, |
| 1181 | "source_plugin_id_idx": { | 1083 | "source_plugin_id_idx": { |
| 1182 | "name": "source_plugin_id_idx", | 1084 | "name": "source_plugin_id_idx", |
| 1183 | "columns": [ | 1085 | "columns": ["plugin_id"], |
| 1184 | "plugin_id" | ||
| 1185 | ], | ||
| 1186 | "isUnique": false | 1086 | "isUnique": false |
| 1187 | } | 1087 | } |
| 1188 | }, | 1088 | }, |
| 1189 | "foreignKeys": {}, | 1089 | "foreignKeys": {}, |
| 1190 | "compositePrimaryKeys": { | 1090 | "compositePrimaryKeys": { |
| 1191 | "source_scope_id_id_pk": { | 1091 | "source_scope_id_id_pk": { |
| 1192 | "columns": [ | 1092 | "columns": ["scope_id", "id"], |
| 1193 | "scope_id", | ||
| 1194 | "id" | ||
| 1195 | ], | ||
| 1196 | "name": "source_scope_id_id_pk" | 1093 | "name": "source_scope_id_id_pk" |
| 1197 | } | 1094 | } |
| 1198 | }, | 1095 | }, |
| @@ -1276,33 +1173,24 @@ · 77 unmodified lines | |||
| 1276 | "indexes": { | 1173 | "indexes": { |
| 1277 | "tool_scope_id_idx": { | 1174 | "tool_scope_id_idx": { |
| 1278 | "name": "tool_scope_id_idx", | 1175 | "name": "tool_scope_id_idx", |
| 1279 | "columns": [ | 1176 | "columns": ["scope_id"], |
| 1280 | "scope_id" | ||
| 1281 | ], | ||
| 1282 | "isUnique": false | 1177 | "isUnique": false |
| 1283 | }, | 1178 | }, |
| 1284 | "tool_source_id_idx": { | 1179 | "tool_source_id_idx": { |
| 1285 | "name": "tool_source_id_idx", | 1180 | "name": "tool_source_id_idx", |
| 1286 | "columns": [ | 1181 | "columns": ["source_id"], |
| 1287 | "source_id" | ||
| 1288 | ], | ||
| 1289 | "isUnique": false | 1182 | "isUnique": false |
| 1290 | }, | 1183 | }, |
| 1291 | "tool_plugin_id_idx": { | 1184 | "tool_plugin_id_idx": { |
| 1292 | "name": "tool_plugin_id_idx", | 1185 | "name": "tool_plugin_id_idx", |
| 1293 | "columns": [ | 1186 | "columns": ["plugin_id"], |
| 1294 | "plugin_id" | ||
| 1295 | ], | ||
| 1296 | "isUnique": false | 1187 | "isUnique": false |
| 1297 | } | 1188 | } |
| 1298 | }, | 1189 | }, |
| 1299 | "foreignKeys": {}, | 1190 | "foreignKeys": {}, |
| 1300 | "compositePrimaryKeys": { | 1191 | "compositePrimaryKeys": { |
| 1301 | "tool_scope_id_id_pk": { | 1192 | "tool_scope_id_id_pk": { |
| 1302 | "columns": [ | 1193 | "columns": ["scope_id", "id"], |
| 1303 | "scope_id", | ||
| 1304 | "id" | ||
| 1305 | ], | ||
| 1306 | "name": "tool_scope_id_id_pk" | 1194 | "name": "tool_scope_id_id_pk" |
| 1307 | } | 1195 | } |
| 1308 | }, | 1196 | }, |
| @@ -1365,20 +1253,14 @@ · 56 unmodified lines | |||
| 1365 | "indexes": { | 1253 | "indexes": { |
| 1366 | "tool_policy_scope_id_position_idx": { | 1254 | "tool_policy_scope_id_position_idx": { |
| 1367 | "name": "tool_policy_scope_id_position_idx", | 1255 | "name": "tool_policy_scope_id_position_idx", |
| 1368 | "columns": [ | 1256 | "columns": ["scope_id", "position"], |
| 1369 | "scope_id", | ||
| 1370 | "position" | ||
| 1371 | ], | ||
| 1372 | "isUnique": false | 1257 | "isUnique": false |
| 1373 | } | 1258 | } |
| 1374 | }, | 1259 | }, |
| 1375 | "foreignKeys": {}, | 1260 | "foreignKeys": {}, |
| 1376 | "compositePrimaryKeys": { | 1261 | "compositePrimaryKeys": { |
| 1377 | "tool_policy_scope_id_id_pk": { | 1262 | "tool_policy_scope_id_id_pk": { |
| 1378 | "columns": [ | 1263 | "columns": ["scope_id", "id"], |
| 1379 | "scope_id", | ||
| 1380 | "id" | ||
| 1381 | ], | ||
| 1382 | "name": "tool_policy_scope_id_id_pk" | 1264 | "name": "tool_policy_scope_id_id_pk" |
| 1383 | } | 1265 | } |
| 1384 | }, | 1266 | }, |
| @@ -1414,10 +1296,7 @@ · 29 unmodified lines | |||
| 1414 | "foreignKeys": {}, | 1296 | "foreignKeys": {}, |
| 1415 | "compositePrimaryKeys": { | 1297 | "compositePrimaryKeys": { |
| 1416 | "blob_namespace_key_pk": { | 1298 | "blob_namespace_key_pk": { |
| 1417 | "columns": [ | 1299 | "columns": ["namespace", "key"], |
| 1418 | "namespace", | ||
| 1419 | "key" | ||
| 1420 | ], | ||
| 1421 | "name": "blob_namespace_key_pk" | 1300 | "name": "blob_namespace_key_pk" |
| 1422 | } | 1301 | } |
| 1423 | }, | 1302 | }, |
| @@ -88,26 +88,19 @@ · 87 unmodified lines | |||
| 88 | "indexes": { | 88 | "indexes": { |
| 89 | "connection_scope_id_idx": { | 89 | "connection_scope_id_idx": { |
| 90 | "name": "connection_scope_id_idx", | 90 | "name": "connection_scope_id_idx", |
| 91 | "columns": [ | 91 | "columns": ["scope_id"], |
| 92 | "scope_id" | ||
| 93 | ], | ||
| 94 | "isUnique": false | 92 | "isUnique": false |
| 95 | }, | 93 | }, |
| 96 | "connection_provider_idx": { | 94 | "connection_provider_idx": { |
| 97 | "name": "connection_provider_idx", | 95 | "name": "connection_provider_idx", |
| 98 | "columns": [ | 96 | "columns": ["provider"], |
| 99 | "provider" | ||
| 100 | ], | ||
| 101 | "isUnique": false | 97 | "isUnique": false |
| 102 | } | 98 | } |
| 103 | }, | 99 | }, |
| 104 | "foreignKeys": {}, | 100 | "foreignKeys": {}, |
| 105 | "compositePrimaryKeys": { | 101 | "compositePrimaryKeys": { |
| 106 | "connection_scope_id_id_pk": { | 102 | "connection_scope_id_id_pk": { |
| 107 | "columns": [ | 103 | "columns": ["scope_id", "id"], |
| 108 | "scope_id", | ||
| 109 | "id" | ||
| 110 | ], | ||
| 111 | "name": "connection_scope_id_id_pk" | 104 | "name": "connection_scope_id_id_pk" |
| 112 | } | 105 | } |
| 113 | }, | 106 | }, |
| @@ -170,33 +163,24 @@ · 56 unmodified lines | |||
| 170 | "indexes": { | 163 | "indexes": { |
| 171 | "definition_scope_id_idx": { | 164 | "definition_scope_id_idx": { |
| 172 | "name": "definition_scope_id_idx", | 165 | "name": "definition_scope_id_idx", |
| 173 | "columns": [ | 166 | "columns": ["scope_id"], |
| 174 | "scope_id" | ||
| 175 | ], | ||
| 176 | "isUnique": false | 167 | "isUnique": false |
| 177 | }, | 168 | }, |
| 178 | "definition_source_id_idx": { | 169 | "definition_source_id_idx": { |
| 179 | "name": "definition_source_id_idx", | 170 | "name": "definition_source_id_idx", |
| 180 | "columns": [ | 171 | "columns": ["source_id"], |
| 181 | "source_id" | ||
| 182 | ], | ||
| 183 | "isUnique": false | 172 | "isUnique": false |
| 184 | }, | 173 | }, |
| 185 | "definition_plugin_id_idx": { | 174 | "definition_plugin_id_idx": { |
| 186 | "name": "definition_plugin_id_idx", | 175 | "name": "definition_plugin_id_idx", |
| 187 | "columns": [ | 176 | "columns": ["plugin_id"], |
| 188 | "plugin_id" | ||
| 189 | ], | ||
| 190 | "isUnique": false | 177 | "isUnique": false |
| 191 | } | 178 | } |
| 192 | }, | 179 | }, |
| 193 | "foreignKeys": {}, | 180 | "foreignKeys": {}, |
| 194 | "compositePrimaryKeys": { | 181 | "compositePrimaryKeys": { |
| 195 | "definition_scope_id_id_pk": { | 182 | "definition_scope_id_id_pk": { |
| 196 | "columns": [ | 183 | "columns": ["scope_id", "id"], |
| 197 | "scope_id", | ||
| 198 | "id" | ||
| 199 | ], | ||
| 200 | "name": "definition_scope_id_id_pk" | 184 | "name": "definition_scope_id_id_pk" |
| 201 | } | 185 | } |
| 202 | }, | 186 | }, |
| @@ -245,26 +229,19 @@ · 42 unmodified lines | |||
| 245 | "indexes": { | 229 | "indexes": { |
| 246 | "google_discovery_binding_scope_id_idx": { | 230 | "google_discovery_binding_scope_id_idx": { |
| 247 | "name": "google_discovery_binding_scope_id_idx", | 231 | "name": "google_discovery_binding_scope_id_idx", |
| 248 | "columns": [ | 232 | "columns": ["scope_id"], |
| 249 | "scope_id" | ||
| 250 | ], | ||
| 251 | "isUnique": false | 233 | "isUnique": false |
| 252 | }, | 234 | }, |
| 253 | "google_discovery_binding_source_id_idx": { | 235 | "google_discovery_binding_source_id_idx": { |
| 254 | "name": "google_discovery_binding_source_id_idx", | 236 | "name": "google_discovery_binding_source_id_idx", |
| 255 | "columns": [ | 237 | "columns": ["source_id"], |
| 256 | "source_id" | ||
| 257 | ], | ||
| 258 | "isUnique": false | 238 | "isUnique": false |
| 259 | } | 239 | } |
| 260 | }, | 240 | }, |
| 261 | "foreignKeys": {}, | 241 | "foreignKeys": {}, |
| 262 | "compositePrimaryKeys": { | 242 | "compositePrimaryKeys": { |
| 263 | "google_discovery_binding_scope_id_id_pk": { | 243 | "google_discovery_binding_scope_id_id_pk": { |
| 264 | "columns": [ | 244 | "columns": ["scope_id", "id"], |
| 265 | "scope_id", | ||
| 266 | "id" | ||
| 267 | ], | ||
| 268 | "name": "google_discovery_binding_scope_id_id_pk" | 245 | "name": "google_discovery_binding_scope_id_id_pk" |
| 269 | } | 246 | } |
| 270 | }, | 247 | }, |
| @@ -306,19 +283,14 @@ · 35 unmodified lines | |||
| 306 | "indexes": { | 283 | "indexes": { |
| 307 | "google_discovery_oauth_session_scope_id_idx": { | 284 | "google_discovery_oauth_session_scope_id_idx": { |
| 308 | "name": "google_discovery_oauth_session_scope_id_idx", | 285 | "name": "google_discovery_oauth_session_scope_id_idx", |
| 309 | "columns": [ | 286 | "columns": ["scope_id"], |
| 310 | "scope_id" | ||
| 311 | ], | ||
| 312 | "isUnique": false | 287 | "isUnique": false |
| 313 | } | 288 | } |
| 314 | }, | 289 | }, |
| 315 | "foreignKeys": {}, | 290 | "foreignKeys": {}, |
| 316 | "compositePrimaryKeys": { | 291 | "compositePrimaryKeys": { |
| 317 | "google_discovery_oauth_session_scope_id_id_pk": { | 292 | "google_discovery_oauth_session_scope_id_id_pk": { |
| 318 | "columns": [ | 293 | "columns": ["scope_id", "id"], |
| 319 | "scope_id", | ||
| 320 | "id" | ||
| 321 | ], | ||
| 322 | "name": "google_discovery_oauth_session_scope_id_id_pk" | 294 | "name": "google_discovery_oauth_session_scope_id_id_pk" |
| 323 | } | 295 | } |
| 324 | }, | 296 | }, |
| @@ -374,19 +346,14 @@ · 49 unmodified lines | |||
| 374 | "indexes": { | 346 | "indexes": { |
| 375 | "google_discovery_source_scope_id_idx": { | 347 | "google_discovery_source_scope_id_idx": { |
| 376 | "name": "google_discovery_source_scope_id_idx", | 348 | "name": "google_discovery_source_scope_id_idx", |
| 377 | "columns": [ | 349 | "columns": ["scope_id"], |
| 378 | "scope_id" | ||
| 379 | ], | ||
| 380 | "isUnique": false | 350 | "isUnique": false |
| 381 | } | 351 | } |
| 382 | }, | 352 | }, |
| 383 | "foreignKeys": {}, | 353 | "foreignKeys": {}, |
| 384 | "compositePrimaryKeys": { | 354 | "compositePrimaryKeys": { |
| 385 | "google_discovery_source_scope_id_id_pk": { | 355 | "google_discovery_source_scope_id_id_pk": { |
| 386 | "columns": [ | 356 | "columns": ["scope_id", "id"], |
| 387 | "scope_id", | ||
| 388 | "id" | ||
| 389 | ], | ||
| 390 | "name": "google_discovery_source_scope_id_id_pk" | 357 | "name": "google_discovery_source_scope_id_id_pk" |
| 391 | } | 358 | } |
| 392 | }, | 359 | }, |
| @@ -428,26 +395,19 @@ · 35 unmodified lines | |||
| 428 | "indexes": { | 395 | "indexes": { |
| 429 | "graphql_operation_scope_id_idx": { | 396 | "graphql_operation_scope_id_idx": { |
| 430 | "name": "graphql_operation_scope_id_idx", | 397 | "name": "graphql_operation_scope_id_idx", |
| 431 | "columns": [ | 398 | "columns": ["scope_id"], |
| 432 | "scope_id" | ||
| 433 | ], | ||
| 434 | "isUnique": false | 399 | "isUnique": false |
| 435 | }, | 400 | }, |
| 436 | "graphql_operation_source_id_idx": { | 401 | "graphql_operation_source_id_idx": { |
| 437 | "name": "graphql_operation_source_id_idx", | 402 | "name": "graphql_operation_source_id_idx", |
| 438 | "columns": [ | 403 | "columns": ["source_id"], |
| 439 | "source_id" | ||
| 440 | ], | ||
| 441 | "isUnique": false | 404 | "isUnique": false |
| 442 | } | 405 | } |
| 443 | }, | 406 | }, |
| 444 | "foreignKeys": {}, | 407 | "foreignKeys": {}, |
| 445 | "compositePrimaryKeys": { | 408 | "compositePrimaryKeys": { |
| 446 | "graphql_operation_scope_id_id_pk": { | 409 | "graphql_operation_scope_id_id_pk": { |
| 447 | "columns": [ | 410 | "columns": ["scope_id", "id"], |
| 448 | "scope_id", | ||
| 449 | "id" | ||
| 450 | ], | ||
| 451 | "name": "graphql_operation_scope_id_id_pk" | 411 | "name": "graphql_operation_scope_id_id_pk" |
| 452 | } | 412 | } |
| 453 | }, | 413 | }, |
| @@ -496,19 +456,14 @@ · 42 unmodified lines | |||
| 496 | "indexes": { | 456 | "indexes": { |
| 497 | "graphql_source_scope_id_idx": { | 457 | "graphql_source_scope_id_idx": { |
| 498 | "name": "graphql_source_scope_id_idx", | 458 | "name": "graphql_source_scope_id_idx", |
| 499 | "columns": [ | 459 | "columns": ["scope_id"], |
| 500 | "scope_id" | ||
| 501 | ], | ||
| 502 | "isUnique": false | 460 | "isUnique": false |
| 503 | } | 461 | } |
| 504 | }, | 462 | }, |
| 505 | "foreignKeys": {}, | 463 | "foreignKeys": {}, |
| 506 | "compositePrimaryKeys": { | 464 | "compositePrimaryKeys": { |
| 507 | "graphql_source_scope_id_id_pk": { | 465 | "graphql_source_scope_id_id_pk": { |
| 508 | "columns": [ | 466 | "columns": ["scope_id", "id"], |
| 509 | "scope_id", | ||
| 510 | "id" | ||
| 511 | ], | ||
| 512 | "name": "graphql_source_scope_id_id_pk" | 467 | "name": "graphql_source_scope_id_id_pk" |
| 513 | } | 468 | } |
| 514 | }, | 469 | }, |
| @@ -557,26 +512,19 @@ · 42 unmodified lines | |||
| 557 | "indexes": { | 512 | "indexes": { |
| 558 | "mcp_binding_scope_id_idx": { | 513 | "mcp_binding_scope_id_idx": { |
| 559 | "name": "mcp_binding_scope_id_idx", | 514 | "name": "mcp_binding_scope_id_idx", |
| 560 | "columns": [ | 515 | "columns": ["scope_id"], |
| 561 | "scope_id" | ||
| 562 | ], | ||
| 563 | "isUnique": false | 516 | "isUnique": false |
| 564 | }, | 517 | }, |
| 565 | "mcp_binding_source_id_idx": { | 518 | "mcp_binding_source_id_idx": { |
| 566 | "name": "mcp_binding_source_id_idx", | 519 | "name": "mcp_binding_source_id_idx", |
| 567 | "columns": [ | 520 | "columns": ["source_id"], |
| 568 | "source_id" | ||
| 569 | ], | ||
| 570 | "isUnique": false | 521 | "isUnique": false |
| 571 | } | 522 | } |
| 572 | }, | 523 | }, |
| 573 | "foreignKeys": {}, | 524 | "foreignKeys": {}, |
| 574 | "compositePrimaryKeys": { | 525 | "compositePrimaryKeys": { |
| 575 | "mcp_binding_scope_id_id_pk": { | 526 | "mcp_binding_scope_id_id_pk": { |
| 576 | "columns": [ | 527 | "columns": ["scope_id", "id"], |
| 577 | "scope_id", | ||
| 578 | "id" | ||
| 579 | ], | ||
| 580 | "name": "mcp_binding_scope_id_id_pk" | 528 | "name": "mcp_binding_scope_id_id_pk" |
| 581 | } | 529 | } |
| 582 | }, | 530 | }, |
| @@ -625,19 +573,14 @@ · 42 unmodified lines | |||
| 625 | "indexes": { | 573 | "indexes": { |
| 626 | "mcp_oauth_session_scope_id_idx": { | 574 | "mcp_oauth_session_scope_id_idx": { |
| 627 | "name": "mcp_oauth_session_scope_id_idx", | 575 | "name": "mcp_oauth_session_scope_id_idx", |
| 628 | "columns": [ | 576 | "columns": ["scope_id"], |
| 629 | "scope_id" | ||
| 630 | ], | ||
| 631 | "isUnique": false | 577 | "isUnique": false |
| 632 | } | 578 | } |
| 633 | }, | 579 | }, |
| 634 | "foreignKeys": {}, | 580 | "foreignKeys": {}, |
| 635 | "compositePrimaryKeys": { | 581 | "compositePrimaryKeys": { |
| 636 | "mcp_oauth_session_scope_id_id_pk": { | 582 | "mcp_oauth_session_scope_id_id_pk": { |
| 637 | "columns": [ | 583 | "columns": ["scope_id", "id"], |
| 638 | "scope_id", | ||
| 639 | "id" | ||
| 640 | ], | ||
| 641 | "name": "mcp_oauth_session_scope_id_id_pk" | 584 | "name": "mcp_oauth_session_scope_id_id_pk" |
| 642 | } | 585 | } |
| 643 | }, | 586 | }, |
| @@ -686,19 +629,14 @@ · 42 unmodified lines | |||
| 686 | "indexes": { | 629 | "indexes": { |
| 687 | "mcp_source_scope_id_idx": { | 630 | "mcp_source_scope_id_idx": { |
| 688 | "name": "mcp_source_scope_id_idx", | 631 | "name": "mcp_source_scope_id_idx", |
| 689 | "columns": [ | 632 | "columns": ["scope_id"], |
| 690 | "scope_id" | ||
| 691 | ], | ||
| 692 | "isUnique": false | 633 | "isUnique": false |
| 693 | } | 634 | } |
| 694 | }, | 635 | }, |
| 695 | "foreignKeys": {}, | 636 | "foreignKeys": {}, |
| 696 | "compositePrimaryKeys": { | 637 | "compositePrimaryKeys": { |
| 697 | "mcp_source_scope_id_id_pk": { | 638 | "mcp_source_scope_id_id_pk": { |
| 698 | "columns": [ | 639 | "columns": ["scope_id", "id"], |
| 699 | "scope_id", | ||
| 700 | "id" | ||
| 701 | ], | ||
| 702 | "name": "mcp_source_scope_id_id_pk" | 640 | "name": "mcp_source_scope_id_id_pk" |
| 703 | } | 641 | } |
| 704 | }, | 642 | }, |
| @@ -740,19 +678,14 @@ · 35 unmodified lines | |||
| 740 | "indexes": { | 678 | "indexes": { |
| 741 | "openapi_oauth_session_scope_id_idx": { | 679 | "openapi_oauth_session_scope_id_idx": { |
| 742 | "name": "openapi_oauth_session_scope_id_idx", | 680 | "name": "openapi_oauth_session_scope_id_idx", |
| 743 | "columns": [ | 681 | "columns": ["scope_id"], |
| 744 | "scope_id" | ||
| 745 | ], | ||
| 746 | "isUnique": false | 682 | "isUnique": false |
| 747 | } | 683 | } |
| 748 | }, | 684 | }, |
| 749 | "foreignKeys": {}, | 685 | "foreignKeys": {}, |
| 750 | "compositePrimaryKeys": { | 686 | "compositePrimaryKeys": { |
| 751 | "openapi_oauth_session_scope_id_id_pk": { | 687 | "openapi_oauth_session_scope_id_id_pk": { |
| 752 | "columns": [ | 688 | "columns": ["scope_id", "id"], |
| 753 | "scope_id", | ||
| 754 | "id" | ||
| 755 | ], | ||
| 756 | "name": "openapi_oauth_session_scope_id_id_pk" | 689 | "name": "openapi_oauth_session_scope_id_id_pk" |
| 757 | } | 690 | } |
| 758 | }, | 691 | }, |
| @@ -794,26 +727,19 @@ · 35 unmodified lines | |||
| 794 | "indexes": { | 727 | "indexes": { |
| 795 | "openapi_operation_scope_id_idx": { | 728 | "openapi_operation_scope_id_idx": { |
| 796 | "name": "openapi_operation_scope_id_idx", | 729 | "name": "openapi_operation_scope_id_idx", |
| 797 | "columns": [ | 730 | "columns": ["scope_id"], |
| 798 | "scope_id" | ||
| 799 | ], | ||
| 800 | "isUnique": false | 731 | "isUnique": false |
| 801 | }, | 732 | }, |
| 802 | "openapi_operation_source_id_idx": { | 733 | "openapi_operation_source_id_idx": { |
| 803 | "name": "openapi_operation_source_id_idx", | 734 | "name": "openapi_operation_source_id_idx", |
| 804 | "columns": [ | 735 | "columns": ["source_id"], |
| 805 | "source_id" | ||
| 806 | ], | ||
| 807 | "isUnique": false | 736 | "isUnique": false |
| 808 | } | 737 | } |
| 809 | }, | 738 | }, |
| 810 | "foreignKeys": {}, | 739 | "foreignKeys": {}, |
| 811 | "compositePrimaryKeys": { | 740 | "compositePrimaryKeys": { |
| 812 | "openapi_operation_scope_id_id_pk": { | 741 | "openapi_operation_scope_id_id_pk": { |
| 813 | "columns": [ | 742 | "columns": ["scope_id", "id"], |
| 814 | "scope_id", | ||
| 815 | "id" | ||
| 816 | ], | ||
| 817 | "name": "openapi_operation_scope_id_id_pk" | 743 | "name": "openapi_operation_scope_id_id_pk" |
| 818 | } | 744 | } |
| 819 | }, | 745 | }, |
| @@ -890,19 +816,14 @@ · 70 unmodified lines | |||
| 890 | "indexes": { | 816 | "indexes": { |
| 891 | "openapi_source_scope_id_idx": { | 817 | "openapi_source_scope_id_idx": { |
| 892 | "name": "openapi_source_scope_id_idx", | 818 | "name": "openapi_source_scope_id_idx", |
| 893 | "columns": [ | 819 | "columns": ["scope_id"], |
| 894 | "scope_id" | ||
| 895 | ], | ||
| 896 | "isUnique": false | 820 | "isUnique": false |
| 897 | } | 821 | } |
| 898 | }, | 822 | }, |
| 899 | "foreignKeys": {}, | 823 | "foreignKeys": {}, |
| 900 | "compositePrimaryKeys": { | 824 | "compositePrimaryKeys": { |
| 901 | "openapi_source_scope_id_id_pk": { | 825 | "openapi_source_scope_id_id_pk": { |
| 902 | "columns": [ | 826 | "columns": ["scope_id", "id"], |
| 903 | "scope_id", | ||
| 904 | "id" | ||
| 905 | ], | ||
| 906 | "name": "openapi_source_scope_id_id_pk" | 827 | "name": "openapi_source_scope_id_id_pk" |
| 907 | } | 828 | } |
| 908 | }, | 829 | }, |
| @@ -972,30 +893,22 @@ · 63 unmodified lines | |||
| 972 | "indexes": { | 893 | "indexes": { |
| 973 | "openapi_source_binding_source_id_idx": { | 894 | "openapi_source_binding_source_id_idx": { |
| 974 | "name": "openapi_source_binding_source_id_idx", | 895 | "name": "openapi_source_binding_source_id_idx", |
| 975 | "columns": [ | 896 | "columns": ["source_id"], |
| 976 | "source_id" | ||
| 977 | ], | ||
| 978 | "isUnique": false | 897 | "isUnique": false |
| 979 | }, | 898 | }, |
| 980 | "openapi_source_binding_source_scope_id_idx": { | 899 | "openapi_source_binding_source_scope_id_idx": { |
| 981 | "name": "openapi_source_binding_source_scope_id_idx", | 900 | "name": "openapi_source_binding_source_scope_id_idx", |
| 982 | "columns": [ | 901 | "columns": ["source_scope_id"], |
| 983 | "source_scope_id" | ||
| 984 | ], | ||
| 985 | "isUnique": false | 902 | "isUnique": false |
| 986 | }, | 903 | }, |
| 987 | "openapi_source_binding_target_scope_id_idx": { | 904 | "openapi_source_binding_target_scope_id_idx": { |
| 988 | "name": "openapi_source_binding_target_scope_id_idx", | 905 | "name": "openapi_source_binding_target_scope_id_idx", |
| 989 | "columns": [ | 906 | "columns": ["target_scope_id"], |
| 990 | "target_scope_id" | ||
| 991 | ], | ||
| 992 | "isUnique": false | 907 | "isUnique": false |
| 993 | }, | 908 | }, |
| 994 | "openapi_source_binding_slot_idx": { | 909 | "openapi_source_binding_slot_idx": { |
| 995 | "name": "openapi_source_binding_slot_idx", | 910 | "name": "openapi_source_binding_slot_idx", |
| 996 | "columns": [ | 911 | "columns": ["slot"], |
| 997 | "slot" | ||
| 998 | ], | ||
| 999 | "isUnique": false | 912 | "isUnique": false |
| 1000 | } | 913 | } |
| 1001 | }, | 914 | }, |
| @@ -1053,33 +966,24 @@ · 51 unmodified lines | |||
| 1053 | "indexes": { | 966 | "indexes": { |
| 1054 | "secret_scope_id_idx": { | 967 | "secret_scope_id_idx": { |
| 1055 | "name": "secret_scope_id_idx", | 968 | "name": "secret_scope_id_idx", |
| 1056 | "columns": [ | 969 | "columns": ["scope_id"], |
| 1057 | "scope_id" | ||
| 1058 | ], | ||
| 1059 | "isUnique": false | 970 | "isUnique": false |
| 1060 | }, | 971 | }, |
| 1061 | "secret_provider_idx": { | 972 | "secret_provider_idx": { |
| 1062 | "name": "secret_provider_idx", | 973 | "name": "secret_provider_idx", |
| 1063 | "columns": [ | 974 | "columns": ["provider"], |
| 1064 | "provider" | ||
| 1065 | ], | ||
| 1066 | "isUnique": false | 975 | "isUnique": false |
| 1067 | }, | 976 | }, |
| 1068 | "secret_owned_by_connection_id_idx": { | 977 | "secret_owned_by_connection_id_idx": { |
| 1069 | "name": "secret_owned_by_connection_id_idx", | 978 | "name": "secret_owned_by_connection_id_idx", |
| 1070 | "columns": [ | 979 | "columns": ["owned_by_connection_id"], |
| 1071 | "owned_by_connection_id" | ||
| 1072 | ], | ||
| 1073 | "isUnique": false | 980 | "isUnique": false |
| 1074 | } | 981 | } |
| 1075 | }, | 982 | }, |
| 1076 | "foreignKeys": {}, | 983 | "foreignKeys": {}, |
| 1077 | "compositePrimaryKeys": { | 984 | "compositePrimaryKeys": { |
| 1078 | "secret_scope_id_id_pk": { | 985 | "secret_scope_id_id_pk": { |
| 1079 | "columns": [ | 986 | "columns": ["scope_id", "id"], |
| 1080 | "scope_id", | ||
| 1081 | "id" | ||
| 1082 | ], | ||
| 1083 | "name": "secret_scope_id_id_pk" | 987 | "name": "secret_scope_id_id_pk" |
| 1084 | } | 988 | } |
| 1085 | }, | 989 | }, |
| @@ -1173,26 +1077,19 @@ · 87 unmodified lines | |||
| 1173 | "indexes": { | 1077 | "indexes": { |
| 1174 | "source_scope_id_idx": { | 1078 | "source_scope_id_idx": { |
| 1175 | "name": "source_scope_id_idx", | 1079 | "name": "source_scope_id_idx", |
| 1176 | "columns": [ | 1080 | "columns": ["scope_id"], |
| 1177 | "scope_id" | ||
| 1178 | ], | ||
| 1179 | "isUnique": false | 1081 | "isUnique": false |
| 1180 | }, | 1082 | }, |
| 1181 | "source_plugin_id_idx": { | 1083 | "source_plugin_id_idx": { |
| 1182 | "name": "source_plugin_id_idx", | 1084 | "name": "source_plugin_id_idx", |
| 1183 | "columns": [ | 1085 | "columns": ["plugin_id"], |
| 1184 | "plugin_id" | ||
| 1185 | ], | ||
| 1186 | "isUnique": false | 1086 | "isUnique": false |
| 1187 | } | 1087 | } |
| 1188 | }, | 1088 | }, |
| 1189 | "foreignKeys": {}, | 1089 | "foreignKeys": {}, |
| 1190 | "compositePrimaryKeys": { | 1090 | "compositePrimaryKeys": { |
| 1191 | "source_scope_id_id_pk": { | 1091 | "source_scope_id_id_pk": { |
| 1192 | "columns": [ | 1092 | "columns": ["scope_id", "id"], |
| 1193 | "scope_id", | ||
| 1194 | "id" | ||
| 1195 | ], | ||
| 1196 | "name": "source_scope_id_id_pk" | 1093 | "name": "source_scope_id_id_pk" |
| 1197 | } | 1094 | } |
| 1198 | }, | 1095 | }, |
| @@ -1276,33 +1173,24 @@ · 77 unmodified lines | |||
| 1276 | "indexes": { | 1173 | "indexes": { |
| 1277 | "tool_scope_id_idx": { | 1174 | "tool_scope_id_idx": { |
| 1278 | "name": "tool_scope_id_idx", | 1175 | "name": "tool_scope_id_idx", |
| 1279 | "columns": [ | 1176 | "columns": ["scope_id"], |
| 1280 | "scope_id" | ||
| 1281 | ], | ||
| 1282 | "isUnique": false | 1177 | "isUnique": false |
| 1283 | }, | 1178 | }, |
| 1284 | "tool_source_id_idx": { | 1179 | "tool_source_id_idx": { |
| 1285 | "name": "tool_source_id_idx", | 1180 | "name": "tool_source_id_idx", |
| 1286 | "columns": [ | 1181 | "columns": ["source_id"], |
| 1287 | "source_id" | ||
| 1288 | ], | ||
| 1289 | "isUnique": false | 1182 | "isUnique": false |
| 1290 | }, | 1183 | }, |
| 1291 | "tool_plugin_id_idx": { | 1184 | "tool_plugin_id_idx": { |
| 1292 | "name": "tool_plugin_id_idx", | 1185 | "name": "tool_plugin_id_idx", |
| 1293 | "columns": [ | 1186 | "columns": ["plugin_id"], |
| 1294 | "plugin_id" | ||
| 1295 | ], | ||
| 1296 | "isUnique": false | 1187 | "isUnique": false |
| 1297 | } | 1188 | } |
| 1298 | }, | 1189 | }, |
| 1299 | "foreignKeys": {}, | 1190 | "foreignKeys": {}, |
| 1300 | "compositePrimaryKeys": { | 1191 | "compositePrimaryKeys": { |
| 1301 | "tool_scope_id_id_pk": { | 1192 | "tool_scope_id_id_pk": { |
| 1302 | "columns": [ | 1193 | "columns": ["scope_id", "id"], |
| 1303 | "scope_id", | ||
| 1304 | "id" | ||
| 1305 | ], | ||
| 1306 | "name": "tool_scope_id_id_pk" | 1194 | "name": "tool_scope_id_id_pk" |
| 1307 | } | 1195 | } |
| 1308 | }, | 1196 | }, |
| @@ -1365,20 +1253,14 @@ · 56 unmodified lines | |||
| 1365 | "indexes": { | 1253 | "indexes": { |
| 1366 | "tool_policy_scope_id_position_idx": { | 1254 | "tool_policy_scope_id_position_idx": { |
| 1367 | "name": "tool_policy_scope_id_position_idx", | 1255 | "name": "tool_policy_scope_id_position_idx", |
| 1368 | "columns": [ | 1256 | "columns": ["scope_id", "position"], |
| 1369 | "scope_id", | ||
| 1370 | "position" | ||
| 1371 | ], | ||
| 1372 | "isUnique": false | 1257 | "isUnique": false |
| 1373 | } | 1258 | } |
| 1374 | }, | 1259 | }, |
| 1375 | "foreignKeys": {}, | 1260 | "foreignKeys": {}, |
| 1376 | "compositePrimaryKeys": { | 1261 | "compositePrimaryKeys": { |
| 1377 | "tool_policy_scope_id_id_pk": { | 1262 | "tool_policy_scope_id_id_pk": { |
| 1378 | "columns": [ | 1263 | "columns": ["scope_id", "id"], |
| 1379 | "scope_id", | ||
| 1380 | "id" | ||
| 1381 | ], | ||
| 1382 | "name": "tool_policy_scope_id_id_pk" | 1264 | "name": "tool_policy_scope_id_id_pk" |
| 1383 | } | 1265 | } |
| 1384 | }, | 1266 | }, |
| @@ -1414,10 +1296,7 @@ · 29 unmodified lines | |||
| 1414 | "foreignKeys": {}, | 1296 | "foreignKeys": {}, |
| 1415 | "compositePrimaryKeys": { | 1297 | "compositePrimaryKeys": { |
| 1416 | "blob_namespace_key_pk": { | 1298 | "blob_namespace_key_pk": { |
| 1417 | "columns": [ | 1299 | "columns": ["namespace", "key"], |
| 1418 | "namespace", | ||
| 1419 | "key" | ||
| 1420 | ], | ||
| 1421 | "name": "blob_namespace_key_pk" | 1300 | "name": "blob_namespace_key_pk" |
| 1422 | } | 1301 | } |
| 1423 | }, | 1302 | }, |
| @@ -33,10 +33,7 @@ · 32 unmodified lines | |||
| 33 | "foreignKeys": {}, | 33 | "foreignKeys": {}, |
| 34 | "compositePrimaryKeys": { | 34 | "compositePrimaryKeys": { |
| 35 | "blob_namespace_key_pk": { | 35 | "blob_namespace_key_pk": { |
| 36 | "columns": [ | 36 | "columns": ["namespace", "key"], |
| 37 | "namespace", | ||
| 38 | "key" | ||
| 39 | ], | ||
| 40 | "name": "blob_namespace_key_pk" | 37 | "name": "blob_namespace_key_pk" |
| 41 | } | 38 | } |
| 42 | }, | 39 | }, |
| @@ -127,26 +124,19 @@ · 84 unmodified lines | |||
| 127 | "indexes": { | 124 | "indexes": { |
| 128 | "connection_scope_id_idx": { | 125 | "connection_scope_id_idx": { |
| 129 | "name": "connection_scope_id_idx", | 126 | "name": "connection_scope_id_idx", |
| 130 | "columns": [ | 127 | "columns": ["scope_id"], |
| 131 | "scope_id" | ||
| 132 | ], | ||
| 133 | "isUnique": false | 128 | "isUnique": false |
| 134 | }, | 129 | }, |
| 135 | "connection_provider_idx": { | 130 | "connection_provider_idx": { |
| 136 | "name": "connection_provider_idx", | 131 | "name": "connection_provider_idx", |
| 137 | "columns": [ | 132 | "columns": ["provider"], |
| 138 | "provider" | ||
| 139 | ], | ||
| 140 | "isUnique": false | 133 | "isUnique": false |
| 141 | } | 134 | } |
| 142 | }, | 135 | }, |
| 143 | "foreignKeys": {}, | 136 | "foreignKeys": {}, |
| 144 | "compositePrimaryKeys": { | 137 | "compositePrimaryKeys": { |
| 145 | "connection_scope_id_id_pk": { | 138 | "connection_scope_id_id_pk": { |
| 146 | "columns": [ | 139 | "columns": ["scope_id", "id"], |
| 147 | "scope_id", | ||
| 148 | "id" | ||
| 149 | ], | ||
| 150 | "name": "connection_scope_id_id_pk" | 140 | "name": "connection_scope_id_id_pk" |
| 151 | } | 141 | } |
| 152 | }, | 142 | }, |
| @@ -209,33 +199,24 @@ · 56 unmodified lines | |||
| 209 | "indexes": { | 199 | "indexes": { |
| 210 | "definition_scope_id_idx": { | 200 | "definition_scope_id_idx": { |
| 211 | "name": "definition_scope_id_idx", | 201 | "name": "definition_scope_id_idx", |
| 212 | "columns": [ | 202 | "columns": ["scope_id"], |
| 213 | "scope_id" | ||
| 214 | ], | ||
| 215 | "isUnique": false | 203 | "isUnique": false |
| 216 | }, | 204 | }, |
| 217 | "definition_source_id_idx": { | 205 | "definition_source_id_idx": { |
| 218 | "name": "definition_source_id_idx", | 206 | "name": "definition_source_id_idx", |
| 219 | "columns": [ | 207 | "columns": ["source_id"], |
| 220 | "source_id" | ||
| 221 | ], | ||
| 222 | "isUnique": false | 208 | "isUnique": false |
| 223 | }, | 209 | }, |
| 224 | "definition_plugin_id_idx": { | 210 | "definition_plugin_id_idx": { |
| 225 | "name": "definition_plugin_id_idx", | 211 | "name": "definition_plugin_id_idx", |
| 226 | "columns": [ | 212 | "columns": ["plugin_id"], |
| 227 | "plugin_id" | ||
| 228 | ], | ||
| 229 | "isUnique": false | 213 | "isUnique": false |
| 230 | } | 214 | } |
| 231 | }, | 215 | }, |
| 232 | "foreignKeys": {}, | 216 | "foreignKeys": {}, |
| 233 | "compositePrimaryKeys": { | 217 | "compositePrimaryKeys": { |
| 234 | "definition_scope_id_id_pk": { | 218 | "definition_scope_id_id_pk": { |
| 235 | "columns": [ | 219 | "columns": ["scope_id", "id"], |
| 236 | "scope_id", | ||
| 237 | "id" | ||
| 238 | ], | ||
| 239 | "name": "definition_scope_id_id_pk" | 220 | "name": "definition_scope_id_id_pk" |
| 240 | } | 221 | } |
| 241 | }, | 222 | }, |
| @@ -284,26 +265,19 @@ · 42 unmodified lines | |||
| 284 | "indexes": { | 265 | "indexes": { |
| 285 | "google_discovery_binding_scope_id_idx": { | 266 | "google_discovery_binding_scope_id_idx": { |
| 286 | "name": "google_discovery_binding_scope_id_idx", | 267 | "name": "google_discovery_binding_scope_id_idx", |
| 287 | "columns": [ | 268 | "columns": ["scope_id"], |
| 288 | "scope_id" | ||
| 289 | ], | ||
| 290 | "isUnique": false | 269 | "isUnique": false |
| 291 | }, | 270 | }, |
| 292 | "google_discovery_binding_source_id_idx": { | 271 | "google_discovery_binding_source_id_idx": { |
| 293 | "name": "google_discovery_binding_source_id_idx", | 272 | "name": "google_discovery_binding_source_id_idx", |
| 294 | "columns": [ | 273 | "columns": ["source_id"], |
| 295 | "source_id" | ||
| 296 | ], | ||
| 297 | "isUnique": false | 274 | "isUnique": false |
| 298 | } | 275 | } |
| 299 | }, | 276 | }, |
| 300 | "foreignKeys": {}, | 277 | "foreignKeys": {}, |
| 301 | "compositePrimaryKeys": { | 278 | "compositePrimaryKeys": { |
| 302 | "google_discovery_binding_scope_id_id_pk": { | 279 | "google_discovery_binding_scope_id_id_pk": { |
| 303 | "columns": [ | 280 | "columns": ["scope_id", "id"], |
| 304 | "scope_id", | ||
| 305 | "id" | ||
| 306 | ], | ||
| 307 | "name": "google_discovery_binding_scope_id_id_pk" | 281 | "name": "google_discovery_binding_scope_id_id_pk" |
| 308 | } | 282 | } |
| 309 | }, | 283 | }, |
| @@ -359,19 +333,14 @@ · 49 unmodified lines | |||
| 359 | "indexes": { | 333 | "indexes": { |
| 360 | "google_discovery_source_scope_id_idx": { | 334 | "google_discovery_source_scope_id_idx": { |
| 361 | "name": "google_discovery_source_scope_id_idx", | 335 | "name": "google_discovery_source_scope_id_idx", |
| 362 | "columns": [ | 336 | "columns": ["scope_id"], |
| 363 | "scope_id" | ||
| 364 | ], | ||
| 365 | "isUnique": false | 337 | "isUnique": false |
| 366 | } | 338 | } |
| 367 | }, | 339 | }, |
| 368 | "foreignKeys": {}, | 340 | "foreignKeys": {}, |
| 369 | "compositePrimaryKeys": { | 341 | "compositePrimaryKeys": { |
| 370 | "google_discovery_source_scope_id_id_pk": { | 342 | "google_discovery_source_scope_id_id_pk": { |
| 371 | "columns": [ | 343 | "columns": ["scope_id", "id"], |
| 372 | "scope_id", | ||
| 373 | "id" | ||
| 374 | ], | ||
| 375 | "name": "google_discovery_source_scope_id_id_pk" | 344 | "name": "google_discovery_source_scope_id_id_pk" |
| 376 | } | 345 | } |
| 377 | }, | 346 | }, |
| @@ -413,26 +382,19 @@ · 35 unmodified lines | |||
| 413 | "indexes": { | 382 | "indexes": { |
| 414 | "graphql_operation_scope_id_idx": { | 383 | "graphql_operation_scope_id_idx": { |
| 415 | "name": "graphql_operation_scope_id_idx", | 384 | "name": "graphql_operation_scope_id_idx", |
| 416 | "columns": [ | 385 | "columns": ["scope_id"], |
| 417 | "scope_id" | ||
| 418 | ], | ||
| 419 | "isUnique": false | 386 | "isUnique": false |
| 420 | }, | 387 | }, |
| 421 | "graphql_operation_source_id_idx": { | 388 | "graphql_operation_source_id_idx": { |
| 422 | "name": "graphql_operation_source_id_idx", | 389 | "name": "graphql_operation_source_id_idx", |
| 423 | "columns": [ | 390 | "columns": ["source_id"], |
| 424 | "source_id" | ||
| 425 | ], | ||
| 426 | "isUnique": false | 391 | "isUnique": false |
| 427 | } | 392 | } |
| 428 | }, | 393 | }, |
| 429 | "foreignKeys": {}, | 394 | "foreignKeys": {}, |
| 430 | "compositePrimaryKeys": { | 395 | "compositePrimaryKeys": { |
| 431 | "graphql_operation_scope_id_id_pk": { | 396 | "graphql_operation_scope_id_id_pk": { |
| 432 | "columns": [ | 397 | "columns": ["scope_id", "id"], |
| 433 | "scope_id", | ||
| 434 | "id" | ||
| 435 | ], | ||
| 436 | "name": "graphql_operation_scope_id_id_pk" | 398 | "name": "graphql_operation_scope_id_id_pk" |
| 437 | } | 399 | } |
| 438 | }, | 400 | }, |
| @@ -495,19 +457,14 @@ · 56 unmodified lines | |||
| 495 | "indexes": { | 457 | "indexes": { |
| 496 | "graphql_source_scope_id_idx": { | 458 | "graphql_source_scope_id_idx": { |
| 497 | "name": "graphql_source_scope_id_idx", | 459 | "name": "graphql_source_scope_id_idx", |
| 498 | "columns": [ | 460 | "columns": ["scope_id"], |
| 499 | "scope_id" | ||
| 500 | ], | ||
| 501 | "isUnique": false | 461 | "isUnique": false |
| 502 | } | 462 | } |
| 503 | }, | 463 | }, |
| 504 | "foreignKeys": {}, | 464 | "foreignKeys": {}, |
| 505 | "compositePrimaryKeys": { | 465 | "compositePrimaryKeys": { |
| 506 | "graphql_source_scope_id_id_pk": { | 466 | "graphql_source_scope_id_id_pk": { |
| 507 | "columns": [ | 467 | "columns": ["scope_id", "id"], |
| 508 | "scope_id", | ||
| 509 | "id" | ||
| 510 | ], | ||
| 511 | "name": "graphql_source_scope_id_id_pk" | 468 | "name": "graphql_source_scope_id_id_pk" |
| 512 | } | 469 | } |
| 513 | }, | 470 | }, |
| @@ -556,26 +513,19 @@ · 42 unmodified lines | |||
| 556 | "indexes": { | 513 | "indexes": { |
| 557 | "mcp_binding_scope_id_idx": { | 514 | "mcp_binding_scope_id_idx": { |
| 558 | "name": "mcp_binding_scope_id_idx", | 515 | "name": "mcp_binding_scope_id_idx", |
| 559 | "columns": [ | 516 | "columns": ["scope_id"], |
| 560 | "scope_id" | ||
| 561 | ], | ||
| 562 | "isUnique": false | 517 | "isUnique": false |
| 563 | }, | 518 | }, |
| 564 | "mcp_binding_source_id_idx": { | 519 | "mcp_binding_source_id_idx": { |
| 565 | "name": "mcp_binding_source_id_idx", | 520 | "name": "mcp_binding_source_id_idx", |
| 566 | "columns": [ | 521 | "columns": ["source_id"], |
| 567 | "source_id" | ||
| 568 | ], | ||
| 569 | "isUnique": false | 522 | "isUnique": false |
| 570 | } | 523 | } |
| 571 | }, | 524 | }, |
| 572 | "foreignKeys": {}, | 525 | "foreignKeys": {}, |
| 573 | "compositePrimaryKeys": { | 526 | "compositePrimaryKeys": { |
| 574 | "mcp_binding_scope_id_id_pk": { | 527 | "mcp_binding_scope_id_id_pk": { |
| 575 | "columns": [ | 528 | "columns": ["scope_id", "id"], |
| 576 | "scope_id", | ||
| 577 | "id" | ||
| 578 | ], | ||
| 579 | "name": "mcp_binding_scope_id_id_pk" | 529 | "name": "mcp_binding_scope_id_id_pk" |
| 580 | } | 530 | } |
| 581 | }, | 531 | }, |
| @@ -624,19 +574,14 @@ · 42 unmodified lines | |||
| 624 | "indexes": { | 574 | "indexes": { |
| 625 | "mcp_source_scope_id_idx": { | 575 | "mcp_source_scope_id_idx": { |
| 626 | "name": "mcp_source_scope_id_idx", | 576 | "name": "mcp_source_scope_id_idx", |
| 627 | "columns": [ | 577 | "columns": ["scope_id"], |
| 628 | "scope_id" | ||
| 629 | ], | ||
| 630 | "isUnique": false | 578 | "isUnique": false |
| 631 | } | 579 | } |
| 632 | }, | 580 | }, |
| 633 | "foreignKeys": {}, | 581 | "foreignKeys": {}, |
| 634 | "compositePrimaryKeys": { | 582 | "compositePrimaryKeys": { |
| 635 | "mcp_source_scope_id_id_pk": { | 583 | "mcp_source_scope_id_id_pk": { |
| 636 | "columns": [ | 584 | "columns": ["scope_id", "id"], |
| 637 | "scope_id", | ||
| 638 | "id" | ||
| 639 | ], | ||
| 640 | "name": "mcp_source_scope_id_id_pk" | 585 | "name": "mcp_source_scope_id_id_pk" |
| 641 | } | 586 | } |
| 642 | }, | 587 | }, |
| @@ -720,33 +665,24 @@ · 77 unmodified lines | |||
| 720 | "indexes": { | 665 | "indexes": { |
| 721 | "oauth2_session_scope_id_idx": { | 666 | "oauth2_session_scope_id_idx": { |
| 722 | "name": "oauth2_session_scope_id_idx", | 667 | "name": "oauth2_session_scope_id_idx", |
| 723 | "columns": [ | 668 | "columns": ["scope_id"], |
| 724 | "scope_id" | ||
| 725 | ], | ||
| 726 | "isUnique": false | 669 | "isUnique": false |
| 727 | }, | 670 | }, |
| 728 | "oauth2_session_plugin_id_idx": { | 671 | "oauth2_session_plugin_id_idx": { |
| 729 | "name": "oauth2_session_plugin_id_idx", | 672 | "name": "oauth2_session_plugin_id_idx", |
| 730 | "columns": [ | 673 | "columns": ["plugin_id"], |
| 731 | "plugin_id" | ||
| 732 | ], | ||
| 733 | "isUnique": false | 674 | "isUnique": false |
| 734 | }, | 675 | }, |
| 735 | "oauth2_session_connection_id_idx": { | 676 | "oauth2_session_connection_id_idx": { |
| 736 | "name": "oauth2_session_connection_id_idx", | 677 | "name": "oauth2_session_connection_id_idx", |
| 737 | "columns": [ | 678 | "columns": ["connection_id"], |
| 738 | "connection_id" | ||
| 739 | ], | ||
| 740 | "isUnique": false | 679 | "isUnique": false |
| 741 | } | 680 | } |
| 742 | }, | 681 | }, |
| 743 | "foreignKeys": {}, | 682 | "foreignKeys": {}, |
| 744 | "compositePrimaryKeys": { | 683 | "compositePrimaryKeys": { |
| 745 | "oauth2_session_scope_id_id_pk": { | 684 | "oauth2_session_scope_id_id_pk": { |
| 746 | "columns": [ | 685 | "columns": ["scope_id", "id"], |
| 747 | "scope_id", | ||
| 748 | "id" | ||
| 749 | ], | ||
| 750 | "name": "oauth2_session_scope_id_id_pk" | 686 | "name": "oauth2_session_scope_id_id_pk" |
| 751 | } | 687 | } |
| 752 | }, | 688 | }, |
| @@ -788,26 +724,19 @@ · 35 unmodified lines | |||
| 788 | "indexes": { | 724 | "indexes": { |
| 789 | "openapi_operation_scope_id_idx": { | 725 | "openapi_operation_scope_id_idx": { |
| 790 | "name": "openapi_operation_scope_id_idx", | 726 | "name": "openapi_operation_scope_id_idx", |
| 791 | "columns": [ | 727 | "columns": ["scope_id"], |
| 792 | "scope_id" | ||
| 793 | ], | ||
| 794 | "isUnique": false | 728 | "isUnique": false |
| 795 | }, | 729 | }, |
| 796 | "openapi_operation_source_id_idx": { | 730 | "openapi_operation_source_id_idx": { |
| 797 | "name": "openapi_operation_source_id_idx", | 731 | "name": "openapi_operation_source_id_idx", |
| 798 | "columns": [ | 732 | "columns": ["source_id"], |
| 799 | "source_id" | ||
| 800 | ], | ||
| 801 | "isUnique": false | 733 | "isUnique": false |
| 802 | } | 734 | } |
| 803 | }, | 735 | }, |
| 804 | "foreignKeys": {}, | 736 | "foreignKeys": {}, |
| 805 | "compositePrimaryKeys": { | 737 | "compositePrimaryKeys": { |
| 806 | "openapi_operation_scope_id_id_pk": { | 738 | "openapi_operation_scope_id_id_pk": { |
| 807 | "columns": [ | 739 | "columns": ["scope_id", "id"], |
| 808 | "scope_id", | ||
| 809 | "id" | ||
| 810 | ], | ||
| 811 | "name": "openapi_operation_scope_id_id_pk" | 740 | "name": "openapi_operation_scope_id_id_pk" |
| 812 | } | 741 | } |
| 813 | }, | 742 | }, |
| @@ -891,19 +820,14 @@ · 77 unmodified lines | |||
| 891 | "indexes": { | 820 | "indexes": { |
| 892 | "openapi_source_scope_id_idx": { | 821 | "openapi_source_scope_id_idx": { |
| 893 | "name": "openapi_source_scope_id_idx", | 822 | "name": "openapi_source_scope_id_idx", |
| 894 | "columns": [ | 823 | "columns": ["scope_id"], |
| 895 | "scope_id" | ||
| 896 | ], | ||
| 897 | "isUnique": false | 824 | "isUnique": false |
| 898 | } | 825 | } |
| 899 | }, | 826 | }, |
| 900 | "foreignKeys": {}, | 827 | "foreignKeys": {}, |
| 901 | "compositePrimaryKeys": { | 828 | "compositePrimaryKeys": { |
| 902 | "openapi_source_scope_id_id_pk": { | 829 | "openapi_source_scope_id_id_pk": { |
| 903 | "columns": [ | 830 | "columns": ["scope_id", "id"], |
| 904 | "scope_id", | ||
| 905 | "id" | ||
| 906 | ], | ||
| 907 | "name": "openapi_source_scope_id_id_pk" | 831 | "name": "openapi_source_scope_id_id_pk" |
| 908 | } | 832 | } |
| 909 | }, | 833 | }, |
| @@ -973,30 +897,22 @@ · 63 unmodified lines | |||
| 973 | "indexes": { | 897 | "indexes": { |
| 974 | "openapi_source_binding_source_id_idx": { | 898 | "openapi_source_binding_source_id_idx": { |
| 975 | "name": "openapi_source_binding_source_id_idx", | 899 | "name": "openapi_source_binding_source_id_idx", |
| 976 | "columns": [ | 900 | "columns": ["source_id"], |
| 977 | "source_id" | ||
| 978 | ], | ||
| 979 | "isUnique": false | 901 | "isUnique": false |
| 980 | }, | 902 | }, |
| 981 | "openapi_source_binding_source_scope_id_idx": { | 903 | "openapi_source_binding_source_scope_id_idx": { |
| 982 | "name": "openapi_source_binding_source_scope_id_idx", | 904 | "name": "openapi_source_binding_source_scope_id_idx", |
| 983 | "columns": [ | 905 | "columns": ["source_scope_id"], |
| 984 | "source_scope_id" | ||
| 985 | ], | ||
| 986 | "isUnique": false | 906 | "isUnique": false |
| 987 | }, | 907 | }, |
| 988 | "openapi_source_binding_target_scope_id_idx": { | 908 | "openapi_source_binding_target_scope_id_idx": { |
| 989 | "name": "openapi_source_binding_target_scope_id_idx", | 909 | "name": "openapi_source_binding_target_scope_id_idx", |
| 990 | "columns": [ | 910 | "columns": ["target_scope_id"], |
| 991 | "target_scope_id" | ||
| 992 | ], | ||
| 993 | "isUnique": false | 911 | "isUnique": false |
| 994 | }, | 912 | }, |
| 995 | "openapi_source_binding_slot_idx": { | 913 | "openapi_source_binding_slot_idx": { |
| 996 | "name": "openapi_source_binding_slot_idx", | 914 | "name": "openapi_source_binding_slot_idx", |
| 997 | "columns": [ | 915 | "columns": ["slot"], |
| 998 | "slot" | ||
| 999 | ], | ||
| 1000 | "isUnique": false | 916 | "isUnique": false |
| 1001 | } | 917 | } |
| 1002 | }, | 918 | }, |
| @@ -1054,33 +970,24 @@ · 51 unmodified lines | |||
| 1054 | "indexes": { | 970 | "indexes": { |
| 1055 | "secret_scope_id_idx": { | 971 | "secret_scope_id_idx": { |
| 1056 | "name": "secret_scope_id_idx", | 972 | "name": "secret_scope_id_idx", |
| 1057 | "columns": [ | 973 | "columns": ["scope_id"], |
| 1058 | "scope_id" | ||
| 1059 | ], | ||
| 1060 | "isUnique": false | 974 | "isUnique": false |
| 1061 | }, | 975 | }, |
| 1062 | "secret_provider_idx": { | 976 | "secret_provider_idx": { |
| 1063 | "name": "secret_provider_idx", | 977 | "name": "secret_provider_idx", |
| 1064 | "columns": [ | 978 | "columns": ["provider"], |
| 1065 | "provider" | ||
| 1066 | ], | ||
| 1067 | "isUnique": false | 979 | "isUnique": false |
| 1068 | }, | 980 | }, |
| 1069 | "secret_owned_by_connection_id_idx": { | 981 | "secret_owned_by_connection_id_idx": { |
| 1070 | "name": "secret_owned_by_connection_id_idx", | 982 | "name": "secret_owned_by_connection_id_idx", |
| 1071 | "columns": [ | 983 | "columns": ["owned_by_connection_id"], |
| 1072 | "owned_by_connection_id" | ||
| 1073 | ], | ||
| 1074 | "isUnique": false | 984 | "isUnique": false |
| 1075 | } | 985 | } |
| 1076 | }, | 986 | }, |
| 1077 | "foreignKeys": {}, | 987 | "foreignKeys": {}, |
| 1078 | "compositePrimaryKeys": { | 988 | "compositePrimaryKeys": { |
| 1079 | "secret_scope_id_id_pk": { | 989 | "secret_scope_id_id_pk": { |
| 1080 | "columns": [ | 990 | "columns": ["scope_id", "id"], |
| 1081 | "scope_id", | ||
| 1082 | "id" | ||
| 1083 | ], | ||
| 1084 | "name": "secret_scope_id_id_pk" | 991 | "name": "secret_scope_id_id_pk" |
| 1085 | } | 992 | } |
| 1086 | }, | 993 | }, |
| @@ -1174,26 +1081,19 @@ · 87 unmodified lines | |||
| 1174 | "indexes": { | 1081 | "indexes": { |
| 1175 | "source_scope_id_idx": { | 1082 | "source_scope_id_idx": { |
| 1176 | "name": "source_scope_id_idx", | 1083 | "name": "source_scope_id_idx", |
| 1177 | "columns": [ | 1084 | "columns": ["scope_id"], |
| 1178 | "scope_id" | ||
| 1179 | ], | ||
| 1180 | "isUnique": false | 1085 | "isUnique": false |
| 1181 | }, | 1086 | }, |
| 1182 | "source_plugin_id_idx": { | 1087 | "source_plugin_id_idx": { |
| 1183 | "name": "source_plugin_id_idx", | 1088 | "name": "source_plugin_id_idx", |
| 1184 | "columns": [ | 1089 | "columns": ["plugin_id"], |
| 1185 | "plugin_id" | ||
| 1186 | ], | ||
| 1187 | "isUnique": false | 1090 | "isUnique": false |
| 1188 | } | 1091 | } |
| 1189 | }, | 1092 | }, |
| 1190 | "foreignKeys": {}, | 1093 | "foreignKeys": {}, |
| 1191 | "compositePrimaryKeys": { | 1094 | "compositePrimaryKeys": { |
| 1192 | "source_scope_id_id_pk": { | 1095 | "source_scope_id_id_pk": { |
| 1193 | "columns": [ | 1096 | "columns": ["scope_id", "id"], |
| 1194 | "scope_id", | ||
| 1195 | "id" | ||
| 1196 | ], | ||
| 1197 | "name": "source_scope_id_id_pk" | 1097 | "name": "source_scope_id_id_pk" |
| 1198 | } | 1098 | } |
| 1199 | }, | 1099 | }, |
| @@ -1277,33 +1177,24 @@ · 77 unmodified lines | |||
| 1277 | "indexes": { | 1177 | "indexes": { |
| 1278 | "tool_scope_id_idx": { | 1178 | "tool_scope_id_idx": { |
| 1279 | "name": "tool_scope_id_idx", | 1179 | "name": "tool_scope_id_idx", |
| 1280 | "columns": [ | 1180 | "columns": ["scope_id"], |
| 1281 | "scope_id" | ||
| 1282 | ], | ||
| 1283 | "isUnique": false | 1181 | "isUnique": false |
| 1284 | }, | 1182 | }, |
| 1285 | "tool_source_id_idx": { | 1183 | "tool_source_id_idx": { |
| 1286 | "name": "tool_source_id_idx", | 1184 | "name": "tool_source_id_idx", |
| 1287 | "columns": [ | 1185 | "columns": ["source_id"], |
| 1288 | "source_id" | ||
| 1289 | ], | ||
| 1290 | "isUnique": false | 1186 | "isUnique": false |
| 1291 | }, | 1187 | }, |
| 1292 | "tool_plugin_id_idx": { | 1188 | "tool_plugin_id_idx": { |
| 1293 | "name": "tool_plugin_id_idx", | 1189 | "name": "tool_plugin_id_idx", |
| 1294 | "columns": [ | 1190 | "columns": ["plugin_id"], |
| 1295 | "plugin_id" | ||
| 1296 | ], | ||
| 1297 | "isUnique": false | 1191 | "isUnique": false |
| 1298 | } | 1192 | } |
| 1299 | }, | 1193 | }, |
| 1300 | "foreignKeys": {}, | 1194 | "foreignKeys": {}, |
| 1301 | "compositePrimaryKeys": { | 1195 | "compositePrimaryKeys": { |
| 1302 | "tool_scope_id_id_pk": { | 1196 | "tool_scope_id_id_pk": { |
| 1303 | "columns": [ | 1197 | "columns": ["scope_id", "id"], |
| 1304 | "scope_id", | ||
| 1305 | "id" | ||
| 1306 | ], | ||
| 1307 | "name": "tool_scope_id_id_pk" | 1198 | "name": "tool_scope_id_id_pk" |
| 1308 | } | 1199 | } |
| 1309 | }, | 1200 | }, |
| @@ -1366,20 +1257,14 @@ · 56 unmodified lines | |||
| 1366 | "indexes": { | 1257 | "indexes": { |
| 1367 | "tool_policy_scope_id_position_idx": { | 1258 | "tool_policy_scope_id_position_idx": { |
| 1368 | "name": "tool_policy_scope_id_position_idx", | 1259 | "name": "tool_policy_scope_id_position_idx", |
| 1369 | "columns": [ | 1260 | "columns": ["scope_id", "position"], |
| 1370 | "scope_id", | ||
| 1371 | "position" | ||
| 1372 | ], | ||
| 1373 | "isUnique": false | 1261 | "isUnique": false |
| 1374 | } | 1262 | } |
| 1375 | }, | 1263 | }, |
| 1376 | "foreignKeys": {}, | 1264 | "foreignKeys": {}, |
| 1377 | "compositePrimaryKeys": { | 1265 | "compositePrimaryKeys": { |
| 1378 | "tool_policy_scope_id_id_pk": { | 1266 | "tool_policy_scope_id_id_pk": { |
| 1379 | "columns": [ | 1267 | "columns": ["scope_id", "id"], |
| 1380 | "scope_id", | ||
| 1381 | "id" | ||
| 1382 | ], | ||
| 1383 | "name": "tool_policy_scope_id_id_pk" | 1268 | "name": "tool_policy_scope_id_id_pk" |
| 1384 | } | 1269 | } |
| 1385 | }, | 1270 | }, |
| @@ -1397,4 +1282,4 @@ · 11 unmodified lines | |||
| 1397 | "internal": { | 1282 | "internal": { |
| 1398 | "indexes": {} | 1283 | "indexes": {} |
| 1399 | } | 1284 | } |
| 1400 | } | 1285 | } |
| @@ -33,10 +33,7 @@ · 32 unmodified lines | |||
| 33 | "foreignKeys": {}, | 33 | "foreignKeys": {}, |
| 34 | "compositePrimaryKeys": { | 34 | "compositePrimaryKeys": { |
| 35 | "blob_namespace_key_pk": { | 35 | "blob_namespace_key_pk": { |
| 36 | "columns": [ | 36 | "columns": ["namespace", "key"], |
| 37 | "namespace", | ||
| 38 | "key" | ||
| 39 | ], | ||
| 40 | "name": "blob_namespace_key_pk" | 37 | "name": "blob_namespace_key_pk" |
| 41 | } | 38 | } |
| 42 | }, | 39 | }, |
| @@ -127,26 +124,19 @@ · 84 unmodified lines | |||
| 127 | "indexes": { | 124 | "indexes": { |
| 128 | "connection_scope_id_idx": { | 125 | "connection_scope_id_idx": { |
| 129 | "name": "connection_scope_id_idx", | 126 | "name": "connection_scope_id_idx", |
| 130 | "columns": [ | 127 | "columns": ["scope_id"], |
| 131 | "scope_id" | ||
| 132 | ], | ||
| 133 | "isUnique": false | 128 | "isUnique": false |
| 134 | }, | 129 | }, |
| 135 | "connection_provider_idx": { | 130 | "connection_provider_idx": { |
| 136 | "name": "connection_provider_idx", | 131 | "name": "connection_provider_idx", |
| 137 | "columns": [ | 132 | "columns": ["provider"], |
| 138 | "provider" | ||
| 139 | ], | ||
| 140 | "isUnique": false | 133 | "isUnique": false |
| 141 | } | 134 | } |
| 142 | }, | 135 | }, |
| 143 | "foreignKeys": {}, | 136 | "foreignKeys": {}, |
| 144 | "compositePrimaryKeys": { | 137 | "compositePrimaryKeys": { |
| 145 | "connection_scope_id_id_pk": { | 138 | "connection_scope_id_id_pk": { |
| 146 | "columns": [ | 139 | "columns": ["scope_id", "id"], |
| 147 | "scope_id", | ||
| 148 | "id" | ||
| 149 | ], | ||
| 150 | "name": "connection_scope_id_id_pk" | 140 | "name": "connection_scope_id_id_pk" |
| 151 | } | 141 | } |
| 152 | }, | 142 | }, |
| @@ -209,33 +199,24 @@ · 56 unmodified lines | |||
| 209 | "indexes": { | 199 | "indexes": { |
| 210 | "definition_scope_id_idx": { | 200 | "definition_scope_id_idx": { |
| 211 | "name": "definition_scope_id_idx", | 201 | "name": "definition_scope_id_idx", |
| 212 | "columns": [ | 202 | "columns": ["scope_id"], |
| 213 | "scope_id" | ||
| 214 | ], | ||
| 215 | "isUnique": false | 203 | "isUnique": false |
| 216 | }, | 204 | }, |
| 217 | "definition_source_id_idx": { | 205 | "definition_source_id_idx": { |
| 218 | "name": "definition_source_id_idx", | 206 | "name": "definition_source_id_idx", |
| 219 | "columns": [ | 207 | "columns": ["source_id"], |
| 220 | "source_id" | ||
| 221 | ], | ||
| 222 | "isUnique": false | 208 | "isUnique": false |
| 223 | }, | 209 | }, |
| 224 | "definition_plugin_id_idx": { | 210 | "definition_plugin_id_idx": { |
| 225 | "name": "definition_plugin_id_idx", | 211 | "name": "definition_plugin_id_idx", |
| 226 | "columns": [ | 212 | "columns": ["plugin_id"], |
| 227 | "plugin_id" | ||
| 228 | ], | ||
| 229 | "isUnique": false | 213 | "isUnique": false |
| 230 | } | 214 | } |
| 231 | }, | 215 | }, |
| 232 | "foreignKeys": {}, | 216 | "foreignKeys": {}, |
| 233 | "compositePrimaryKeys": { | 217 | "compositePrimaryKeys": { |
| 234 | "definition_scope_id_id_pk": { | 218 | "definition_scope_id_id_pk": { |
| 235 | "columns": [ | 219 | "columns": ["scope_id", "id"], |
| 236 | "scope_id", | ||
| 237 | "id" | ||
| 238 | ], | ||
| 239 | "name": "definition_scope_id_id_pk" | 220 | "name": "definition_scope_id_id_pk" |
| 240 | } | 221 | } |
| 241 | }, | 222 | }, |
| @@ -284,26 +265,19 @@ · 42 unmodified lines | |||
| 284 | "indexes": { | 265 | "indexes": { |
| 285 | "google_discovery_binding_scope_id_idx": { | 266 | "google_discovery_binding_scope_id_idx": { |
| 286 | "name": "google_discovery_binding_scope_id_idx", | 267 | "name": "google_discovery_binding_scope_id_idx", |
| 287 | "columns": [ | 268 | "columns": ["scope_id"], |
| 288 | "scope_id" | ||
| 289 | ], | ||
| 290 | "isUnique": false | 269 | "isUnique": false |
| 291 | }, | 270 | }, |
| 292 | "google_discovery_binding_source_id_idx": { | 271 | "google_discovery_binding_source_id_idx": { |
| 293 | "name": "google_discovery_binding_source_id_idx", | 272 | "name": "google_discovery_binding_source_id_idx", |
| 294 | "columns": [ | 273 | "columns": ["source_id"], |
| 295 | "source_id" | ||
| 296 | ], | ||
| 297 | "isUnique": false | 274 | "isUnique": false |
| 298 | } | 275 | } |
| 299 | }, | 276 | }, |
| 300 | "foreignKeys": {}, | 277 | "foreignKeys": {}, |
| 301 | "compositePrimaryKeys": { | 278 | "compositePrimaryKeys": { |
| 302 | "google_discovery_binding_scope_id_id_pk": { | 279 | "google_discovery_binding_scope_id_id_pk": { |
| 303 | "columns": [ | 280 | "columns": ["scope_id", "id"], |
| 304 | "scope_id", | ||
| 305 | "id" | ||
| 306 | ], | ||
| 307 | "name": "google_discovery_binding_scope_id_id_pk" | 281 | "name": "google_discovery_binding_scope_id_id_pk" |
| 308 | } | 282 | } |
| 309 | }, | 283 | }, |
| @@ -395,40 +369,29 @@ · 85 unmodified lines | |||
| 395 | "indexes": { | 369 | "indexes": { |
| 396 | "google_discovery_source_scope_id_idx": { | 370 | "google_discovery_source_scope_id_idx": { |
| 397 | "name": "google_discovery_source_scope_id_idx", | 371 | "name": "google_discovery_source_scope_id_idx", |
| 398 | "columns": [ | 372 | "columns": ["scope_id"], |
| 399 | "scope_id" | ||
| 400 | ], | ||
| 401 | "isUnique": false | 373 | "isUnique": false |
| 402 | }, | 374 | }, |
| 403 | "google_discovery_source_auth_connection_id_idx": { | 375 | "google_discovery_source_auth_connection_id_idx": { |
| 404 | "name": "google_discovery_source_auth_connection_id_idx", | 376 | "name": "google_discovery_source_auth_connection_id_idx", |
| 405 | "columns": [ | 377 | "columns": ["auth_connection_id"], |
| 406 | "auth_connection_id" | ||
| 407 | ], | ||
| 408 | "isUnique": false | 378 | "isUnique": false |
| 409 | }, | 379 | }, |
| 410 | "google_discovery_source_auth_client_id_secret_id_idx": { | 380 | "google_discovery_source_auth_client_id_secret_id_idx": { |
| 411 | "name": "google_discovery_source_auth_client_id_secret_id_idx", | 381 | "name": "google_discovery_source_auth_client_id_secret_id_idx", |
| 412 | "columns": [ | 382 | "columns": ["auth_client_id_secret_id"], |
| 413 | "auth_client_id_secret_id" | ||
| 414 | ], | ||
| 415 | "isUnique": false | 383 | "isUnique": false |
| 416 | }, | 384 | }, |
| 417 | "google_discovery_source_auth_client_secret_secret_id_idx": { | 385 | "google_discovery_source_auth_client_secret_secret_id_idx": { |
| 418 | "name": "google_discovery_source_auth_client_secret_secret_id_idx", | 386 | "name": "google_discovery_source_auth_client_secret_secret_id_idx", |
| 419 | "columns": [ | 387 | "columns": ["auth_client_secret_secret_id"], |
| 420 | "auth_client_secret_secret_id" | ||
| 421 | ], | ||
| 422 | "isUnique": false | 388 | "isUnique": false |
| 423 | } | 389 | } |
| 424 | }, | 390 | }, |
| 425 | "foreignKeys": {}, | 391 | "foreignKeys": {}, |
| 426 | "compositePrimaryKeys": { | 392 | "compositePrimaryKeys": { |
| 427 | "google_discovery_source_scope_id_id_pk": { | 393 | "google_discovery_source_scope_id_id_pk": { |
| 428 | "columns": [ | 394 | "columns": ["scope_id", "id"], |
| 429 | "scope_id", | ||
| 430 | "id" | ||
| 431 | ], | ||
| 432 | "name": "google_discovery_source_scope_id_id_pk" | 395 | "name": "google_discovery_source_scope_id_id_pk" |
| 433 | } | 396 | } |
| 434 | }, | 397 | }, |
| @@ -470,26 +433,19 @@ · 35 unmodified lines | |||
| 470 | "indexes": { | 433 | "indexes": { |
| 471 | "graphql_operation_scope_id_idx": { | 434 | "graphql_operation_scope_id_idx": { |
| 472 | "name": "graphql_operation_scope_id_idx", | 435 | "name": "graphql_operation_scope_id_idx", |
| 473 | "columns": [ | 436 | "columns": ["scope_id"], |
| 474 | "scope_id" | ||
| 475 | ], | ||
| 476 | "isUnique": false | 437 | "isUnique": false |
| 477 | }, | 438 | }, |
| 478 | "graphql_operation_source_id_idx": { | 439 | "graphql_operation_source_id_idx": { |
| 479 | "name": "graphql_operation_source_id_idx", | 440 | "name": "graphql_operation_source_id_idx", |
| 480 | "columns": [ | 441 | "columns": ["source_id"], |
| 481 | "source_id" | ||
| 482 | ], | ||
| 483 | "isUnique": false | 442 | "isUnique": false |
| 484 | } | 443 | } |
| 485 | }, | 444 | }, |
| 486 | "foreignKeys": {}, | 445 | "foreignKeys": {}, |
| 487 | "compositePrimaryKeys": { | 446 | "compositePrimaryKeys": { |
| 488 | "graphql_operation_scope_id_id_pk": { | 447 | "graphql_operation_scope_id_id_pk": { |
| 489 | "columns": [ | 448 | "columns": ["scope_id", "id"], |
| 490 | "scope_id", | ||
| 491 | "id" | ||
| 492 | ], | ||
| 493 | "name": "graphql_operation_scope_id_id_pk" | 449 | "name": "graphql_operation_scope_id_id_pk" |
| 494 | } | 450 | } |
| 495 | }, | 451 | }, |
| @@ -546,26 +502,19 @@ · 50 unmodified lines | |||
| 546 | "indexes": { | 502 | "indexes": { |
| 547 | "graphql_source_scope_id_idx": { | 503 | "graphql_source_scope_id_idx": { |
| 548 | "name": "graphql_source_scope_id_idx", | 504 | "name": "graphql_source_scope_id_idx", |
| 549 | "columns": [ | 505 | "columns": ["scope_id"], |
| 550 | "scope_id" | ||
| 551 | ], | ||
| 552 | "isUnique": false | 506 | "isUnique": false |
| 553 | }, | 507 | }, |
| 554 | "graphql_source_auth_connection_id_idx": { | 508 | "graphql_source_auth_connection_id_idx": { |
| 555 | "name": "graphql_source_auth_connection_id_idx", | 509 | "name": "graphql_source_auth_connection_id_idx", |
| 556 | "columns": [ | 510 | "columns": ["auth_connection_id"], |
| 557 | "auth_connection_id" | ||
| 558 | ], | ||
| 559 | "isUnique": false | 511 | "isUnique": false |
| 560 | } | 512 | } |
| 561 | }, | 513 | }, |
| 562 | "foreignKeys": {}, | 514 | "foreignKeys": {}, |
| 563 | "compositePrimaryKeys": { | 515 | "compositePrimaryKeys": { |
| 564 | "graphql_source_scope_id_id_pk": { | 516 | "graphql_source_scope_id_id_pk": { |
| 565 | "columns": [ | 517 | "columns": ["scope_id", "id"], |
| 566 | "scope_id", | ||
| 567 | "id" | ||
| 568 | ], | ||
| 569 | "name": "graphql_source_scope_id_id_pk" | 518 | "name": "graphql_source_scope_id_id_pk" |
| 570 | } | 519 | } |
| 571 | }, | 520 | }, |
| @@ -614,26 +563,19 @@ · 42 unmodified lines | |||
| 614 | "indexes": { | 563 | "indexes": { |
| 615 | "mcp_binding_scope_id_idx": { | 564 | "mcp_binding_scope_id_idx": { |
| 616 | "name": "mcp_binding_scope_id_idx", | 565 | "name": "mcp_binding_scope_id_idx", |
| 617 | "columns": [ | 566 | "columns": ["scope_id"], |
| 618 | "scope_id" | ||
| 619 | ], | ||
| 620 | "isUnique": false | 567 | "isUnique": false |
| 621 | }, | 568 | }, |
| 622 | "mcp_binding_source_id_idx": { | 569 | "mcp_binding_source_id_idx": { |
| 623 | "name": "mcp_binding_source_id_idx", | 570 | "name": "mcp_binding_source_id_idx", |
| 624 | "columns": [ | 571 | "columns": ["source_id"], |
| 625 | "source_id" | ||
| 626 | ], | ||
| 627 | "isUnique": false | 572 | "isUnique": false |
| 628 | } | 573 | } |
| 629 | }, | 574 | }, |
| 630 | "foreignKeys": {}, | 575 | "foreignKeys": {}, |
| 631 | "compositePrimaryKeys": { | 576 | "compositePrimaryKeys": { |
| 632 | "mcp_binding_scope_id_id_pk": { | 577 | "mcp_binding_scope_id_id_pk": { |
| 633 | "columns": [ | 578 | "columns": ["scope_id", "id"], |
| 634 | "scope_id", | ||
| 635 | "id" | ||
| 636 | ], | ||
| 637 | "name": "mcp_binding_scope_id_id_pk" | 579 | "name": "mcp_binding_scope_id_id_pk" |
| 638 | } | 580 | } |
| 639 | }, | 581 | }, |
| @@ -732,47 +674,34 @@ · 92 unmodified lines | |||
| 732 | "indexes": { | 674 | "indexes": { |
| 733 | "mcp_source_scope_id_idx": { | 675 | "mcp_source_scope_id_idx": { |
| 734 | "name": "mcp_source_scope_id_idx", | 676 | "name": "mcp_source_scope_id_idx", |
| 735 | "columns": [ | 677 | "columns": ["scope_id"], |
| 736 | "scope_id" | ||
| 737 | ], | ||
| 738 | "isUnique": false | 678 | "isUnique": false |
| 739 | }, | 679 | }, |
| 740 | "mcp_source_auth_secret_id_idx": { | 680 | "mcp_source_auth_secret_id_idx": { |
| 741 | "name": "mcp_source_auth_secret_id_idx", | 681 | "name": "mcp_source_auth_secret_id_idx", |
| 742 | "columns": [ | 682 | "columns": ["auth_secret_id"], |
| 743 | "auth_secret_id" | ||
| 744 | ], | ||
| 745 | "isUnique": false | 683 | "isUnique": false |
| 746 | }, | 684 | }, |
| 747 | "mcp_source_auth_connection_id_idx": { | 685 | "mcp_source_auth_connection_id_idx": { |
| 748 | "name": "mcp_source_auth_connection_id_idx", | 686 | "name": "mcp_source_auth_connection_id_idx", |
| 749 | "columns": [ | 687 | "columns": ["auth_connection_id"], |
| 750 | "auth_connection_id" | ||
| 751 | ], | ||
| 752 | "isUnique": false | 688 | "isUnique": false |
| 753 | }, | 689 | }, |
| 754 | "mcp_source_auth_client_id_secret_id_idx": { | 690 | "mcp_source_auth_client_id_secret_id_idx": { |
| 755 | "name": "mcp_source_auth_client_id_secret_id_idx", | 691 | "name": "mcp_source_auth_client_id_secret_id_idx", |
| 756 | "columns": [ | 692 | "columns": ["auth_client_id_secret_id"], |
| 757 | "auth_client_id_secret_id" | ||
| 758 | ], | ||
| 759 | "isUnique": false | 693 | "isUnique": false |
| 760 | }, | 694 | }, |
| 761 | "mcp_source_auth_client_secret_secret_id_idx": { | 695 | "mcp_source_auth_client_secret_secret_id_idx": { |
| 762 | "name": "mcp_source_auth_client_secret_secret_id_idx", | 696 | "name": "mcp_source_auth_client_secret_secret_id_idx", |
| 763 | "columns": [ | 697 | "columns": ["auth_client_secret_secret_id"], |
| 764 | "auth_client_secret_secret_id" | ||
| 765 | ], | ||
| 766 | "isUnique": false | 698 | "isUnique": false |
| 767 | } | 699 | } |
| 768 | }, | 700 | }, |
| 769 | "foreignKeys": {}, | 701 | "foreignKeys": {}, |
| 770 | "compositePrimaryKeys": { | 702 | "compositePrimaryKeys": { |
| 771 | "mcp_source_scope_id_id_pk": { | 703 | "mcp_source_scope_id_id_pk": { |
| 772 | "columns": [ | 704 | "columns": ["scope_id", "id"], |
| 773 | "scope_id", | ||
| 774 | "id" | ||
| 775 | ], | ||
| 776 | "name": "mcp_source_scope_id_id_pk" | 705 | "name": "mcp_source_scope_id_id_pk" |
| 777 | } | 706 | } |
| 778 | }, | 707 | }, |
| @@ -856,33 +785,24 @@ · 77 unmodified lines | |||
| 856 | "indexes": { | 785 | "indexes": { |
| 857 | "oauth2_session_scope_id_idx": { | 786 | "oauth2_session_scope_id_idx": { |
| 858 | "name": "oauth2_session_scope_id_idx", | 787 | "name": "oauth2_session_scope_id_idx", |
| 859 | "columns": [ | 788 | "columns": ["scope_id"], |
| 860 | "scope_id" | ||
| 861 | ], | ||
| 862 | "isUnique": false | 789 | "isUnique": false |
| 863 | }, | 790 | }, |
| 864 | "oauth2_session_plugin_id_idx": { | 791 | "oauth2_session_plugin_id_idx": { |
| 865 | "name": "oauth2_session_plugin_id_idx", | 792 | "name": "oauth2_session_plugin_id_idx", |
| 866 | "columns": [ | 793 | "columns": ["plugin_id"], |
| 867 | "plugin_id" | ||
| 868 | ], | ||
| 869 | "isUnique": false | 794 | "isUnique": false |
| 870 | }, | 795 | }, |
| 871 | "oauth2_session_connection_id_idx": { | 796 | "oauth2_session_connection_id_idx": { |
| 872 | "name": "oauth2_session_connection_id_idx", | 797 | "name": "oauth2_session_connection_id_idx", |
| 873 | "columns": [ | 798 | "columns": ["connection_id"], |
| 874 | "connection_id" | ||
| 875 | ], | ||
| 876 | "isUnique": false | 799 | "isUnique": false |
| 877 | } | 800 | } |
| 878 | }, | 801 | }, |
| 879 | "foreignKeys": {}, | 802 | "foreignKeys": {}, |
| 880 | "compositePrimaryKeys": { | 803 | "compositePrimaryKeys": { |
| 881 | "oauth2_session_scope_id_id_pk": { | 804 | "oauth2_session_scope_id_id_pk": { |
| 882 | "columns": [ | 805 | "columns": ["scope_id", "id"], |
| 883 | "scope_id", | ||
| 884 | "id" | ||
| 885 | ], | ||
| 886 | "name": "oauth2_session_scope_id_id_pk" | 806 | "name": "oauth2_session_scope_id_id_pk" |
| 887 | } | 807 | } |
| 888 | }, | 808 | }, |
| @@ -924,26 +844,19 @@ · 35 unmodified lines | |||
| 924 | "indexes": { | 844 | "indexes": { |
| 925 | "openapi_operation_scope_id_idx": { | 845 | "openapi_operation_scope_id_idx": { |
| 926 | "name": "openapi_operation_scope_id_idx", | 846 | "name": "openapi_operation_scope_id_idx", |
| 927 | "columns": [ | 847 | "columns": ["scope_id"], |
| 928 | "scope_id" | ||
| 929 | ], | ||
| 930 | "isUnique": false | 848 | "isUnique": false |
| 931 | }, | 849 | }, |
| 932 | "openapi_operation_source_id_idx": { | 850 | "openapi_operation_source_id_idx": { |
| 933 | "name": "openapi_operation_source_id_idx", | 851 | "name": "openapi_operation_source_id_idx", |
| 934 | "columns": [ | 852 | "columns": ["source_id"], |
| 935 | "source_id" | ||
| 936 | ], | ||
| 937 | "isUnique": false | 853 | "isUnique": false |
| 938 | } | 854 | } |
| 939 | }, | 855 | }, |
| 940 | "foreignKeys": {}, | 856 | "foreignKeys": {}, |
| 941 | "compositePrimaryKeys": { | 857 | "compositePrimaryKeys": { |
| 942 | "openapi_operation_scope_id_id_pk": { | 858 | "openapi_operation_scope_id_id_pk": { |
| 943 | "columns": [ | 859 | "columns": ["scope_id", "id"], |
| 944 | "scope_id", | ||
| 945 | "id" | ||
| 946 | ], | ||
| 947 | "name": "openapi_operation_scope_id_id_pk" | 860 | "name": "openapi_operation_scope_id_id_pk" |
| 948 | } | 861 | } |
| 949 | }, | 862 | }, |
| @@ -1013,19 +926,14 @@ · 63 unmodified lines | |||
| 1013 | "indexes": { | 926 | "indexes": { |
| 1014 | "openapi_source_scope_id_idx": { | 927 | "openapi_source_scope_id_idx": { |
| 1015 | "name": "openapi_source_scope_id_idx", | 928 | "name": "openapi_source_scope_id_idx", |
| 1016 | "columns": [ | 929 | "columns": ["scope_id"], |
| 1017 | "scope_id" | ||
| 1018 | ], | ||
| 1019 | "isUnique": false | 930 | "isUnique": false |
| 1020 | } | 931 | } |
| 1021 | }, | 932 | }, |
| 1022 | "foreignKeys": {}, | 933 | "foreignKeys": {}, |
| 1023 | "compositePrimaryKeys": { | 934 | "compositePrimaryKeys": { |
| 1024 | "openapi_source_scope_id_id_pk": { | 935 | "openapi_source_scope_id_id_pk": { |
| 1025 | "columns": [ | 936 | "columns": ["scope_id", "id"], |
| 1026 | "scope_id", | ||
| 1027 | "id" | ||
| 1028 | ], | ||
| 1029 | "name": "openapi_source_scope_id_id_pk" | 937 | "name": "openapi_source_scope_id_id_pk" |
| 1030 | } | 938 | } |
| 1031 | }, | 939 | }, |
| @@ -1117,44 +1025,32 @@ · 85 unmodified lines | |||
| 1117 | "indexes": { | 1025 | "indexes": { |
| 1118 | "openapi_source_binding_source_id_idx": { | 1026 | "openapi_source_binding_source_id_idx": { |
| 1119 | "name": "openapi_source_binding_source_id_idx", | 1027 | "name": "openapi_source_binding_source_id_idx", |
| 1120 | "columns": [ | 1028 | "columns": ["source_id"], |
| 1121 | "source_id" | ||
| 1122 | ], | ||
| 1123 | "isUnique": false | 1029 | "isUnique": false |
| 1124 | }, | 1030 | }, |
| 1125 | "openapi_source_binding_source_scope_id_idx": { | 1031 | "openapi_source_binding_source_scope_id_idx": { |
| 1126 | "name": "openapi_source_binding_source_scope_id_idx", | 1032 | "name": "openapi_source_binding_source_scope_id_idx", |
| 1127 | "columns": [ | 1033 | "columns": ["source_scope_id"], |
| 1128 | "source_scope_id" | ||
| 1129 | ], | ||
| 1130 | "isUnique": false | 1034 | "isUnique": false |
| 1131 | }, | 1035 | }, |
| 1132 | "openapi_source_binding_target_scope_id_idx": { | 1036 | "openapi_source_binding_target_scope_id_idx": { |
| 1133 | "name": "openapi_source_binding_target_scope_id_idx", | 1037 | "name": "openapi_source_binding_target_scope_id_idx", |
| 1134 | "columns": [ | 1038 | "columns": ["target_scope_id"], |
| 1135 | "target_scope_id" | ||
| 1136 | ], | ||
| 1137 | "isUnique": false | 1039 | "isUnique": false |
| 1138 | }, | 1040 | }, |
| 1139 | "openapi_source_binding_slot_idx": { | 1041 | "openapi_source_binding_slot_idx": { |
| 1140 | "name": "openapi_source_binding_slot_idx", | 1042 | "name": "openapi_source_binding_slot_idx", |
| 1141 | "columns": [ | 1043 | "columns": ["slot"], |
| 1142 | "slot" | ||
| 1143 | ], | ||
| 1144 | "isUnique": false | 1044 | "isUnique": false |
| 1145 | }, | 1045 | }, |
| 1146 | "openapi_source_binding_secret_id_idx": { | 1046 | "openapi_source_binding_secret_id_idx": { |
| 1147 | "name": "openapi_source_binding_secret_id_idx", | 1047 | "name": "openapi_source_binding_secret_id_idx", |
| 1148 | "columns": [ | 1048 | "columns": ["secret_id"], |
| 1149 | "secret_id" | ||
| 1150 | ], | ||
| 1151 | "isUnique": false | 1049 | "isUnique": false |
| 1152 | }, | 1050 | }, |
| 1153 | "openapi_source_binding_connection_id_idx": { | 1051 | "openapi_source_binding_connection_id_idx": { |
| 1154 | "name": "openapi_source_binding_connection_id_idx", | 1052 | "name": "openapi_source_binding_connection_id_idx", |
| 1155 | "columns": [ | 1053 | "columns": ["connection_id"], |
| 1156 | "connection_id" | ||
| 1157 | ], | ||
| 1158 | "isUnique": false | 1054 | "isUnique": false |
| 1159 | } | 1055 | } |
| 1160 | }, | 1056 | }, |
| @@ -1212,33 +1108,24 @@ · 51 unmodified lines | |||
| 1212 | "indexes": { | 1108 | "indexes": { |
| 1213 | "secret_scope_id_idx": { | 1109 | "secret_scope_id_idx": { |
| 1214 | "name": "secret_scope_id_idx", | 1110 | "name": "secret_scope_id_idx", |
| 1215 | "columns": [ | 1111 | "columns": ["scope_id"], |
| 1216 | "scope_id" | ||
| 1217 | ], | ||
| 1218 | "isUnique": false | 1112 | "isUnique": false |
| 1219 | }, | 1113 | }, |
| 1220 | "secret_provider_idx": { | 1114 | "secret_provider_idx": { |
| 1221 | "name": "secret_provider_idx", | 1115 | "name": "secret_provider_idx", |
| 1222 | "columns": [ | 1116 | "columns": ["provider"], |
| 1223 | "provider" | ||
| 1224 | ], | ||
| 1225 | "isUnique": false | 1117 | "isUnique": false |
| 1226 | }, | 1118 | }, |
| 1227 | "secret_owned_by_connection_id_idx": { | 1119 | "secret_owned_by_connection_id_idx": { |
| 1228 | "name": "secret_owned_by_connection_id_idx", | 1120 | "name": "secret_owned_by_connection_id_idx", |
| 1229 | "columns": [ | 1121 | "columns": ["owned_by_connection_id"], |
| 1230 | "owned_by_connection_id" | ||
| 1231 | ], | ||
| 1232 | "isUnique": false | 1122 | "isUnique": false |
| 1233 | } | 1123 | } |
| 1234 | }, | 1124 | }, |
| 1235 | "foreignKeys": {}, | 1125 | "foreignKeys": {}, |
| 1236 | "compositePrimaryKeys": { | 1126 | "compositePrimaryKeys": { |
| 1237 | "secret_scope_id_id_pk": { | 1127 | "secret_scope_id_id_pk": { |
| 1238 | "columns": [ | 1128 | "columns": ["scope_id", "id"], |
| 1239 | "scope_id", | ||
| 1240 | "id" | ||
| 1241 | ], | ||
| 1242 | "name": "secret_scope_id_id_pk" | 1129 | "name": "secret_scope_id_id_pk" |
| 1243 | } | 1130 | } |
| 1244 | }, | 1131 | }, |
| @@ -1332,26 +1219,19 @@ · 87 unmodified lines | |||
| 1332 | "indexes": { | 1219 | "indexes": { |
| 1333 | "source_scope_id_idx": { | 1220 | "source_scope_id_idx": { |
| 1334 | "name": "source_scope_id_idx", | 1221 | "name": "source_scope_id_idx", |
| 1335 | "columns": [ | 1222 | "columns": ["scope_id"], |
| 1336 | "scope_id" | ||
| 1337 | ], | ||
| 1338 | "isUnique": false | 1223 | "isUnique": false |
| 1339 | }, | 1224 | }, |
| 1340 | "source_plugin_id_idx": { | 1225 | "source_plugin_id_idx": { |
| 1341 | "name": "source_plugin_id_idx", | 1226 | "name": "source_plugin_id_idx", |
| 1342 | "columns": [ | 1227 | "columns": ["plugin_id"], |
| 1343 | "plugin_id" | ||
| 1344 | ], | ||
| 1345 | "isUnique": false | 1228 | "isUnique": false |
| 1346 | } | 1229 | } |
| 1347 | }, | 1230 | }, |
| 1348 | "foreignKeys": {}, | 1231 | "foreignKeys": {}, |
| 1349 | "compositePrimaryKeys": { | 1232 | "compositePrimaryKeys": { |
| 1350 | "source_scope_id_id_pk": { | 1233 | "source_scope_id_id_pk": { |
| 1351 | "columns": [ | 1234 | "columns": ["scope_id", "id"], |
| 1352 | "scope_id", | ||
| 1353 | "id" | ||
| 1354 | ], | ||
| 1355 | "name": "source_scope_id_id_pk" | 1235 | "name": "source_scope_id_id_pk" |
| 1356 | } | 1236 | } |
| 1357 | }, | 1237 | }, |
| @@ -1435,33 +1315,24 @@ · 77 unmodified lines | |||
| 1435 | "indexes": { | 1315 | "indexes": { |
| 1436 | "tool_scope_id_idx": { | 1316 | "tool_scope_id_idx": { |
| 1437 | "name": "tool_scope_id_idx", | 1317 | "name": "tool_scope_id_idx", |
| 1438 | "columns": [ | 1318 | "columns": ["scope_id"], |
| 1439 | "scope_id" | ||
| 1440 | ], | ||
| 1441 | "isUnique": false | 1319 | "isUnique": false |
| 1442 | }, | 1320 | }, |
| 1443 | "tool_source_id_idx": { | 1321 | "tool_source_id_idx": { |
| 1444 | "name": "tool_source_id_idx", | 1322 | "name": "tool_source_id_idx", |
| 1445 | "columns": [ | 1323 | "columns": ["source_id"], |
| 1446 | "source_id" | ||
| 1447 | ], | ||
| 1448 | "isUnique": false | 1324 | "isUnique": false |
| 1449 | }, | 1325 | }, |
| 1450 | "tool_plugin_id_idx": { | 1326 | "tool_plugin_id_idx": { |
| 1451 | "name": "tool_plugin_id_idx", | 1327 | "name": "tool_plugin_id_idx", |
| 1452 | "columns": [ | 1328 | "columns": ["plugin_id"], |
| 1453 | "plugin_id" | ||
| 1454 | ], | ||
| 1455 | "isUnique": false | 1329 | "isUnique": false |
| 1456 | } | 1330 | } |
| 1457 | }, | 1331 | }, |
| 1458 | "foreignKeys": {}, | 1332 | "foreignKeys": {}, |
| 1459 | "compositePrimaryKeys": { | 1333 | "compositePrimaryKeys": { |
| 1460 | "tool_scope_id_id_pk": { | 1334 | "tool_scope_id_id_pk": { |
| 1461 | "columns": [ | 1335 | "columns": ["scope_id", "id"], |
| 1462 | "scope_id", | ||
| 1463 | "id" | ||
| 1464 | ], | ||
| 1465 | "name": "tool_scope_id_id_pk" | 1336 | "name": "tool_scope_id_id_pk" |
| 1466 | } | 1337 | } |
| 1467 | }, | 1338 | }, |
| @@ -1524,20 +1395,14 @@ · 56 unmodified lines | |||
| 1524 | "indexes": { | 1395 | "indexes": { |
| 1525 | "tool_policy_scope_id_position_idx": { | 1396 | "tool_policy_scope_id_position_idx": { |
| 1526 | "name": "tool_policy_scope_id_position_idx", | 1397 | "name": "tool_policy_scope_id_position_idx", |
| 1527 | "columns": [ | 1398 | "columns": ["scope_id", "position"], |
| 1528 | "scope_id", | ||
| 1529 | "position" | ||
| 1530 | ], | ||
| 1531 | "isUnique": false | 1399 | "isUnique": false |
| 1532 | } | 1400 | } |
| 1533 | }, | 1401 | }, |
| 1534 | "foreignKeys": {}, | 1402 | "foreignKeys": {}, |
| 1535 | "compositePrimaryKeys": { | 1403 | "compositePrimaryKeys": { |
| 1536 | "tool_policy_scope_id_id_pk": { | 1404 | "tool_policy_scope_id_id_pk": { |
| 1537 | "columns": [ | 1405 | "columns": ["scope_id", "id"], |
| 1538 | "scope_id", | ||
| 1539 | "id" | ||
| 1540 | ], | ||
| 1541 | "name": "tool_policy_scope_id_id_pk" | 1406 | "name": "tool_policy_scope_id_id_pk" |
| 1542 | } | 1407 | } |
| 1543 | }, | 1408 | }, |
| @@ -1607,33 +1472,24 @@ · 63 unmodified lines | |||
| 1607 | "indexes": { | 1472 | "indexes": { |
| 1608 | "graphql_source_header_scope_id_idx": { | 1473 | "graphql_source_header_scope_id_idx": { |
| 1609 | "name": "graphql_source_header_scope_id_idx", | 1474 | "name": "graphql_source_header_scope_id_idx", |
| 1610 | "columns": [ | 1475 | "columns": ["scope_id"], |
| 1611 | "scope_id" | ||
| 1612 | ], | ||
| 1613 | "isUnique": false | 1476 | "isUnique": false |
| 1614 | }, | 1477 | }, |
| 1615 | "graphql_source_header_source_id_idx": { | 1478 | "graphql_source_header_source_id_idx": { |
| 1616 | "name": "graphql_source_header_source_id_idx", | 1479 | "name": "graphql_source_header_source_id_idx", |
| 1617 | "columns": [ | 1480 | "columns": ["source_id"], |
| 1618 | "source_id" | ||
| 1619 | ], | ||
| 1620 | "isUnique": false | 1481 | "isUnique": false |
| 1621 | }, | 1482 | }, |
| 1622 | "graphql_source_header_secret_id_idx": { | 1483 | "graphql_source_header_secret_id_idx": { |
| 1623 | "name": "graphql_source_header_secret_id_idx", | 1484 | "name": "graphql_source_header_secret_id_idx", |
| 1624 | "columns": [ | 1485 | "columns": ["secret_id"], |
| 1625 | "secret_id" | ||
| 1626 | ], | ||
| 1627 | "isUnique": false | 1486 | "isUnique": false |
| 1628 | } | 1487 | } |
| 1629 | }, | 1488 | }, |
| 1630 | "foreignKeys": {}, | 1489 | "foreignKeys": {}, |
| 1631 | "compositePrimaryKeys": { | 1490 | "compositePrimaryKeys": { |
| 1632 | "graphql_source_header_scope_id_id_pk": { | 1491 | "graphql_source_header_scope_id_id_pk": { |
| 1633 | "columns": [ | 1492 | "columns": ["scope_id", "id"], |
| 1634 | "scope_id", | ||
| 1635 | "id" | ||
| 1636 | ], | ||
| 1637 | "name": "graphql_source_header_scope_id_id_pk" | 1493 | "name": "graphql_source_header_scope_id_id_pk" |
| 1638 | } | 1494 | } |
| 1639 | }, | 1495 | }, |
| @@ -1703,33 +1559,24 @@ · 63 unmodified lines | |||
| 1703 | "indexes": { | 1559 | "indexes": { |
| 1704 | "graphql_source_query_param_scope_id_idx": { | 1560 | "graphql_source_query_param_scope_id_idx": { |
| 1705 | "name": "graphql_source_query_param_scope_id_idx", | 1561 | "name": "graphql_source_query_param_scope_id_idx", |
| 1706 | "columns": [ | 1562 | "columns": ["scope_id"], |
| 1707 | "scope_id" | ||
| 1708 | ], | ||
| 1709 | "isUnique": false | 1563 | "isUnique": false |
| 1710 | }, | 1564 | }, |
| 1711 | "graphql_source_query_param_source_id_idx": { | 1565 | "graphql_source_query_param_source_id_idx": { |
| 1712 | "name": "graphql_source_query_param_source_id_idx", | 1566 | "name": "graphql_source_query_param_source_id_idx", |
| 1713 | "columns": [ | 1567 | "columns": ["source_id"], |
| 1714 | "source_id" | ||
| 1715 | ], | ||
| 1716 | "isUnique": false | 1568 | "isUnique": false |
| 1717 | }, | 1569 | }, |
| 1718 | "graphql_source_query_param_secret_id_idx": { | 1570 | "graphql_source_query_param_secret_id_idx": { |
| 1719 | "name": "graphql_source_query_param_secret_id_idx", | 1571 | "name": "graphql_source_query_param_secret_id_idx", |
| 1720 | "columns": [ | 1572 | "columns": ["secret_id"], |
| 1721 | "secret_id" | ||
| 1722 | ], | ||
| 1723 | "isUnique": false | 1573 | "isUnique": false |
| 1724 | } | 1574 | } |
| 1725 | }, | 1575 | }, |
| 1726 | "foreignKeys": {}, | 1576 | "foreignKeys": {}, |
| 1727 | "compositePrimaryKeys": { | 1577 | "compositePrimaryKeys": { |
| 1728 | "graphql_source_query_param_scope_id_id_pk": { | 1578 | "graphql_source_query_param_scope_id_id_pk": { |
| 1729 | "columns": [ | 1579 | "columns": ["scope_id", "id"], |
| 1730 | "scope_id", | ||
| 1731 | "id" | ||
| 1732 | ], | ||
| 1733 | "name": "graphql_source_query_param_scope_id_id_pk" | 1580 | "name": "graphql_source_query_param_scope_id_id_pk" |
| 1734 | } | 1581 | } |
| 1735 | }, | 1582 | }, |
| @@ -1799,33 +1646,24 @@ · 63 unmodified lines | |||
| 1799 | "indexes": { | 1646 | "indexes": { |
| 1800 | "openapi_source_query_param_scope_id_idx": { | 1647 | "openapi_source_query_param_scope_id_idx": { |
| 1801 | "name": "openapi_source_query_param_scope_id_idx", | 1648 | "name": "openapi_source_query_param_scope_id_idx", |
| 1802 | "columns": [ | 1649 | "columns": ["scope_id"], |
| 1803 | "scope_id" | ||
| 1804 | ], | ||
| 1805 | "isUnique": false | 1650 | "isUnique": false |
| 1806 | }, | 1651 | }, |
| 1807 | "openapi_source_query_param_source_id_idx": { | 1652 | "openapi_source_query_param_source_id_idx": { |
| 1808 | "name": "openapi_source_query_param_source_id_idx", | 1653 | "name": "openapi_source_query_param_source_id_idx", |
| 1809 | "columns": [ | 1654 | "columns": ["source_id"], |
| 1810 | "source_id" | ||
| 1811 | ], | ||
| 1812 | "isUnique": false | 1655 | "isUnique": false |
| 1813 | }, | 1656 | }, |
| 1814 | "openapi_source_query_param_secret_id_idx": { | 1657 | "openapi_source_query_param_secret_id_idx": { |
| 1815 | "name": "openapi_source_query_param_secret_id_idx", | 1658 | "name": "openapi_source_query_param_secret_id_idx", |
| 1816 | "columns": [ | 1659 | "columns": ["secret_id"], |
| 1817 | "secret_id" | ||
| 1818 | ], | ||
| 1819 | "isUnique": false | 1660 | "isUnique": false |
| 1820 | } | 1661 | } |
| 1821 | }, | 1662 | }, |
| 1822 | "foreignKeys": {}, | 1663 | "foreignKeys": {}, |
| 1823 | "compositePrimaryKeys": { | 1664 | "compositePrimaryKeys": { |
| 1824 | "openapi_source_query_param_scope_id_id_pk": { | 1665 | "openapi_source_query_param_scope_id_id_pk": { |
| 1825 | "columns": [ | 1666 | "columns": ["scope_id", "id"], |
| 1826 | "scope_id", | ||
| 1827 | "id" | ||
| 1828 | ], | ||
| 1829 | "name": "openapi_source_query_param_scope_id_id_pk" | 1667 | "name": "openapi_source_query_param_scope_id_id_pk" |
| 1830 | } | 1668 | } |
| 1831 | }, | 1669 | }, |
| @@ -1895,33 +1733,24 @@ · 63 unmodified lines | |||
| 1895 | "indexes": { | 1733 | "indexes": { |
| 1896 | "openapi_source_spec_fetch_header_scope_id_idx": { | 1734 | "openapi_source_spec_fetch_header_scope_id_idx": { |
| 1897 | "name": "openapi_source_spec_fetch_header_scope_id_idx", | 1735 | "name": "openapi_source_spec_fetch_header_scope_id_idx", |
| 1898 | "columns": [ | 1736 | "columns": ["scope_id"], |
| 1899 | "scope_id" | ||
| 1900 | ], | ||
| 1901 | "isUnique": false | 1737 | "isUnique": false |
| 1902 | }, | 1738 | }, |
| 1903 | "openapi_source_spec_fetch_header_source_id_idx": { | 1739 | "openapi_source_spec_fetch_header_source_id_idx": { |
| 1904 | "name": "openapi_source_spec_fetch_header_source_id_idx", | 1740 | "name": "openapi_source_spec_fetch_header_source_id_idx", |
| 1905 | "columns": [ | 1741 | "columns": ["source_id"], |
| 1906 | "source_id" | ||
| 1907 | ], | ||
| 1908 | "isUnique": false | 1742 | "isUnique": false |
| 1909 | }, | 1743 | }, |
| 1910 | "openapi_source_spec_fetch_header_secret_id_idx": { | 1744 | "openapi_source_spec_fetch_header_secret_id_idx": { |
| 1911 | "name": "openapi_source_spec_fetch_header_secret_id_idx", | 1745 | "name": "openapi_source_spec_fetch_header_secret_id_idx", |
| 1912 | "columns": [ | 1746 | "columns": ["secret_id"], |
| 1913 | "secret_id" | ||
| 1914 | ], | ||
| 1915 | "isUnique": false | 1747 | "isUnique": false |
| 1916 | } | 1748 | } |
| 1917 | }, | 1749 | }, |
| 1918 | "foreignKeys": {}, | 1750 | "foreignKeys": {}, |
| 1919 | "compositePrimaryKeys": { | 1751 | "compositePrimaryKeys": { |
| 1920 | "openapi_source_spec_fetch_header_scope_id_id_pk": { | 1752 | "openapi_source_spec_fetch_header_scope_id_id_pk": { |
| 1921 | "columns": [ | 1753 | "columns": ["scope_id", "id"], |
| 1922 | "scope_id", | ||
| 1923 | "id" | ||
| 1924 | ], | ||
| 1925 | "name": "openapi_source_spec_fetch_header_scope_id_id_pk" | 1754 | "name": "openapi_source_spec_fetch_header_scope_id_id_pk" |
| 1926 | } | 1755 | } |
| 1927 | }, | 1756 | }, |
| @@ -1991,33 +1820,24 @@ · 63 unmodified lines | |||
| 1991 | "indexes": { | 1820 | "indexes": { |
| 1992 | "openapi_source_spec_fetch_query_param_scope_id_idx": { | 1821 | "openapi_source_spec_fetch_query_param_scope_id_idx": { |
| 1993 | "name": "openapi_source_spec_fetch_query_param_scope_id_idx", | 1822 | "name": "openapi_source_spec_fetch_query_param_scope_id_idx", |
| 1994 | "columns": [ | 1823 | "columns": ["scope_id"], |
| 1995 | "scope_id" | ||
| 1996 | ], | ||
| 1997 | "isUnique": false | 1824 | "isUnique": false |
| 1998 | }, | 1825 | }, |
| 1999 | "openapi_source_spec_fetch_query_param_source_id_idx": { | 1826 | "openapi_source_spec_fetch_query_param_source_id_idx": { |
| 2000 | "name": "openapi_source_spec_fetch_query_param_source_id_idx", | 1827 | "name": "openapi_source_spec_fetch_query_param_source_id_idx", |
| 2001 | "columns": [ | 1828 | "columns": ["source_id"], |
| 2002 | "source_id" | ||
| 2003 | ], | ||
| 2004 | "isUnique": false | 1829 | "isUnique": false |
| 2005 | }, | 1830 | }, |
| 2006 | "openapi_source_spec_fetch_query_param_secret_id_idx": { | 1831 | "openapi_source_spec_fetch_query_param_secret_id_idx": { |
| 2007 | "name": "openapi_source_spec_fetch_query_param_secret_id_idx", | 1832 | "name": "openapi_source_spec_fetch_query_param_secret_id_idx", |
| 2008 | "columns": [ | 1833 | "columns": ["secret_id"], |
| 2009 | "secret_id" | ||
| 2010 | ], | ||
| 2011 | "isUnique": false | 1834 | "isUnique": false |
| 2012 | } | 1835 | } |
| 2013 | }, | 1836 | }, |
| 2014 | "foreignKeys": {}, | 1837 | "foreignKeys": {}, |
| 2015 | "compositePrimaryKeys": { | 1838 | "compositePrimaryKeys": { |
| 2016 | "openapi_source_spec_fetch_query_param_scope_id_id_pk": { | 1839 | "openapi_source_spec_fetch_query_param_scope_id_id_pk": { |
| 2017 | "columns": [ | 1840 | "columns": ["scope_id", "id"], |
| 2018 | "scope_id", | ||
| 2019 | "id" | ||
| 2020 | ], | ||
| 2021 | "name": "openapi_source_spec_fetch_query_param_scope_id_id_pk" | 1841 | "name": "openapi_source_spec_fetch_query_param_scope_id_id_pk" |
| 2022 | } | 1842 | } |
| 2023 | }, | 1843 | }, |
| @@ -2087,33 +1907,24 @@ · 63 unmodified lines | |||
| 2087 | "indexes": { | 1907 | "indexes": { |
| 2088 | "mcp_source_header_scope_id_idx": { | 1908 | "mcp_source_header_scope_id_idx": { |
| 2089 | "name": "mcp_source_header_scope_id_idx", | 1909 | "name": "mcp_source_header_scope_id_idx", |
| 2090 | "columns": [ | 1910 | "columns": ["scope_id"], |
| 2091 | "scope_id" | ||
| 2092 | ], | ||
| 2093 | "isUnique": false | 1911 | "isUnique": false |
| 2094 | }, | 1912 | }, |
| 2095 | "mcp_source_header_source_id_idx": { | 1913 | "mcp_source_header_source_id_idx": { |
| 2096 | "name": "mcp_source_header_source_id_idx", | 1914 | "name": "mcp_source_header_source_id_idx", |
| 2097 | "columns": [ | 1915 | "columns": ["source_id"], |
| 2098 | "source_id" | ||
| 2099 | ], | ||
| 2100 | "isUnique": false | 1916 | "isUnique": false |
| 2101 | }, | 1917 | }, |
| 2102 | "mcp_source_header_secret_id_idx": { | 1918 | "mcp_source_header_secret_id_idx": { |
| 2103 | "name": "mcp_source_header_secret_id_idx", | 1919 | "name": "mcp_source_header_secret_id_idx", |
| 2104 | "columns": [ | 1920 | "columns": ["secret_id"], |
| 2105 | "secret_id" | ||
| 2106 | ], | ||
| 2107 | "isUnique": false | 1921 | "isUnique": false |
| 2108 | } | 1922 | } |
| 2109 | }, | 1923 | }, |
| 2110 | "foreignKeys": {}, | 1924 | "foreignKeys": {}, |
| 2111 | "compositePrimaryKeys": { | 1925 | "compositePrimaryKeys": { |
| 2112 | "mcp_source_header_scope_id_id_pk": { | 1926 | "mcp_source_header_scope_id_id_pk": { |
| 2113 | "columns": [ | 1927 | "columns": ["scope_id", "id"], |
| 2114 | "scope_id", | ||
| 2115 | "id" | ||
| 2116 | ], | ||
| 2117 | "name": "mcp_source_header_scope_id_id_pk" | 1928 | "name": "mcp_source_header_scope_id_id_pk" |
| 2118 | } | 1929 | } |
| 2119 | }, | 1930 | }, |
| @@ -2183,33 +1994,24 @@ · 63 unmodified lines | |||
| 2183 | "indexes": { | 1994 | "indexes": { |
| 2184 | "mcp_source_query_param_scope_id_idx": { | 1995 | "mcp_source_query_param_scope_id_idx": { |
| 2185 | "name": "mcp_source_query_param_scope_id_idx", | 1996 | "name": "mcp_source_query_param_scope_id_idx", |
| 2186 | "columns": [ | 1997 | "columns": ["scope_id"], |
| 2187 | "scope_id" | ||
| 2188 | ], | ||
| 2189 | "isUnique": false | 1998 | "isUnique": false |
| 2190 | }, | 1999 | }, |
| 2191 | "mcp_source_query_param_source_id_idx": { | 2000 | "mcp_source_query_param_source_id_idx": { |
| 2192 | "name": "mcp_source_query_param_source_id_idx", | 2001 | "name": "mcp_source_query_param_source_id_idx", |
| 2193 | "columns": [ | 2002 | "columns": ["source_id"], |
| 2194 | "source_id" | ||
| 2195 | ], | ||
| 2196 | "isUnique": false | 2003 | "isUnique": false |
| 2197 | }, | 2004 | }, |
| 2198 | "mcp_source_query_param_secret_id_idx": { | 2005 | "mcp_source_query_param_secret_id_idx": { |
| 2199 | "name": "mcp_source_query_param_secret_id_idx", | 2006 | "name": "mcp_source_query_param_secret_id_idx", |
| 2200 | "columns": [ | 2007 | "columns": ["secret_id"], |
| 2201 | "secret_id" | ||
| 2202 | ], | ||
| 2203 | "isUnique": false | 2008 | "isUnique": false |
| 2204 | } | 2009 | } |
| 2205 | }, | 2010 | }, |
| 2206 | "foreignKeys": {}, | 2011 | "foreignKeys": {}, |
| 2207 | "compositePrimaryKeys": { | 2012 | "compositePrimaryKeys": { |
| 2208 | "mcp_source_query_param_scope_id_id_pk": { | 2013 | "mcp_source_query_param_scope_id_id_pk": { |
| 2209 | "columns": [ | 2014 | "columns": ["scope_id", "id"], |
| 2210 | "scope_id", | ||
| 2211 | "id" | ||
| 2212 | ], | ||
| 2213 | "name": "mcp_source_query_param_scope_id_id_pk" | 2015 | "name": "mcp_source_query_param_scope_id_id_pk" |
| 2214 | } | 2016 | } |
| 2215 | }, | 2017 | }, |
| @@ -2279,33 +2081,24 @@ · 63 unmodified lines | |||
| 2279 | "indexes": { | 2081 | "indexes": { |
| 2280 | "google_discovery_source_credential_header_scope_id_idx": { | 2082 | "google_discovery_source_credential_header_scope_id_idx": { |
| 2281 | "name": "google_discovery_source_credential_header_scope_id_idx", | 2083 | "name": "google_discovery_source_credential_header_scope_id_idx", |
| 2282 | "columns": [ | 2084 | "columns": ["scope_id"], |
| 2283 | "scope_id" | ||
| 2284 | ], | ||
| 2285 | "isUnique": false | 2085 | "isUnique": false |
| 2286 | }, | 2086 | }, |
| 2287 | "google_discovery_source_credential_header_source_id_idx": { | 2087 | "google_discovery_source_credential_header_source_id_idx": { |
| 2288 | "name": "google_discovery_source_credential_header_source_id_idx", | 2088 | "name": "google_discovery_source_credential_header_source_id_idx", |
| 2289 | "columns": [ | 2089 | "columns": ["source_id"], |
| 2290 | "source_id" | ||
| 2291 | ], | ||
| 2292 | "isUnique": false | 2090 | "isUnique": false |
| 2293 | }, | 2091 | }, |
| 2294 | "google_discovery_source_credential_header_secret_id_idx": { | 2092 | "google_discovery_source_credential_header_secret_id_idx": { |
| 2295 | "name": "google_discovery_source_credential_header_secret_id_idx", | 2093 | "name": "google_discovery_source_credential_header_secret_id_idx", |
| 2296 | "columns": [ | 2094 | "columns": ["secret_id"], |
| 2297 | "secret_id" | ||
| 2298 | ], | ||
| 2299 | "isUnique": false | 2095 | "isUnique": false |
| 2300 | } | 2096 | } |
| 2301 | }, | 2097 | }, |
| 2302 | "foreignKeys": {}, | 2098 | "foreignKeys": {}, |
| 2303 | "compositePrimaryKeys": { | 2099 | "compositePrimaryKeys": { |
| 2304 | "google_discovery_source_credential_header_scope_id_id_pk": { | 2100 | "google_discovery_source_credential_header_scope_id_id_pk": { |
| 2305 | "columns": [ | 2101 | "columns": ["scope_id", "id"], |
| 2306 | "scope_id", | ||
| 2307 | "id" | ||
| 2308 | ], | ||
| 2309 | "name": "google_discovery_source_credential_header_scope_id_id_pk" | 2102 | "name": "google_discovery_source_credential_header_scope_id_id_pk" |
| 2310 | } | 2103 | } |
| 2311 | }, | 2104 | }, |
| @@ -2375,33 +2168,24 @@ · 63 unmodified lines | |||
| 2375 | "indexes": { | 2168 | "indexes": { |
| 2376 | "google_discovery_source_credential_query_param_scope_id_idx": { | 2169 | "google_discovery_source_credential_query_param_scope_id_idx": { |
| 2377 | "name": "google_discovery_source_credential_query_param_scope_id_idx", | 2170 | "name": "google_discovery_source_credential_query_param_scope_id_idx", |
| 2378 | "columns": [ | 2171 | "columns": ["scope_id"], |
| 2379 | "scope_id" | ||
| 2380 | ], | ||
| 2381 | "isUnique": false | 2172 | "isUnique": false |
| 2382 | }, | 2173 | }, |
| 2383 | "google_discovery_source_credential_query_param_source_id_idx": { | 2174 | "google_discovery_source_credential_query_param_source_id_idx": { |
| 2384 | "name": "google_discovery_source_credential_query_param_source_id_idx", | 2175 | "name": "google_discovery_source_credential_query_param_source_id_idx", |
| 2385 | "columns": [ | 2176 | "columns": ["source_id"], |
| 2386 | "source_id" | ||
| 2387 | ], | ||
| 2388 | "isUnique": false | 2177 | "isUnique": false |
| 2389 | }, | 2178 | }, |
| 2390 | "google_discovery_source_credential_query_param_secret_id_idx": { | 2179 | "google_discovery_source_credential_query_param_secret_id_idx": { |
| 2391 | "name": "google_discovery_source_credential_query_param_secret_id_idx", | 2180 | "name": "google_discovery_source_credential_query_param_secret_id_idx", |
| 2392 | "columns": [ | 2181 | "columns": ["secret_id"], |
| 2393 | "secret_id" | ||
| 2394 | ], | ||
| 2395 | "isUnique": false | 2182 | "isUnique": false |
| 2396 | } | 2183 | } |
| 2397 | }, | 2184 | }, |
| 2398 | "foreignKeys": {}, | 2185 | "foreignKeys": {}, |
| 2399 | "compositePrimaryKeys": { | 2186 | "compositePrimaryKeys": { |
| 2400 | "google_discovery_source_credential_query_param_scope_id_id_pk": { | 2187 | "google_discovery_source_credential_query_param_scope_id_id_pk": { |
| 2401 | "columns": [ | 2188 | "columns": ["scope_id", "id"], |
| 2402 | "scope_id", | ||
| 2403 | "id" | ||
| 2404 | ], | ||
| 2405 | "name": "google_discovery_source_credential_query_param_scope_id_id_pk" | 2189 | "name": "google_discovery_source_credential_query_param_scope_id_id_pk" |
| 2406 | } | 2190 | } |
| 2407 | }, | 2191 | }, |
| @@ -30,7 +30,10 @@ const startTestServer = async (): Promise<string> => { · 29 unmodified lines | |||
| 30 | beforeEach(() => { | 30 | beforeEach(() => { |
| 31 | clientDir = mkdtempSync(join(tmpdir(), "exec-local-serve-")); | 31 | clientDir = mkdtempSync(join(tmpdir(), "exec-local-serve-")); |
| 32 | mkdirSync(join(clientDir, "assets"), { recursive: true }); | 32 | mkdirSync(join(clientDir, "assets"), { recursive: true }); |
| 33 | writeFileSync(join(clientDir, "index.html"), "<!doctype html><html><body>index-shell</body></html>"); | 33 | writeFileSync( |
| 34 | join(clientDir, "index.html"), | ||
| 35 | "<!doctype html><html><body>index-shell</body></html>", | ||
| 36 | ); | ||
| 34 | writeFileSync(join(clientDir, "assets", "app.js"), "console.log('ok')"); | 37 | writeFileSync(join(clientDir, "assets", "app.js"), "console.log('ok')"); |
| 35 | }); | 38 | }); |
| 36 | 39 | ||
| @@ -17,12 +17,14 @@ import { getServerHandlers } from "./server/main"; · 16 unmodified lines | |||
| 17 | 17 | ||
| 18 | const DEFAULT_ALLOWED_HOSTS = ["localhost", "127.0.0.1", "[::1]", "::1"]; | 18 | const DEFAULT_ALLOWED_HOSTS = ["localhost", "127.0.0.1", "[::1]", "::1"]; |
| 19 | 19 | ||
| 20 | const makeIsAllowedHost = (allowed: ReadonlySet<string>) => (request: Request): boolean => { | 20 | const makeIsAllowedHost = |
| 21 | const host = request.headers.get("host"); | 21 | (allowed: ReadonlySet<string>) => |
| 22 | if (!host) return true; | 22 | (request: Request): boolean => { |
| 23 | const hostname = host.replace(/:\d+$/, ""); | 23 | const host = request.headers.get("host"); |
| 24 | return allowed.has(hostname); | 24 | if (!host) return true; |
| 25 | }; | 25 | const hostname = host.replace(/:\d+$/, ""); |
| 26 | return allowed.has(hostname); | ||
| 27 | }; | ||
| 26 | 28 | ||
| 27 | // --------------------------------------------------------------------------- | 29 | // --------------------------------------------------------------------------- |
| 28 | // Static files | 30 | // Static files |
| @@ -10,11 +10,7 @@ import { join } from "node:path"; · 9 unmodified lines | |||
| 10 | import * as fs from "node:fs"; | 10 | import * as fs from "node:fs"; |
| 11 | import * as jsonc from "jsonc-parser"; | 11 | import * as jsonc from "jsonc-parser"; |
| 12 | 12 | ||
| 13 | import type { | 13 | import type { SourceConfig, ExecutorFileConfig, ConfigHeaderValue } from "@executor-js/config"; |
| 14 | SourceConfig, | ||
| 15 | ExecutorFileConfig, | ||
| 16 | ConfigHeaderValue, | ||
| 17 | } from "@executor-js/config"; | ||
| 18 | import { SECRET_REF_PREFIX } from "@executor-js/config"; | 14 | import { SECRET_REF_PREFIX } from "@executor-js/config"; |
| 19 | 15 | ||
| 20 | import type { LocalExecutor } from "./executor"; | 16 | import type { LocalExecutor } from "./executor"; |
| @@ -57,8 +53,7 @@ const translateHeaders = ( · 36 unmodified lines | |||
| 57 | // Config path resolution | 53 | // Config path resolution |
| 58 | // --------------------------------------------------------------------------- | 54 | // --------------------------------------------------------------------------- |
| 59 | 55 | ||
| 60 | export const resolveConfigPath = (scopeDir: string): string => | 56 | export const resolveConfigPath = (scopeDir: string): string => join(scopeDir, "executor.jsonc"); |
| 61 | join(scopeDir, "executor.jsonc"); | ||
| 62 | 57 | ||
| 63 | // --------------------------------------------------------------------------- | 58 | // --------------------------------------------------------------------------- |
| 64 | // Load config (sync, no Effect deps — runs at startup) | 59 | // Load config (sync, no Effect deps — runs at startup) |
| @@ -91,45 +86,53 @@ const addSourceFromConfig = ( · 26 unmodified lines | |||
| 91 | const scope = executor.scopes.at(-1)!.id; | 86 | const scope = executor.scopes.at(-1)!.id; |
| 92 | switch (source.kind) { | 87 | switch (source.kind) { |
| 93 | case "openapi": | 88 | case "openapi": |
| 94 | return executor.openapi.addSpec({ | 89 | return executor.openapi |
| 95 | spec: source.spec, | 90 | .addSpec({ |
| 96 | scope, | 91 | spec: source.spec, |
| 97 | baseUrl: source.baseUrl, | 92 | scope, |
| 98 | namespace: source.namespace, | 93 | baseUrl: source.baseUrl, |
| 99 | headers: translateHeaders(source.headers), | 94 | namespace: source.namespace, |
| 100 | }).pipe(Effect.asVoid); | 95 | headers: translateHeaders(source.headers), |
| 96 | }) | ||
| 97 | .pipe(Effect.asVoid); | ||
| 101 | 98 | ||
| 102 | case "graphql": | 99 | case "graphql": |
| 103 | return executor.graphql.addSource({ | 100 | return executor.graphql |
| 104 | endpoint: source.endpoint, | 101 | .addSource({ |
| 105 | scope, | 102 | endpoint: source.endpoint, |
| 106 | namespace: source.namespace, | 103 | scope, |
| 107 | headers: translateHeaders(source.headers) as Record<string, string> | undefined, | 104 | namespace: source.namespace, |
| 108 | }).pipe(Effect.asVoid); | 105 | headers: translateHeaders(source.headers) as Record<string, string> | undefined, |
| 106 | }) | ||
| 107 | .pipe(Effect.asVoid); | ||
| 109 | 108 | ||
| 110 | case "mcp": | 109 | case "mcp": |
| 111 | if (source.transport === "stdio") { | 110 | if (source.transport === "stdio") { |
| 112 | return executor.mcp.addSource({ | 111 | return executor.mcp |
| 113 | transport: "stdio", | 112 | .addSource({ |
| 113 | transport: "stdio", | ||
| 114 | scope, | ||
| 115 | name: source.name, | ||
| 116 | command: source.command, | ||
| 117 | args: source.args ? [...source.args] : undefined, | ||
| 118 | env: source.env, | ||
| 119 | cwd: source.cwd, | ||
| 120 | namespace: source.namespace, | ||
| 121 | }) | ||
| 122 | .pipe(Effect.asVoid); | ||
| 123 | } | ||
| 124 | return executor.mcp | ||
| 125 | .addSource({ | ||
| 126 | transport: "remote", | ||
| 114 | scope, | 127 | scope, |
| 115 | name: source.name, | 128 | name: source.name, |
| 116 | command: source.command, | 129 | endpoint: source.endpoint, |
| 117 | args: source.args ? [...source.args] : undefined, | 130 | remoteTransport: source.remoteTransport, |
| 118 | env: source.env, | 131 | queryParams: source.queryParams, |
| 119 | cwd: source.cwd, | 132 | headers: source.headers, |
| 120 | namespace: source.namespace, | 133 | namespace: source.namespace, |
| 121 | }).pipe(Effect.asVoid); | 134 | }) |
| 122 | } | 135 | .pipe(Effect.asVoid); |
| 123 | return executor.mcp.addSource({ | ||
| 124 | transport: "remote", | ||
| 125 | scope, | ||
| 126 | name: source.name, | ||
| 127 | endpoint: source.endpoint, | ||
| 128 | remoteTransport: source.remoteTransport, | ||
| 129 | queryParams: source.queryParams, | ||
| 130 | headers: source.headers, | ||
| 131 | namespace: source.namespace, | ||
| 132 | }).pipe(Effect.asVoid); | ||
| 133 | } | 136 | } |
| 134 | }; | 137 | }; |
| 135 | 138 | ||
| @@ -137,10 +140,7 @@ const addSourceFromConfig = ( · 1 unmodified line | |||
| 137 | * Read executor.jsonc and replay all sources into the executor. | 140 | * Read executor.jsonc and replay all sources into the executor. |
| 138 | * Each source is added independently — if one fails, the rest still load. | 141 | * Each source is added independently — if one fails, the rest still load. |
| 139 | */ | 142 | */ |
| 140 | export const syncFromConfig = ( | 143 | export const syncFromConfig = (executor: LocalExecutor, configPath: string): Effect.Effect<void> => |
| 141 | executor: LocalExecutor, | ||
| 142 | configPath: string, | ||
| 143 | ): Effect.Effect<void> => | ||
| 144 | Effect.gen(function* () { | 144 | Effect.gen(function* () { |
| 145 | const config = loadConfigSync(configPath); | 145 | const config = loadConfigSync(configPath); |
| 146 | if (!config?.sources?.length) { | 146 | if (!config?.sources?.length) { |
| @@ -148,9 +148,7 @@ export const syncFromConfig = ( · 1 unmodified line | |||
| 148 | return; | 148 | return; |
| 149 | } | 149 | } |
| 150 | 150 | ||
| 151 | console.log( | 151 | console.log(`[config-sync] syncing ${config.sources.length} source(s) from ${configPath}`); |
| 152 | `[config-sync] syncing ${config.sources.length} source(s) from ${configPath}`, | ||
| 153 | ); | ||
| 154 | 152 | ||
| 155 | const results = yield* Effect.forEach( | 153 | const results = yield* Effect.forEach( |
| 156 | config.sources, | 154 | config.sources, |
| @@ -158,11 +156,9 @@ export const syncFromConfig = ( · 1 unmodified line | |||
| 158 | addSourceFromConfig(executor, source).pipe( | 156 | addSourceFromConfig(executor, source).pipe( |
| 159 | Effect.map(() => true as const), | 157 | Effect.map(() => true as const), |
| 160 | Effect.catchCause((cause) => { | 158 | Effect.catchCause((cause) => { |
| 161 | const ns = "namespace" in source ? source.namespace : ("name" in source ? source.name : "unknown"); | 159 | const ns = |
| 162 | console.warn( | 160 | "namespace" in source ? source.namespace : "name" in source ? source.name : "unknown"; |
| 163 | `[config-sync] Failed to load source "${ns}":`, | 161 | console.warn(`[config-sync] Failed to load source "${ns}":`, Cause.pretty(cause)); |
| 164 | Cause.pretty(cause), | ||
| 165 | ); | ||
| 166 | return Effect.succeed(false as const); | 162 | return Effect.succeed(false as const); |
| 167 | }), | 163 | }), |
| 168 | ), | 164 | ), |
| @@ -156,9 +156,9 @@ describe("move-aside + fresh migrate end-to-end", () => { · 155 unmodified lines | |||
| 156 | migrationsFolder: join(__dirname, "../../drizzle"), | 156 | migrationsFolder: join(__dirname, "../../drizzle"), |
| 157 | }); | 157 | }); |
| 158 | // migrate() should have produced the new schema — source now has scope_id. | 158 | // migrate() should have produced the new schema — source now has scope_id. |
| 159 | const cols = db | 159 | const cols = db.prepare("PRAGMA table_info('source')").all() as ReadonlyArray<{ |
| 160 | .prepare("PRAGMA table_info('source')") | 160 | readonly name: string; |
| 161 | .all() as ReadonlyArray<{ readonly name: string }>; | 161 | }>; |
| 162 | expect(cols.some((c) => c.name === "scope_id")).toBe(true); | 162 | expect(cols.some((c) => c.name === "scope_id")).toBe(true); |
| 163 | db.close(); | 163 | db.close(); |
| 164 | }); | 164 | }); |
| @@ -169,12 +169,18 @@ describe("readLegacySecrets", () => { · 4 unmodified lines | |||
| 169 | const path = join(workDir, "data.db"); | 169 | const path = join(workDir, "data.db"); |
| 170 | seed(path, PRE_SCOPE_SCHEMA); | 170 | seed(path, PRE_SCOPE_SCHEMA); |
| 171 | const db = new Database(path); | 171 | const db = new Database(path); |
| 172 | db.prepare( | 172 | db.prepare("INSERT INTO secret (id, name, provider, created_at) VALUES (?, ?, ?, ?)").run( |
| 173 | "INSERT INTO secret (id, name, provider, created_at) VALUES (?, ?, ?, ?)", | 173 | "sec_1", |
| 174 | ).run("sec_1", "GitHub Token", "onepassword", 1700000000); | 174 | "GitHub Token", |
| 175 | db.prepare( | 175 | "onepassword", |
| 176 | "INSERT INTO secret (id, name, provider, created_at) VALUES (?, ?, ?, ?)", | 176 | 1700000000, |
| 177 | ).run("sec_2", "Stripe", "keychain", 1700000001); | 177 | ); |
| 178 | db.prepare("INSERT INTO secret (id, name, provider, created_at) VALUES (?, ?, ?, ?)").run( | ||
| 179 | "sec_2", | ||
| 180 | "Stripe", | ||
| 181 | "keychain", | ||
| 182 | 1700000001, | ||
| 183 | ); | ||
| 178 | db.close(); | 184 | db.close(); |
| 179 | 185 | ||
| 180 | const rows = readLegacySecrets(path); | 186 | const rows = readLegacySecrets(path); |
| @@ -248,9 +254,7 @@ describe("importLegacySecrets", () => { · 67 unmodified lines | |||
| 248 | it("uses INSERT OR IGNORE so a second import of the same ids is a no-op", () => { | 254 | it("uses INSERT OR IGNORE so a second import of the same ids is a no-op", () => { |
| 249 | const path = join(workDir, "data.db"); | 255 | const path = join(workDir, "data.db"); |
| 250 | const db = createScopedDb(path); | 256 | const db = createScopedDb(path); |
| 251 | const rows = [ | 257 | const rows = [{ id: "sec_1", name: "GH", provider: "onepassword", createdAt: 1 }]; |
| 252 | { id: "sec_1", name: "GH", provider: "onepassword", createdAt: 1 }, | ||
| 253 | ]; | ||
| 254 | importLegacySecrets(db, "scope_a", rows); | 258 | importLegacySecrets(db, "scope_a", rows); |
| 255 | // If the user's already re-registered the secret via a different | 259 | // If the user's already re-registered the secret via a different |
| 256 | // provider, the legacy row must NOT clobber it. | 260 | // provider, the legacy row must NOT clobber it. |
| @@ -27,9 +27,9 @@ export const isPreScopeSchema = (dbPath: string): boolean => { · 26 unmodified lines | |||
| 27 | .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='source'") | 27 | .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='source'") |
| 28 | .get(); | 28 | .get(); |
| 29 | if (!tableExists) return false; | 29 | if (!tableExists) return false; |
| 30 | const columns = db | 30 | const columns = db.prepare("PRAGMA table_info('source')").all() as ReadonlyArray<{ |
| 31 | .prepare("PRAGMA table_info('source')") | 31 | readonly name: string; |
| 32 | .all() as ReadonlyArray<{ readonly name: string }>; | 32 | }>; |
| 33 | return !columns.some((c) => c.name === "scope_id"); | 33 | return !columns.some((c) => c.name === "scope_id"); |
| 34 | } finally { | 34 | } finally { |
| 35 | db.close(); | 35 | db.close(); |
| @@ -1,396 +1,505 @@ | |||
| 1 | import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core"; | 1 | import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core"; |
| 2 | 2 | ||
| 3 | export const source = sqliteTable("source", { | 3 | export const source = sqliteTable( |
| 4 | id: text('id').notNull(), | 4 | "source", |
| 5 | scope_id: text('scope_id').notNull(), | 5 | { |
| 6 | plugin_id: text('plugin_id').notNull(), | 6 | id: text("id").notNull(), |
| 7 | kind: text('kind').notNull(), | 7 | scope_id: text("scope_id").notNull(), |
| 8 | name: text('name').notNull(), | 8 | plugin_id: text("plugin_id").notNull(), |
| 9 | url: text('url'), | 9 | kind: text("kind").notNull(), |
| 10 | can_remove: integer('can_remove', { mode: 'boolean' }).default(true).notNull(), | 10 | name: text("name").notNull(), |
| 11 | can_refresh: integer('can_refresh', { mode: 'boolean' }).default(false).notNull(), | 11 | url: text("url"), |
| 12 | can_edit: integer('can_edit', { mode: 'boolean' }).default(false).notNull(), | 12 | can_remove: integer("can_remove", { mode: "boolean" }).default(true).notNull(), |
| 13 | created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(), | 13 | can_refresh: integer("can_refresh", { mode: "boolean" }).default(false).notNull(), |
| 14 | updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull() | 14 | can_edit: integer("can_edit", { mode: "boolean" }).default(false).notNull(), |
| 15 | }, (table) => [ | 15 | created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), |
| 16 | primaryKey({ columns: [table.scope_id, table.id] }), | 16 | updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(), |
| 17 | index("source_scope_id_idx").on(table.scope_id), | 17 | }, |
| 18 | index("source_plugin_id_idx").on(table.plugin_id), | 18 | (table) => [ |
| 19 | ]); | 19 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 20 | index("source_scope_id_idx").on(table.scope_id), | ||
| 21 | index("source_plugin_id_idx").on(table.plugin_id), | ||
| 22 | ], | ||
| 23 | ); | ||
| 20 | 24 | ||
| 21 | export const tool = sqliteTable("tool", { | 25 | export const tool = sqliteTable( |
| 22 | id: text('id').notNull(), | 26 | "tool", |
| 23 | scope_id: text('scope_id').notNull(), | 27 | { |
| 24 | source_id: text('source_id').notNull(), | 28 | id: text("id").notNull(), |
| 25 | plugin_id: text('plugin_id').notNull(), | 29 | scope_id: text("scope_id").notNull(), |
| 26 | name: text('name').notNull(), | 30 | source_id: text("source_id").notNull(), |
| 27 | description: text('description').notNull(), | 31 | plugin_id: text("plugin_id").notNull(), |
| 28 | input_schema: text('input_schema', { mode: "json" }), | 32 | name: text("name").notNull(), |
| 29 | output_schema: text('output_schema', { mode: "json" }), | 33 | description: text("description").notNull(), |
| 30 | created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(), | 34 | input_schema: text("input_schema", { mode: "json" }), |
| 31 | updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull() | 35 | output_schema: text("output_schema", { mode: "json" }), |
| 32 | }, (table) => [ | 36 | created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), |
| 33 | primaryKey({ columns: [table.scope_id, table.id] }), | 37 | updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(), |
| 34 | index("tool_scope_id_idx").on(table.scope_id), | 38 | }, |
| 35 | index("tool_source_id_idx").on(table.source_id), | 39 | (table) => [ |
| 36 | index("tool_plugin_id_idx").on(table.plugin_id), | 40 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 37 | ]); | 41 | index("tool_scope_id_idx").on(table.scope_id), |
| 42 | index("tool_source_id_idx").on(table.source_id), | ||
| 43 | index("tool_plugin_id_idx").on(table.plugin_id), | ||
| 44 | ], | ||
| 45 | ); | ||
| 38 | 46 | ||
| 39 | export const definition = sqliteTable("definition", { | 47 | export const definition = sqliteTable( |
| 40 | id: text('id').notNull(), | 48 | "definition", |
| 41 | scope_id: text('scope_id').notNull(), | 49 | { |
| 42 | source_id: text('source_id').notNull(), | 50 | id: text("id").notNull(), |
| 43 | plugin_id: text('plugin_id').notNull(), | 51 | scope_id: text("scope_id").notNull(), |
| 44 | name: text('name').notNull(), | 52 | source_id: text("source_id").notNull(), |
| 45 | schema: text('schema', { mode: "json" }).notNull(), | 53 | plugin_id: text("plugin_id").notNull(), |
| 46 | created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull() | 54 | name: text("name").notNull(), |
| 47 | }, (table) => [ | 55 | schema: text("schema", { mode: "json" }).notNull(), |
| 48 | primaryKey({ columns: [table.scope_id, table.id] }), | 56 | created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), |
| 49 | index("definition_scope_id_idx").on(table.scope_id), | 57 | }, |
| 50 | index("definition_source_id_idx").on(table.source_id), | 58 | (table) => [ |
| 51 | index("definition_plugin_id_idx").on(table.plugin_id), | 59 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 52 | ]); | 60 | index("definition_scope_id_idx").on(table.scope_id), |
| 61 | index("definition_source_id_idx").on(table.source_id), | ||
| 62 | index("definition_plugin_id_idx").on(table.plugin_id), | ||
| 63 | ], | ||
| 64 | ); | ||
| 53 | 65 | ||
| 54 | export const secret = sqliteTable("secret", { | 66 | export const secret = sqliteTable( |
| 55 | id: text('id').notNull(), | 67 | "secret", |
| 56 | scope_id: text('scope_id').notNull(), | 68 | { |
| 57 | name: text('name').notNull(), | 69 | id: text("id").notNull(), |
| 58 | provider: text('provider').notNull(), | 70 | scope_id: text("scope_id").notNull(), |
| 59 | owned_by_connection_id: text('owned_by_connection_id'), | 71 | name: text("name").notNull(), |
| 60 | created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull() | 72 | provider: text("provider").notNull(), |
| 61 | }, (table) => [ | 73 | owned_by_connection_id: text("owned_by_connection_id"), |
| 62 | primaryKey({ columns: [table.scope_id, table.id] }), | 74 | created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), |
| 63 | index("secret_scope_id_idx").on(table.scope_id), | 75 | }, |
| 64 | index("secret_provider_idx").on(table.provider), | 76 | (table) => [ |
| 65 | index("secret_owned_by_connection_id_idx").on(table.owned_by_connection_id), | 77 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 66 | ]); | 78 | index("secret_scope_id_idx").on(table.scope_id), |
| 79 | index("secret_provider_idx").on(table.provider), | ||
| 80 | index("secret_owned_by_connection_id_idx").on(table.owned_by_connection_id), | ||
| 81 | ], | ||
| 82 | ); | ||
| 67 | 83 | ||
| 68 | export const connection = sqliteTable("connection", { | 84 | export const connection = sqliteTable( |
| 69 | id: text('id').notNull(), | 85 | "connection", |
| 70 | scope_id: text('scope_id').notNull(), | 86 | { |
| 71 | provider: text('provider').notNull(), | 87 | id: text("id").notNull(), |
| 72 | identity_label: text('identity_label'), | 88 | scope_id: text("scope_id").notNull(), |
| 73 | access_token_secret_id: text('access_token_secret_id').notNull(), | 89 | provider: text("provider").notNull(), |
| 74 | refresh_token_secret_id: text('refresh_token_secret_id'), | 90 | identity_label: text("identity_label"), |
| 75 | expires_at: integer('expires_at'), | 91 | access_token_secret_id: text("access_token_secret_id").notNull(), |
| 76 | scope: text('scope'), | 92 | refresh_token_secret_id: text("refresh_token_secret_id"), |
| 77 | provider_state: text('provider_state', { mode: "json" }), | 93 | expires_at: integer("expires_at"), |
| 78 | created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(), | 94 | scope: text("scope"), |
| 79 | updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull() | 95 | provider_state: text("provider_state", { mode: "json" }), |
| 80 | }, (table) => [ | 96 | created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), |
| 81 | primaryKey({ columns: [table.scope_id, table.id] }), | 97 | updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(), |
| 82 | index("connection_scope_id_idx").on(table.scope_id), | 98 | }, |
| 83 | index("connection_provider_idx").on(table.provider), | 99 | (table) => [ |
| 84 | ]); | 100 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 101 | index("connection_scope_id_idx").on(table.scope_id), | ||
| 102 | index("connection_provider_idx").on(table.provider), | ||
| 103 | ], | ||
| 104 | ); | ||
| 85 | 105 | ||
| 86 | export const oauth2_session = sqliteTable("oauth2_session", { | 106 | export const oauth2_session = sqliteTable( |
| 87 | id: text('id').notNull(), | 107 | "oauth2_session", |
| 88 | scope_id: text('scope_id').notNull(), | 108 | { |
| 89 | plugin_id: text('plugin_id').notNull(), | 109 | id: text("id").notNull(), |
| 90 | strategy: text('strategy').notNull(), | 110 | scope_id: text("scope_id").notNull(), |
| 91 | connection_id: text('connection_id').notNull(), | 111 | plugin_id: text("plugin_id").notNull(), |
| 92 | token_scope: text('token_scope').notNull(), | 112 | strategy: text("strategy").notNull(), |
| 93 | redirect_url: text('redirect_url').notNull(), | 113 | connection_id: text("connection_id").notNull(), |
| 94 | payload: text('payload', { mode: "json" }).notNull(), | 114 | token_scope: text("token_scope").notNull(), |
| 95 | expires_at: integer('expires_at').notNull(), | 115 | redirect_url: text("redirect_url").notNull(), |
| 96 | created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull() | 116 | payload: text("payload", { mode: "json" }).notNull(), |
| 97 | }, (table) => [ | 117 | expires_at: integer("expires_at").notNull(), |
| 98 | primaryKey({ columns: [table.scope_id, table.id] }), | 118 | created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), |
| 99 | index("oauth2_session_scope_id_idx").on(table.scope_id), | 119 | }, |
| 100 | index("oauth2_session_plugin_id_idx").on(table.plugin_id), | 120 | (table) => [ |
| 101 | index("oauth2_session_connection_id_idx").on(table.connection_id), | 121 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 102 | ]); | 122 | index("oauth2_session_scope_id_idx").on(table.scope_id), |
| 123 | index("oauth2_session_plugin_id_idx").on(table.plugin_id), | ||
| 124 | index("oauth2_session_connection_id_idx").on(table.connection_id), | ||
| 125 | ], | ||
| 126 | ); | ||
| 103 | 127 | ||
| 104 | export const tool_policy = sqliteTable("tool_policy", { | 128 | export const tool_policy = sqliteTable( |
| 105 | id: text('id').notNull(), | 129 | "tool_policy", |
| 106 | scope_id: text('scope_id').notNull(), | 130 | { |
| 107 | pattern: text('pattern').notNull(), | 131 | id: text("id").notNull(), |
| 108 | action: text('action').notNull(), | 132 | scope_id: text("scope_id").notNull(), |
| 109 | position: text('position').notNull(), | 133 | pattern: text("pattern").notNull(), |
| 110 | created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(), | 134 | action: text("action").notNull(), |
| 111 | updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull() | 135 | position: text("position").notNull(), |
| 112 | }, (table) => [ | 136 | created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), |
| 113 | primaryKey({ columns: [table.scope_id, table.id] }), | 137 | updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(), |
| 114 | index("tool_policy_scope_id_position_idx").on(table.scope_id, table.position), | 138 | }, |
| 115 | ]); | 139 | (table) => [ |
| 140 | primaryKey({ columns: [table.scope_id, table.id] }), | ||
| 141 | index("tool_policy_scope_id_position_idx").on(table.scope_id, table.position), | ||
| 142 | ], | ||
| 143 | ); | ||
| 116 | 144 | ||
| 117 | export const openapi_source = sqliteTable("openapi_source", { | 145 | export const openapi_source = sqliteTable( |
| 118 | id: text('id').notNull(), | 146 | "openapi_source", |
| 119 | scope_id: text('scope_id').notNull(), | 147 | { |
| 120 | name: text('name').notNull(), | 148 | id: text("id").notNull(), |
| 121 | spec: text('spec').notNull(), | 149 | scope_id: text("scope_id").notNull(), |
| 122 | source_url: text('source_url'), | 150 | name: text("name").notNull(), |
| 123 | base_url: text('base_url'), | 151 | spec: text("spec").notNull(), |
| 124 | headers: text('headers', { mode: "json" }), | 152 | source_url: text("source_url"), |
| 125 | oauth2: text('oauth2', { mode: "json" }) | 153 | base_url: text("base_url"), |
| 126 | }, (table) => [ | 154 | headers: text("headers", { mode: "json" }), |
| 127 | primaryKey({ columns: [table.scope_id, table.id] }), | 155 | oauth2: text("oauth2", { mode: "json" }), |
| 128 | index("openapi_source_scope_id_idx").on(table.scope_id), | 156 | }, |
| 129 | ]); | 157 | (table) => [ |
| 158 | primaryKey({ columns: [table.scope_id, table.id] }), | ||
| 159 | index("openapi_source_scope_id_idx").on(table.scope_id), | ||
| 160 | ], | ||
| 161 | ); | ||
| 130 | 162 | ||
| 131 | export const openapi_operation = sqliteTable("openapi_operation", { | 163 | export const openapi_operation = sqliteTable( |
| 132 | id: text('id').notNull(), | 164 | "openapi_operation", |
| 133 | scope_id: text('scope_id').notNull(), | 165 | { |
| 134 | source_id: text('source_id').notNull(), | 166 | id: text("id").notNull(), |
| 135 | binding: text('binding', { mode: "json" }).notNull() | 167 | scope_id: text("scope_id").notNull(), |
| 136 | }, (table) => [ | 168 | source_id: text("source_id").notNull(), |
| 137 | primaryKey({ columns: [table.scope_id, table.id] }), | 169 | binding: text("binding", { mode: "json" }).notNull(), |
| 138 | index("openapi_operation_scope_id_idx").on(table.scope_id), | 170 | }, |
| 139 | index("openapi_operation_source_id_idx").on(table.source_id), | 171 | (table) => [ |
| 140 | ]); | 172 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 173 | index("openapi_operation_scope_id_idx").on(table.scope_id), | ||
| 174 | index("openapi_operation_source_id_idx").on(table.source_id), | ||
| 175 | ], | ||
| 176 | ); | ||
| 141 | 177 | ||
| 142 | export const openapi_source_binding = sqliteTable("openapi_source_binding", { | 178 | export const openapi_source_binding = sqliteTable( |
| 143 | id: text('id').primaryKey(), | 179 | "openapi_source_binding", |
| 144 | source_id: text('source_id').notNull(), | 180 | { |
| 145 | source_scope_id: text('source_scope_id').notNull(), | 181 | id: text("id").primaryKey(), |
| 146 | target_scope_id: text('target_scope_id').notNull(), | 182 | source_id: text("source_id").notNull(), |
| 147 | slot: text('slot').notNull(), | 183 | source_scope_id: text("source_scope_id").notNull(), |
| 148 | kind: text({ enum: ['secret', 'connection', 'text'] }).notNull(), | 184 | target_scope_id: text("target_scope_id").notNull(), |
| 149 | secret_id: text('secret_id'), | 185 | slot: text("slot").notNull(), |
| 150 | connection_id: text('connection_id'), | 186 | kind: text({ enum: ["secret", "connection", "text"] }).notNull(), |
| 151 | text_value: text('text_value'), | 187 | secret_id: text("secret_id"), |
| 152 | created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(), | 188 | connection_id: text("connection_id"), |
| 153 | updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull() | 189 | text_value: text("text_value"), |
| 154 | }, (table) => [ | 190 | created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), |
| 155 | index("openapi_source_binding_source_id_idx").on(table.source_id), | 191 | updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(), |
| 156 | index("openapi_source_binding_source_scope_id_idx").on(table.source_scope_id), | 192 | }, |
| 157 | index("openapi_source_binding_target_scope_id_idx").on(table.target_scope_id), | 193 | (table) => [ |
| 158 | index("openapi_source_binding_slot_idx").on(table.slot), | 194 | index("openapi_source_binding_source_id_idx").on(table.source_id), |
| 159 | index("openapi_source_binding_secret_id_idx").on(table.secret_id), | 195 | index("openapi_source_binding_source_scope_id_idx").on(table.source_scope_id), |
| 160 | index("openapi_source_binding_connection_id_idx").on(table.connection_id), | 196 | index("openapi_source_binding_target_scope_id_idx").on(table.target_scope_id), |
| 161 | ]); | 197 | index("openapi_source_binding_slot_idx").on(table.slot), |
| 198 | index("openapi_source_binding_secret_id_idx").on(table.secret_id), | ||
| 199 | index("openapi_source_binding_connection_id_idx").on(table.connection_id), | ||
| 200 | ], | ||
| 201 | ); | ||
| 162 | 202 | ||
| 163 | export const openapi_source_query_param = sqliteTable("openapi_source_query_param", { | 203 | export const openapi_source_query_param = sqliteTable( |
| 164 | id: text('id').notNull(), | 204 | "openapi_source_query_param", |
| 165 | scope_id: text('scope_id').notNull(), | 205 | { |
| 166 | source_id: text('source_id').notNull(), | 206 | id: text("id").notNull(), |
| 167 | name: text('name').notNull(), | 207 | scope_id: text("scope_id").notNull(), |
| 168 | kind: text({ enum: ['text', 'secret'] }).notNull(), | 208 | source_id: text("source_id").notNull(), |
| 169 | text_value: text('text_value'), | 209 | name: text("name").notNull(), |
| 170 | secret_id: text('secret_id'), | 210 | kind: text({ enum: ["text", "secret"] }).notNull(), |
| 171 | secret_prefix: text('secret_prefix') | 211 | text_value: text("text_value"), |
| 172 | }, (table) => [ | 212 | secret_id: text("secret_id"), |
| 173 | primaryKey({ columns: [table.scope_id, table.id] }), | 213 | secret_prefix: text("secret_prefix"), |
| 174 | index("openapi_source_query_param_scope_id_idx").on(table.scope_id), | 214 | }, |
| 175 | index("openapi_source_query_param_source_id_idx").on(table.source_id), | 215 | (table) => [ |
| 176 | index("openapi_source_query_param_secret_id_idx").on(table.secret_id), | 216 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 177 | ]); | 217 | index("openapi_source_query_param_scope_id_idx").on(table.scope_id), |
| 218 | index("openapi_source_query_param_source_id_idx").on(table.source_id), | ||
| 219 | index("openapi_source_query_param_secret_id_idx").on(table.secret_id), | ||
| 220 | ], | ||
| 221 | ); | ||
| 178 | 222 | ||
| 179 | export const openapi_source_spec_fetch_header = sqliteTable("openapi_source_spec_fetch_header", { | 223 | export const openapi_source_spec_fetch_header = sqliteTable( |
| 180 | id: text('id').notNull(), | 224 | "openapi_source_spec_fetch_header", |
| 181 | scope_id: text('scope_id').notNull(), | 225 | { |
| 182 | source_id: text('source_id').notNull(), | 226 | id: text("id").notNull(), |
| 183 | name: text('name').notNull(), | 227 | scope_id: text("scope_id").notNull(), |
| 184 | kind: text({ enum: ['text', 'secret'] }).notNull(), | 228 | source_id: text("source_id").notNull(), |
| 185 | text_value: text('text_value'), | 229 | name: text("name").notNull(), |
| 186 | secret_id: text('secret_id'), | 230 | kind: text({ enum: ["text", "secret"] }).notNull(), |
| 187 | secret_prefix: text('secret_prefix') | 231 | text_value: text("text_value"), |
| 188 | }, (table) => [ | 232 | secret_id: text("secret_id"), |
| 189 | primaryKey({ columns: [table.scope_id, table.id] }), | 233 | secret_prefix: text("secret_prefix"), |
| 190 | index("openapi_source_spec_fetch_header_scope_id_idx").on(table.scope_id), | 234 | }, |
| 191 | index("openapi_source_spec_fetch_header_source_id_idx").on(table.source_id), | 235 | (table) => [ |
| 192 | index("openapi_source_spec_fetch_header_secret_id_idx").on(table.secret_id), | 236 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 193 | ]); | 237 | index("openapi_source_spec_fetch_header_scope_id_idx").on(table.scope_id), |
| 238 | index("openapi_source_spec_fetch_header_source_id_idx").on(table.source_id), | ||
| 239 | index("openapi_source_spec_fetch_header_secret_id_idx").on(table.secret_id), | ||
| 240 | ], | ||
| 241 | ); | ||
| 194 | 242 | ||
| 195 | export const openapi_source_spec_fetch_query_param = sqliteTable("openapi_source_spec_fetch_query_param", { | 243 | export const openapi_source_spec_fetch_query_param = sqliteTable( |
| 196 | id: text('id').notNull(), | 244 | "openapi_source_spec_fetch_query_param", |
| 197 | scope_id: text('scope_id').notNull(), | 245 | { |
| 198 | source_id: text('source_id').notNull(), | 246 | id: text("id").notNull(), |
| 199 | name: text('name').notNull(), | 247 | scope_id: text("scope_id").notNull(), |
| 200 | kind: text({ enum: ['text', 'secret'] }).notNull(), | 248 | source_id: text("source_id").notNull(), |
| 201 | text_value: text('text_value'), | 249 | name: text("name").notNull(), |
| 202 | secret_id: text('secret_id'), | 250 | kind: text({ enum: ["text", "secret"] }).notNull(), |
| 203 | secret_prefix: text('secret_prefix') | 251 | text_value: text("text_value"), |
| 204 | }, (table) => [ | 252 | secret_id: text("secret_id"), |
| 205 | primaryKey({ columns: [table.scope_id, table.id] }), | 253 | secret_prefix: text("secret_prefix"), |
| 206 | index("openapi_source_spec_fetch_query_param_scope_id_idx").on(table.scope_id), | 254 | }, |
| 207 | index("openapi_source_spec_fetch_query_param_source_id_idx").on(table.source_id), | 255 | (table) => [ |
| 208 | index("openapi_source_spec_fetch_query_param_secret_id_idx").on(table.secret_id), | 256 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 209 | ]); | 257 | index("openapi_source_spec_fetch_query_param_scope_id_idx").on(table.scope_id), |
| 258 | index("openapi_source_spec_fetch_query_param_source_id_idx").on(table.source_id), | ||
| 259 | index("openapi_source_spec_fetch_query_param_secret_id_idx").on(table.secret_id), | ||
| 260 | ], | ||
| 261 | ); | ||
| 210 | 262 | ||
| 211 | export const mcp_source = sqliteTable("mcp_source", { | 263 | export const mcp_source = sqliteTable( |
| 212 | id: text('id').notNull(), | 264 | "mcp_source", |
| 213 | scope_id: text('scope_id').notNull(), | 265 | { |
| 214 | name: text('name').notNull(), | 266 | id: text("id").notNull(), |
| 215 | config: text('config', { mode: "json" }).notNull(), | 267 | scope_id: text("scope_id").notNull(), |
| 216 | auth_kind: text({ enum: ['none', 'header', 'oauth2'] }).default("none").notNull(), | 268 | name: text("name").notNull(), |
| 217 | auth_header_name: text('auth_header_name'), | 269 | config: text("config", { mode: "json" }).notNull(), |
| 218 | auth_secret_id: text('auth_secret_id'), | 270 | auth_kind: text({ enum: ["none", "header", "oauth2"] }) |
| 219 | auth_secret_prefix: text('auth_secret_prefix'), | 271 | .default("none") |
| 220 | auth_connection_id: text('auth_connection_id'), | 272 | .notNull(), |
| 221 | auth_client_id_secret_id: text('auth_client_id_secret_id'), | 273 | auth_header_name: text("auth_header_name"), |
| 222 | auth_client_secret_secret_id: text('auth_client_secret_secret_id'), | 274 | auth_secret_id: text("auth_secret_id"), |
| 223 | created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull() | 275 | auth_secret_prefix: text("auth_secret_prefix"), |
| 224 | }, (table) => [ | 276 | auth_connection_id: text("auth_connection_id"), |
| 225 | primaryKey({ columns: [table.scope_id, table.id] }), | 277 | auth_client_id_secret_id: text("auth_client_id_secret_id"), |
| 226 | index("mcp_source_scope_id_idx").on(table.scope_id), | 278 | auth_client_secret_secret_id: text("auth_client_secret_secret_id"), |
| 227 | index("mcp_source_auth_secret_id_idx").on(table.auth_secret_id), | 279 | created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), |
| 228 | index("mcp_source_auth_connection_id_idx").on(table.auth_connection_id), | 280 | }, |
| 229 | index("mcp_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id), | 281 | (table) => [ |
| 230 | index("mcp_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id), | 282 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 231 | ]); | 283 | index("mcp_source_scope_id_idx").on(table.scope_id), |
| 284 | index("mcp_source_auth_secret_id_idx").on(table.auth_secret_id), | ||
| 285 | index("mcp_source_auth_connection_id_idx").on(table.auth_connection_id), | ||
| 286 | index("mcp_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id), | ||
| 287 | index("mcp_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id), | ||
| 288 | ], | ||
| 289 | ); | ||
| 232 | 290 | ||
| 233 | export const mcp_source_header = sqliteTable("mcp_source_header", { | 291 | export const mcp_source_header = sqliteTable( |
| 234 | id: text('id').notNull(), | 292 | "mcp_source_header", |
| 235 | scope_id: text('scope_id').notNull(), | 293 | { |
| 236 | source_id: text('source_id').notNull(), | 294 | id: text("id").notNull(), |
| 237 | name: text('name').notNull(), | 295 | scope_id: text("scope_id").notNull(), |
| 238 | kind: text({ enum: ['text', 'secret'] }).notNull(), | 296 | source_id: text("source_id").notNull(), |
| 239 | text_value: text('text_value'), | 297 | name: text("name").notNull(), |
| 240 | secret_id: text('secret_id'), | 298 | kind: text({ enum: ["text", "secret"] }).notNull(), |
| 241 | secret_prefix: text('secret_prefix') | 299 | text_value: text("text_value"), |
| 242 | }, (table) => [ | 300 | secret_id: text("secret_id"), |
| 243 | primaryKey({ columns: [table.scope_id, table.id] }), | 301 | secret_prefix: text("secret_prefix"), |
| 244 | index("mcp_source_header_scope_id_idx").on(table.scope_id), | 302 | }, |
| 245 | index("mcp_source_header_source_id_idx").on(table.source_id), | 303 | (table) => [ |
| 246 | index("mcp_source_header_secret_id_idx").on(table.secret_id), | 304 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 247 | ]); | 305 | index("mcp_source_header_scope_id_idx").on(table.scope_id), |
| 306 | index("mcp_source_header_source_id_idx").on(table.source_id), | ||
| 307 | index("mcp_source_header_secret_id_idx").on(table.secret_id), | ||
| 308 | ], | ||
| 309 | ); | ||
| 248 | 310 | ||
| 249 | export const mcp_source_query_param = sqliteTable("mcp_source_query_param", { | 311 | export const mcp_source_query_param = sqliteTable( |
| 250 | id: text('id').notNull(), | 312 | "mcp_source_query_param", |
| 251 | scope_id: text('scope_id').notNull(), | 313 | { |
| 252 | source_id: text('source_id').notNull(), | 314 | id: text("id").notNull(), |
| 253 | name: text('name').notNull(), | 315 | scope_id: text("scope_id").notNull(), |
| 254 | kind: text({ enum: ['text', 'secret'] }).notNull(), | 316 | source_id: text("source_id").notNull(), |
| 255 | text_value: text('text_value'), | 317 | name: text("name").notNull(), |
| 256 | secret_id: text('secret_id'), | 318 | kind: text({ enum: ["text", "secret"] }).notNull(), |
| 257 | secret_prefix: text('secret_prefix') | 319 | text_value: text("text_value"), |
| 258 | }, (table) => [ | 320 | secret_id: text("secret_id"), |
| 259 | primaryKey({ columns: [table.scope_id, table.id] }), | 321 | secret_prefix: text("secret_prefix"), |
| 260 | index("mcp_source_query_param_scope_id_idx").on(table.scope_id), | 322 | }, |
| 261 | index("mcp_source_query_param_source_id_idx").on(table.source_id), | 323 | (table) => [ |
| 262 | index("mcp_source_query_param_secret_id_idx").on(table.secret_id), | 324 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 263 | ]); | 325 | index("mcp_source_query_param_scope_id_idx").on(table.scope_id), |
| 326 | index("mcp_source_query_param_source_id_idx").on(table.source_id), | ||
| 327 | index("mcp_source_query_param_secret_id_idx").on(table.secret_id), | ||
| 328 | ], | ||
| 329 | ); | ||
| 264 | 330 | ||
| 265 | export const mcp_binding = sqliteTable("mcp_binding", { | 331 | export const mcp_binding = sqliteTable( |
| 266 | id: text('id').notNull(), | 332 | "mcp_binding", |
| 267 | scope_id: text('scope_id').notNull(), | 333 | { |
| 268 | source_id: text('source_id').notNull(), | 334 | id: text("id").notNull(), |
| 269 | binding: text('binding', { mode: "json" }).notNull(), | 335 | scope_id: text("scope_id").notNull(), |
| 270 | created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull() | 336 | source_id: text("source_id").notNull(), |
| 271 | }, (table) => [ | 337 | binding: text("binding", { mode: "json" }).notNull(), |
| 272 | primaryKey({ columns: [table.scope_id, table.id] }), | 338 | created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), |
| 273 | index("mcp_binding_scope_id_idx").on(table.scope_id), | 339 | }, |
| 274 | index("mcp_binding_source_id_idx").on(table.source_id), | 340 | (table) => [ |
| 275 | ]); | 341 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 342 | index("mcp_binding_scope_id_idx").on(table.scope_id), | ||
| 343 | index("mcp_binding_source_id_idx").on(table.source_id), | ||
| 344 | ], | ||
| 345 | ); | ||
| 276 | 346 | ||
| 277 | export const google_discovery_source = sqliteTable("google_discovery_source", { | 347 | export const google_discovery_source = sqliteTable( |
| 278 | id: text('id').notNull(), | 348 | "google_discovery_source", |
| 279 | scope_id: text('scope_id').notNull(), | 349 | { |
| 280 | name: text('name').notNull(), | 350 | id: text("id").notNull(), |
| 281 | config: text('config', { mode: "json" }).notNull(), | 351 | scope_id: text("scope_id").notNull(), |
| 282 | auth_kind: text({ enum: ['none', 'oauth2'] }).default("none").notNull(), | 352 | name: text("name").notNull(), |
| 283 | auth_connection_id: text('auth_connection_id'), | 353 | config: text("config", { mode: "json" }).notNull(), |
| 284 | auth_client_id_secret_id: text('auth_client_id_secret_id'), | 354 | auth_kind: text({ enum: ["none", "oauth2"] }) |
| 285 | auth_client_secret_secret_id: text('auth_client_secret_secret_id'), | 355 | .default("none") |
| 286 | auth_scopes: text('auth_scopes', { mode: "json" }), | 356 | .notNull(), |
| 287 | created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(), | 357 | auth_connection_id: text("auth_connection_id"), |
| 288 | updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull() | 358 | auth_client_id_secret_id: text("auth_client_id_secret_id"), |
| 289 | }, (table) => [ | 359 | auth_client_secret_secret_id: text("auth_client_secret_secret_id"), |
| 290 | primaryKey({ columns: [table.scope_id, table.id] }), | 360 | auth_scopes: text("auth_scopes", { mode: "json" }), |
| 291 | index("google_discovery_source_scope_id_idx").on(table.scope_id), | 361 | created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), |
| 292 | index("google_discovery_source_auth_connection_id_idx").on(table.auth_connection_id), | 362 | updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(), |
| 293 | index("google_discovery_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id), | 363 | }, |
| 294 | index("google_discovery_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id), | 364 | (table) => [ |
| 295 | ]); | 365 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 366 | index("google_discovery_source_scope_id_idx").on(table.scope_id), | ||
| 367 | index("google_discovery_source_auth_connection_id_idx").on(table.auth_connection_id), | ||
| 368 | index("google_discovery_source_auth_client_id_secret_id_idx").on( | ||
| 369 | table.auth_client_id_secret_id, | ||
| 370 | ), | ||
| 371 | index("google_discovery_source_auth_client_secret_secret_id_idx").on( | ||
| 372 | table.auth_client_secret_secret_id, | ||
| 373 | ), | ||
| 374 | ], | ||
| 375 | ); | ||
| 296 | 376 | ||
| 297 | export const google_discovery_source_credential_header = sqliteTable("google_discovery_source_credential_header", { | 377 | export const google_discovery_source_credential_header = sqliteTable( |
| 298 | id: text('id').notNull(), | 378 | "google_discovery_source_credential_header", |
| 299 | scope_id: text('scope_id').notNull(), | 379 | { |
| 300 | source_id: text('source_id').notNull(), | 380 | id: text("id").notNull(), |
| 301 | name: text('name').notNull(), | 381 | scope_id: text("scope_id").notNull(), |
| 302 | kind: text({ enum: ['text', 'secret'] }).notNull(), | 382 | source_id: text("source_id").notNull(), |
| 303 | text_value: text('text_value'), | 383 | name: text("name").notNull(), |
| 304 | secret_id: text('secret_id'), | 384 | kind: text({ enum: ["text", "secret"] }).notNull(), |
| 305 | secret_prefix: text('secret_prefix') | 385 | text_value: text("text_value"), |
| 306 | }, (table) => [ | 386 | secret_id: text("secret_id"), |
| 307 | primaryKey({ columns: [table.scope_id, table.id] }), | 387 | secret_prefix: text("secret_prefix"), |
| 308 | index("google_discovery_source_credential_header_scope_id_idx").on(table.scope_id), | 388 | }, |
| 309 | index("google_discovery_source_credential_header_source_id_idx").on(table.source_id), | 389 | (table) => [ |
| 310 | index("google_discovery_source_credential_header_secret_id_idx").on(table.secret_id), | 390 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 311 | ]); | 391 | index("google_discovery_source_credential_header_scope_id_idx").on(table.scope_id), |
| 392 | index("google_discovery_source_credential_header_source_id_idx").on(table.source_id), | ||
| 393 | index("google_discovery_source_credential_header_secret_id_idx").on(table.secret_id), | ||
| 394 | ], | ||
| 395 | ); | ||
| 312 | 396 | ||
| 313 | export const google_discovery_source_credential_query_param = sqliteTable("google_discovery_source_credential_query_param", { | 397 | export const google_discovery_source_credential_query_param = sqliteTable( |
| 314 | id: text('id').notNull(), | 398 | "google_discovery_source_credential_query_param", |
| 315 | scope_id: text('scope_id').notNull(), | 399 | { |
| 316 | source_id: text('source_id').notNull(), | 400 | id: text("id").notNull(), |
| 317 | name: text('name').notNull(), | 401 | scope_id: text("scope_id").notNull(), |
| 318 | kind: text({ enum: ['text', 'secret'] }).notNull(), | 402 | source_id: text("source_id").notNull(), |
| 319 | text_value: text('text_value'), | 403 | name: text("name").notNull(), |
| 320 | secret_id: text('secret_id'), | 404 | kind: text({ enum: ["text", "secret"] }).notNull(), |
| 321 | secret_prefix: text('secret_prefix') | 405 | text_value: text("text_value"), |
| 322 | }, (table) => [ | 406 | secret_id: text("secret_id"), |
| 323 | primaryKey({ columns: [table.scope_id, table.id] }), | 407 | secret_prefix: text("secret_prefix"), |
| 324 | index("google_discovery_source_credential_query_param_scope_id_idx").on(table.scope_id), | 408 | }, |
| 325 | index("google_discovery_source_credential_query_param_source_id_idx").on(table.source_id), | 409 | (table) => [ |
| 326 | index("google_discovery_source_credential_query_param_secret_id_idx").on(table.secret_id), | 410 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 327 | ]); | 411 | index("google_discovery_source_credential_query_param_scope_id_idx").on(table.scope_id), |
| 412 | index("google_discovery_source_credential_query_param_source_id_idx").on(table.source_id), | ||
| 413 | index("google_discovery_source_credential_query_param_secret_id_idx").on(table.secret_id), | ||
| 414 | ], | ||
| 415 | ); | ||
| 328 | 416 | ||
| 329 | export const google_discovery_binding = sqliteTable("google_discovery_binding", { | 417 | export const google_discovery_binding = sqliteTable( |
| 330 | id: text('id').notNull(), | 418 | "google_discovery_binding", |
| 331 | scope_id: text('scope_id').notNull(), | 419 | { |
| 332 | source_id: text('source_id').notNull(), | 420 | id: text("id").notNull(), |
| 333 | binding: text('binding', { mode: "json" }).notNull(), | 421 | scope_id: text("scope_id").notNull(), |
| 334 | created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull() | 422 | source_id: text("source_id").notNull(), |
| 335 | }, (table) => [ | 423 | binding: text("binding", { mode: "json" }).notNull(), |
| 336 | primaryKey({ columns: [table.scope_id, table.id] }), | 424 | created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), |
| 337 | index("google_discovery_binding_scope_id_idx").on(table.scope_id), | 425 | }, |
| 338 | index("google_discovery_binding_source_id_idx").on(table.source_id), | 426 | (table) => [ |
| 339 | ]); | 427 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 428 | index("google_discovery_binding_scope_id_idx").on(table.scope_id), | ||
| 429 | index("google_discovery_binding_source_id_idx").on(table.source_id), | ||
| 430 | ], | ||
| 431 | ); | ||
| 340 | 432 | ||
| 341 | export const graphql_source = sqliteTable("graphql_source", { | 433 | export const graphql_source = sqliteTable( |
| 342 | id: text('id').notNull(), | 434 | "graphql_source", |
| 343 | scope_id: text('scope_id').notNull(), | 435 | { |
| 344 | name: text('name').notNull(), | 436 | id: text("id").notNull(), |
| 345 | endpoint: text('endpoint').notNull(), | 437 | scope_id: text("scope_id").notNull(), |
| 346 | auth_kind: text({ enum: ['none', 'oauth2'] }).default("none").notNull(), | 438 | name: text("name").notNull(), |
| 347 | auth_connection_id: text('auth_connection_id') | 439 | endpoint: text("endpoint").notNull(), |
| 348 | }, (table) => [ | 440 | auth_kind: text({ enum: ["none", "oauth2"] }) |
| 349 | primaryKey({ columns: [table.scope_id, table.id] }), | 441 | .default("none") |
| 350 | index("graphql_source_scope_id_idx").on(table.scope_id), | 442 | .notNull(), |
| 351 | index("graphql_source_auth_connection_id_idx").on(table.auth_connection_id), | 443 | auth_connection_id: text("auth_connection_id"), |
| 352 | ]); | 444 | }, |
| 445 | (table) => [ | ||
| 446 | primaryKey({ columns: [table.scope_id, table.id] }), | ||
| 447 | index("graphql_source_scope_id_idx").on(table.scope_id), | ||
| 448 | index("graphql_source_auth_connection_id_idx").on(table.auth_connection_id), | ||
| 449 | ], | ||
| 450 | ); | ||
| 353 | 451 | ||
| 354 | export const graphql_source_header = sqliteTable("graphql_source_header", { | 452 | export const graphql_source_header = sqliteTable( |
| 355 | id: text('id').notNull(), | 453 | "graphql_source_header", |
| 356 | scope_id: text('scope_id').notNull(), | 454 | { |
| 357 | source_id: text('source_id').notNull(), | 455 | id: text("id").notNull(), |
| 358 | name: text('name').notNull(), | 456 | scope_id: text("scope_id").notNull(), |
| 359 | kind: text({ enum: ['text', 'secret'] }).notNull(), | 457 | source_id: text("source_id").notNull(), |
| 360 | text_value: text('text_value'), | 458 | name: text("name").notNull(), |
| 361 | secret_id: text('secret_id'), | 459 | kind: text({ enum: ["text", "secret"] }).notNull(), |
| 362 | secret_prefix: text('secret_prefix') | 460 | text_value: text("text_value"), |
| 363 | }, (table) => [ | 461 | secret_id: text("secret_id"), |
| 364 | primaryKey({ columns: [table.scope_id, table.id] }), | 462 | secret_prefix: text("secret_prefix"), |
| 365 | index("graphql_source_header_scope_id_idx").on(table.scope_id), | 463 | }, |
| 366 | index("graphql_source_header_source_id_idx").on(table.source_id), | 464 | (table) => [ |
| 367 | index("graphql_source_header_secret_id_idx").on(table.secret_id), | 465 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 368 | ]); | 466 | index("graphql_source_header_scope_id_idx").on(table.scope_id), |
| 467 | index("graphql_source_header_source_id_idx").on(table.source_id), | ||
| 468 | index("graphql_source_header_secret_id_idx").on(table.secret_id), | ||
| 469 | ], | ||
| 470 | ); | ||
| 369 | 471 | ||
| 370 | export const graphql_source_query_param = sqliteTable("graphql_source_query_param", { | 472 | export const graphql_source_query_param = sqliteTable( |
| 371 | id: text('id').notNull(), | 473 | "graphql_source_query_param", |
| 372 | scope_id: text('scope_id').notNull(), | 474 | { |
| 373 | source_id: text('source_id').notNull(), | 475 | id: text("id").notNull(), |
| 374 | name: text('name').notNull(), | 476 | scope_id: text("scope_id").notNull(), |
| 375 | kind: text({ enum: ['text', 'secret'] }).notNull(), | 477 | source_id: text("source_id").notNull(), |
| 376 | text_value: text('text_value'), | 478 | name: text("name").notNull(), |
| 377 | secret_id: text('secret_id'), | 479 | kind: text({ enum: ["text", "secret"] }).notNull(), |
| 378 | secret_prefix: text('secret_prefix') | 480 | text_value: text("text_value"), |
| 379 | }, (table) => [ | 481 | secret_id: text("secret_id"), |
| 380 | primaryKey({ columns: [table.scope_id, table.id] }), | 482 | secret_prefix: text("secret_prefix"), |
| 381 | index("graphql_source_query_param_scope_id_idx").on(table.scope_id), | 483 | }, |
| 382 | index("graphql_source_query_param_source_id_idx").on(table.source_id), | 484 | (table) => [ |
| 383 | index("graphql_source_query_param_secret_id_idx").on(table.secret_id), | 485 | primaryKey({ columns: [table.scope_id, table.id] }), |
| 384 | ]); | 486 | index("graphql_source_query_param_scope_id_idx").on(table.scope_id), |
| 385 | 487 | index("graphql_source_query_param_source_id_idx").on(table.source_id), | |
| 386 | export const graphql_operation = sqliteTable("graphql_operation", { | 488 | index("graphql_source_query_param_secret_id_idx").on(table.secret_id), |
| 387 | id: text('id').notNull(), | 489 | ], |
| 388 | scope_id: text('scope_id').notNull(), | 490 | ); |
| 389 | source_id: text('source_id').notNull(), | ||
| 390 | binding: text('binding', { mode: "json" }).notNull() | ||
| 391 | }, (table) => [ | ||
| 392 | primaryKey({ columns: [table.scope_id, table.id] }), | ||
| 393 | index("graphql_operation_scope_id_idx").on(table.scope_id), | ||
| 394 | index("graphql_operation_source_id_idx").on(table.source_id), | ||
| 395 | ]); | ||
| 396 | 491 | ||
| 492 | export const graphql_operation = sqliteTable( | ||
| 493 | "graphql_operation", | ||
| 494 | { | ||
| 495 | id: text("id").notNull(), | ||
| 496 | scope_id: text("scope_id").notNull(), | ||
| 497 | source_id: text("source_id").notNull(), | ||
| 498 | binding: text("binding", { mode: "json" }).notNull(), | ||
| 499 | }, | ||
| 500 | (table) => [ | ||
| 501 | primaryKey({ columns: [table.scope_id, table.id] }), | ||
| 502 | index("graphql_operation_scope_id_idx").on(table.scope_id), | ||
| 503 | index("graphql_operation_source_id_idx").on(table.source_id), | ||
| 504 | ], | ||
| 505 | ); | ||
| @@ -16,17 +16,8 @@ import { · 15 unmodified lines | |||
| 16 | } from "./db-upgrade"; | 16 | } from "./db-upgrade"; |
| 17 | import { migrateLegacyConnections } from "./migrate-connections"; | 17 | import { migrateLegacyConnections } from "./migrate-connections"; |
| 18 | 18 | ||
| 19 | import { | 19 | import { Scope, ScopeId, type AnyPlugin, collectSchemas, createExecutor } from "@executor-js/sdk"; |
| 20 | Scope, | 20 | import { makeSqliteAdapter, makeSqliteBlobStore } from "@executor-js/storage-file"; |
| 21 | ScopeId, | ||
| 22 | type AnyPlugin, | ||
| 23 | collectSchemas, | ||
| 24 | createExecutor, | ||
| 25 | } from "@executor-js/sdk"; | ||
| 26 | import { | ||
| 27 | makeSqliteAdapter, | ||
| 28 | makeSqliteBlobStore, | ||
| 29 | } from "@executor-js/storage-file"; | ||
| 30 | import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"; | 21 | import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"; |
| 31 | import { loadPluginsFromJsonc, makeFileConfigSink } from "@executor-js/config"; | 22 | import { loadPluginsFromJsonc, makeFileConfigSink } from "@executor-js/config"; |
| 32 | import * as executorSchema from "./executor-schema"; | 23 | import * as executorSchema from "./executor-schema"; |
| @@ -107,9 +98,7 @@ class LocalExecutorTag extends Context.Service<LocalExecutorTag, LocalExecutorBu · 74 unmodified lines | |||
| 107 | 98 | ||
| 108 | export type LocalExecutor = LocalExecutorBundle["executor"]; | 99 | export type LocalExecutor = LocalExecutorBundle["executor"]; |
| 109 | 100 | ||
| 110 | class LocalExecutorDisposeError extends Data.TaggedError( | 101 | class LocalExecutorDisposeError extends Data.TaggedError("LocalExecutorDisposeError")<{ |
| 111 | "LocalExecutorDisposeError", | ||
| 112 | )<{ | ||
| 113 | readonly operation: "createHandle" | "disposeExecutor" | "disposeRuntime"; | 102 | readonly operation: "createHandle" | "disposeExecutor" | "disposeRuntime"; |
| 114 | readonly cause: unknown; | 103 | readonly cause: unknown; |
| 115 | }> {} | 104 | }> {} |
| @@ -131,13 +120,10 @@ const handleOrNull = (promise: ReturnType<typeof createExecutorHandle>) => · 15 unmodified lines | |||
| 131 | Effect.runPromise( | 120 | Effect.runPromise( |
| 132 | Effect.tryPromise({ | 121 | Effect.tryPromise({ |
| 133 | try: () => promise, | 122 | try: () => promise, |
| 134 | catch: (cause) => | 123 | catch: (cause) => new LocalExecutorDisposeError({ operation: "createHandle", cause }), |
| 135 | new LocalExecutorDisposeError({ operation: "createHandle", cause }), | ||
| 136 | }).pipe( | 124 | }).pipe( |
| 137 | Effect.catch(() => | 125 | Effect.catch(() => |
| 138 | Effect.succeed<Awaited<ReturnType<typeof createExecutorHandle>> | null>( | 126 | Effect.succeed<Awaited<ReturnType<typeof createExecutorHandle>> | null>(null), |
| 139 | null, | ||
| 140 | ), | ||
| 141 | ), | 127 | ), |
| 142 | ), | 128 | ), |
| 143 | ); | 129 | ); |
| @@ -145,7 +131,8 @@ const handleOrNull = (promise: ReturnType<typeof createExecutorHandle>) => · 1 unmodified line | |||
| 145 | const createLocalExecutorLayer = () => { | 131 | const createLocalExecutorLayer = () => { |
| 146 | const { path: dbPath, legacySecrets } = resolveDbPath(); | 132 | const { path: dbPath, legacySecrets } = resolveDbPath(); |
| 147 | 133 | ||
| 148 | return Layer.effect(LocalExecutorTag)(Effect.gen(function* () { | 134 | return Layer.effect(LocalExecutorTag)( |
| 135 | Effect.gen(function* () { | ||
| 149 | const sqlite = yield* Effect.acquireRelease( | 136 | const sqlite = yield* Effect.acquireRelease( |
| 150 | Effect.sync(() => new Database(dbPath)), | 137 | Effect.sync(() => new Database(dbPath)), |
| 151 | (conn) => Effect.sync(() => conn.close()), | 138 | (conn) => Effect.sync(() => conn.close()), |
| @@ -183,9 +170,7 @@ const createLocalExecutorLayer = () => { · 31 unmodified lines | |||
| 183 | // ordering. Without this, a package listed in both surfaces would | 170 | // ordering. Without this, a package listed in both surfaces would |
| 184 | // boot twice (double routes, double in-memory storage). | 171 | // boot twice (double routes, double in-memory storage). |
| 185 | const staticPackageNames = new Set( | 172 | const staticPackageNames = new Set( |
| 186 | staticPlugins | 173 | staticPlugins.map((p) => p.packageName).filter((n): n is string => !!n), |
| 187 | .map((p) => p.packageName) | ||
| 188 | .filter((n): n is string => !!n), | ||
| 189 | ); | 174 | ); |
| 190 | const dedupedDynamic = dynamicPlugins.filter((p) => { | 175 | const dedupedDynamic = dynamicPlugins.filter((p) => { |
| 191 | if (p.packageName && staticPackageNames.has(p.packageName)) { | 176 | if (p.packageName && staticPackageNames.has(p.packageName)) { |
| @@ -261,9 +246,7 @@ export const disposeExecutor = async (): Promise<void> => { · 69 unmodified lines | |||
| 261 | const currentHandlePromise = sharedHandlePromise; | 246 | const currentHandlePromise = sharedHandlePromise; |
| 262 | sharedHandlePromise = null; | 247 | sharedHandlePromise = null; |
| 263 | 248 | ||
| 264 | const handle = currentHandlePromise | 249 | const handle = currentHandlePromise ? await handleOrNull(currentHandlePromise) : null; |
| 265 | ? await handleOrNull(currentHandlePromise) | ||
| 266 | : null; | ||
| 267 | if (handle) { | 250 | if (handle) { |
| 268 | await ignorePromiseFailure("disposeExecutor", () => handle.dispose()); | 251 | await ignorePromiseFailure("disposeExecutor", () => handle.dispose()); |
| 269 | } | 252 | } |
| @@ -103,10 +103,12 @@ export const createServerHandlers = async (): Promise<ServerHandlers> => { · 102 unmodified lines | |||
| 103 | return { api: apiHandler, mcp }; | 103 | return { api: apiHandler, mcp }; |
| 104 | }; | 104 | }; |
| 105 | 105 | ||
| 106 | export class ServerHandlersService extends Context.Service<ServerHandlersService, ServerHandlers | 106 | export class ServerHandlersService extends Context.Service<ServerHandlersService, ServerHandlers>()( |
| 107 | >()("@executor-js/local/ServerHandlersService") {} | 107 | "@executor-js/local/ServerHandlersService", |
| 108 | ) {} | ||
| 108 | 109 | ||
| 109 | const ServerHandlersLive = Layer.effect(ServerHandlersService)(Effect.acquireRelease( | 110 | const ServerHandlersLive = Layer.effect(ServerHandlersService)( |
| 111 | Effect.acquireRelease( | ||
| 110 | Effect.promise(() => createServerHandlers()), | 112 | Effect.promise(() => createServerHandlers()), |
| 111 | (handlers) => Effect.promise(() => closeServerHandlers(handlers)), | 113 | (handlers) => Effect.promise(() => closeServerHandlers(handlers)), |
| 112 | ), | 114 | ), |
| @@ -34,30 +34,14 @@ import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http"; · 33 unmodified lines | |||
| 34 | import { Effect, Layer, Option, Schema } from "effect"; | 34 | import { Effect, Layer, Option, Schema } from "effect"; |
| 35 | 35 | ||
| 36 | import { addGroup, observabilityMiddleware } from "@executor-js/api"; | 36 | import { addGroup, observabilityMiddleware } from "@executor-js/api"; |
| 37 | import { | 37 | import { CoreHandlers, ExecutionEngineService, ExecutorService } from "@executor-js/api/server"; |
| 38 | CoreHandlers, | ||
| 39 | ExecutionEngineService, | ||
| 40 | ExecutorService, | ||
| 41 | } from "@executor-js/api/server"; | ||
| 42 | import { createExecutionEngine } from "@executor-js/execution"; | 38 | import { createExecutionEngine } from "@executor-js/execution"; |
| 43 | import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs"; | 39 | import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs"; |
| 44 | import { | 40 | import { Scope, ScopeId, collectSchemas, createExecutor } from "@executor-js/sdk"; |
| 45 | Scope, | 41 | import { makeSqliteAdapter, makeSqliteBlobStore } from "@executor-js/storage-file"; |
| 46 | ScopeId, | ||
| 47 | collectSchemas, | ||
| 48 | createExecutor, | ||
| 49 | } from "@executor-js/sdk"; | ||
| 50 | import { | ||
| 51 | makeSqliteAdapter, | ||
| 52 | makeSqliteBlobStore, | ||
| 53 | } from "@executor-js/storage-file"; | ||
| 54 | import { fileSecretsPlugin } from "@executor-js/plugin-file-secrets"; | 42 | import { fileSecretsPlugin } from "@executor-js/plugin-file-secrets"; |
| 55 | import { mcpPlugin } from "@executor-js/plugin-mcp"; | 43 | import { mcpPlugin } from "@executor-js/plugin-mcp"; |
| 56 | import { | 44 | import { McpExtensionService, McpGroup, McpHandlers } from "@executor-js/plugin-mcp/api"; |
| 57 | McpExtensionService, | ||
| 58 | McpGroup, | ||
| 59 | McpHandlers, | ||
| 60 | } from "@executor-js/plugin-mcp/api"; | ||
| 61 | 45 | ||
| 62 | import * as executorSchema from "./executor-schema"; | 46 | import * as executorSchema from "./executor-schema"; |
| 63 | import { ErrorCaptureLive } from "./observability"; | 47 | import { ErrorCaptureLive } from "./observability"; |
| @@ -66,9 +50,10 @@ import { ErrorCaptureLive } from "./observability"; · 2 unmodified lines | |||
| 66 | // the top level so `observabilityMiddleware` can land its typed-error | 50 | // the top level so `observabilityMiddleware` can land its typed-error |
| 67 | // bridge on every endpoint. | 51 | // bridge on every endpoint. |
| 68 | const TestApi = addGroup(McpGroup); | 52 | const TestApi = addGroup(McpGroup); |
| 69 | type TestApiShape = typeof TestApi extends HttpApi.HttpApi<infer _Id, infer Groups> | 53 | type TestApiShape = |
| 70 | ? HttpApiClient.Client<Groups, never> | 54 | typeof TestApi extends HttpApi.HttpApi<infer _Id, infer Groups> |
| 71 | : never; | 55 | ? HttpApiClient.Client<Groups, never> |
| 56 | : never; | ||
| 72 | 57 | ||
| 73 | // --------------------------------------------------------------------------- | 58 | // --------------------------------------------------------------------------- |
| 74 | // Fake OAuth + MCP server (mirrors the cloud test) | 59 | // Fake OAuth + MCP server (mirrors the cloud test) |
| @@ -84,21 +69,15 @@ const RegistrationBody = Schema.Struct({ · 9 unmodified lines | |||
| 84 | grant_types: Schema.optional(Schema.Array(Schema.String)), | 69 | grant_types: Schema.optional(Schema.Array(Schema.String)), |
| 85 | response_types: Schema.optional(Schema.Array(Schema.String)), | 70 | response_types: Schema.optional(Schema.Array(Schema.String)), |
| 86 | }); | 71 | }); |
| 87 | const decodeRegistrationBody = Schema.decodeUnknownOption( | 72 | const decodeRegistrationBody = Schema.decodeUnknownOption(Schema.fromJsonString(RegistrationBody)); |
| 88 | Schema.fromJsonString(RegistrationBody), | ||
| 89 | ); | ||
| 90 | 73 | ||
| 91 | const startFakeServer = async (): Promise<FakeServer> => { | 74 | const startFakeServer = async (): Promise<FakeServer> => { |
| 92 | const clients = new Map<string, { redirect_uris: readonly string[] }>(); | 75 | const clients = new Map<string, { redirect_uris: readonly string[] }>(); |
| 93 | const codes = new Map< | 76 | const codes = new Map<string, { readonly clientId: string; readonly codeChallenge: string }>(); |
| 94 | string, | ||
| 95 | { readonly clientId: string; readonly codeChallenge: string } | ||
| 96 | >(); | ||
| 97 | const accessTokens = new Map<string, { readonly refresh: string }>(); | 77 | const accessTokens = new Map<string, { readonly refresh: string }>(); |
| 98 | const refreshTokens = new Map<string, string>(); | 78 | const refreshTokens = new Map<string, string>(); |
| 99 | let seq = 0; | 79 | let seq = 0; |
| 100 | const next = (p: string) => | 80 | const next = (p: string) => `${p}_${++seq}_${randomBytes(6).toString("hex")}`; |
| 101 | `${p}_${++seq}_${randomBytes(6).toString("hex")}`; | ||
| 102 | 81 | ||
| 103 | const readBody = (req: import("node:http").IncomingMessage): Promise<string> => | 82 | const readBody = (req: import("node:http").IncomingMessage): Promise<string> => |
| 104 | new Promise((resolve, reject) => { | 83 | new Promise((resolve, reject) => { |
| @@ -110,15 +89,10 @@ const startFakeServer = async (): Promise<FakeServer> => { · 5 unmodified lines | |||
| 110 | 89 | ||
| 111 | const server: Server = createServer(async (req, res) => { | 90 | const server: Server = createServer(async (req, res) => { |
| 112 | const url = new URL(req.url!, `http://${req.headers.host}`); | 91 | const url = new URL(req.url!, `http://${req.headers.host}`); |
| 113 | const send = ( | 92 | const send = (status: number, body: unknown, headers: Record<string, string> = {}) => { |
| 114 | status: number, | ||
| 115 | body: unknown, | ||
| 116 | headers: Record<string, string> = {}, | ||
| 117 | ) => { | ||
| 118 | const payload = typeof body === "string" ? body : JSON.stringify(body); | 93 | const payload = typeof body === "string" ? body : JSON.stringify(body); |
| 119 | res.writeHead(status, { | 94 | res.writeHead(status, { |
| 120 | "content-type": | 95 | "content-type": typeof body === "string" ? "text/plain" : "application/json", |
| 121 | typeof body === "string" ? "text/plain" : "application/json", | ||
| 122 | ...headers, | 96 | ...headers, |
| 123 | }); | 97 | }); |
| 124 | res.end(payload); | 98 | res.end(payload); |
| @@ -162,10 +136,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 37 unmodified lines | |||
| 162 | client_id: clientId, | 136 | client_id: clientId, |
| 163 | client_id_issued_at: Math.floor(Date.now() / 1000), | 137 | client_id_issued_at: Math.floor(Date.now() / 1000), |
| 164 | redirect_uris: parsed.redirect_uris ?? [], | 138 | redirect_uris: parsed.redirect_uris ?? [], |
| 165 | grant_types: parsed.grant_types ?? [ | 139 | grant_types: parsed.grant_types ?? ["authorization_code", "refresh_token"], |
| 166 | "authorization_code", | ||
| 167 | "refresh_token", | ||
| 168 | ], | ||
| 169 | response_types: parsed.response_types ?? ["code"], | 140 | response_types: parsed.response_types ?? ["code"], |
| 170 | token_endpoint_auth_method: "none", | 141 | token_endpoint_auth_method: "none", |
| 171 | }); | 142 | }); |
| @@ -202,9 +173,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 30 unmodified lines | |||
| 202 | const record = codes.get(code); | 173 | const record = codes.get(code); |
| 203 | if (!record) return send(400, { error: "invalid_grant" }); | 174 | if (!record) return send(400, { error: "invalid_grant" }); |
| 204 | codes.delete(code); | 175 | codes.delete(code); |
| 205 | const computed = createHash("sha256") | 176 | const computed = createHash("sha256").update(verifier).digest("base64url"); |
| 206 | .update(verifier) | ||
| 207 | .digest("base64url"); | ||
| 208 | if (computed !== record.codeChallenge) { | 177 | if (computed !== record.codeChallenge) { |
| 209 | return send(400, { error: "invalid_grant" }); | 178 | return send(400, { error: "invalid_grant" }); |
| 210 | } | 179 | } |
| @@ -240,9 +209,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 29 unmodified lines | |||
| 240 | } | 209 | } |
| 241 | }); | 210 | }); |
| 242 | 211 | ||
| 243 | await new Promise<void>((resolve) => | 212 | await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", () => resolve())); |
| 244 | server.listen(0, "127.0.0.1", () => resolve()), | ||
| 245 | ); | ||
| 246 | const address = server.address() as AddressInfo; | 213 | const address = server.address() as AddressInfo; |
| 247 | 214 | ||
| 248 | return { | 215 | return { |
| @@ -328,15 +295,9 @@ const startHarness = async (tmpDir: string): Promise<Harness> => { · 79 unmodified lines | |||
| 328 | )) as typeof globalThis.fetch, | 295 | )) as typeof globalThis.fetch, |
| 329 | scopeId, | 296 | scopeId, |
| 330 | dispose: async () => { | 297 | dispose: async () => { |
| 298 | await Effect.runPromise(Effect.ignore(Effect.tryPromise(() => disposeHandler()))); | ||
| 331 | await Effect.runPromise( | 299 | await Effect.runPromise( |
| 332 | Effect.ignore( | 300 | Effect.ignore(Effect.tryPromise(() => Effect.runPromise(executor.close()))), |
| 333 | Effect.tryPromise(() => disposeHandler()), | ||
| 334 | ), | ||
| 335 | ); | ||
| 336 | await Effect.runPromise( | ||
| 337 | Effect.ignore( | ||
| 338 | Effect.tryPromise(() => Effect.runPromise(executor.close())), | ||
| 339 | ), | ||
| 340 | ); | 301 | ); |
| 341 | sqlite.close(); | 302 | sqlite.close(); |
| 342 | }, | 303 | }, |
| @@ -400,9 +361,7 @@ describe("local mcp oauth (real OAuth + MCP server)", () => { · 57 unmodified lines | |||
| 400 | const redirectUrl = "http://local.test/api/mcp/oauth/callback"; | 361 | const redirectUrl = "http://local.test/api/mcp/oauth/callback"; |
| 401 | const scopeId = ScopeId.make(harness.scopeId); | 362 | const scopeId = ScopeId.make(harness.scopeId); |
| 402 | 363 | ||
| 403 | const run = <A, E>( | 364 | const run = <A, E>(body: (client: TestApiShape) => Effect.Effect<A, E>): Effect.Effect<A, E> => |
| 404 | body: (client: TestApiShape) => Effect.Effect<A, E>, | ||
| 405 | ): Effect.Effect<A, E> => | ||
| 406 | Effect.gen(function* () { | 365 | Effect.gen(function* () { |
| 407 | const client = yield* HttpApiClient.make(TestApi, { | 366 | const client = yield* HttpApiClient.make(TestApi, { |
| 408 | baseUrl: TEST_BASE_URL, | 367 | baseUrl: TEST_BASE_URL, |
| @@ -40,9 +40,7 @@ const columnNames = (db: Database, table: string): ReadonlyArray<string> => · 39 unmodified lines | |||
| 40 | const MigratedMcpConfig = Schema.Struct({ | 40 | const MigratedMcpConfig = Schema.Struct({ |
| 41 | auth: Schema.optional(Schema.Unknown), | 41 | auth: Schema.optional(Schema.Unknown), |
| 42 | }); | 42 | }); |
| 43 | const decodeMigratedMcpConfig = Schema.decodeUnknownSync( | 43 | const decodeMigratedMcpConfig = Schema.decodeUnknownSync(Schema.fromJsonString(MigratedMcpConfig)); |
| 44 | Schema.fromJsonString(MigratedMcpConfig), | ||
| 45 | ); | ||
| 46 | 44 | ||
| 47 | const MigratedOpenApiOAuth2 = Schema.Struct({ | 45 | const MigratedOpenApiOAuth2 = Schema.Struct({ |
| 48 | kind: Schema.Literal("oauth2"), | 46 | kind: Schema.Literal("oauth2"), |
| @@ -130,9 +128,7 @@ describe("migrateLegacyConnections", () => { · 81 unmodified lines | |||
| 130 | expect(source.auth_connection_id).toBe("mcp-oauth2-remote-mcp"); | 128 | expect(source.auth_connection_id).toBe("mcp-oauth2-remote-mcp"); |
| 131 | 129 | ||
| 132 | const ownedSecrets = db | 130 | const ownedSecrets = db |
| 133 | .prepare( | 131 | .prepare("SELECT id, owned_by_connection_id FROM secret WHERE scope_id = ? ORDER BY id") |
| 134 | "SELECT id, owned_by_connection_id FROM secret WHERE scope_id = ? ORDER BY id", | ||
| 135 | ) | ||
| 136 | .all("scope-1"); | 132 | .all("scope-1"); |
| 137 | expect(ownedSecrets).toEqual([ | 133 | expect(ownedSecrets).toEqual([ |
| 138 | { | 134 | { |
| @@ -208,9 +204,7 @@ describe("migrateLegacyConnections", () => { · 69 unmodified lines | |||
| 208 | await migrateLegacyConnections(db); | 204 | await migrateLegacyConnections(db); |
| 209 | 205 | ||
| 210 | const connection = db | 206 | const connection = db |
| 211 | .prepare( | 207 | .prepare("SELECT id, provider, access_token_secret_id FROM connection WHERE scope_id = ?") |
| 212 | "SELECT id, provider, access_token_secret_id FROM connection WHERE scope_id = ?", | ||
| 213 | ) | ||
| 214 | .get("scope-1") as | 208 | .get("scope-1") as |
| 215 | | { readonly id: string; readonly provider: string; readonly access_token_secret_id: string } | 209 | | { readonly id: string; readonly provider: string; readonly access_token_secret_id: string } |
| 216 | | undefined; | 210 | | undefined; |
| @@ -16,11 +16,7 @@ import { Database } from "bun:sqlite"; · 15 unmodified lines | |||
| 16 | import { randomUUID } from "node:crypto"; | 16 | import { randomUUID } from "node:crypto"; |
| 17 | import { Effect, Option, Result, Schema } from "effect"; | 17 | import { Effect, Option, Result, Schema } from "effect"; |
| 18 | import { FetchHttpClient } from "effect/unstable/http"; | 18 | import { FetchHttpClient } from "effect/unstable/http"; |
| 19 | import { | 19 | import { parse as parseOpenApi, resolveSpecText, OAuth2Auth } from "@executor-js/plugin-openapi"; |
| 20 | parse as parseOpenApi, | ||
| 21 | resolveSpecText, | ||
| 22 | OAuth2Auth, | ||
| 23 | } from "@executor-js/plugin-openapi"; | ||
| 24 | import { McpConnectionAuth } from "@executor-js/plugin-mcp"; | 20 | import { McpConnectionAuth } from "@executor-js/plugin-mcp"; |
| 25 | 21 | ||
| 26 | // --------------------------------------------------------------------------- | 22 | // --------------------------------------------------------------------------- |
| @@ -51,9 +47,9 @@ const formatBoundaryError = (error: unknown): string => { · 24 unmodified lines | |||
| 51 | /** Pre-flight: bail unless the drizzle migration that added the Connection | 47 | /** Pre-flight: bail unless the drizzle migration that added the Connection |
| 52 | * table + `secret.owned_by_connection_id` has completed. */ | 48 | * table + `secret.owned_by_connection_id` has completed. */ |
| 53 | const connectionsReady = (sqlite: Database): boolean => { | 49 | const connectionsReady = (sqlite: Database): boolean => { |
| 54 | const secretColumns = sqlite | 50 | const secretColumns = sqlite.prepare("PRAGMA table_info('secret')").all() as ReadonlyArray<{ |
| 55 | .prepare("PRAGMA table_info('secret')") | 51 | readonly name: string; |
| 56 | .all() as ReadonlyArray<{ readonly name: string }>; | 52 | }>; |
| 57 | if (!secretColumns.some((c) => c.name === "owned_by_connection_id")) return false; | 53 | if (!secretColumns.some((c) => c.name === "owned_by_connection_id")) return false; |
| 58 | const connectionTable = sqlite | 54 | const connectionTable = sqlite |
| 59 | .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='connection'") | 55 | .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='connection'") |
| @@ -103,16 +99,10 @@ const rewireSecrets = ( · 43 unmodified lines | |||
| 103 | ) VALUES (?, ?, ?, ?, ?, ?)`, | 99 | ) VALUES (?, ?, ?, ?, ?, ?)`, |
| 104 | ); | 100 | ); |
| 105 | 101 | ||
| 106 | const rows = secretIds.map( | 102 | const rows = secretIds.map((sid) => selectSecret.get(scopeId, sid) as SecretRow | undefined); |
| 107 | (sid) => selectSecret.get(scopeId, sid) as SecretRow | undefined, | ||
| 108 | ); | ||
| 109 | const alreadyOwned = rows | 103 | const alreadyOwned = rows |
| 110 | .filter((r): r is SecretRow => !!r) | 104 | .filter((r): r is SecretRow => !!r) |
| 111 | .filter( | 105 | .filter((r) => r.owned_by_connection_id !== null && r.owned_by_connection_id !== connectionId); |
| 112 | (r) => | ||
| 113 | r.owned_by_connection_id !== null && | ||
| 114 | r.owned_by_connection_id !== connectionId, | ||
| 115 | ); | ||
| 116 | if (alreadyOwned.length > 0) return "secret(s) already owned"; | 106 | if (alreadyOwned.length > 0) return "secret(s) already owned"; |
| 117 | 107 | ||
| 118 | // Early-onboarded rows never got a `secret` routing row — pre-refactor | 108 | // Early-onboarded rows never got a `secret` routing row — pre-refactor |
| @@ -123,9 +113,7 @@ const rewireSecrets = ( · 4 unmodified lines | |||
| 123 | const missingCount = rows.filter((r) => r === undefined).length; | 113 | const missingCount = rows.filter((r) => r === undefined).length; |
| 124 | let fallbackProvider: string | null = null; | 114 | let fallbackProvider: string | null = null; |
| 125 | if (missingCount > 0) { | 115 | if (missingCount > 0) { |
| 126 | const existing = selectAnySecretProvider.get(scopeId) as | 116 | const existing = selectAnySecretProvider.get(scopeId) as { provider: string } | undefined; |
| 127 | | { provider: string } | ||
| 128 | | undefined; | ||
| 129 | fallbackProvider = existing?.provider ?? "keychain"; | 117 | fallbackProvider = existing?.provider ?? "keychain"; |
| 130 | } | 118 | } |
| 131 | 119 | ||
| @@ -213,9 +201,7 @@ class LegacyOpenApiOAuth2 extends Schema.Class<LegacyOpenApiOAuth2>("LegacyOpenA · 81 unmodified lines | |||
| 213 | }) {} | 201 | }) {} |
| 214 | 202 | ||
| 215 | const decodeOpenApiCurrent = Schema.decodeUnknownOption(OAuth2Auth); | 203 | const decodeOpenApiCurrent = Schema.decodeUnknownOption(OAuth2Auth); |
| 216 | const decodeOpenApiLegacy = decodeUnknownOptionAs<LegacyOpenApiOAuth2>( | 204 | const decodeOpenApiLegacy = decodeUnknownOptionAs<LegacyOpenApiOAuth2>(LegacyOpenApiOAuth2); |
| 217 | LegacyOpenApiOAuth2, | ||
| 218 | ); | ||
| 219 | 205 | ||
| 220 | const extractAuthorizationUrl = async ( | 206 | const extractAuthorizationUrl = async ( |
| 221 | rawSpec: string, | 207 | rawSpec: string, |
| @@ -234,19 +220,18 @@ const extractAuthorizationUrl = async ( · 12 unmodified lines | |||
| 234 | const spec = parsed.success as unknown; | 220 | const spec = parsed.success as unknown; |
| 235 | if (!isRecord(spec)) return null; | 221 | if (!isRecord(spec)) return null; |
| 236 | const components = isRecord(spec.components) ? spec.components : null; | 222 | const components = isRecord(spec.components) ? spec.components : null; |
| 237 | const schemes = components && isRecord(components.securitySchemes) | 223 | const schemes = |
| 238 | ? components.securitySchemes | 224 | components && isRecord(components.securitySchemes) ? components.securitySchemes : null; |
| 239 | : null; | 225 | const scheme = |
| 240 | const scheme = schemes && isRecord(schemes[securitySchemeName]) | 226 | schemes && isRecord(schemes[securitySchemeName]) |
| 241 | ? (schemes[securitySchemeName] as Record<string, unknown>) | 227 | ? (schemes[securitySchemeName] as Record<string, unknown>) |
| 242 | : null; | 228 | : null; |
| 243 | const flows = scheme && isRecord(scheme.flows) ? scheme.flows : null; | 229 | const flows = scheme && isRecord(scheme.flows) ? scheme.flows : null; |
| 244 | const flowObj = flows && isRecord(flows.authorizationCode) | 230 | const flowObj = |
| 245 | ? (flows.authorizationCode as Record<string, unknown>) | 231 | flows && isRecord(flows.authorizationCode) |
| 246 | : null; | 232 | ? (flows.authorizationCode as Record<string, unknown>) |
| 247 | return flowObj && isString(flowObj.authorizationUrl) | 233 | : null; |
| 248 | ? flowObj.authorizationUrl | 234 | return flowObj && isString(flowObj.authorizationUrl) ? flowObj.authorizationUrl : null; |
| 249 | : null; | ||
| 250 | }; | 235 | }; |
| 251 | 236 | ||
| 252 | type OpenApiRow = { | 237 | type OpenApiRow = { |
| @@ -266,11 +251,7 @@ const migrateOpenApi = async (sqlite: Database): Promise<void> => { · 13 unmodified lines | |||
| 266 | // pointer. Pre-migration, both columns mirror each other; post- | 251 | // pointer. Pre-migration, both columns mirror each other; post- |
| 267 | // migration, only `oauth2` is left. We read both when available and | 252 | // migration, only `oauth2` is left. We read both when available and |
| 268 | // fall back to `oauth2` so legacy data isn't silently skipped. | 253 | // fall back to `oauth2` so legacy data isn't silently skipped. |
| 269 | const hasInvocationConfig = columnExists( | 254 | const hasInvocationConfig = columnExists(sqlite, "openapi_source", "invocation_config"); |
| 270 | sqlite, | ||
| 271 | "openapi_source", | ||
| 272 | "invocation_config", | ||
| 273 | ); | ||
| 274 | const selectCols = hasInvocationConfig | 255 | const selectCols = hasInvocationConfig |
| 275 | ? "scope_id, id, name, spec, invocation_config, oauth2" | 256 | ? "scope_id, id, name, spec, invocation_config, oauth2" |
| 276 | : "scope_id, id, name, spec, NULL AS invocation_config, oauth2"; | 257 | : "scope_id, id, name, spec, NULL AS invocation_config, oauth2"; |
| @@ -283,9 +264,7 @@ const migrateOpenApi = async (sqlite: Database): Promise<void> => { · 6 unmodified lines | |||
| 283 | ? sqlite.prepare( | 264 | ? sqlite.prepare( |
| 284 | "UPDATE openapi_source SET oauth2 = ?, invocation_config = ? WHERE scope_id = ? AND id = ?", | 265 | "UPDATE openapi_source SET oauth2 = ?, invocation_config = ? WHERE scope_id = ? AND id = ?", |
| 285 | ) | 266 | ) |
| 286 | : sqlite.prepare( | 267 | : sqlite.prepare("UPDATE openapi_source SET oauth2 = ? WHERE scope_id = ? AND id = ?"); |
| 287 | "UPDATE openapi_source SET oauth2 = ? WHERE scope_id = ? AND id = ?", | ||
| 288 | ); | ||
| 289 | 268 | ||
| 290 | for (const row of rows) { | 269 | for (const row of rows) { |
| 291 | let invocation: Record<string, unknown> = {}; | 270 | let invocation: Record<string, unknown> = {}; |
| @@ -370,19 +349,13 @@ const migrateOpenApi = async (sqlite: Database): Promise<void> => { · 78 unmodified lines | |||
| 370 | row.id, | 349 | row.id, |
| 371 | ); | 350 | ); |
| 372 | } else { | 351 | } else { |
| 373 | updateSource.run( | 352 | updateSource.run(JSON.stringify(oauth2Pointer), row.scope_id, row.id); |
| 374 | JSON.stringify(oauth2Pointer), | ||
| 375 | row.scope_id, | ||
| 376 | row.id, | ||
| 377 | ); | ||
| 378 | } | 353 | } |
| 379 | }); | 354 | }); |
| 380 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: bun:sqlite transaction reports rollback failures by throwing | 355 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: bun:sqlite transaction reports rollback failures by throwing |
| 381 | try { | 356 | try { |
| 382 | txn(); | 357 | txn(); |
| 383 | console.log( | 358 | console.log(`[migrate-connections] openapi ${row.scope_id}/${row.id} -> ${connectionId}`); |
| 384 | `[migrate-connections] openapi ${row.scope_id}/${row.id} -> ${connectionId}`, | ||
| 385 | ); | ||
| 386 | } catch (err) { | 359 | } catch (err) { |
| 387 | console.warn( | 360 | console.warn( |
| 388 | `[migrate-connections] fail openapi ${row.scope_id}/${row.id}: ${formatBoundaryError(err)}`, | 361 | `[migrate-connections] fail openapi ${row.scope_id}/${row.id}: ${formatBoundaryError(err)}`, |
| @@ -471,9 +444,7 @@ const migrateMcp = (sqlite: Database): void => { · 82 unmodified lines | |||
| 471 | 444 | ||
| 472 | const endpoint = typeof config.endpoint === "string" ? config.endpoint : null; | 445 | const endpoint = typeof config.endpoint === "string" ? config.endpoint : null; |
| 473 | if (!endpoint) { | 446 | if (!endpoint) { |
| 474 | console.warn( | 447 | console.warn(`[migrate-connections] skip mcp ${row.scope_id}/${row.id}: endpoint missing`); |
| 475 | `[migrate-connections] skip mcp ${row.scope_id}/${row.id}: endpoint missing`, | ||
| 476 | ); | ||
| 477 | continue; | 448 | continue; |
| 478 | } | 449 | } |
| 479 | const connectionId = `mcp-oauth2-${row.id}`; | 450 | const connectionId = `mcp-oauth2-${row.id}`; |
| @@ -518,12 +489,7 @@ const migrateMcp = (sqlite: Database): void => { · 38 unmodified lines | |||
| 518 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: bun:sqlite transaction callback must throw to roll back | 489 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: bun:sqlite transaction callback must throw to roll back |
| 519 | if (err) throw new Error(err); | 490 | if (err) throw new Error(err); |
| 520 | if (updateConfigAndAuth) { | 491 | if (updateConfigAndAuth) { |
| 521 | updateConfigAndAuth.run( | 492 | updateConfigAndAuth.run(JSON.stringify(nextConfig), connectionId, row.scope_id, row.id); |
| 522 | JSON.stringify(nextConfig), | ||
| 523 | connectionId, | ||
| 524 | row.scope_id, | ||
| 525 | row.id, | ||
| 526 | ); | ||
| 527 | } else { | 493 | } else { |
| 528 | updateConfig.run(JSON.stringify(nextConfig), row.scope_id, row.id); | 494 | updateConfig.run(JSON.stringify(nextConfig), row.scope_id, row.id); |
| 529 | } | 495 | } |
| @@ -531,9 +497,7 @@ const migrateMcp = (sqlite: Database): void => { · 1 unmodified line | |||
| 531 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: bun:sqlite transaction reports rollback failures by throwing | 497 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: bun:sqlite transaction reports rollback failures by throwing |
| 532 | try { | 498 | try { |
| 533 | txn(); | 499 | txn(); |
| 534 | console.log( | 500 | console.log(`[migrate-connections] mcp ${row.scope_id}/${row.id} -> ${connectionId}`); |
| 535 | `[migrate-connections] mcp ${row.scope_id}/${row.id} -> ${connectionId}`, | ||
| 536 | ); | ||
| 537 | } catch (err) { | 501 | } catch (err) { |
| 538 | console.warn( | 502 | console.warn( |
| 539 | `[migrate-connections] fail mcp ${row.scope_id}/${row.id}: ${formatBoundaryError(err)}`, | 503 | `[migrate-connections] fail mcp ${row.scope_id}/${row.id}: ${formatBoundaryError(err)}`, |
| @@ -57,9 +57,7 @@ const decodeTableInfoRows = Schema.decodeUnknownSync(Schema.Array(TableInfoRow)) · 56 unmodified lines | |||
| 57 | const decodeHeaderRows = Schema.decodeUnknownSync(Schema.Array(GraphqlHeaderRow)); | 57 | const decodeHeaderRows = Schema.decodeUnknownSync(Schema.Array(GraphqlHeaderRow)); |
| 58 | const decodeQueryParamRow = Schema.decodeUnknownSync(GraphqlQueryParamRow); | 58 | const decodeQueryParamRow = Schema.decodeUnknownSync(GraphqlQueryParamRow); |
| 59 | const decodeCountRow = Schema.decodeUnknownSync(CountRow); | 59 | const decodeCountRow = Schema.decodeUnknownSync(CountRow); |
| 60 | const decodeHeaderIdRows = Schema.decodeUnknownSync( | 60 | const decodeHeaderIdRows = Schema.decodeUnknownSync(Schema.Array(GraphqlHeaderIdRow)); |
| 61 | Schema.Array(GraphqlHeaderIdRow), | ||
| 62 | ); | ||
| 63 | 61 | ||
| 64 | let dir: string; | 62 | let dir: string; |
| 65 | 63 | ||
| @@ -96,19 +94,13 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 30 unmodified lines | |||
| 96 | const after = new Database(dbPath, { readonly: true }); | 94 | const after = new Database(dbPath, { readonly: true }); |
| 97 | const row = decodeAuthRow( | 95 | const row = decodeAuthRow( |
| 98 | after | 96 | after |
| 99 | .prepare( | 97 | .prepare("SELECT auth_kind, auth_connection_id FROM graphql_source WHERE id = ?") |
| 100 | "SELECT auth_kind, auth_connection_id FROM graphql_source WHERE id = ?", | ||
| 101 | ) | ||
| 102 | .get("github"), | 98 | .get("github"), |
| 103 | ); | 99 | ); |
| 104 | expect(row.auth_kind).toBe("oauth2"); | 100 | expect(row.auth_kind).toBe("oauth2"); |
| 105 | expect(row.auth_connection_id).toBe("conn-1"); | 101 | expect(row.auth_connection_id).toBe("conn-1"); |
| 106 | // Old json column is gone. | 102 | // Old json column is gone. |
| 107 | const cols = decodeTableInfoRows( | 103 | const cols = decodeTableInfoRows(after.prepare("PRAGMA table_info('graphql_source')").all()); |
| 108 | after | ||
| 109 | .prepare("PRAGMA table_info('graphql_source')") | ||
| 110 | .all(), | ||
| 111 | ); | ||
| 112 | expect(cols.some((c) => c.name === "auth")).toBe(false); | 104 | expect(cols.some((c) => c.name === "auth")).toBe(false); |
| 113 | expect(cols.some((c) => c.name === "headers")).toBe(false); | 105 | expect(cols.some((c) => c.name === "headers")).toBe(false); |
| 114 | expect(cols.some((c) => c.name === "query_params")).toBe(false); | 106 | expect(cols.some((c) => c.name === "query_params")).toBe(false); |
| @@ -180,9 +172,7 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 65 unmodified lines | |||
| 180 | 172 | ||
| 181 | const paramRow = decodeQueryParamRow( | 173 | const paramRow = decodeQueryParamRow( |
| 182 | after | 174 | after |
| 183 | .prepare( | 175 | .prepare("SELECT kind, secret_id FROM graphql_source_query_param WHERE source_id = ?") |
| 184 | "SELECT kind, secret_id FROM graphql_source_query_param WHERE source_id = ?", | ||
| 185 | ) | ||
| 186 | .get("example"), | 176 | .get("example"), |
| 187 | ); | 177 | ); |
| 188 | expect(paramRow).toMatchObject({ kind: "secret", secret_id: "sec-key" }); | 178 | expect(paramRow).toMatchObject({ kind: "secret", secret_id: "sec-key" }); |
| @@ -196,9 +186,12 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 7 unmodified lines | |||
| 196 | db.exec(PRE_0007_SQL); | 186 | db.exec(PRE_0007_SQL); |
| 197 | stampPriorMigrationsApplied(db); | 187 | stampPriorMigrationsApplied(db); |
| 198 | 188 | ||
| 199 | db.prepare( | 189 | db.prepare("INSERT INTO graphql_source (scope_id, id, name, endpoint) VALUES (?, ?, ?, ?)").run( |
| 200 | "INSERT INTO graphql_source (scope_id, id, name, endpoint) VALUES (?, ?, ?, ?)", | 190 | "default-scope", |
| 201 | ).run("default-scope", "bare", "Bare", "https://bare.example/graphql"); | 191 | "bare", |
| 192 | "Bare", | ||
| 193 | "https://bare.example/graphql", | ||
| 194 | ); | ||
| 202 | db.close(); | 195 | db.close(); |
| 203 | 196 | ||
| 204 | const drizzleDb = drizzle(new Database(dbPath)); | 197 | const drizzleDb = drizzle(new Database(dbPath)); |
| @@ -207,9 +200,7 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 2 unmodified lines | |||
| 207 | const after = new Database(dbPath, { readonly: true }); | 200 | const after = new Database(dbPath, { readonly: true }); |
| 208 | const row = decodeAuthRow( | 201 | const row = decodeAuthRow( |
| 209 | after | 202 | after |
| 210 | .prepare( | 203 | .prepare("SELECT auth_kind, auth_connection_id FROM graphql_source WHERE id = ?") |
| 211 | "SELECT auth_kind, auth_connection_id FROM graphql_source WHERE id = ?", | ||
| 212 | ) | ||
| 213 | .get("bare"), | 204 | .get("bare"), |
| 214 | ); | 205 | ); |
| 215 | expect(row.auth_kind).toBe("none"); | 206 | expect(row.auth_kind).toBe("none"); |
| @@ -217,9 +208,7 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 1 unmodified line | |||
| 217 | 208 | ||
| 218 | const headerCount = decodeCountRow( | 209 | const headerCount = decodeCountRow( |
| 219 | after | 210 | after |
| 220 | .prepare( | 211 | .prepare("SELECT count(*) as n FROM graphql_source_header WHERE source_id = ?") |
| 221 | "SELECT count(*) as n FROM graphql_source_header WHERE source_id = ?", | ||
| 222 | ) | ||
| 223 | .get("bare"), | 212 | .get("bare"), |
| 224 | ).n; | 213 | ).n; |
| 225 | expect(headerCount).toBe(0); | 214 | expect(headerCount).toBe(0); |
| @@ -283,9 +283,7 @@ describe("0007_normalize_plugin_secret_refs (openapi)", () => { · 282 unmodified lines | |||
| 283 | const after = openDatabase(dbPath, { readonly: true }); | 283 | const after = openDatabase(dbPath, { readonly: true }); |
| 284 | const qpCount = Schema.decodeUnknownSync(CountRow)( | 284 | const qpCount = Schema.decodeUnknownSync(CountRow)( |
| 285 | after | 285 | after |
| 286 | .prepare( | 286 | .prepare("SELECT count(*) as n FROM openapi_source_query_param WHERE source_id = ?") |
| 287 | "SELECT count(*) as n FROM openapi_source_query_param WHERE source_id = ?", | ||
| 288 | ) | ||
| 289 | .get("bare"), | 287 | .get("bare"), |
| 290 | ).n; | 288 | ).n; |
| 291 | expect(qpCount).toBe(0); | 289 | expect(qpCount).toBe(0); |
| @@ -24,7 +24,7 @@ export const ErrorCaptureLive: Layer.Layer<ErrorCapture> = Layer.succeed( · 23 unmodified lines | |||
| 24 | console.error( | 24 | console.error( |
| 25 | `[executor ${traceId}]`, | 25 | `[executor ${traceId}]`, |
| 26 | // oxlint-disable-next-line executor/no-instanceof-error -- boundary: console logger preserves native Error stack output | 26 | // oxlint-disable-next-line executor/no-instanceof-error -- boundary: console logger preserves native Error stack output |
| 27 | squashed instanceof Error ? squashed.stack ?? squashed : squashed, | 27 | squashed instanceof Error ? (squashed.stack ?? squashed) : squashed, |
| 28 | ); | 28 | ); |
| 29 | console.error(`[executor ${traceId}] cause:`, Cause.pretty(cause)); | 29 | console.error(`[executor ${traceId}] cause:`, Cause.pretty(cause)); |
| 30 | return traceId; | 30 | return traceId; |
| @@ -218,7 +218,9 @@ body { · 217 unmodified lines | |||
| 218 | text-decoration: underline; | 218 | text-decoration: underline; |
| 219 | text-decoration-color: color-mix(in srgb, var(--color-teal) 35%, transparent); | 219 | text-decoration-color: color-mix(in srgb, var(--color-teal) 35%, transparent); |
| 220 | text-underline-offset: 3px; | 220 | text-underline-offset: 3px; |
| 221 | transition: color 0.15s ease, text-decoration-color 0.15s ease; | 221 | transition: |
| 222 | color 0.15s ease, | ||
| 223 | text-decoration-color 0.15s ease; | ||
| 222 | } | 224 | } |
| 223 | 225 | ||
| 224 | .legal-prose a:hover { | 226 | .legal-prose a:hover { |
| @@ -230,4 +232,3 @@ body { · 5 unmodified lines | |||
| 230 | color: var(--color-ink); | 232 | color: var(--color-ink); |
| 231 | font-weight: 600; | 233 | font-weight: 600; |
| 232 | } | 234 | } |
| 233 | |||
| @@ -2,84 +2,84 @@ import { feature, item, plan } from "atmn"; · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | // Features | 3 | // Features |
| 4 | export const executions = feature({ | 4 | export const executions = feature({ |
| 5 | id: 'executions', | 5 | id: "executions", |
| 6 | name: 'Executions', | 6 | name: "Executions", |
| 7 | type: 'metered', | 7 | type: "metered", |
| 8 | consumable: true, | 8 | consumable: true, |
| 9 | }); | 9 | }); |
| 10 | 10 | ||
| 11 | export const domainVerification = feature({ | 11 | export const domainVerification = feature({ |
| 12 | id: 'domain-verification', | 12 | id: "domain-verification", |
| 13 | name: 'Domain Verification', | 13 | name: "Domain Verification", |
| 14 | type: 'boolean', | 14 | type: "boolean", |
| 15 | }); | 15 | }); |
| 16 | 16 | ||
| 17 | export const members = feature({ | 17 | export const members = feature({ |
| 18 | id: 'members', | 18 | id: "members", |
| 19 | name: 'Members', | 19 | name: "Members", |
| 20 | type: 'metered', | 20 | type: "metered", |
| 21 | consumable: false, | 21 | consumable: false, |
| 22 | archived: true, | 22 | archived: true, |
| 23 | }); | 23 | }); |
| 24 | 24 | ||
| 25 | // Plans | 25 | // Plans |
| 26 | export const free = plan({ | 26 | export const free = plan({ |
| 27 | id: 'free', | 27 | id: "free", |
| 28 | name: 'Free', | 28 | name: "Free", |
| 29 | addOn: false, | 29 | addOn: false, |
| 30 | autoEnable: true, | 30 | autoEnable: true, |
| 31 | items: [ | 31 | items: [ |
| 32 | item({ | 32 | item({ |
| 33 | featureId: executions.id, | 33 | featureId: executions.id, |
| 34 | included: 10000, | 34 | included: 10000, |
| 35 | reset: { | 35 | reset: { |
| 36 | interval: 'month', | 36 | interval: "month", |
| 37 | }, | 37 | }, |
| 38 | }), | 38 | }), |
| 39 | ], | 39 | ], |
| 40 | }); | 40 | }); |
| 41 | 41 | ||
| 42 | export const freePayAsYouGo = plan({ | 42 | export const freePayAsYouGo = plan({ |
| 43 | id: 'free-pay-as-you-go', | 43 | id: "free-pay-as-you-go", |
| 44 | name: 'Free Pay As You Go', | 44 | name: "Free Pay As You Go", |
| 45 | addOn: false, | 45 | addOn: false, |
| 46 | autoEnable: false, | 46 | autoEnable: false, |
| 47 | items: [ | 47 | items: [ |
| 48 | item({ | 48 | item({ |
| 49 | featureId: executions.id, | 49 | featureId: executions.id, |
| 50 | included: 10000, | 50 | included: 10000, |
| 51 | price: { | 51 | price: { |
| 52 | amount: 0.2, | 52 | amount: 0.2, |
| 53 | billingUnits: 1000, | 53 | billingUnits: 1000, |
| 54 | billingMethod: 'usage_based', | 54 | billingMethod: "usage_based", |
| 55 | interval: 'month', | 55 | interval: "month", |
| 56 | }, | 56 | }, |
| 57 | }), | 57 | }), |
| 58 | ], | 58 | ], |
| 59 | }); | 59 | }); |
| 60 | 60 | ||
| 61 | export const team = plan({ | 61 | export const team = plan({ |
| 62 | id: 'team', | 62 | id: "team", |
| 63 | name: 'Team', | 63 | name: "Team", |
| 64 | addOn: false, | 64 | addOn: false, |
| 65 | autoEnable: false, | 65 | autoEnable: false, |
| 66 | price: { | 66 | price: { |
| 67 | amount: 49, | 67 | amount: 49, |
| 68 | interval: 'month', | 68 | interval: "month", |
| 69 | }, | 69 | }, |
| 70 | items: [ | 70 | items: [ |
| 71 | item({ | 71 | item({ |
| 72 | featureId: executions.id, | 72 | featureId: executions.id, |
| 73 | included: 250000, | 73 | included: 250000, |
| 74 | price: { | 74 | price: { |
| 75 | amount: 0.2, | 75 | amount: 0.2, |
| 76 | billingUnits: 1000, | 76 | billingUnits: 1000, |
| 77 | billingMethod: 'usage_based', | 77 | billingMethod: "usage_based", |
| 78 | interval: 'month', | 78 | interval: "month", |
| 79 | }, | 79 | }, |
| 80 | }), | 80 | }), |
| 81 | item({ | 81 | item({ |
| 82 | featureId: domainVerification.id, | 82 | featureId: domainVerification.id, |
| 83 | }), | 83 | }), |
| 84 | ], | 84 | ], |
| 85 | }); | 85 | }); |
| @@ -141,17 +141,13 @@ const exampleOpenApiSpec = JSON.stringify({ · 140 unmodified lines | |||
| 141 | get: { | 141 | get: { |
| 142 | operationId: "items.get", | 142 | operationId: "items.get", |
| 143 | tags: ["items"], | 143 | tags: ["items"], |
| 144 | parameters: [ | 144 | parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], |
| 145 | { name: "id", in: "path", required: true, schema: { type: "string" } }, | ||
| 146 | ], | ||
| 147 | responses: { "200": { description: "ok" } }, | 145 | responses: { "200": { description: "ok" } }, |
| 148 | }, | 146 | }, |
| 149 | delete: { | 147 | delete: { |
| 150 | operationId: "items.delete", | 148 | operationId: "items.delete", |
| 151 | tags: ["items"], | 149 | tags: ["items"], |
| 152 | parameters: [ | 150 | parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], |
| 153 | { name: "id", in: "path", required: true, schema: { type: "string" } }, | ||
| 154 | ], | ||
| 155 | responses: { "204": { description: "deleted" } }, | 151 | responses: { "204": { description: "deleted" } }, |
| 156 | }, | 152 | }, |
| 157 | }, | 153 | }, |
| @@ -400,10 +396,7 @@ const program = Effect.gen(function* () { · 242 unmodified lines | |||
| 400 | console.log(" executor.keychain.isSupported:", executor.keychain.isSupported); | 396 | console.log(" executor.keychain.isSupported:", executor.keychain.isSupported); |
| 401 | console.log(" executor.keychain.displayName:", executor.keychain.displayName); | 397 | console.log(" executor.keychain.displayName:", executor.keychain.displayName); |
| 402 | 398 | ||
| 403 | console.log( | 399 | console.log(" executor.fileSecrets.filePath: ", executor.fileSecrets.filePath); |
| 404 | " executor.fileSecrets.filePath: ", | ||
| 405 | executor.fileSecrets.filePath, | ||
| 406 | ); | ||
| 407 | 400 | ||
| 408 | // executor.mcp.addSource({ connector: { kind: "remote", endpoint: "..." } }); | 401 | // executor.mcp.addSource({ connector: { kind: "remote", endpoint: "..." } }); |
| 409 | // executor.googleDiscovery.addSource({ discoveryUrl: "..." }); | 402 | // executor.googleDiscovery.addSource({ discoveryUrl: "..." }); |
| @@ -64,13 +64,7 @@ Do not make this workflow-specific. Model the generic thing as an artifact · 63 unmodified lines | |||
| 64 | project, with workflows as the first consumer. | 64 | project, with workflows as the first consumer. |
| 65 | 65 | ||
| 66 | ```ts | 66 | ```ts |
| 67 | type ArtifactKind = | 67 | type ArtifactKind = "workflow" | "component" | "ui" | "template" | "connector" | "document"; |
| 68 | | "workflow" | ||
| 69 | | "component" | ||
| 70 | | "ui" | ||
| 71 | | "template" | ||
| 72 | | "connector" | ||
| 73 | | "document"; | ||
| 74 | 68 | ||
| 75 | type ArtifactProject = { | 69 | type ArtifactProject = { |
| 76 | id: string; | 70 | id: string; |
| @@ -171,18 +165,13 @@ export interface ArtifactStore { · 94 unmodified lines | |||
| 171 | input: CreateArtifactProjectInput, | 165 | input: CreateArtifactProjectInput, |
| 172 | ) => Effect.Effect<ArtifactProject, ArtifactError>; | 166 | ) => Effect.Effect<ArtifactProject, ArtifactError>; |
| 173 | 167 | ||
| 174 | readonly readFile: ( | 168 | readonly readFile: (ref: ArtifactRef, path: string) => Effect.Effect<Uint8Array, ArtifactError>; |
| 175 | ref: ArtifactRef, | ||
| 176 | path: string, | ||
| 177 | ) => Effect.Effect<Uint8Array, ArtifactError>; | ||
| 178 | 169 | ||
| 179 | readonly writeFiles: ( | 170 | readonly writeFiles: ( |
| 180 | input: WriteArtifactFilesInput, | 171 | input: WriteArtifactFilesInput, |
| 181 | ) => Effect.Effect<ArtifactCommit, ArtifactError>; | 172 | ) => Effect.Effect<ArtifactCommit, ArtifactError>; |
| 182 | 173 | ||
| 183 | readonly diff: ( | 174 | readonly diff: (input: ArtifactDiffInput) => Effect.Effect<ArtifactDiff, ArtifactError>; |
| 184 | input: ArtifactDiffInput, | ||
| 185 | ) => Effect.Effect<ArtifactDiff, ArtifactError>; | ||
| 186 | 175 | ||
| 187 | readonly forkProject: ( | 176 | readonly forkProject: ( |
| 188 | input: ForkArtifactProjectInput, | 177 | input: ForkArtifactProjectInput, |
| @@ -365,10 +354,7 @@ export interface ArtifactStore { · 176 unmodified lines | |||
| 365 | name: string; | 354 | name: string; |
| 366 | }) => Effect.Effect<ArtifactProject, ArtifactError>; | 355 | }) => Effect.Effect<ArtifactProject, ArtifactError>; |
| 367 | 356 | ||
| 368 | readonly readFile: ( | 357 | readonly readFile: (ref: ArtifactRef, path: string) => Effect.Effect<Uint8Array, ArtifactError>; |
| 369 | ref: ArtifactRef, | ||
| 370 | path: string, | ||
| 371 | ) => Effect.Effect<Uint8Array, ArtifactError>; | ||
| 372 | 358 | ||
| 373 | readonly writeFiles: (input: { | 359 | readonly writeFiles: (input: { |
| 374 | projectId: string; | 360 | projectId: string; |
| @@ -422,10 +408,7 @@ Feature plugin: · 47 unmodified lines | |||
| 422 | ```ts | 408 | ```ts |
| 423 | // @executor/workflows | 409 | // @executor/workflows |
| 424 | import { definePlugin, type PluginCtx } from "@executor/sdk"; | 410 | import { definePlugin, type PluginCtx } from "@executor/sdk"; |
| 425 | import { | 411 | import { ArtifactStoreProtocol, type ArtifactsProtocolCtx } from "@executor/artifacts"; |
| 426 | ArtifactStoreProtocol, | ||
| 427 | type ArtifactsProtocolCtx, | ||
| 428 | } from "@executor/artifacts"; | ||
| 429 | 412 | ||
| 430 | type WorkflowCtx<TStore> = PluginCtx<TStore, ArtifactsProtocolCtx>; | 413 | type WorkflowCtx<TStore> = PluginCtx<TStore, ArtifactsProtocolCtx>; |
| 431 | 414 | ||
| @@ -546,56 +529,47 @@ export async function summarizeTicket(input: SupportEmail) { · 114 unmodified lines | |||
| 546 | E2E flow: | 529 | E2E flow: |
| 547 | 530 | ||
| 548 | ```ts | 531 | ```ts |
| 549 | it.effect( | 532 | it.effect("creates a code-first workflow artifact and indexes a visual graph", () => |
| 550 | "creates a code-first workflow artifact and indexes a visual graph", | 533 | Effect.gen(function* () { |
| 551 | () => | 534 | const executor = yield* createExecutor({ |
| 552 | Effect.gen(function* () { | 535 | scopes: [testScope], |
| 553 | const executor = yield* createExecutor({ | 536 | adapter, |
| 554 | scopes: [testScope], | 537 | blobs, |
| 555 | adapter, | 538 | plugins: [artifactsLocalGitPlugin({ rootDir: tempDir }), workflowsPlugin()] as const, |
| 556 | blobs, | 539 | protocols: { |
| 557 | plugins: [ | 540 | "executor.artifacts.store": "artifacts-local-git", |
| 558 | artifactsLocalGitPlugin({ rootDir: tempDir }), | 541 | }, |
| 559 | workflowsPlugin(), | 542 | }); |
| 560 | ] as const, | 543 | |
| 561 | protocols: { | 544 | const created = yield* executor.workflows.create({ |
| 562 | "executor.artifacts.store": "artifacts-local-git", | 545 | name: "Support triage", |
| 546 | files: [ | ||
| 547 | { | ||
| 548 | type: "put", | ||
| 549 | path: "artifact.json", | ||
| 550 | contents: JSON.stringify({ | ||
| 551 | schemaVersion: 1, | ||
| 552 | kind: "workflow", | ||
| 553 | entrypoints: ["src/workflows/support-triage.workflow.ts"], | ||
| 554 | }), | ||
| 555 | }, | ||
| 556 | { | ||
| 557 | type: "put", | ||
| 558 | path: "src/workflows/support-triage.workflow.ts", | ||
| 559 | contents: supportTriageWorkflowSource, | ||
| 563 | }, | 560 | }, |
| 564 | }); | 561 | ], |
| 565 | 562 | }); | |
| 566 | const created = yield* executor.workflows.create({ | 563 | |
| 567 | name: "Support triage", | 564 | const source = yield* executor.artifacts.readFile( |
| 568 | files: [ | 565 | { projectId: created.project.id, ref: created.commit.id }, |
| 569 | { | 566 | "src/workflows/support-triage.workflow.ts", |
| 570 | type: "put", | 567 | ); |
| 571 | path: "artifact.json", | 568 | expect(new TextDecoder().decode(source)).toContain('"use workflow"'); |
| 572 | contents: JSON.stringify({ | 569 | |
| 573 | schemaVersion: 1, | 570 | expect(created.manifest.entrypoints).toEqual(["src/workflows/support-triage.workflow.ts"]); |
| 574 | kind: "workflow", | 571 | expect(created.manifest.graph.nodes.map((node) => node.kind)).toContain("step"); |
| 575 | entrypoints: ["src/workflows/support-triage.workflow.ts"], | 572 | }), |
| 576 | }), | ||
| 577 | }, | ||
| 578 | { | ||
| 579 | type: "put", | ||
| 580 | path: "src/workflows/support-triage.workflow.ts", | ||
| 581 | contents: supportTriageWorkflowSource, | ||
| 582 | }, | ||
| 583 | ], | ||
| 584 | }); | ||
| 585 | |||
| 586 | const source = yield* executor.artifacts.readFile( | ||
| 587 | { projectId: created.project.id, ref: created.commit.id }, | ||
| 588 | "src/workflows/support-triage.workflow.ts", | ||
| 589 | ); | ||
| 590 | expect(new TextDecoder().decode(source)).toContain('"use workflow"'); | ||
| 591 | |||
| 592 | expect(created.manifest.entrypoints).toEqual([ | ||
| 593 | "src/workflows/support-triage.workflow.ts", | ||
| 594 | ]); | ||
| 595 | expect(created.manifest.graph.nodes.map((node) => node.kind)).toContain( | ||
| 596 | "step", | ||
| 597 | ); | ||
| 598 | }), | ||
| 599 | ); | 573 | ); |
| 600 | ``` | 574 | ``` |
| 601 | 575 | ||
| @@ -89,7 +89,7 @@ Surveyed five plugin systems in `.reference/`: · 88 unmodified lines | |||
| 89 | - **emdash** — **closest match to our setup.** Astro/Vite + React, plugins are | 89 | - **emdash** — **closest match to our setup.** Astro/Vite + React, plugins are |
| 90 | npm packages with separate `./` and `./admin` exports, registered via | 90 | npm packages with separate `./` and `./admin` exports, registered via |
| 91 | import-and-call in `astro.config.mjs`. `definePlugin({ id, hooks, routes, | 91 | import-and-call in `astro.config.mjs`. `definePlugin({ id, hooks, routes, |
| 92 | admin })` declarative shape. | 92 | admin })` declarative shape. |
| 93 | - **dynamic-software** — most ambitious; Cloudflare Worker Loader for cloud | 93 | - **dynamic-software** — most ambitious; Cloudflare Worker Loader for cloud |
| 94 | isolation, iframe + postMessage RPC for UI, Proxy-based typed API client. | 94 | isolation, iframe + postMessage RPC for UI, Proxy-based typed API client. |
| 95 | 95 | ||
| @@ -109,10 +109,10 @@ inference flows naturally; no codegen step. · 13 unmodified lines | |||
| 109 | 109 | ||
| 110 | ```ts | 110 | ```ts |
| 111 | // apps/local/executor.config.ts (after) | 111 | // apps/local/executor.config.ts (after) |
| 112 | import { defineExecutorConfig } from "@executor-js/sdk" | 112 | import { defineExecutorConfig } from "@executor-js/sdk"; |
| 113 | import { openApiPlugin } from "@executor-js/plugin-openapi" | 113 | import { openApiPlugin } from "@executor-js/plugin-openapi"; |
| 114 | import { mcpPlugin } from "@executor-js/plugin-mcp" | 114 | import { mcpPlugin } from "@executor-js/plugin-mcp"; |
| 115 | import { fileSecretsPlugin } from "@executor-js/plugin-file-secrets" | 115 | import { fileSecretsPlugin } from "@executor-js/plugin-file-secrets"; |
| 116 | 116 | ||
| 117 | export default defineExecutorConfig({ | 117 | export default defineExecutorConfig({ |
| 118 | dialect: "sqlite", | 118 | dialect: "sqlite", |
| @@ -121,7 +121,7 @@ export default defineExecutorConfig({ · 2 unmodified lines | |||
| 121 | mcpPlugin({ dangerouslyAllowStdioMCP: true }), | 121 | mcpPlugin({ dangerouslyAllowStdioMCP: true }), |
| 122 | fileSecretsPlugin(), | 122 | fileSecretsPlugin(), |
| 123 | ] as const, | 123 | ] as const, |
| 124 | }) | 124 | }); |
| 125 | ``` | 125 | ``` |
| 126 | 126 | ||
| 127 | "Dynamic" means npm-installable, not load-by-string-name. Same model as emdash. | 127 | "Dynamic" means npm-installable, not load-by-string-name. Same model as emdash. |
| @@ -137,12 +137,12 @@ config becomes a need. · 9 unmodified lines | |||
| 137 | "type": "module", | 137 | "type": "module", |
| 138 | "exports": { | 138 | "exports": { |
| 139 | "./server": "./dist/server.js", | 139 | "./server": "./dist/server.js", |
| 140 | "./client": "./dist/client.js" | 140 | "./client": "./dist/client.js", |
| 141 | }, | 141 | }, |
| 142 | "executor": { | 142 | "executor": { |
| 143 | "id": "foo", | 143 | "id": "foo", |
| 144 | "version": "0.1.0" | 144 | "version": "0.1.0", |
| 145 | } | 145 | }, |
| 146 | } | 146 | } |
| 147 | ``` | 147 | ``` |
| 148 | 148 | ||
| @@ -171,7 +171,7 @@ Client side: separate primitive `defineClientPlugin` lives in · 22 unmodified lines | |||
| 171 | React types never leak into server bundles. | 171 | React types never leak into server bundles. |
| 172 | 172 | ||
| 173 | **Layering — extension is the canonical SDK; routes/handlers is optional HTTP transport.** | 173 | **Layering — extension is the canonical SDK; routes/handlers is optional HTTP transport.** |
| 174 | The HTTP layer is *not a peer* of the SDK; it's a transport over it. Plugin | 174 | The HTTP layer is _not a peer_ of the SDK; it's a transport over it. Plugin |
| 175 | authors should treat extension as the implementation and write handlers as | 175 | authors should treat extension as the implementation and write handlers as |
| 176 | thin wrappers that delegate via the `self` parameter (same pattern as | 176 | thin wrappers that delegate via the `self` parameter (same pattern as |
| 177 | `staticSources(self) => [...]` already in the codebase). This keeps: | 177 | `staticSources(self) => [...]` already in the codebase). This keeps: |
| @@ -188,7 +188,7 @@ Three plugin shapes fall out of this layering: · 10 unmodified lines | |||
| 188 | utility for other plugins or scripts. CLI/embedded consumers use | 188 | utility for other plugins or scripts. CLI/embedded consumers use |
| 189 | `executor.<id>.method()`. Vite plugin notices no `./client` and skips the | 189 | `executor.<id>.method()`. Vite plugin notices no `./client` and skips the |
| 190 | plugin in the frontend bundle entirely. | 190 | plugin in the frontend bundle entirely. |
| 191 | - **Both.** Extension *and* routes/handlers *and* a `./client`. Examples: | 191 | - **Both.** Extension _and_ routes/handlers _and_ a `./client`. Examples: |
| 192 | openapi, mcp, anything that needs a frontend. Routes are thin wrappers | 192 | openapi, mcp, anything that needs a frontend. Routes are thin wrappers |
| 193 | over extension methods. | 193 | over extension methods. |
| 194 | - **HTTP-only.** Rare — webhook receivers, OAuth callback URLs. Routes | 194 | - **HTTP-only.** Rare — webhook receivers, OAuth callback URLs. Routes |
| @@ -214,7 +214,7 @@ Plugin authors can write a complete plugin importing only from · 19 unmodified lines | |||
| 214 | `@executor-js/sdk` (server) and `@executor-js/sdk/client` (frontend). The SDK | 214 | `@executor-js/sdk` (server) and `@executor-js/sdk/client` (frontend). The SDK |
| 215 | re-exports `Schema`, `HttpApi`, `HttpApiGroup`, `HttpApiEndpoint`, | 215 | re-exports `Schema`, `HttpApi`, `HttpApiGroup`, `HttpApiEndpoint`, |
| 216 | `HttpApiBuilder`, `Effect` so authors who don't want to dig into Effect | 216 | `HttpApiBuilder`, `Effect` so authors who don't want to dig into Effect |
| 217 | don't have to. Authors who *do* want Effect-native code keep importing from | 217 | don't have to. Authors who _do_ want Effect-native code keep importing from |
| 218 | `effect` directly. This keeps the door open without forcing the dependency. | 218 | `effect` directly. This keeps the door open without forcing the dependency. |
| 219 | 219 | ||
| 220 | ### 6. Skip `capabilities` declaration for v1 | 220 | ### 6. Skip `capabilities` declaration for v1 |
| @@ -247,7 +247,7 @@ already handles this exact pattern for secrets and works fine: · 26 unmodified lines | |||
| 247 | secretProviders: (ctx) => [makeScopedProvider(...)] | 247 | secretProviders: (ctx) => [makeScopedProvider(...)] |
| 248 | ``` | 248 | ``` |
| 249 | 249 | ||
| 250 | Each new pluggable capability gets its *own* typed field on `PluginSpec`, | 250 | Each new pluggable capability gets its _own_ typed field on `PluginSpec`, |
| 251 | same shape. When artifacts lands, that's `artifactStore: () => | 251 | same shape. When artifacts lands, that's `artifactStore: () => |
| 252 | ArtifactStore`. If connection providers want to be modeled this way, the | 252 | ArtifactStore`. If connection providers want to be modeled this way, the |
| 253 | existing `connectionProviders` field already is. | 253 | existing `connectionProviders` field already is. |
| @@ -335,24 +335,21 @@ server both import it: · 81 unmodified lines | |||
| 335 | 335 | ||
| 336 | ```ts | 336 | ```ts |
| 337 | // @executor-js/plugin-foo/src/shared.ts | 337 | // @executor-js/plugin-foo/src/shared.ts |
| 338 | import { HttpApiEndpoint, HttpApiGroup, Schema } from "@executor-js/sdk" | 338 | import { HttpApiEndpoint, HttpApiGroup, Schema } from "@executor-js/sdk"; |
| 339 | 339 | ||
| 340 | export const Thing = Schema.Struct({ | 340 | export const Thing = Schema.Struct({ |
| 341 | id: Schema.String, | 341 | id: Schema.String, |
| 342 | name: Schema.String, | 342 | name: Schema.String, |
| 343 | }) | 343 | }); |
| 344 | 344 | ||
| 345 | export const FooApi = HttpApiGroup.make("foo") | 345 | export const FooApi = HttpApiGroup.make("foo") |
| 346 | .add( | 346 | .add(HttpApiEndpoint.get("listThings")`/things`.addSuccess(Schema.Array(Thing))) |
| 347 | HttpApiEndpoint.get("listThings")`/things` | ||
| 348 | .addSuccess(Schema.Array(Thing)), | ||
| 349 | ) | ||
| 350 | .add( | 347 | .add( |
| 351 | HttpApiEndpoint.post("syncThing")`/sync/:id` | 348 | HttpApiEndpoint.post("syncThing")`/sync/:id` |
| 352 | .setPath(Schema.Struct({ id: Schema.String })) | 349 | .setPath(Schema.Struct({ id: Schema.String })) |
| 353 | .addSuccess(Thing) | 350 | .addSuccess(Thing) |
| 354 | .addError(SyncError), | 351 | .addError(SyncError), |
| 355 | ) | 352 | ); |
| 356 | ``` | 353 | ``` |
| 357 | 354 | ||
| 358 | ```ts | 355 | ```ts |
| @@ -429,49 +426,47 @@ Lives in `@executor-js/sdk/client`. Server bundles cannot import this module. · 70 unmodified lines | |||
| 429 | ```ts | 426 | ```ts |
| 430 | // packages/core/sdk/src/client.ts | 427 | // packages/core/sdk/src/client.ts |
| 431 | export interface ClientPluginSpec<TId extends string = string> { | 428 | export interface ClientPluginSpec<TId extends string = string> { |
| 432 | readonly id: TId | 429 | readonly id: TId; |
| 433 | 430 | ||
| 434 | /** Pages contributed to the host's TanStack router. Mounted under | 431 | /** Pages contributed to the host's TanStack router. Mounted under |
| 435 | * /plugins/{id}/{path}. Sidebar nav metadata declared on the route. */ | 432 | * /plugins/{id}/{path}. Sidebar nav metadata declared on the route. */ |
| 436 | readonly pages?: readonly PageDecl[] | 433 | readonly pages?: readonly PageDecl[]; |
| 437 | 434 | ||
| 438 | /** Dashboard / overview widgets the host can render in known slots. */ | 435 | /** Dashboard / overview widgets the host can render in known slots. */ |
| 439 | readonly widgets?: readonly WidgetDecl[] | 436 | readonly widgets?: readonly WidgetDecl[]; |
| 440 | 437 | ||
| 441 | /** Components the host can render in named slots (e.g., source-detail | 438 | /** Components the host can render in named slots (e.g., source-detail |
| 442 | * panels, secret-picker variants). Slot names are part of the host | 439 | * panels, secret-picker variants). Slot names are part of the host |
| 443 | * contract — plugin opts in by registering. */ | 440 | * contract — plugin opts in by registering. */ |
| 444 | readonly slots?: Record<string, SlotComponent> | 441 | readonly slots?: Record<string, SlotComponent>; |
| 445 | 442 | ||
| 446 | /** Source-plugin contribution — populated by plugins that expose | 443 | /** Source-plugin contribution — populated by plugins that expose |
| 447 | * `kind` rows in the core `source` table (openapi, mcp, graphql, | 444 | * `kind` rows in the core `source` table (openapi, mcp, graphql, |
| 448 | * google-discovery). The host's sources page derives its provider | 445 | * google-discovery). The host's sources page derives its provider |
| 449 | * list from the union of every loaded plugin's `sourcePlugin` | 446 | * list from the union of every loaded plugin's `sourcePlugin` |
| 450 | * via `useSourcePlugins()`. */ | 447 | * via `useSourcePlugins()`. */ |
| 451 | readonly sourcePlugin?: SourcePlugin | 448 | readonly sourcePlugin?: SourcePlugin; |
| 452 | 449 | ||
| 453 | /** Secret-provider-plugin contribution — populated by plugins that | 450 | /** Secret-provider-plugin contribution — populated by plugins that |
| 454 | * also ship a `secretProviders` server-side capability AND want to | 451 | * also ship a `secretProviders` server-side capability AND want to |
| 455 | * expose a settings card on the host's secrets page. Surfaced via | 452 | * expose a settings card on the host's secrets page. Surfaced via |
| 456 | * `useSecretProviderPlugins()`. */ | 453 | * `useSecretProviderPlugins()`. */ |
| 457 | readonly secretProviderPlugin?: SecretProviderPlugin | 454 | readonly secretProviderPlugin?: SecretProviderPlugin; |
| 458 | } | 455 | } |
| 459 | 456 | ||
| 460 | type PageDecl = { | 457 | type PageDecl = { |
| 461 | path: string // "/", "/edit/$id" | 458 | path: string; // "/", "/edit/$id" |
| 462 | component: ComponentType | 459 | component: ComponentType; |
| 463 | nav?: { label: string; section?: string } | 460 | nav?: { label: string; section?: string }; |
| 464 | } | 461 | }; |
| 465 | 462 | ||
| 466 | type WidgetDecl = { | 463 | type WidgetDecl = { |
| 467 | id: string | 464 | id: string; |
| 468 | component: ComponentType<WidgetProps> | 465 | component: ComponentType<WidgetProps>; |
| 469 | size?: "half" | "full" | 466 | size?: "half" | "full"; |
| 470 | } | 467 | }; |
| 471 | 468 | ||
| 472 | export const defineClientPlugin = <TId extends string>( | 469 | export const defineClientPlugin = <TId extends string>(spec: ClientPluginSpec<TId>) => spec; |
| 473 | spec: ClientPluginSpec<TId>, | ||
| 474 | ) => spec | ||
| 475 | ``` | 470 | ``` |
| 476 | 471 | ||
| 477 | ### `createPluginAtomClient` — typed reactive client per plugin | 472 | ### `createPluginAtomClient` — typed reactive client per plugin |
| @@ -483,24 +478,21 @@ authors write one line per atom. Same shape as the existing · 5 unmodified lines | |||
| 483 | 478 | ||
| 484 | ```ts | 479 | ```ts |
| 485 | // packages/core/sdk/src/client.ts (helper) | 480 | // packages/core/sdk/src/client.ts (helper) |
| 486 | import { HttpApi } from "effect/unstable/httpapi" | 481 | import { HttpApi } from "effect/unstable/httpapi"; |
| 487 | import { FetchHttpClient } from "effect/unstable/http" | 482 | import { FetchHttpClient } from "effect/unstable/http"; |
| 488 | import * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi" | 483 | import * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi"; |
| 489 | 484 | ||
| 490 | export const createPluginAtomClient = <G extends HttpApiGroup.Any>( | 485 | export const createPluginAtomClient = <G extends HttpApiGroup.Any>( |
| 491 | group: G, | 486 | group: G, |
| 492 | opts: { pluginId: string }, | 487 | opts: { pluginId: string }, |
| 493 | ) => { | 488 | ) => { |
| 494 | const bundle = HttpApi.make(`plugin-${opts.pluginId}`).add(group) | 489 | const bundle = HttpApi.make(`plugin-${opts.pluginId}`).add(group); |
| 495 | return AtomHttpApi.Service<`Plugin_${string}Client`>()( | 490 | return AtomHttpApi.Service<`Plugin_${string}Client`>()(`Plugin_${opts.pluginId}Client`, { |
| 496 | `Plugin_${opts.pluginId}Client`, | 491 | api: bundle, |
| 497 | { | 492 | httpClient: FetchHttpClient.layer, |
| 498 | api: bundle, | 493 | baseUrl: `/_executor/plugins/${opts.pluginId}`, |
| 499 | httpClient: FetchHttpClient.layer, | 494 | }); |
| 500 | baseUrl: `/_executor/plugins/${opts.pluginId}`, | 495 | }; |
| 501 | }, | ||
| 502 | ) | ||
| 503 | } | ||
| 504 | ``` | 496 | ``` |
| 505 | 497 | ||
| 506 | Plugin author writes: | 498 | Plugin author writes: |
| @@ -513,39 +505,34 @@ import { · 6 unmodified lines | |||
| 513 | useAtomValue, | 505 | useAtomValue, |
| 514 | useAtomSet, | 506 | useAtomSet, |
| 515 | AsyncResult, | 507 | AsyncResult, |
| 516 | } from "@executor-js/sdk/client" | 508 | } from "@executor-js/sdk/client"; |
| 517 | import { FooApi } from "./shared" | 509 | import { FooApi } from "./shared"; |
| 518 | 510 | ||
| 519 | const FooClient = createPluginAtomClient(FooApi, { pluginId: "foo" }) | 511 | const FooClient = createPluginAtomClient(FooApi, { pluginId: "foo" }); |
| 520 | 512 | ||
| 521 | export const fooThingsAtom = FooClient.query("foo", "listThings", { | 513 | export const fooThingsAtom = FooClient.query("foo", "listThings", { |
| 522 | timeToLive: "30 seconds", | 514 | timeToLive: "30 seconds", |
| 523 | reactivityKeys: ["foo:things"], | 515 | reactivityKeys: ["foo:things"], |
| 524 | }) | 516 | }); |
| 525 | 517 | ||
| 526 | export const fooSync = FooClient.mutation("foo", "syncThing") | 518 | export const fooSync = FooClient.mutation("foo", "syncThing"); |
| 527 | 519 | ||
| 528 | const FooPage = () => { | 520 | const FooPage = () => { |
| 529 | const things = useAtomValue(fooThingsAtom) | 521 | const things = useAtomValue(fooThingsAtom); |
| 530 | const doSync = useAtomSet(fooSync, { mode: "promise" }) | 522 | const doSync = useAtomSet(fooSync, { mode: "promise" }); |
| 531 | 523 | ||
| 532 | return AsyncResult.match(things, { | 524 | return AsyncResult.match(things, { |
| 533 | onInitial: () => <Skeleton />, | 525 | onInitial: () => <Skeleton />, |
| 534 | onFailure: () => <p>Failed to load</p>, | 526 | onFailure: () => <p>Failed to load</p>, |
| 535 | onSuccess: ({ value }) => ( | 527 | onSuccess: ({ value }) => <Table rows={value} onSync={(id) => doSync({ path: { id } })} />, |
| 536 | <Table | 528 | }); |
| 537 | rows={value} | 529 | }; |
| 538 | onSync={(id) => doSync({ path: { id } })} | ||
| 539 | /> | ||
| 540 | ), | ||
| 541 | }) | ||
| 542 | } | ||
| 543 | 530 | ||
| 544 | export default defineClientPlugin({ | 531 | export default defineClientPlugin({ |
| 545 | id: "foo" as const, | 532 | id: "foo" as const, |
| 546 | pages: [{ path: "/", component: FooPage, nav: { label: "Foo" } }], | 533 | pages: [{ path: "/", component: FooPage, nav: { label: "Foo" } }], |
| 547 | widgets: [{ id: "foo-status", component: FooStatus, size: "half" }], | 534 | widgets: [{ id: "foo-status", component: FooStatus, size: "half" }], |
| 548 | }) | 535 | }); |
| 549 | ``` | 536 | ``` |
| 550 | 537 | ||
| 551 | Type inference: `FooClient.query("foo", "listThings", ...)` is fully typed | 538 | Type inference: `FooClient.query("foo", "listThings", ...)` is fully typed |
| @@ -608,15 +595,17 @@ Single config, two consumers. · 56 unmodified lines | |||
| 608 | 595 | ||
| 609 | ```ts | 596 | ```ts |
| 610 | // apps/local/src/server/executor.ts (after) | 597 | // apps/local/src/server/executor.ts (after) |
| 611 | import config from "../../executor.config.ts" | 598 | import config from "../../executor.config.ts"; |
| 612 | 599 | ||
| 613 | const executor = yield* createExecutor({ | 600 | const executor = |
| 614 | scopes: [scope], | 601 | yield * |
| 615 | adapter, | 602 | createExecutor({ |
| 616 | blobs, | 603 | scopes: [scope], |
| 617 | plugins: config.plugins, | 604 | adapter, |
| 618 | onElicitation: "accept-all", | 605 | blobs, |
| 619 | }) | 606 | plugins: config.plugins, |
| 607 | onElicitation: "accept-all", | ||
| 608 | }); | ||
| 620 | ``` | 609 | ``` |
| 621 | 610 | ||
| 622 | The plugins are already configured (factory called) by the time | 611 | The plugins are already configured (factory called) by the time |
| @@ -631,18 +620,16 @@ export default function executorVite(): Plugin { · 8 unmodified lines | |||
| 631 | return { | 620 | return { |
| 632 | name: "executor-plugins", | 621 | name: "executor-plugins", |
| 633 | resolveId(id) { | 622 | resolveId(id) { |
| 634 | if (id === "virtual:executor/plugins-client") return "\0" + id | 623 | if (id === "virtual:executor/plugins-client") return "\0" + id; |
| 635 | }, | 624 | }, |
| 636 | async load(id) { | 625 | async load(id) { |
| 637 | if (id !== "\0virtual:executor/plugins-client") return | 626 | if (id !== "\0virtual:executor/plugins-client") return; |
| 638 | const config = await loadExecutorConfig() | 627 | const config = await loadExecutorConfig(); |
| 639 | const imports = config.plugins | 628 | const imports = config.plugins.map((p, i) => `import p${i} from "${p.id}/client"`).join("\n"); |
| 640 | .map((p, i) => `import p${i} from "${p.id}/client"`) | 629 | const list = config.plugins.map((_, i) => `p${i}`).join(", "); |
| 641 | .join("\n") | 630 | return `${imports}\nexport const plugins = [${list}]`; |
| 642 | const list = config.plugins.map((_, i) => `p${i}`).join(", ") | ||
| 643 | return `${imports}\nexport const plugins = [${list}]` | ||
| 644 | }, | 631 | }, |
| 645 | } | 632 | }; |
| 646 | } | 633 | } |
| 647 | ``` | 634 | ``` |
| 648 | 635 | ||
| @@ -655,11 +642,11 @@ file — `__root.tsx` is the only place that touches · 6 unmodified lines | |||
| 655 | 642 | ||
| 656 | ```tsx | 643 | ```tsx |
| 657 | // apps/local/src/routes/__root.tsx | 644 | // apps/local/src/routes/__root.tsx |
| 658 | import { createRootRoute } from "@tanstack/react-router" | 645 | import { createRootRoute } from "@tanstack/react-router"; |
| 659 | import { ExecutorProvider } from "@executor-js/react/api/provider" | 646 | import { ExecutorProvider } from "@executor-js/react/api/provider"; |
| 660 | import { ExecutorPluginsProvider } from "@executor-js/sdk/client" | 647 | import { ExecutorPluginsProvider } from "@executor-js/sdk/client"; |
| 661 | import { plugins as clientPlugins } from "virtual:executor/plugins-client" | 648 | import { plugins as clientPlugins } from "virtual:executor/plugins-client"; |
| 662 | import { Shell } from "../web/shell" | 649 | import { Shell } from "../web/shell"; |
| 663 | 650 | ||
| 664 | export const Route = createRootRoute({ | 651 | export const Route = createRootRoute({ |
| 665 | component: () => ( | 652 | component: () => ( |
| @@ -669,15 +656,15 @@ export const Route = createRootRoute({ · 3 unmodified lines | |||
| 669 | </ExecutorPluginsProvider> | 656 | </ExecutorPluginsProvider> |
| 670 | </ExecutorProvider> | 657 | </ExecutorProvider> |
| 671 | ), | 658 | ), |
| 672 | }) | 659 | }); |
| 673 | ``` | 660 | ``` |
| 674 | 661 | ||
| 675 | ```tsx | 662 | ```tsx |
| 676 | // e.g. inside packages/react/src/pages/sources.tsx | 663 | // e.g. inside packages/react/src/pages/sources.tsx |
| 677 | import { useSourcePlugins } from "@executor-js/sdk/client" | 664 | import { useSourcePlugins } from "@executor-js/sdk/client"; |
| 678 | 665 | ||
| 679 | export function SourcesPage() { | 666 | export function SourcesPage() { |
| 680 | const sourcePlugins = useSourcePlugins() | 667 | const sourcePlugins = useSourcePlugins(); |
| 681 | // ... | 668 | // ... |
| 682 | } | 669 | } |
| 683 | ``` | 670 | ``` |
| @@ -701,49 +688,48 @@ needs a Vite restart (not a hot update — config changed). · 17 unmodified lines | |||
| 701 | 688 | ||
| 702 | ```ts | 689 | ```ts |
| 703 | // src/shared.ts — only @executor-js/sdk imports, no raw effect imports | 690 | // src/shared.ts — only @executor-js/sdk imports, no raw effect imports |
| 704 | import { HttpApiEndpoint, HttpApiGroup, Schema } from "@executor-js/sdk" | 691 | import { HttpApiEndpoint, HttpApiGroup, Schema } from "@executor-js/sdk"; |
| 705 | 692 | ||
| 706 | export const Greeting = Schema.Struct({ | 693 | export const Greeting = Schema.Struct({ |
| 707 | message: Schema.String, | 694 | message: Schema.String, |
| 708 | count: Schema.Number, | 695 | count: Schema.Number, |
| 709 | }) | 696 | }); |
| 710 | export type Greeting = typeof Greeting.Type | 697 | export type Greeting = typeof Greeting.Type; |
| 711 | 698 | ||
| 712 | export const ExampleApi = HttpApiGroup.make("example").add( | 699 | export const ExampleApi = HttpApiGroup.make("example").add( |
| 713 | HttpApiEndpoint.post("greet", "/greet", { | 700 | HttpApiEndpoint.post("greet", "/greet", { |
| 714 | payload: Schema.Struct({ name: Schema.String }), | 701 | payload: Schema.Struct({ name: Schema.String }), |
| 715 | success: Greeting, | 702 | success: Greeting, |
| 716 | }), | 703 | }), |
| 717 | ) | 704 | ); |
| 718 | ``` | 705 | ``` |
| 719 | 706 | ||
| 720 | ```ts | 707 | ```ts |
| 721 | // src/server.ts | 708 | // src/server.ts |
| 722 | import { | 709 | import { Context, definePlugin, Effect, HttpApi, HttpApiBuilder } from "@executor-js/sdk"; |
| 723 | Context, definePlugin, Effect, HttpApi, HttpApiBuilder, | 710 | import { ExampleApi } from "./shared"; |
| 724 | } from "@executor-js/sdk" | ||
| 725 | import { ExampleApi } from "./shared" | ||
| 726 | 711 | ||
| 727 | const ExampleApiBundle = HttpApi.make("example").add(ExampleApi) | 712 | const ExampleApiBundle = HttpApi.make("example").add(ExampleApi); |
| 728 | 713 | ||
| 729 | interface ExampleExtension { | 714 | interface ExampleExtension { |
| 730 | readonly greet: ( | 715 | readonly greet: ( |
| 731 | name: string, | 716 | name: string, |
| 732 | ) => Effect.Effect<{ readonly message: string; readonly count: number }> | 717 | ) => Effect.Effect<{ readonly message: string; readonly count: number }>; |
| 733 | } | 718 | } |
| 734 | 719 | ||
| 735 | export class ExampleExtensionService extends Context.Service< | 720 | export class ExampleExtensionService extends Context.Service< |
| 736 | ExampleExtensionService, ExampleExtension, | 721 | ExampleExtensionService, |
| 722 | ExampleExtension | ||
| 737 | >()("ExampleExtensionService") {} | 723 | >()("ExampleExtensionService") {} |
| 738 | 724 | ||
| 739 | const ExampleHandlers = HttpApiBuilder.group(ExampleApiBundle, "example", (h) => | 725 | const ExampleHandlers = HttpApiBuilder.group(ExampleApiBundle, "example", (h) => |
| 740 | h.handle("greet", ({ payload }) => | 726 | h.handle("greet", ({ payload }) => |
| 741 | Effect.gen(function* () { | 727 | Effect.gen(function* () { |
| 742 | const ext = yield* ExampleExtensionService | 728 | const ext = yield* ExampleExtensionService; |
| 743 | return yield* ext.greet(payload.name) | 729 | return yield* ext.greet(payload.name); |
| 744 | }), | 730 | }), |
| 745 | ), | 731 | ), |
| 746 | ) | 732 | ); |
| 747 | 733 | ||
| 748 | export const examplePlugin = definePlugin(() => ({ | 734 | export const examplePlugin = definePlugin(() => ({ |
| 749 | id: "example" as const, | 735 | id: "example" as const, |
| @@ -754,57 +740,57 @@ export const examplePlugin = definePlugin(() => ({ · 4 unmodified lines | |||
| 754 | extension: (ctx): ExampleExtension => ({ | 740 | extension: (ctx): ExampleExtension => ({ |
| 755 | greet: (name: string) => | 741 | greet: (name: string) => |
| 756 | Effect.sync(() => { | 742 | Effect.sync(() => { |
| 757 | ctx.storage.count += 1 | 743 | ctx.storage.count += 1; |
| 758 | return { | 744 | return { |
| 759 | message: `hello ${name}`, | 745 | message: `hello ${name}`, |
| 760 | count: ctx.storage.count, | 746 | count: ctx.storage.count, |
| 761 | } | 747 | }; |
| 762 | }), | 748 | }), |
| 763 | }), | 749 | }), |
| 764 | 750 | ||
| 765 | routes: () => ExampleApi, | 751 | routes: () => ExampleApi, |
| 766 | handlers: () => ExampleHandlers, | 752 | handlers: () => ExampleHandlers, |
| 767 | extensionService: ExampleExtensionService, | 753 | extensionService: ExampleExtensionService, |
| 768 | })) | 754 | })); |
| 769 | 755 | ||
| 770 | export default examplePlugin | 756 | export default examplePlugin; |
| 771 | ``` | 757 | ``` |
| 772 | 758 | ||
| 773 | ```tsx | 759 | ```tsx |
| 774 | // src/client.tsx | 760 | // src/client.tsx |
| 775 | import { | 761 | import { defineClientPlugin, createPluginAtomClient, useAtomSet } from "@executor-js/sdk/client"; |
| 776 | defineClientPlugin, | 762 | import { useState } from "react"; |
| 777 | createPluginAtomClient, | 763 | import { ExampleApi } from "./shared"; |
| 778 | useAtomSet, | ||
| 779 | } from "@executor-js/sdk/client" | ||
| 780 | import { useState } from "react" | ||
| 781 | import { ExampleApi } from "./shared" | ||
| 782 | 764 | ||
| 783 | const ExampleClient = createPluginAtomClient(ExampleApi, { pluginId: "example" }) | 765 | const ExampleClient = createPluginAtomClient(ExampleApi, { pluginId: "example" }); |
| 784 | 766 | ||
| 785 | const greetAtom = ExampleClient.mutation("example", "greet") | 767 | const greetAtom = ExampleClient.mutation("example", "greet"); |
| 786 | 768 | ||
| 787 | const ExamplePage = () => { | 769 | const ExamplePage = () => { |
| 788 | const [name, setName] = useState("world") | 770 | const [name, setName] = useState("world"); |
| 789 | const [result, setResult] = useState<string>() | 771 | const [result, setResult] = useState<string>(); |
| 790 | const doGreet = useAtomSet(greetAtom, { mode: "promise" }) | 772 | const doGreet = useAtomSet(greetAtom, { mode: "promise" }); |
| 791 | 773 | ||
| 792 | return ( | 774 | return ( |
| 793 | <div> | 775 | <div> |
| 794 | <input value={name} onChange={(e) => setName(e.target.value)} /> | 776 | <input value={name} onChange={(e) => setName(e.target.value)} /> |
| 795 | <button onClick={async () => { | 777 | <button |
| 796 | const g = await doGreet({ payload: { name } }) | 778 | onClick={async () => { |
| 797 | setResult(`${g.message} (#${g.count})`) | 779 | const g = await doGreet({ payload: { name } }); |
| 798 | }}>Greet</button> | 780 | setResult(`${g.message} (#${g.count})`); |
| 781 | }} | ||
| 782 | > | ||
| 783 | Greet | ||
| 784 | </button> | ||
| 799 | {result && <pre>{result}</pre>} | 785 | {result && <pre>{result}</pre>} |
| 800 | </div> | 786 | </div> |
| 801 | ) | 787 | ); |
| 802 | } | 788 | }; |
| 803 | 789 | ||
| 804 | export default defineClientPlugin({ | 790 | export default defineClientPlugin({ |
| 805 | id: "example" as const, | 791 | id: "example" as const, |
| 806 | pages: [{ path: "/", component: ExamplePage, nav: { label: "Example" } }], | 792 | pages: [{ path: "/", component: ExamplePage, nav: { label: "Example" } }], |
| 807 | }) | 793 | }); |
| 808 | ``` | 794 | ``` |
| 809 | 795 | ||
| 810 | ```jsonc | 796 | ```jsonc |
| @@ -814,13 +800,13 @@ export default defineClientPlugin({ · 3 unmodified lines | |||
| 814 | "type": "module", | 800 | "type": "module", |
| 815 | "exports": { | 801 | "exports": { |
| 816 | "./server": "./dist/server.js", | 802 | "./server": "./dist/server.js", |
| 817 | "./client": "./dist/client.js" | 803 | "./client": "./dist/client.js", |
| 818 | }, | 804 | }, |
| 819 | "executor": { "id": "example", "version": "0.1.0" }, | 805 | "executor": { "id": "example", "version": "0.1.0" }, |
| 820 | "peerDependencies": { | 806 | "peerDependencies": { |
| 821 | "@executor-js/sdk": "workspace:*", | 807 | "@executor-js/sdk": "workspace:*", |
| 822 | "react": "catalog:" | 808 | "react": "catalog:", |
| 823 | } | 809 | }, |
| 824 | } | 810 | } |
| 825 | ``` | 811 | ``` |
| 826 | 812 | ||
| @@ -828,8 +814,8 @@ User adds it: · 1 unmodified line | |||
| 828 | 814 | ||
| 829 | ```ts | 815 | ```ts |
| 830 | // apps/local/executor.config.ts | 816 | // apps/local/executor.config.ts |
| 831 | import { examplePlugin } from "@executor-js/plugin-example/server" | 817 | import { examplePlugin } from "@executor-js/plugin-example/server"; |
| 832 | plugins: [/* ... */, examplePlugin()] | 818 | plugins: [, /* ... */ examplePlugin()]; |
| 833 | ``` | 819 | ``` |
| 834 | 820 | ||
| 835 | ## SDK-only plugin example | 821 | ## SDK-only plugin example |
| @@ -852,81 +838,82 @@ file with no `./client` export, no `routes`, no `handlers`. · 16 unmodified lines | |||
| 852 | "type": "module", | 838 | "type": "module", |
| 853 | "exports": { "./server": "./dist/server.js" }, | 839 | "exports": { "./server": "./dist/server.js" }, |
| 854 | "executor": { "id": "rateLimiter", "version": "0.1.0" }, | 840 | "executor": { "id": "rateLimiter", "version": "0.1.0" }, |
| 855 | "peerDependencies": { "@executor-js/sdk": "workspace:*" } | 841 | "peerDependencies": { "@executor-js/sdk": "workspace:*" }, |
| 856 | } | 842 | } |
| 857 | ``` | 843 | ``` |
| 858 | 844 | ||
| 859 | ```ts | 845 | ```ts |
| 860 | // src/server.ts | 846 | // src/server.ts |
| 861 | import { definePlugin, defineSchema, Effect, Schema } from "@executor-js/sdk" | 847 | import { definePlugin, defineSchema, Effect, Schema } from "@executor-js/sdk"; |
| 862 | 848 | ||
| 863 | interface RateLimiterConfig { | 849 | interface RateLimiterConfig { |
| 864 | defaultLimit?: number | 850 | defaultLimit?: number; |
| 865 | } | 851 | } |
| 866 | 852 | ||
| 867 | class RateLimitExceeded extends Schema.TaggedError<RateLimitExceeded>()( | 853 | class RateLimitExceeded extends Schema.TaggedError<RateLimitExceeded>()("RateLimitExceeded", { |
| 868 | "RateLimitExceeded", | 854 | key: Schema.String, |
| 869 | { key: Schema.String, retryAfterMs: Schema.Number }, | 855 | retryAfterMs: Schema.Number, |
| 870 | ) {} | 856 | }) {} |
| 871 | 857 | ||
| 872 | export const rateLimiterPlugin = definePlugin( | 858 | export const rateLimiterPlugin = definePlugin((opts: RateLimiterConfig = {}) => ({ |
| 873 | (opts: RateLimiterConfig = {}) => ({ | 859 | id: "rateLimiter" as const, |
| 874 | id: "rateLimiter" as const, | 860 | |
| 875 | 861 | schema: defineSchema({ | |
| 876 | schema: defineSchema({ | 862 | rate_buckets: { |
| 877 | rate_buckets: { | 863 | fields: { |
| 878 | fields: { | 864 | key: { type: "string", primary: true }, |
| 879 | key: { type: "string", primary: true }, | 865 | tokens: { type: "number" }, |
| 880 | tokens: { type: "number" }, | 866 | updated_at: { type: "number" }, |
| 881 | updated_at: { type: "number" }, | ||
| 882 | }, | ||
| 883 | }, | 867 | }, |
| 884 | }), | 868 | }, |
| 869 | }), | ||
| 885 | 870 | ||
| 886 | storage: ({ adapter }) => ({ | 871 | storage: ({ adapter }) => ({ |
| 887 | adapter, | 872 | adapter, |
| 888 | defaultLimit: opts.defaultLimit ?? 60, | 873 | defaultLimit: opts.defaultLimit ?? 60, |
| 889 | }), | 874 | }), |
| 890 | 875 | ||
| 891 | extension: (ctx) => ({ | 876 | extension: (ctx) => ({ |
| 892 | check: (key: string, cost = 1) => | 877 | check: (key: string, cost = 1) => |
| 893 | Effect.gen(function* () { | 878 | Effect.gen(function* () { |
| 894 | const bucket = yield* readOrCreateBucket(ctx.storage, key) | 879 | const bucket = yield* readOrCreateBucket(ctx.storage, key); |
| 895 | const refilled = refill(bucket, ctx.storage.defaultLimit) | 880 | const refilled = refill(bucket, ctx.storage.defaultLimit); |
| 896 | if (refilled.tokens < cost) { | 881 | if (refilled.tokens < cost) { |
| 897 | return yield* new RateLimitExceeded({ | 882 | return yield* new RateLimitExceeded({ |
| 898 | key, | 883 | key, |
| 899 | retryAfterMs: estimateRetry(refilled), | 884 | retryAfterMs: estimateRetry(refilled), |
| 900 | }) | 885 | }); |
| 901 | } | 886 | } |
| 902 | yield* writeBucket(ctx.storage, key, refilled.tokens - cost) | 887 | yield* writeBucket(ctx.storage, key, refilled.tokens - cost); |
| 903 | return { allowed: true, remaining: refilled.tokens - cost } | 888 | return { allowed: true, remaining: refilled.tokens - cost }; |
| 904 | }), | 889 | }), |
| 905 | 890 | ||
| 906 | reset: (key: string) => | 891 | reset: (key: string) => |
| 907 | Effect.tryPromise(() => | 892 | Effect.tryPromise(() => |
| 908 | ctx.storage.adapter.delete({ | 893 | ctx.storage.adapter.delete({ |
| 909 | model: "rate_buckets", | 894 | model: "rate_buckets", |
| 910 | where: [["key", "=", key]], | 895 | where: [["key", "=", key]], |
| 911 | }), | 896 | }), |
| 912 | ), | 897 | ), |
| 913 | }), | ||
| 914 | }), | 898 | }), |
| 915 | ) | 899 | })); |
| 916 | ``` | 900 | ``` |
| 917 | 901 | ||
| 918 | Pure programmatic consumption — works identically from CLI, tests, | 902 | Pure programmatic consumption — works identically from CLI, tests, |
| 919 | embedded library use, or another plugin's `extension`: | 903 | embedded library use, or another plugin's `extension`: |
| 920 | 904 | ||
| 921 | ```ts | 905 | ```ts |
| 922 | const result = yield* executor.rateLimiter.check("user-123", 5) | 906 | const result = yield * executor.rateLimiter.check("user-123", 5); |
| 923 | // ^? { allowed: true; remaining: number } | 907 | // ^? { allowed: true; remaining: number } |
| 924 | 908 | ||
| 925 | yield* executor.rateLimiter.check("user-123", 1000).pipe( | 909 | yield * |
| 926 | Effect.catchTag("RateLimitExceeded", (err) => | 910 | executor.rateLimiter |
| 927 | Effect.log(`hit limit on ${err.key}, retry in ${err.retryAfterMs}ms`), | 911 | .check("user-123", 1000) |
| 928 | ), | 912 | .pipe( |
| 929 | ) | 913 | Effect.catchTag("RateLimitExceeded", (err) => |
| 914 | Effect.log(`hit limit on ${err.key}, retry in ${err.retryAfterMs}ms`), | ||
| 915 | ), | ||
| 916 | ); | ||
| 930 | ``` | 917 | ``` |
| 931 | 918 | ||
| 932 | What the host does with this plugin: | 919 | What the host does with this plugin: |
| @@ -1000,6 +987,7 @@ Build order — all shipped. · 67 unmodified lines | |||
| 1000 | ## References | 987 | ## References |
| 1001 | 988 | ||
| 1002 | **Reference research (pre-design):** | 989 | **Reference research (pre-design):** |
| 990 | |||
| 1003 | - emdash: `astro.config.mjs` import-and-call pattern, `package.json` exports | 991 | - emdash: `astro.config.mjs` import-and-call pattern, `package.json` exports |
| 1004 | for separate admin entry — see `.reference/emdash/demos/plugins-demo/`. | 992 | for separate admin entry — see `.reference/emdash/demos/plugins-demo/`. |
| 1005 | - dynamic-software: PluginManifest + capability declarations idea (deferred); | 993 | - dynamic-software: PluginManifest + capability declarations idea (deferred); |
| @@ -1008,6 +996,7 @@ Build order — all shipped. · 2 unmodified lines | |||
| 1008 | when we add capability enforcement). | 996 | when we add capability enforcement). |
| 1009 | 997 | ||
| 1010 | **Shipped code:** | 998 | **Shipped code:** |
| 999 | |||
| 1011 | - Plugin contract: `packages/core/sdk/src/plugin.ts` (`PluginSpec`, | 1000 | - Plugin contract: `packages/core/sdk/src/plugin.ts` (`PluginSpec`, |
| 1012 | `definePlugin`). | 1001 | `definePlugin`). |
| 1013 | - Client contract: `packages/core/sdk/src/client.ts` | 1002 | - Client contract: `packages/core/sdk/src/client.ts` |
| @@ -1030,6 +1019,7 @@ Build order — all shipped. · 16 unmodified lines | |||
| 1030 | follows the same per-capability-typed-field pattern. | 1019 | follows the same per-capability-typed-field pattern. |
| 1031 | 1020 | ||
| 1032 | **Adjacent plans:** | 1021 | **Adjacent plans:** |
| 1022 | |||
| 1033 | - `notes/artifacts-workflows-and-generated-ui.md` — the artifacts/workflows | 1023 | - `notes/artifacts-workflows-and-generated-ui.md` — the artifacts/workflows |
| 1034 | plan. Uses "protocol" terminology that we're not adopting; read its | 1024 | plan. Uses "protocol" terminology that we're not adopting; read its |
| 1035 | "ArtifactStoreProtocol" as "the typed interface that goes into the | 1025 | "ArtifactStoreProtocol" as "the typed interface that goes into the |
| @@ -4,7 +4,7 @@ · 3 unmodified lines | |||
| 4 | 4 | ||
| 5 | The MCP plugin **already pools connections within a session DO**. Production | 5 | The MCP plugin **already pools connections within a session DO**. Production |
| 6 | telemetry attributing 17% of `plugin.mcp.connection.acquire` calls to a | 6 | telemetry attributing 17% of `plugin.mcp.connection.acquire` calls to a |
| 7 | fresh `plugin.mcp.connection.handshake` represents *cold-start sessions*, | 7 | fresh `plugin.mcp.connection.handshake` represents _cold-start sessions_, |
| 8 | not redundant in-session handshakes. No structural change is required; | 8 | not redundant in-session handshakes. No structural change is required; |
| 9 | this PR ships a strict regression test that pins the existing contract | 9 | this PR ships a strict regression test that pins the existing contract |
| 10 | plus a `plugin.mcp.cache_hit` span attribute so future telemetry can | 10 | plus a `plugin.mcp.cache_hit` span attribute so future telemetry can |
| @@ -68,11 +68,12 @@ mutating the cache key per call) trips immediately in CI. · 57 unmodified lines | |||
| 68 | ## Re-reading the production trace | 68 | ## Re-reading the production trace |
| 69 | 69 | ||
| 70 | Trace `b7102047bed975da461c0519d1251de4`: | 70 | Trace `b7102047bed975da461c0519d1251de4`: |
| 71 | |||
| 71 | - `mcp.plugin.resolve_connector` 2.72s | 72 | - `mcp.plugin.resolve_connector` 2.72s |
| 72 | - `plugin.mcp.connection.acquire` 2.72s | 73 | - `plugin.mcp.connection.acquire` 2.72s |
| 73 | - `plugin.mcp.connection.handshake` 2.52s | 74 | - `plugin.mcp.connection.handshake` 2.52s |
| 74 | - `executor.storage.transaction` 1.02s (token persist) | 75 | - `executor.storage.transaction` 1.02s (token persist) |
| 75 | - `plugin.mcp.client.call_tool` 1.48s | 76 | - `plugin.mcp.client.call_tool` 1.48s |
| 76 | 77 | ||
| 77 | Span nesting plus `resolve_connector ≈ acquire ≈ handshake` durations is | 78 | Span nesting plus `resolve_connector ≈ acquire ≈ handshake` durations is |
| 78 | the cache-miss path: `resolveConnector` only runs (and emits its span) | 79 | the cache-miss path: `resolveConnector` only runs (and emits its span) |
| @@ -80,11 +81,12 @@ when the cache lookup actually invokes the lookup closure, which only · 1 unmodified line | |||
| 80 | happens on a miss. | 81 | happens on a miss. |
| 81 | 82 | ||
| 82 | 8h aggregate: | 83 | 8h aggregate: |
| 84 | |||
| 83 | - `plugin.mcp.connection.handshake` count 4 | 85 | - `plugin.mcp.connection.handshake` count 4 |
| 84 | - `plugin.mcp.connection.acquire` count 24 | 86 | - `plugin.mcp.connection.acquire` count 24 |
| 85 | - `mcp.plugin.resolve_connector` count 4 | 87 | - `mcp.plugin.resolve_connector` count 4 |
| 86 | 88 | ||
| 87 | Acquire count is 6× handshake count. The cache *is* hitting on 20 of 24 | 89 | Acquire count is 6× handshake count. The cache _is_ hitting on 20 of 24 |
| 88 | calls. The 4 misses are cold-start sessions (each MCP session DO's first | 90 | calls. The 4 misses are cold-start sessions (each MCP session DO's first |
| 89 | tool call, plus any session where the connection went stale and was | 91 | tool call, plus any session where the connection went stale and was |
| 90 | invalidated by the retry path). Six tool calls per session is consistent | 92 | invalidated by the retry path). Six tool calls per session is consistent |
| @@ -21,7 +21,7 @@ to preserve auth state across the upgrade. · 20 unmodified lines | |||
| 21 | 21 | ||
| 22 | 1. `INSERT INTO connection` with the new pointer's `connectionId`, | 22 | 1. `INSERT INTO connection` with the new pointer's `connectionId`, |
| 23 | `provider = "openapi:oauth2"`, and `provider_state` = `{flow, | 23 | `provider = "openapi:oauth2"`, and `provider_state` = `{flow, |
| 24 | tokenUrl, clientIdSecretId, clientSecretSecretId, scopes}` lifted | 24 | tokenUrl, clientIdSecretId, clientSecretSecretId, scopes}` lifted |
| 25 | from the legacy row. `access_token_secret_id` + `refresh_token_secret_id` | 25 | from the legacy row. `access_token_secret_id` + `refresh_token_secret_id` |
| 26 | reuse the legacy secret ids — tokens themselves never move, only | 26 | reuse the legacy secret ids — tokens themselves never move, only |
| 27 | ownership links flip. | 27 | ownership links flip. |
| @@ -87,7 +87,7 @@ Owns the translation, the opaque wire schema, and the capture service. · 86 unmodified lines | |||
| 87 | 87 | ||
| 88 | ```ts | 88 | ```ts |
| 89 | interface ErrorCaptureShape { | 89 | interface ErrorCaptureShape { |
| 90 | readonly captureException: (cause: Cause<unknown>) => Effect<string> | 90 | readonly captureException: (cause: Cause<unknown>) => Effect<string>; |
| 91 | } | 91 | } |
| 92 | ``` | 92 | ``` |
| 93 | 93 | ||
| @@ -130,7 +130,7 @@ Plugin authors write normal Effect code. Their extension method error · 36 unmodified lines | |||
| 130 | unions look like: | 130 | unions look like: |
| 131 | 131 | ||
| 132 | ```ts | 132 | ```ts |
| 133 | Effect.Effect<X, MyPluginTypedError | StorageError, never> | 133 | Effect.Effect<X, MyPluginTypedError | StorageError, never>; |
| 134 | ``` | 134 | ``` |
| 135 | 135 | ||
| 136 | Where `MyPluginTypedError` is the union of their own | 136 | Where `MyPluginTypedError` is the union of their own |
| @@ -152,9 +152,9 @@ class McpGroup extends HttpApiGroup.make("mcp") · 15 unmodified lines | |||
| 152 | // … | 152 | // … |
| 153 | .addError(InternalError) | 153 | .addError(InternalError) |
| 154 | .addError(McpOAuthError) | 154 | .addError(McpOAuthError) |
| 155 | .addError(McpConnectionError) | 155 | .addError(McpConnectionError) { |
| 156 | // … | 156 | // … |
| 157 | {} | 157 | } |
| 158 | ``` | 158 | ``` |
| 159 | 159 | ||
| 160 | No per-endpoint `addError`. The framework encodes each tagged error by | 160 | No per-endpoint `addError`. The framework encodes each tagged error by |
| @@ -54,7 +54,7 @@ source: { · 53 unmodified lines | |||
| 54 | Effect.catchAll((e) => { | 54 | Effect.catchAll((e) => { |
| 55 | const message = e instanceof Error ? e.message : String(e); | 55 | const message = e instanceof Error ? e.message : String(e); |
| 56 | return executor.sources.recordError({ source, message }).pipe(Effect.asVoid); | 56 | return executor.sources.recordError({ source, message }).pipe(Effect.asVoid); |
| 57 | }) | 57 | }); |
| 58 | ``` | 58 | ``` |
| 59 | 59 | ||
| 60 | `executor.sources.recordError` is a new core method that writes a | 60 | `executor.sources.recordError` is a new core method that writes a |
| @@ -76,6 +76,7 @@ a special case — current `removeSource` already works regardless of · 15 unmodified lines | |||
| 76 | ## Scope | 76 | ## Scope |
| 77 | 77 | ||
| 78 | Not for the scope-refactor PR. Separate change: | 78 | Not for the scope-refactor PR. Separate change: |
| 79 | |||
| 79 | - Schema migration adds two columns. | 80 | - Schema migration adds two columns. |
| 80 | - `executor.sources.recordError` on the core SDK. | 81 | - `executor.sources.recordError` on the core SDK. |
| 81 | - Wire up in config-sync. | 82 | - Wire up in config-sync. |
| @@ -25,7 +25,7 @@ Tables affected (all drop candidates): · 24 unmodified lines | |||
| 25 | - `workos_vault_metadata_scope_id_idx` | 25 | - `workos_vault_metadata_scope_id_idx` |
| 26 | 26 | ||
| 27 | Root cause: `core-schema.ts` and plugin schemas mark `scope_id: { | 27 | Root cause: `core-schema.ts` and plugin schemas mark `scope_id: { |
| 28 | index: true }`. The drizzle generator should probably *skip* emitting | 28 | index: true }`. The drizzle generator should probably _skip_ emitting |
| 29 | a single-column index on a column that's already the leftmost column | 29 | a single-column index on a column that's already the leftmost column |
| 30 | of a composite primary key. Fix in two places: | 30 | of a composite primary key. Fix in two places: |
| 31 | 31 | ||
| @@ -5,10 +5,10 @@ deleting one silently drops coverage that the others can't replace. · 4 unmodified lines | |||
| 5 | 5 | ||
| 6 | ## Suites at a glance | 6 | ## Suites at a glance |
| 7 | 7 | ||
| 8 | | file | pool | drives | what it proves | | 8 | | file | pool | drives | what it proves | |
| 9 | |---|---|---|---| | 9 | | ----------------------------------------------- | ----------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------- | |
| 10 | | `apps/cloud/src/mcp-session.e2e.node.test.ts` | node | `InMemoryTransport` + SDK `Client` | engine + plugin wiring; schema drift; elicitation semantics | | 10 | | `apps/cloud/src/mcp-session.e2e.node.test.ts` | node | `InMemoryTransport` + SDK `Client` | engine + plugin wiring; schema drift; elicitation semantics | |
| 11 | | `apps/cloud/src/mcp-flow.test.ts` | workerd (vitest-pool-workers) | `SELF.fetch` + hand-rolled JSON-RPC | edge HTTP pipeline that does not require a live multi-request DO session | | 11 | | `apps/cloud/src/mcp-flow.test.ts` | workerd (vitest-pool-workers) | `SELF.fetch` + hand-rolled JSON-RPC | edge HTTP pipeline that does not require a live multi-request DO session | |
| 12 | | `apps/cloud/src/mcp-miniflare.e2e.node.test.ts` | node + miniflare-on-real-port | SDK `Client` + real `StreamableHTTPClientTransport` | the long-lived-socket DO runtime actually works end-to-end; elicitation round-trips real HTTP | | 12 | | `apps/cloud/src/mcp-miniflare.e2e.node.test.ts` | node + miniflare-on-real-port | SDK `Client` + real `StreamableHTTPClientTransport` | the long-lived-socket DO runtime actually works end-to-end; elicitation round-trips real HTTP | |
| 13 | 13 | ||
| 14 | ## The workerd cross-request I/O wall | 14 | ## The workerd cross-request I/O wall |
| @@ -86,12 +86,12 @@ Pattern used in `mcp-miniflare.e2e.node.test.ts`: · 71 unmodified lines | |||
| 86 | 3. Read the bound port from `HttpServer.HttpServer.address` (TcpAddress tag) | 86 | 3. Read the bound port from `HttpServer.HttpServer.address` (TcpAddress tag) |
| 87 | inside a `Layer.effect` that produces the test service. | 87 | inside a `Layer.effect` that produces the test service. |
| 88 | 4. Generate the spec with `OpenApi.fromApi(api)` and inject | 88 | 4. Generate the spec with `OpenApi.fromApi(api)` and inject |
| 89 | `servers: [{ url: `http://127.0.0.1:${port}` }]` before `JSON.stringify`. | 89 | `servers: [{ url: `http://127.0.0.1:${port}` }]`before`JSON.stringify`. |
| 90 | 5. SDK Client advertises `capabilities: { elicitation: { form: {} } }` and | 90 | 5. SDK Client advertises `capabilities: { elicitation: { form: {} } }` and |
| 91 | registers `client.setRequestHandler(ElicitRequestSchema, ...)` that returns | 91 | registers `client.setRequestHandler(ElicitRequestSchema, ...)` that returns |
| 92 | `{ action: "accept", content: {} }`. | 92 | `{ action: "accept", content: {} }`. |
| 93 | 6. Call `execute` with code that (a) calls `tools.openapi.addSource({ spec, | 93 | 6. Call `execute` with code that (a) calls `tools.openapi.addSource({ spec, |
| 94 | namespace })`, (b) invokes the POST operation. | 94 | namespace })`, (b) invokes the POST operation. |
| 95 | 95 | ||
| 96 | **Tool id format inside the `execute` sandbox.** | 96 | **Tool id format inside the `execute` sandbox.** |
| 97 | `tools.<sourceId>.<group>.<operation>` for openapi. The Effect | 97 | `tools.<sourceId>.<group>.<operation>` for openapi. The Effect |
| @@ -35,7 +35,9 @@ export interface CoreStore { · 34 unmodified lines | |||
| 35 | 35 | ||
| 36 | // definitions | 36 | // definitions |
| 37 | readonly putDefinitions: (defs: readonly StoredDefinition[]) => Effect.Effect<void, Error>; | 37 | readonly putDefinitions: (defs: readonly StoredDefinition[]) => Effect.Effect<void, Error>; |
| 38 | readonly listDefinitionsBySource: (sourceId: string) => Effect.Effect<readonly StoredDefinition[], Error>; | 38 | readonly listDefinitionsBySource: ( |
| 39 | sourceId: string, | ||
| 40 | ) => Effect.Effect<readonly StoredDefinition[], Error>; | ||
| 39 | readonly removeDefinitionsBySource: (sourceId: string) => Effect.Effect<void, Error>; | 41 | readonly removeDefinitionsBySource: (sourceId: string) => Effect.Effect<void, Error>; |
| 40 | 42 | ||
| 41 | // secrets (routing rows; provider-specific data lives in provider) | 43 | // secrets (routing rows; provider-specific data lives in provider) |
| @@ -45,10 +47,9 @@ export interface CoreStore { · 3 unmodified lines | |||
| 45 | readonly removeSecret: (id: string) => Effect.Effect<void, Error>; | 47 | readonly removeSecret: (id: string) => Effect.Effect<void, Error>; |
| 46 | } | 48 | } |
| 47 | 49 | ||
| 48 | export const makeDefaultCoreStore = (deps: { | 50 | export const makeDefaultCoreStore = (deps: { adapter: DBAdapter; scope: Scope }): CoreStore => { |
| 49 | adapter: DBAdapter; | 51 | /* typedAdapter<CoreSchema> wiring, one place */ |
| 50 | scope: Scope; | 52 | }; |
| 51 | }): CoreStore => { /* typedAdapter<CoreSchema> wiring, one place */ }; | ||
| 52 | ``` | 53 | ``` |
| 53 | 54 | ||
| 54 | ## Wiring — defaults passed in deps, force the spread | 55 | ## Wiring — defaults passed in deps, force the spread |
| @@ -70,9 +71,7 @@ export interface ExecutorConfig<TPlugins> { · 15 unmodified lines | |||
| 70 | 71 | ||
| 71 | // Inside createExecutor: | 72 | // Inside createExecutor: |
| 72 | const defaults = makeDefaultCoreStore({ adapter, scope }); | 73 | const defaults = makeDefaultCoreStore({ adapter, scope }); |
| 73 | const core = config.coreStorage | 74 | const core = config.coreStorage ? config.coreStorage({ adapter, scope, defaults }) : defaults; |
| 74 | ? config.coreStorage({ adapter, scope, defaults }) | ||
| 75 | : defaults; | ||
| 76 | // Executor now calls core.putTools(...), core.removeSource(...), etc. | 75 | // Executor now calls core.putTools(...), core.removeSource(...), etc. |
| 77 | // No more adapter.create({ model: "tool", ... }) scattered through executor.ts. | 76 | // No more adapter.create({ model: "tool", ... }) scattered through executor.ts. |
| 78 | ``` | 77 | ``` |
| @@ -83,7 +82,7 @@ const core = config.coreStorage · 4 unmodified lines | |||
| 83 | with no type help. | 82 | with no type help. |
| 84 | - Anyone implementing from scratch (test doubles, a non-drizzle backend) | 83 | - Anyone implementing from scratch (test doubles, a non-drizzle backend) |
| 85 | gets no squigglies when they miss a method. | 84 | gets no squigglies when they miss a method. |
| 86 | - Defaults-in-deps gets "only write what you override" ergonomics *and* | 85 | - Defaults-in-deps gets "only write what you override" ergonomics _and_ |
| 87 | keeps the full `CoreStore` return type enforced. | 86 | keeps the full `CoreStore` return type enforced. |
| 88 | 87 | ||
| 89 | ## What it unlocks | 88 | ## What it unlocks |
| @@ -91,30 +90,39 @@ const core = config.coreStorage · 1 unmodified line | |||
| 91 | **Algolia sidecar — no string-match, just method overrides:** | 90 | **Algolia sidecar — no string-match, just method overrides:** |
| 92 | 91 | ||
| 93 | ```ts | 92 | ```ts |
| 94 | const executor = yield* createExecutor({ | 93 | const executor = |
| 95 | scope, adapter, blobs, plugins, | 94 | yield * |
| 96 | 95 | createExecutor({ | |
| 97 | coreStorage: ({ defaults }) => ({ | 96 | scope, |
| 98 | ...defaults, | 97 | adapter, |
| 99 | putTools: (tools) => | 98 | blobs, |
| 100 | Effect.gen(function* () { | 99 | plugins, |
| 101 | yield* defaults.putTools(tools); | 100 | |
| 102 | yield* Effect.promise(() => algolia.saveObjects({ | 101 | coreStorage: ({ defaults }) => ({ |
| 103 | indexName: `tools_${scope.id}`, | 102 | ...defaults, |
| 104 | objects: tools.map((t) => ({ objectID: t.id, ...t })), | 103 | putTools: (tools) => |
| 105 | })); | 104 | Effect.gen(function* () { |
| 106 | }), | 105 | yield* defaults.putTools(tools); |
| 107 | removeToolsBySource: (sourceId) => | 106 | yield* Effect.promise(() => |
| 108 | Effect.gen(function* () { | 107 | algolia.saveObjects({ |
| 109 | const affected = yield* defaults.listTools(); | 108 | indexName: `tools_${scope.id}`, |
| 110 | yield* defaults.removeToolsBySource(sourceId); | 109 | objects: tools.map((t) => ({ objectID: t.id, ...t })), |
| 111 | yield* Effect.promise(() => algolia.deleteObjects({ | 110 | }), |
| 112 | indexName: `tools_${scope.id}`, | 111 | ); |
| 113 | objectIDs: affected.filter((t) => t.sourceId === sourceId).map((t) => t.id), | 112 | }), |
| 114 | })); | 113 | removeToolsBySource: (sourceId) => |
| 115 | }), | 114 | Effect.gen(function* () { |
| 116 | }), | 115 | const affected = yield* defaults.listTools(); |
| 117 | }); | 116 | yield* defaults.removeToolsBySource(sourceId); |
| 117 | yield* Effect.promise(() => | ||
| 118 | algolia.deleteObjects({ | ||
| 119 | indexName: `tools_${scope.id}`, | ||
| 120 | objectIDs: affected.filter((t) => t.sourceId === sourceId).map((t) => t.id), | ||
| 121 | }), | ||
| 122 | ); | ||
| 123 | }), | ||
| 124 | }), | ||
| 125 | }); | ||
| 118 | ``` | 126 | ``` |
| 119 | 127 | ||
| 120 | Test doubles become trivial — either hand-roll the whole `CoreStore` | 128 | Test doubles become trivial — either hand-roll the whole `CoreStore` |
| @@ -128,13 +136,13 @@ The factory name leaks. Rename to the defaults-in-deps shape: · 7 unmodified lines | |||
| 128 | 136 | ||
| 129 | ```ts | 137 | ```ts |
| 130 | // before | 138 | // before |
| 131 | storage: (deps) => makeDefaultOpenapiStore(deps) | 139 | storage: (deps) => makeDefaultOpenapiStore(deps); |
| 132 | 140 | ||
| 133 | // after | 141 | // after |
| 134 | storage: ({ defaults }) => defaults // no-op, identical to leaving off `storage:` | 142 | storage: ({ defaults }) => defaults; // no-op, identical to leaving off `storage:` |
| 135 | 143 | ||
| 136 | // override one method | 144 | // override one method |
| 137 | storage: ({ defaults }) => ({ ...defaults, upsertSource: myUpsert }) | 145 | storage: ({ defaults }) => ({ ...defaults, upsertSource: myUpsert }); |
| 138 | ``` | 146 | ``` |
| 139 | 147 | ||
| 140 | Mechanical sweep across openapi / mcp / graphql / workos-vault plugins. | 148 | Mechanical sweep across openapi / mcp / graphql / workos-vault plugins. |
| @@ -152,6 +160,6 @@ Not intellectually hard, but a broad diff: · 11 unmodified lines | |||
| 152 | factory renamed to follow the `defaults` contract. | 160 | factory renamed to follow the `defaults` contract. |
| 153 | - Docs/readme updates showing the new override pattern. | 161 | - Docs/readme updates showing the new override pattern. |
| 154 | 162 | ||
| 155 | Ideally lands *before* pluggable search — the search sidecar story | 163 | Ideally lands _before_ pluggable search — the search sidecar story |
| 156 | depends on this refactor to avoid the `if (model === "tool")` antipattern | 164 | depends on this refactor to avoid the `if (model === "tool")` antipattern |
| 157 | I'd otherwise be stuck with. | 165 | I'd otherwise be stuck with. |
| @@ -6,7 +6,7 @@ · 5 unmodified lines | |||
| 6 | `Effect.runPromise(...)`. That works but it's lossy at the Promise | 6 | `Effect.runPromise(...)`. That works but it's lossy at the Promise |
| 7 | boundary: | 7 | boundary: |
| 8 | 8 | ||
| 9 | - The promise's *rejection type* is `unknown` (or `any` after | 9 | - The promise's _rejection type_ is `unknown` (or `any` after |
| 10 | `try/catch`). Consumers have to `instanceof InternalError` / | 10 | `try/catch`). Consumers have to `instanceof InternalError` / |
| 11 | `err._tag === "..."` to discriminate, even though the underlying | 11 | `err._tag === "..."` to discriminate, even though the underlying |
| 12 | Effect had a fully typed error union. | 12 | Effect had a fully typed error union. |
| @@ -43,12 +43,14 @@ Do we expose Effect's `Exit` / `Cause` directly to promise consumers, · 30 unmodified lines | |||
| 43 | or wrap them in a promise-native `Result<A, E>`? | 43 | or wrap them in a promise-native `Result<A, E>`? |
| 44 | 44 | ||
| 45 | **Expose `Exit`/`Cause`:** | 45 | **Expose `Exit`/`Cause`:** |
| 46 | |||
| 46 | - Pros: one fewer abstraction, no translation layer to maintain, cause | 47 | - Pros: one fewer abstraction, no translation layer to maintain, cause |
| 47 | preserves parallel/sequential composition. | 48 | preserves parallel/sequential composition. |
| 48 | - Cons: consumers depend on `effect`'s API even though they're using a | 49 | - Cons: consumers depend on `effect`'s API even though they're using a |
| 49 | "Promise" SDK — partly defeats the abstraction. | 50 | "Promise" SDK — partly defeats the abstraction. |
| 50 | 51 | ||
| 51 | **Wrap in a `Result<A, E>`:** | 52 | **Wrap in a `Result<A, E>`:** |
| 53 | |||
| 52 | - Pros: `@executor-js/sdk/promise` consumers don't import `effect` at all, | 54 | - Pros: `@executor-js/sdk/promise` consumers don't import `effect` at all, |
| 53 | surface stays small. | 55 | surface stays small. |
| 54 | - Cons: another type to learn / document; loses some Cause structure | 56 | - Cons: another type to learn / document; loses some Cause structure |
| @@ -54,8 +54,8 @@ And app-side, once per request inside a transaction (so `SET LOCAL` · 53 unmodified lines | |||
| 54 | takes): | 54 | takes): |
| 55 | 55 | ||
| 56 | ```ts | 56 | ```ts |
| 57 | yield* sql`SET LOCAL app.scope_chain = ${toPgArray(chain)}`; | 57 | yield * sql`SET LOCAL app.scope_chain = ${toPgArray(chain)}`; |
| 58 | yield* sql`SET LOCAL app.write_scope = ${writeScope}`; | 58 | yield * sql`SET LOCAL app.write_scope = ${writeScope}`; |
| 59 | ``` | 59 | ``` |
| 60 | 60 | ||
| 61 | `current_setting('…', true)` returns null when the setting is missing, | 61 | `current_setting('…', true)` returns null when the setting is missing, |
| @@ -69,7 +69,7 @@ scope sees zero rows, not an error that might be handled into a 500. · 7 unmodified lines | |||
| 69 | tables is whatever `executor-schema.ts` exports with a `scope_id` | 69 | tables is whatever `executor-schema.ts` exports with a `scope_id` |
| 70 | column — same set the scope adapter already knows about. | 70 | column — same set the scope adapter already knows about. |
| 71 | 2. Add an Effect layer that wraps the DbService to issue the two `SET | 71 | 2. Add an Effect layer that wraps the DbService to issue the two `SET |
| 72 | LOCAL`s at connection acquire. `postgres.js`'s `sql.begin` already | 72 | LOCAL`s at connection acquire. `postgres.js`'s `sql.begin` already |
| 73 | gives us the transaction boundary. | 73 | gives us the transaction boundary. |
| 74 | 3. Leave `BYPASSRLS` off the worker's role so we actually get the | 74 | 3. Leave `BYPASSRLS` off the worker's role so we actually get the |
| 75 | enforcement; grant it on the role used by the migration / admin | 75 | enforcement; grant it on the role used by the migration / admin |
| @@ -20,7 +20,7 @@ The common primitive is not "org auth" or "user auth". It is an ordered · 19 unmodified lines | |||
| 20 | scope stack: | 20 | scope stack: |
| 21 | 21 | ||
| 22 | ```ts | 22 | ```ts |
| 23 | [user, workspace, org] | 23 | [user, workspace, org]; |
| 24 | ``` | 24 | ``` |
| 25 | 25 | ||
| 26 | Rows still belong to exactly one scope. Resolution walks the stack | 26 | Rows still belong to exactly one scope. Resolution walks the stack |
| @@ -68,12 +68,7 @@ For example: · 41 unmodified lines | |||
| 68 | 68 | ||
| 69 | ```ts | 69 | ```ts |
| 70 | oauth2: { | 70 | oauth2: { |
| 71 | flow: "clientCredentials", | 71 | flow: ("clientCredentials", tokenUrl, clientIdSlot, clientSecretSlot, connectionSlot, scopes); |
| 72 | tokenUrl, | ||
| 73 | clientIdSlot, | ||
| 74 | clientSecretSlot, | ||
| 75 | connectionSlot, | ||
| 76 | scopes | ||
| 77 | } | 72 | } |
| 78 | ``` | 73 | ``` |
| 79 | 74 | ||
| @@ -10,15 +10,15 @@ scopes by construction. · 9 unmodified lines | |||
| 10 | 10 | ||
| 11 | - **SDK** — `createExecutor({ scope: Scope, adapter, blobs, plugins })`. | 11 | - **SDK** — `createExecutor({ scope: Scope, adapter, blobs, plugins })`. |
| 12 | One `Scope` per executor instance. `scopeAdapter(rootAdapter, {...}, | 12 | One `Scope` per executor instance. `scopeAdapter(rootAdapter, {...}, |
| 13 | schema)` wraps the adapter before it reaches plugin storage or the | 13 | schema)` wraps the adapter before it reaches plugin storage or the |
| 14 | core-table writers. Every read on a scoped table gets `where scope_id | 14 | core-table writers. Every read on a scoped table gets `where scope_id |
| 15 | = scope.id` ANDed in; every write gets `scope_id = scope.id` stamped | 15 | = scope.id` ANDed in; every write gets `scope_id = scope.id` stamped |
| 16 | into the payload. Tx handles passed into transaction callbacks are | 16 | into the payload. Tx handles passed into transaction callbacks are |
| 17 | also wrapped, so nested writes stay inside the same scope. | 17 | also wrapped, so nested writes stay inside the same scope. |
| 18 | 18 | ||
| 19 | - **Plugins** — see a plain `DBAdapter`. They do not know or care about | 19 | - **Plugins** — see a plain `DBAdapter`. They do not know or care about |
| 20 | scope. Every plugin schema that wants isolation declares `scope_id: | 20 | scope. Every plugin schema that wants isolation declares `scope_id: |
| 21 | { type: "string", required: true, index: true }`. Forgetting the | 21 | { type: "string", required: true, index: true }`. Forgetting the |
| 22 | column means the adapter passes the plugin's reads/writes through | 22 | column means the adapter passes the plugin's reads/writes through |
| 23 | unscoped (documented failure mode, not silent) — tests catch the | 23 | unscoped (documented failure mode, not silent) — tests catch the |
| 24 | concrete cases we care about. | 24 | concrete cases we care about. |
| @@ -44,8 +44,8 @@ sources, tools, secrets, and plugin-owned source detail lookups. · 19 unmodified lines | |||
| 44 | 44 | ||
| 45 | ```ts | 45 | ```ts |
| 46 | interface ScopeContext { | 46 | interface ScopeContext { |
| 47 | readonly read: readonly string[]; // precedence-ordered, innermost first | 47 | readonly read: readonly string[]; // precedence-ordered, innermost first |
| 48 | readonly write: string; // exactly one scope for writes | 48 | readonly write: string; // exactly one scope for writes |
| 49 | } | 49 | } |
| 50 | ``` | 50 | ``` |
| 51 | 51 | ||
| @@ -91,7 +91,7 @@ Writes land in exactly one scope chosen by the caller. · 39 unmodified lines | |||
| 91 | there. When a user invokes Gmail, secret resolution walks the scope | 91 | there. When a user invokes Gmail, secret resolution walks the scope |
| 92 | stack (user → workspace → org) and only finds a token if that user | 92 | stack (user → workspace → org) and only finds a token if that user |
| 93 | personally oauthed. Policy on the source row — `auth_scope_mandate: | 93 | personally oauthed. Policy on the source row — `auth_scope_mandate: |
| 94 | "user"` — forces this: secret lookup refuses to return | 94 | "user"` — forces this: secret lookup refuses to return |
| 95 | workspace-level tokens even if one existed. | 95 | workspace-level tokens even if one existed. |
| 96 | 96 | ||
| 97 | - **Local global + per-folder.** `~/.executor/global` holds the outer | 97 | - **Local global + per-folder.** `~/.executor/global` holds the outer |
| @@ -9,7 +9,7 @@ whatever) without baking any of it into the core. · 8 unmodified lines | |||
| 9 | 9 | ||
| 10 | 1. `executor.tools.list()` — `SELECT * FROM tool WHERE scope_id = ?`. | 10 | 1. `executor.tools.list()` — `SELECT * FROM tool WHERE scope_id = ?`. |
| 11 | 2. In JS, for every tool: normalize + tokenize `id / sourceId / name / | 11 | 2. In JS, for every tool: normalize + tokenize `id / sourceId / name / |
| 12 | description`, score per field with weights, apply coverage + exact- | 12 | description`, score per field with weights, apply coverage + exact- |
| 13 | phrase boosts, filter, sort, slice. | 13 | phrase boosts, filter, sort, slice. |
| 14 | 14 | ||
| 15 | O(N) per query over the scope. Fine at hundreds of tools, painful past | 15 | O(N) per query over the scope. Fine at hundreds of tools, painful past |
| @@ -74,11 +74,13 @@ type. Same pattern as plugin `storage:` options and the future `coreStorage:`. · 58 unmodified lines | |||
| 74 | ## Override shapes | 74 | ## Override shapes |
| 75 | 75 | ||
| 76 | **Replace entirely:** | 76 | **Replace entirely:** |
| 77 | |||
| 77 | ```ts | 78 | ```ts |
| 78 | search: ({ listTools }) => algoliaSearch({ listTools }), | 79 | search: ({ listTools }) => algoliaSearch({ listTools }), |
| 79 | ``` | 80 | ``` |
| 80 | 81 | ||
| 81 | **Decorate — mix in logging/timing:** | 82 | **Decorate — mix in logging/timing:** |
| 83 | |||
| 82 | ```ts | 84 | ```ts |
| 83 | search: ({ defaults }) => ({ | 85 | search: ({ defaults }) => ({ |
| 84 | query: (q) => Effect.gen(function* () { | 86 | query: (q) => Effect.gen(function* () { |
| @@ -91,6 +93,7 @@ search: ({ defaults }) => ({ · 6 unmodified lines | |||
| 91 | ``` | 93 | ``` |
| 92 | 94 | ||
| 93 | **Hybrid — try Algolia, fall back to local:** | 95 | **Hybrid — try Algolia, fall back to local:** |
| 96 | |||
| 94 | ```ts | 97 | ```ts |
| 95 | search: ({ listTools, defaults }) => ({ | 98 | search: ({ listTools, defaults }) => ({ |
| 96 | query: (q) => | 99 | query: (q) => |
| @@ -109,6 +112,7 @@ it's a storage concern. Handled by wrapping the core store (see · 12 unmodified lines | |||
| 109 | delegate to the default and mirror to the external index. | 112 | delegate to the default and mirror to the external index. |
| 110 | 113 | ||
| 111 | The two halves are independent: | 114 | The two halves are independent: |
| 115 | |||
| 112 | - Indexing only (manual reindex): override `coreStorage`, leave `search` | 116 | - Indexing only (manual reindex): override `coreStorage`, leave `search` |
| 113 | as default. | 117 | as default. |
| 114 | - Search only (some other populates the index): override `search`, leave | 118 | - Search only (some other populates the index): override `search`, leave |
| @@ -122,7 +126,7 @@ scorer: · 7 unmodified lines | |||
| 122 | 1. **Projection on `tools.list`** — expose a `select` option so search | 126 | 1. **Projection on `tools.list`** — expose a `select` option so search |
| 123 | doesn't pull `input_schema` / `output_schema`. Biggest per-query win. | 127 | doesn't pull `input_schema` / `output_schema`. Biggest per-query win. |
| 124 | 2. **Postgres FTS** — `tsvector` generated column on `(name, description, | 128 | 2. **Postgres FTS** — `tsvector` generated column on `(name, description, |
| 125 | id, source_id)`, GIN index, use `websearch_to_tsquery`. Native | 129 | id, source_id)`, GIN index, use `websearch_to_tsquery`. Native |
| 126 | ranking, fuzzy via `pg_trgm`. Would replace the in-memory scorer for | 130 | ranking, fuzzy via `pg_trgm`. Would replace the in-memory scorer for |
| 127 | the cloud app. | 131 | the cloud app. |
| 128 | 3. **Per-scope LRU** — cache scored results; invalidate on source | 132 | 3. **Per-scope LRU** — cache scored results; invalidate on source |
| @@ -3,11 +3,13 @@ · 2 unmodified lines | |||
| 3 | ## What was done | 3 | ## What was done |
| 4 | 4 | ||
| 5 | ### 1. DBSchema alignment with better-auth | 5 | ### 1. DBSchema alignment with better-auth |
| 6 | |||
| 6 | - Vendored better-auth's `BetterAuthPluginDBSchema` pattern into `storage-core/src/schema.ts` | 7 | - Vendored better-auth's `BetterAuthPluginDBSchema` pattern into `storage-core/src/schema.ts` |
| 7 | - Made `modelName` optional (falls back to the key), renamed `disableMigrations` → `disableMigration`, dropped `order` | 8 | - Made `modelName` optional (falls back to the key), renamed `disableMigrations` → `disableMigration`, dropped `order` |
| 8 | - Removed explicit `modelName` from all plugin schemas (core-schema, openapi, mcp, graphql, google-discovery, workos-vault) — keys already match table names | 9 | - Removed explicit `modelName` from all plugin schemas (core-schema, openapi, mcp, graphql, google-discovery, workos-vault) — keys already match table names |
| 9 | 10 | ||
| 10 | ### 2. @executor-js/cli package (packages/core/cli/) | 11 | ### 2. @executor-js/cli package (packages/core/cli/) |
| 12 | |||
| 11 | - Mirrors better-auth's CLI approach: load config → collect plugin schemas → generate drizzle TS | 13 | - Mirrors better-auth's CLI approach: load config → collect plugin schemas → generate drizzle TS |
| 12 | - `executor generate --config ./executor.config.ts --output ./src/services/executor-schema.ts` | 14 | - `executor generate --config ./executor.config.ts --output ./src/services/executor-schema.ts` |
| 13 | - Generator ported from better-auth's drizzle generator, adapted for our DBSchema | 15 | - Generator ported from better-auth's drizzle generator, adapted for our DBSchema |
| @@ -15,11 +17,13 @@ · 1 unmodified line | |||
| 15 | - Uses jiti for config loading (supports TS configs) | 17 | - Uses jiti for config loading (supports TS configs) |
| 16 | 18 | ||
| 17 | ### 3. apps/cloud wired up | 19 | ### 3. apps/cloud wired up |
| 20 | |||
| 18 | - `executor.config.ts` defines plugins with stub credentials (only used for schema shape) | 21 | - `executor.config.ts` defines plugins with stub credentials (only used for schema shape) |
| 19 | - `drizzle.config.ts` points at both `schema.ts` (cloud tables) and `executor-schema.ts` (executor tables) | 22 | - `drizzle.config.ts` points at both `schema.ts` (cloud tables) and `executor-schema.ts` (executor tables) |
| 20 | - Fresh `drizzle/0000_initial.sql` with all tables (cloud + core + plugin + blob), indexes, FKs | 23 | - Fresh `drizzle/0000_initial.sql` with all tables (cloud + core + plugin + blob), indexes, FKs |
| 21 | 24 | ||
| 22 | ### 4. apps/local wired up | 25 | ### 4. apps/local wired up |
| 26 | |||
| 23 | - `executor.config.ts` defines plugins + dialect sqlite | 27 | - `executor.config.ts` defines plugins + dialect sqlite |
| 24 | - `drizzle.config.ts` for drizzle-kit generate/push | 28 | - `drizzle.config.ts` for drizzle-kit generate/push |
| 25 | - Generated `executor-schema.ts` with all sqlite tables + blob table | 29 | - Generated `executor-schema.ts` with all sqlite tables + blob table |
| @@ -27,6 +31,7 @@ · 1 unmodified line | |||
| 27 | - At startup: `migrate(db, { migrationsFolder })` from `drizzle-orm/bun-sqlite/migrator` applies pending migrations before constructing the adapter | 31 | - At startup: `migrate(db, { migrationsFolder })` from `drizzle-orm/bun-sqlite/migrator` applies pending migrations before constructing the adapter |
| 28 | 32 | ||
| 29 | ### 5. Adapter DDL removed | 33 | ### 5. Adapter DDL removed |
| 34 | |||
| 30 | - `makeSqliteAdapter` and `makePostgresAdapter` no longer run DDL — they return `DBAdapter` directly (not `Effect.Effect<DBAdapter>`) | 35 | - `makeSqliteAdapter` and `makePostgresAdapter` no longer run DDL — they return `DBAdapter` directly (not `Effect.Effect<DBAdapter>`) |
| 31 | - `makeSqliteBlobStore` and `makePostgresBlobStore` no longer run DDL — they return `BlobStore` directly | 36 | - `makeSqliteBlobStore` and `makePostgresBlobStore` no longer run DDL — they return `BlobStore` directly |
| 32 | - `buildCreateTableStatements` deleted from `storage-file/compile.ts` | 37 | - `buildCreateTableStatements` deleted from `storage-file/compile.ts` |
| @@ -39,6 +44,7 @@ · 6 unmodified lines | |||
| 39 | The `@executor-js/config` package already maintains `executor.jsonc` as a source of truth for source configurations. The `config-store.ts` decorator intercepts `putSource`/`removeSource` on each plugin and mirrors to this file. So for existing users, `executor.jsonc` already has everything. | 44 | The `@executor-js/config` package already maintains `executor.jsonc` as a source of truth for source configurations. The `config-store.ts` decorator intercepts `putSource`/`removeSource` on each plugin and mirrors to this file. So for existing users, `executor.jsonc` already has everything. |
| 40 | 45 | ||
| 41 | ### Local app migration | 46 | ### Local app migration |
| 47 | |||
| 42 | 1. On first run, `migrate()` creates all tables from the initial migration | 48 | 1. On first run, `migrate()` creates all tables from the initial migration |
| 43 | 2. Add a **sync-from-config step** after executor creation: | 49 | 2. Add a **sync-from-config step** after executor creation: |
| 44 | - `loadConfig(configPath)` reads `executor.jsonc` | 50 | - `loadConfig(configPath)` reads `executor.jsonc` |
| @@ -47,28 +53,30 @@ The `@executor-js/config` package already maintains `executor.jsonc` as a source · 2 unmodified lines | |||
| 47 | 3. Secrets survive untouched — they live in keychain/1password/file-secrets, not in the DB. The `secret` routing table gets re-populated when sources are added. | 53 | 3. Secrets survive untouched — they live in keychain/1password/file-secrets, not in the DB. The `secret` routing table gets re-populated when sources are added. |
| 48 | 54 | ||
| 49 | ### What won't survive (acceptable losses) | 55 | ### What won't survive (acceptable losses) |
| 56 | |||
| 50 | - Tool invocation history / blobs (old KV format) | 57 | - Tool invocation history / blobs (old KV format) |
| 51 | - OAuth sessions (ephemeral, users re-auth) | 58 | - OAuth sessions (ephemeral, users re-auth) |
| 52 | - Stale source state (re-fetching is actually a benefit) | 59 | - Stale source state (re-fetching is actually a benefit) |
| 53 | 60 | ||
| 54 | ### Cloud migration | 61 | ### Cloud migration |
| 62 | |||
| 55 | - Only ~2 users, just drop old tables and re-add manually | 63 | - Only ~2 users, just drop old tables and re-add manually |
| 56 | - Or write a one-off SQL migration | 64 | - Or write a one-off SQL migration |
| 57 | 65 | ||
| 58 | ## Key files | 66 | ## Key files |
| 59 | 67 | ||
| 60 | | File | Role | | 68 | | File | Role | |
| 61 | |------|------| | 69 | | --------------------------------------------- | ----------------------------------------- | |
| 62 | | `packages/core/cli/src/commands/generate.ts` | CLI generate command | | 70 | | `packages/core/cli/src/commands/generate.ts` | CLI generate command | |
| 63 | | `packages/core/cli/src/generators/drizzle.ts` | Drizzle schema generator | | 71 | | `packages/core/cli/src/generators/drizzle.ts` | Drizzle schema generator | |
| 64 | | `packages/core/sdk/src/config.ts` | `defineExecutorConfig` helper | | 72 | | `packages/core/sdk/src/config.ts` | `defineExecutorConfig` helper | |
| 65 | | `packages/core/config/src/schema.ts` | `executor.jsonc` schema | | 73 | | `packages/core/config/src/schema.ts` | `executor.jsonc` schema | |
| 66 | | `packages/core/config/src/config-store.ts` | Plugin store → config file decorator | | 74 | | `packages/core/config/src/config-store.ts` | Plugin store → config file decorator | |
| 67 | | `packages/core/config/src/load.ts` | Config loading | | 75 | | `packages/core/config/src/load.ts` | Config loading | |
| 68 | | `apps/cloud/executor.config.ts` | Cloud config for CLI | | 76 | | `apps/cloud/executor.config.ts` | Cloud config for CLI | |
| 69 | | `apps/cloud/src/services/executor-schema.ts` | Generated drizzle schema | | 77 | | `apps/cloud/src/services/executor-schema.ts` | Generated drizzle schema | |
| 70 | | `apps/cloud/drizzle/0000_*.sql` | Fresh migration | | 78 | | `apps/cloud/drizzle/0000_*.sql` | Fresh migration | |
| 71 | | `apps/local/executor.config.ts` | Local config for CLI | | 79 | | `apps/local/executor.config.ts` | Local config for CLI | |
| 72 | | `apps/local/src/server/executor-schema.ts` | Generated drizzle schema | | 80 | | `apps/local/src/server/executor-schema.ts` | Generated drizzle schema | |
| 73 | | `apps/local/drizzle/0000_*.sql` | Fresh migration | | 81 | | `apps/local/drizzle/0000_*.sql` | Fresh migration | |
| 74 | | `apps/local/src/server/executor.ts` | Local executor bootstrap with `migrate()` | | 82 | | `apps/local/src/server/executor.ts` | Local executor bootstrap with `migrate()` | |
| @@ -72,24 +72,24 @@ The manifest should likely be a JSON-serializable object with these top-level se · 71 unmodified lines | |||
| 72 | 72 | ||
| 73 | ```ts | 73 | ```ts |
| 74 | type PluginManifest = { | 74 | type PluginManifest = { |
| 75 | schemaVersion: string | 75 | schemaVersion: string; |
| 76 | id: string | 76 | id: string; |
| 77 | name: string | 77 | name: string; |
| 78 | version: string | 78 | version: string; |
| 79 | description?: string | 79 | description?: string; |
| 80 | publisher?: PluginPublisher | 80 | publisher?: PluginPublisher; |
| 81 | license?: string | 81 | license?: string; |
| 82 | homepage?: string | 82 | homepage?: string; |
| 83 | repository?: string | 83 | repository?: string; |
| 84 | categories?: PluginCategory[] | 84 | categories?: PluginCategory[]; |
| 85 | compatibility: PluginCompatibility | 85 | compatibility: PluginCompatibility; |
| 86 | entrypoints: PluginEntrypoints | 86 | entrypoints: PluginEntrypoints; |
| 87 | capabilities?: PluginCapabilities | 87 | capabilities?: PluginCapabilities; |
| 88 | extensions?: PluginExtensions | 88 | extensions?: PluginExtensions; |
| 89 | storage?: PluginStorageDeclaration | 89 | storage?: PluginStorageDeclaration; |
| 90 | package?: PluginPackageDeclaration | 90 | package?: PluginPackageDeclaration; |
| 91 | metadata?: Record<string, unknown> | 91 | metadata?: Record<string, unknown>; |
| 92 | } | 92 | }; |
| 93 | ``` | 93 | ``` |
| 94 | 94 | ||
| 95 | This is a planning type, not a final API. | 95 | This is a planning type, not a final API. |
| @@ -135,9 +135,7 @@ The install surface should support package sources rather than assuming one cust · 39 unmodified lines | |||
| 135 | User-facing install config can stay simple: | 135 | User-facing install config can stay simple: |
| 136 | 136 | ||
| 137 | ```ts | 137 | ```ts |
| 138 | type PluginInstallSpec = | 138 | type PluginInstallSpec = string | [string, Record<string, unknown>]; |
| 139 | | string | ||
| 140 | | [string, Record<string, unknown>] | ||
| 141 | ``` | 139 | ``` |
| 142 | 140 | ||
| 143 | The string identifies the package source. Examples might include `@executor/openapi`, `github:owner/repo`, `https://github.com/owner/repo.git`, or `./plugins/my-plugin`. Tuple options are installation configuration, not manifest data. | 141 | The string identifies the package source. Examples might include `@executor/openapi`, `github:owner/repo`, `https://github.com/owner/repo.git`, or `./plugins/my-plugin`. Tuple options are installation configuration, not manifest data. |
| @@ -146,10 +144,10 @@ Resolved install metadata should be stored separately from the manifest. It shou · 2 unmodified lines | |||
| 146 | 144 | ||
| 147 | ```ts | 145 | ```ts |
| 148 | type PluginPackageDeclaration = { | 146 | type PluginPackageDeclaration = { |
| 149 | source?: "npm" | "git" | "github" | "local" | "registry" | 147 | source?: "npm" | "git" | "github" | "local" | "registry"; |
| 150 | packageName?: string | 148 | packageName?: string; |
| 151 | repository?: string | 149 | repository?: string; |
| 152 | } | 150 | }; |
| 153 | ``` | 151 | ``` |
| 154 | 152 | ||
| 155 | This `package` block is optional planning shape, not final schema. For npm packages, standard `package.json` fields may already provide most of this information. | 153 | This `package` block is optional planning shape, not final schema. For npm packages, standard `package.json` fields may already provide most of this information. |
| @@ -160,12 +158,12 @@ Compatibility should be explicit and checked before plugin code is loaded. · 4 unmodified lines | |||
| 160 | 158 | ||
| 161 | ```ts | 159 | ```ts |
| 162 | type PluginCompatibility = { | 160 | type PluginCompatibility = { |
| 163 | executor: string | 161 | executor: string; |
| 164 | runtimeApi?: string | 162 | runtimeApi?: string; |
| 165 | platforms?: PluginPlatform[] | 163 | platforms?: PluginPlatform[]; |
| 166 | } | 164 | }; |
| 167 | 165 | ||
| 168 | type PluginPlatform = "local" | "cloud" | 166 | type PluginPlatform = "local" | "cloud"; |
| 169 | ``` | 167 | ``` |
| 170 | 168 | ||
| 171 | `executor` should probably be a semver range for supported Executor versions. | 169 | `executor` should probably be a semver range for supported Executor versions. |
| @@ -182,27 +180,27 @@ Entrypoints describe executable code or assets the host may load. · 10 unmodified lines | |||
| 182 | 180 | ||
| 183 | ```ts | 181 | ```ts |
| 184 | type PluginEntrypoints = { | 182 | type PluginEntrypoints = { |
| 185 | main?: PluginCodeEntrypoint | 183 | main?: PluginCodeEntrypoint; |
| 186 | sdk?: PluginCodeEntrypoint | 184 | sdk?: PluginCodeEntrypoint; |
| 187 | source?: PluginCodeEntrypoint | 185 | source?: PluginCodeEntrypoint; |
| 188 | secretStore?: PluginCodeEntrypoint | 186 | secretStore?: PluginCodeEntrypoint; |
| 189 | apiGroup?: PluginCodeEntrypoint | 187 | apiGroup?: PluginCodeEntrypoint; |
| 190 | apiHandlers?: PluginCodeEntrypoint | 188 | apiHandlers?: PluginCodeEntrypoint; |
| 191 | routeHandlers?: Record<string, PluginCodeEntrypoint> | 189 | routeHandlers?: Record<string, PluginCodeEntrypoint>; |
| 192 | backgroundServices?: Record<string, PluginCodeEntrypoint> | 190 | backgroundServices?: Record<string, PluginCodeEntrypoint>; |
| 193 | reactSource?: PluginUiEntrypoint | 191 | reactSource?: PluginUiEntrypoint; |
| 194 | reactSecretProvider?: PluginUiEntrypoint | 192 | reactSecretProvider?: PluginUiEntrypoint; |
| 195 | ui?: PluginUiEntrypoint | 193 | ui?: PluginUiEntrypoint; |
| 196 | adminUi?: PluginUiEntrypoint | 194 | adminUi?: PluginUiEntrypoint; |
| 197 | } | 195 | }; |
| 198 | 196 | ||
| 199 | type PluginCodeEntrypoint = { | 197 | type PluginCodeEntrypoint = { |
| 200 | module: string | 198 | module: string; |
| 201 | export?: string | 199 | export?: string; |
| 202 | runtime?: PluginRuntimeTarget | 200 | runtime?: PluginRuntimeTarget; |
| 203 | } | 201 | }; |
| 204 | 202 | ||
| 205 | type PluginRuntimeTarget = "worker" | "isolate" | "host-trusted" | 203 | type PluginRuntimeTarget = "worker" | "isolate" | "host-trusted"; |
| 206 | ``` | 204 | ``` |
| 207 | 205 | ||
| 208 | The exact shape may change, but the manifest should avoid requiring the host to import a plugin just to discover what code exists. | 206 | The exact shape may change, but the manifest should avoid requiring the host to import a plugin just to discover what code exists. |
| @@ -237,21 +235,21 @@ Example planning shape: · 28 unmodified lines | |||
| 237 | ```ts | 235 | ```ts |
| 238 | type PluginLayerDeclaration = { | 236 | type PluginLayerDeclaration = { |
| 239 | sdk?: { | 237 | sdk?: { |
| 240 | pluginId: string | 238 | pluginId: string; |
| 241 | entrypoint: keyof PluginEntrypoints | 239 | entrypoint: keyof PluginEntrypoints; |
| 242 | } | 240 | }; |
| 243 | api?: { | 241 | api?: { |
| 244 | requiresSdkPlugin: string | 242 | requiresSdkPlugin: string; |
| 245 | groupEntrypoint?: keyof PluginEntrypoints | 243 | groupEntrypoint?: keyof PluginEntrypoints; |
| 246 | handlersEntrypoint?: keyof PluginEntrypoints | 244 | handlersEntrypoint?: keyof PluginEntrypoints; |
| 247 | } | 245 | }; |
| 248 | react?: Array<{ | 246 | react?: Array<{ |
| 249 | kind: "source" | "secret-provider" | "settings" | "admin" | 247 | kind: "source" | "secret-provider" | "settings" | "admin"; |
| 250 | requiresSdkPlugin?: string | 248 | requiresSdkPlugin?: string; |
| 251 | requiresApi?: boolean | 249 | requiresApi?: boolean; |
| 252 | entrypoint: keyof PluginEntrypoints | 250 | entrypoint: keyof PluginEntrypoints; |
| 253 | }> | 251 | }>; |
| 254 | } | 252 | }; |
| 255 | ``` | 253 | ``` |
| 256 | 254 | ||
| 257 | This is planning shape, not final schema. The important requirement is that hosts can opt into layers independently while still validating that the selected layers are compatible. | 255 | This is planning shape, not final schema. The important requirement is that hosts can opt into layers independently while still validating that the selected layers are compatible. |
| @@ -264,13 +262,13 @@ Extensions declare what the plugin contributes at a product level. · 6 unmodified lines | |||
| 264 | 262 | ||
| 265 | ```ts | 263 | ```ts |
| 266 | type PluginExtensions = { | 264 | type PluginExtensions = { |
| 267 | sources?: SourceExtensionDeclaration[] | 265 | sources?: SourceExtensionDeclaration[]; |
| 268 | secretStores?: SecretStoreExtensionDeclaration[] | 266 | secretStores?: SecretStoreExtensionDeclaration[]; |
| 269 | tools?: ToolExtensionDeclaration[] | 267 | tools?: ToolExtensionDeclaration[]; |
| 270 | routes?: RouteExtensionDeclaration[] | 268 | routes?: RouteExtensionDeclaration[]; |
| 271 | uiSurfaces?: UiSurfaceDeclaration[] | 269 | uiSurfaces?: UiSurfaceDeclaration[]; |
| 272 | backgroundServices?: BackgroundServiceDeclaration[] | 270 | backgroundServices?: BackgroundServiceDeclaration[]; |
| 273 | } | 271 | }; |
| 274 | ``` | 272 | ``` |
| 275 | 273 | ||
| 276 | Some extension declarations may be fully static. Others may be partial declarations that are completed by plugin registration at load time. | 274 | Some extension declarations may be fully static. Others may be partial declarations that are completed by plugin registration at load time. |
| @@ -283,16 +281,16 @@ Capabilities declare requested access. Grants are separate host state. · 6 unmodified lines | |||
| 283 | 281 | ||
| 284 | ```ts | 282 | ```ts |
| 285 | type PluginCapabilities = { | 283 | type PluginCapabilities = { |
| 286 | tools?: ToolCapabilityDeclaration[] | 284 | tools?: ToolCapabilityDeclaration[]; |
| 287 | sources?: SourceCapabilityDeclaration[] | 285 | sources?: SourceCapabilityDeclaration[]; |
| 288 | secrets?: SecretCapabilityDeclaration[] | 286 | secrets?: SecretCapabilityDeclaration[]; |
| 289 | network?: NetworkCapabilityDeclaration[] | 287 | network?: NetworkCapabilityDeclaration[]; |
| 290 | storage?: StorageCapabilityDeclaration[] | 288 | storage?: StorageCapabilityDeclaration[]; |
| 291 | scopes?: ScopeCapabilityDeclaration[] | 289 | scopes?: ScopeCapabilityDeclaration[]; |
| 292 | filesystem?: FilesystemCapabilityDeclaration[] | 290 | filesystem?: FilesystemCapabilityDeclaration[]; |
| 293 | execution?: ExecutionCapabilityDeclaration[] | 291 | execution?: ExecutionCapabilityDeclaration[]; |
| 294 | host?: HostCapabilityDeclaration[] | 292 | host?: HostCapabilityDeclaration[]; |
| 295 | } | 293 | }; |
| 296 | ``` | 294 | ``` |
| 297 | 295 | ||
| 298 | Capabilities should be deny-by-default. If a capability is missing, the runtime should deny that access. | 296 | Capabilities should be deny-by-default. If a capability is missing, the runtime should deny that access. |
| @@ -344,11 +342,11 @@ The manifest should let plugins declare storage needs separately from storage gr · 45 unmodified lines | |||
| 344 | ```ts | 342 | ```ts |
| 345 | type PluginStorageDeclaration = { | 343 | type PluginStorageDeclaration = { |
| 346 | collections?: Array<{ | 344 | collections?: Array<{ |
| 347 | name: string | 345 | name: string; |
| 348 | kind: "kv" | "document" | "sql" | 346 | kind: "kv" | "document" | "sql"; |
| 349 | description?: string | 347 | description?: string; |
| 350 | }> | 348 | }>; |
| 351 | } | 349 | }; |
| 352 | ``` | 350 | ``` |
| 353 | 351 | ||
| 354 | Storage declarations help the host pre-create, migrate, display, or approve plugin storage. | 352 | Storage declarations help the host pre-create, migrate, display, or approve plugin storage. |
| @@ -364,18 +362,18 @@ Executor does not need a custom publishing flow immediately. npm, GitHub/git, an · 9 unmodified lines | |||
| 364 | ```ts | 362 | ```ts |
| 365 | type PluginArtifacts = { | 363 | type PluginArtifacts = { |
| 366 | files?: Array<{ | 364 | files?: Array<{ |
| 367 | path: string | 365 | path: string; |
| 368 | size?: number | 366 | size?: number; |
| 369 | sha256?: string | 367 | sha256?: string; |
| 370 | kind?: "code" | "asset" | "manifest" | "source-map" | 368 | kind?: "code" | "asset" | "manifest" | "source-map"; |
| 371 | }> | 369 | }>; |
| 372 | bundle?: { | 370 | bundle?: { |
| 373 | format: "esm" | 371 | format: "esm"; |
| 374 | main: string | 372 | main: string; |
| 375 | size?: number | 373 | size?: number; |
| 376 | sha256?: string | 374 | sha256?: string; |
| 377 | } | 375 | }; |
| 378 | } | 376 | }; |
| 379 | ``` | 377 | ``` |
| 380 | 378 | ||
| 381 | Cloud and local do not need to store cached artifacts the same way, but they should agree on immutable identity and verification where possible. | 379 | Cloud and local do not need to store cached artifacts the same way, but they should agree on immutable identity and verification where possible. |
| @@ -1,12 +1,7 @@ | |||
| 1 | import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"; | 1 | import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"; |
| 2 | import { Schema } from "effect"; | 2 | import { Schema } from "effect"; |
| 3 | 3 | ||
| 4 | import { | 4 | import { ConnectionId, ConnectionInUseError, ScopeId, Usage } from "@executor-js/sdk"; |
| 5 | ConnectionId, | ||
| 6 | ConnectionInUseError, | ||
| 7 | ScopeId, | ||
| 8 | Usage, | ||
| 9 | } from "@executor-js/sdk"; | ||
| 10 | 5 | ||
| 11 | import { InternalError } from "../observability"; | 6 | import { InternalError } from "../observability"; |
| 12 | 7 | ||
| @@ -54,13 +49,9 @@ export const ConnectionsApi = HttpApiGroup.make("connections") · 41 unmodified lines | |||
| 54 | }), | 49 | }), |
| 55 | ) | 50 | ) |
| 56 | .add( | 51 | .add( |
| 57 | HttpApiEndpoint.get( | 52 | HttpApiEndpoint.get("usages", "/scopes/:scopeId/connections/:connectionId/usages", { |
| 58 | "usages", | 53 | params: ConnectionParams, |
| 59 | "/scopes/:scopeId/connections/:connectionId/usages", | 54 | success: Schema.Array(Usage), |
| 60 | { | 55 | error: InternalError, |
| 61 | params: ConnectionParams, | 56 | }), |
| 62 | success: Schema.Array(Usage), | ||
| 63 | error: InternalError, | ||
| 64 | }, | ||
| 65 | ), | ||
| 66 | ); | 57 | ); |
| @@ -18,35 +18,32 @@ const refToResponse = (ref: ConnectionRef) => ({ · 17 unmodified lines | |||
| 18 | updatedAt: ref.updatedAt.getTime(), | 18 | updatedAt: ref.updatedAt.getTime(), |
| 19 | }); | 19 | }); |
| 20 | 20 | ||
| 21 | export const ConnectionsHandlers = HttpApiBuilder.group( | 21 | export const ConnectionsHandlers = HttpApiBuilder.group(ExecutorApi, "connections", (handlers) => |
| 22 | ExecutorApi, | 22 | handlers |
| 23 | "connections", | 23 | .handle("list", () => |
| 24 | (handlers) => | 24 | capture( |
| 25 | handlers | 25 | Effect.gen(function* () { |
| 26 | .handle("list", () => | 26 | const executor = yield* ExecutorService; |
| 27 | capture( | 27 | const refs = yield* executor.connections.list(); |
| 28 | Effect.gen(function* () { | 28 | return refs.map(refToResponse); |
| 29 | const executor = yield* ExecutorService; | 29 | }), |
| 30 | const refs = yield* executor.connections.list(); | ||
| 31 | return refs.map(refToResponse); | ||
| 32 | }), | ||
| 33 | ), | ||
| 34 | ) | ||
| 35 | .handle("remove", ({ params: path }) => | ||
| 36 | capture( | ||
| 37 | Effect.gen(function* () { | ||
| 38 | const executor = yield* ExecutorService; | ||
| 39 | yield* executor.connections.remove(path.connectionId); | ||
| 40 | return { removed: true }; | ||
| 41 | }), | ||
| 42 | ), | ||
| 43 | ) | ||
| 44 | .handle("usages", ({ params: path }) => | ||
| 45 | capture( | ||
| 46 | Effect.gen(function* () { | ||
| 47 | const executor = yield* ExecutorService; | ||
| 48 | return yield* executor.connections.usages(path.connectionId); | ||
| 49 | }), | ||
| 50 | ), | ||
| 51 | ), | 30 | ), |
| 31 | ) | ||
| 32 | .handle("remove", ({ params: path }) => | ||
| 33 | capture( | ||
| 34 | Effect.gen(function* () { | ||
| 35 | const executor = yield* ExecutorService; | ||
| 36 | yield* executor.connections.remove(path.connectionId); | ||
| 37 | return { removed: true }; | ||
| 38 | }), | ||
| 39 | ), | ||
| 40 | ) | ||
| 41 | .handle("usages", ({ params: path }) => | ||
| 42 | capture( | ||
| 43 | Effect.gen(function* () { | ||
| 44 | const executor = yield* ExecutorService; | ||
| 45 | return yield* executor.connections.usages(path.connectionId); | ||
| 46 | }), | ||
| 47 | ), | ||
| 48 | ), | ||
| 52 | ); | 49 | ); |
| @@ -9,60 +9,64 @@ import { capture, captureEngineError } from "@executor-js/api"; · 8 unmodified lines | |||
| 9 | export const ExecutionsHandlers = HttpApiBuilder.group(ExecutorApi, "executions", (handlers) => | 9 | export const ExecutionsHandlers = HttpApiBuilder.group(ExecutorApi, "executions", (handlers) => |
| 10 | handlers | 10 | handlers |
| 11 | .handle("execute", ({ payload }) => | 11 | .handle("execute", ({ payload }) => |
| 12 | capture(Effect.gen(function* () { | 12 | capture( |
| 13 | const engine = yield* ExecutionEngineService; | 13 | Effect.gen(function* () { |
| 14 | const outcome = yield* captureEngineError(engine.executeWithPause(payload.code)); | 14 | const engine = yield* ExecutionEngineService; |
| 15 | const outcome = yield* captureEngineError(engine.executeWithPause(payload.code)); | ||
| 15 | 16 | ||
| 16 | if (outcome.status === "completed") { | 17 | if (outcome.status === "completed") { |
| 17 | const formatted = formatExecuteResult(outcome.result); | 18 | const formatted = formatExecuteResult(outcome.result); |
| 19 | return { | ||
| 20 | status: "completed" as const, | ||
| 21 | text: formatted.text, | ||
| 22 | structured: formatted.structured, | ||
| 23 | isError: formatted.isError, | ||
| 24 | }; | ||
| 25 | } | ||
| 26 | |||
| 27 | const formatted = formatPausedExecution(outcome.execution); | ||
| 18 | return { | 28 | return { |
| 19 | status: "completed" as const, | 29 | status: "paused" as const, |
| 20 | text: formatted.text, | 30 | text: formatted.text, |
| 21 | structured: formatted.structured, | 31 | structured: formatted.structured, |
| 22 | isError: formatted.isError, | ||
| 23 | }; | 32 | }; |
| 24 | } | 33 | }), |
| 25 | 34 | ), | |
| 26 | const formatted = formatPausedExecution(outcome.execution); | ||
| 27 | return { | ||
| 28 | status: "paused" as const, | ||
| 29 | text: formatted.text, | ||
| 30 | structured: formatted.structured, | ||
| 31 | }; | ||
| 32 | })), | ||
| 33 | ) | 35 | ) |
| 34 | .handle("resume", ({ params: path, payload }) => | 36 | .handle("resume", ({ params: path, payload }) => |
| 35 | capture(Effect.gen(function* () { | 37 | capture( |
| 36 | const engine = yield* ExecutionEngineService; | 38 | Effect.gen(function* () { |
| 37 | const result = yield* captureEngineError( | 39 | const engine = yield* ExecutionEngineService; |
| 38 | engine.resume(path.executionId, { | 40 | const result = yield* captureEngineError( |
| 39 | action: payload.action, | 41 | engine.resume(path.executionId, { |
| 40 | content: payload.content as Record<string, unknown> | undefined, | 42 | action: payload.action, |
| 41 | }), | 43 | content: payload.content as Record<string, unknown> | undefined, |
| 42 | ); | 44 | }), |
| 45 | ); | ||
| 43 | 46 | ||
| 44 | if (!result) { | 47 | if (!result) { |
| 45 | return yield* Effect.fail({ | 48 | return yield* Effect.fail({ |
| 46 | _tag: "ExecutionNotFoundError" as const, | 49 | _tag: "ExecutionNotFoundError" as const, |
| 47 | executionId: path.executionId, | 50 | executionId: path.executionId, |
| 48 | }); | 51 | }); |
| 49 | } | 52 | } |
| 50 | 53 | ||
| 51 | if (result.status === "completed") { | 54 | if (result.status === "completed") { |
| 52 | const formatted = formatExecuteResult(result.result); | 55 | const formatted = formatExecuteResult(result.result); |
| 56 | return { | ||
| 57 | text: formatted.text, | ||
| 58 | structured: formatted.structured, | ||
| 59 | isError: formatted.isError, | ||
| 60 | }; | ||
| 61 | } | ||
| 62 | |||
| 63 | const formatted = formatPausedExecution(result.execution); | ||
| 53 | return { | 64 | return { |
| 54 | text: formatted.text, | 65 | text: formatted.text, |
| 55 | structured: formatted.structured, | 66 | structured: formatted.structured, |
| 56 | isError: formatted.isError, | 67 | isError: false, |
| 57 | }; | 68 | }; |
| 58 | } | 69 | }), |
| 59 | 70 | ), | |
| 60 | const formatted = formatPausedExecution(result.execution); | ||
| 61 | return { | ||
| 62 | text: formatted.text, | ||
| 63 | structured: formatted.structured, | ||
| 64 | isError: false, | ||
| 65 | }; | ||
| 66 | })), | ||
| 67 | ), | 71 | ), |
| 68 | ); | 72 | ); |
| @@ -67,7 +67,8 @@ const toPopupErrorMessage = (error: unknown): string => { · 66 unmodified lines | |||
| 67 | if (message) return message; | 67 | if (message) return message; |
| 68 | 68 | ||
| 69 | const sessionNotFound = decodeOAuthSessionNotFoundError(error); | 69 | const sessionNotFound = decodeOAuthSessionNotFoundError(error); |
| 70 | if (Option.isSome(sessionNotFound)) return `OAuth session not found: ${sessionNotFound.value.sessionId}`; | 70 | if (Option.isSome(sessionNotFound)) |
| 71 | return `OAuth session not found: ${sessionNotFound.value.sessionId}`; | ||
| 71 | return "Authentication failed"; | 72 | return "Authentication failed"; |
| 72 | }; | 73 | }; |
| 73 | 74 | ||
| @@ -7,23 +7,25 @@ import { capture } from "@executor-js/api"; · 6 unmodified lines | |||
| 7 | 7 | ||
| 8 | export const ScopeHandlers = HttpApiBuilder.group(ExecutorApi, "scope", (handlers) => | 8 | export const ScopeHandlers = HttpApiBuilder.group(ExecutorApi, "scope", (handlers) => |
| 9 | handlers.handle("info", () => | 9 | handlers.handle("info", () => |
| 10 | capture(Effect.gen(function* () { | 10 | capture( |
| 11 | const executor = yield* ExecutorService; | 11 | Effect.gen(function* () { |
| 12 | // `id` / `name` / `dir` continue to point at the outermost scope so | 12 | const executor = yield* ExecutorService; |
| 13 | // existing clients keep their source writes org/workspace-scoped. | 13 | // `id` / `name` / `dir` continue to point at the outermost scope so |
| 14 | // `stack` exposes the full innermost-first scope stack so the UI can | 14 | // existing clients keep their source writes org/workspace-scoped. |
| 15 | // deliberately target per-user secret writes when binding credentials. | 15 | // `stack` exposes the full innermost-first scope stack so the UI can |
| 16 | const scope = executor.scopes.at(-1)!; | 16 | // deliberately target per-user secret writes when binding credentials. |
| 17 | return { | 17 | const scope = executor.scopes.at(-1)!; |
| 18 | id: scope.id, | 18 | return { |
| 19 | name: scope.name, | 19 | id: scope.id, |
| 20 | dir: scope.name, | 20 | name: scope.name, |
| 21 | stack: executor.scopes.map((entry) => ({ | 21 | dir: scope.name, |
| 22 | id: entry.id, | 22 | stack: executor.scopes.map((entry) => ({ |
| 23 | name: entry.name, | 23 | id: entry.id, |
| 24 | dir: entry.name, | 24 | name: entry.name, |
| 25 | })), | 25 | dir: entry.name, |
| 26 | }; | 26 | })), |
| 27 | })), | 27 | }; |
| 28 | }), | ||
| 29 | ), | ||
| 28 | ), | 30 | ), |
| 29 | ); | 31 | ); |
| @@ -17,45 +17,55 @@ const refToResponse = (ref: SecretRef) => ({ · 16 unmodified lines | |||
| 17 | export const SecretsHandlers = HttpApiBuilder.group(ExecutorApi, "secrets", (handlers) => | 17 | export const SecretsHandlers = HttpApiBuilder.group(ExecutorApi, "secrets", (handlers) => |
| 18 | handlers | 18 | handlers |
| 19 | .handle("list", () => | 19 | .handle("list", () => |
| 20 | capture(Effect.gen(function* () { | 20 | capture( |
| 21 | const executor = yield* ExecutorService; | 21 | Effect.gen(function* () { |
| 22 | const refs = yield* executor.secrets.list(); | 22 | const executor = yield* ExecutorService; |
| 23 | return refs.map(refToResponse); | 23 | const refs = yield* executor.secrets.list(); |
| 24 | })), | 24 | return refs.map(refToResponse); |
| 25 | }), | ||
| 26 | ), | ||
| 25 | ) | 27 | ) |
| 26 | .handle("status", ({ params: path }) => | 28 | .handle("status", ({ params: path }) => |
| 27 | capture(Effect.gen(function* () { | 29 | capture( |
| 28 | const executor = yield* ExecutorService; | 30 | Effect.gen(function* () { |
| 29 | const status = yield* executor.secrets.status(path.secretId); | 31 | const executor = yield* ExecutorService; |
| 30 | return { secretId: path.secretId, status }; | 32 | const status = yield* executor.secrets.status(path.secretId); |
| 31 | })), | 33 | return { secretId: path.secretId, status }; |
| 34 | }), | ||
| 35 | ), | ||
| 32 | ) | 36 | ) |
| 33 | .handle("set", ({ params: path, payload }) => | 37 | .handle("set", ({ params: path, payload }) => |
| 34 | capture(Effect.gen(function* () { | 38 | capture( |
| 35 | const executor = yield* ExecutorService; | 39 | Effect.gen(function* () { |
| 36 | const ref = yield* executor.secrets.set( | 40 | const executor = yield* ExecutorService; |
| 37 | new SetSecretInput({ | 41 | const ref = yield* executor.secrets.set( |
| 38 | id: payload.id, | 42 | new SetSecretInput({ |
| 39 | scope: path.scopeId, | 43 | id: payload.id, |
| 40 | name: payload.name, | 44 | scope: path.scopeId, |
| 41 | value: payload.value, | 45 | name: payload.name, |
| 42 | provider: payload.provider, | 46 | value: payload.value, |
| 43 | }), | 47 | provider: payload.provider, |
| 44 | ); | 48 | }), |
| 45 | return refToResponse(ref); | 49 | ); |
| 46 | })), | 50 | return refToResponse(ref); |
| 51 | }), | ||
| 52 | ), | ||
| 47 | ) | 53 | ) |
| 48 | .handle("remove", ({ params: path }) => | 54 | .handle("remove", ({ params: path }) => |
| 49 | capture(Effect.gen(function* () { | 55 | capture( |
| 50 | const executor = yield* ExecutorService; | 56 | Effect.gen(function* () { |
| 51 | yield* executor.secrets.remove(path.secretId); | 57 | const executor = yield* ExecutorService; |
| 52 | return { removed: true }; | 58 | yield* executor.secrets.remove(path.secretId); |
| 53 | })), | 59 | return { removed: true }; |
| 60 | }), | ||
| 61 | ), | ||
| 54 | ) | 62 | ) |
| 55 | .handle("usages", ({ params: path }) => | 63 | .handle("usages", ({ params: path }) => |
| 56 | capture(Effect.gen(function* () { | 64 | capture( |
| 57 | const executor = yield* ExecutorService; | 65 | Effect.gen(function* () { |
| 58 | return yield* executor.secrets.usages(path.secretId); | 66 | const executor = yield* ExecutorService; |
| 59 | })), | 67 | return yield* executor.secrets.usages(path.secretId); |
| 68 | }), | ||
| 69 | ), | ||
| 60 | ), | 70 | ), |
| 61 | ); | 71 | ); |
| @@ -9,72 +9,82 @@ import { capture } from "@executor-js/api"; · 8 unmodified lines | |||
| 9 | export const SourcesHandlers = HttpApiBuilder.group(ExecutorApi, "sources", (handlers) => | 9 | export const SourcesHandlers = HttpApiBuilder.group(ExecutorApi, "sources", (handlers) => |
| 10 | handlers | 10 | handlers |
| 11 | .handle("list", () => | 11 | .handle("list", () => |
| 12 | capture(Effect.gen(function* () { | 12 | capture( |
| 13 | const executor = yield* ExecutorService; | 13 | Effect.gen(function* () { |
| 14 | const sources = yield* executor.sources.list(); | 14 | const executor = yield* ExecutorService; |
| 15 | return sources.map((s) => ({ | 15 | const sources = yield* executor.sources.list(); |
| 16 | id: s.id, | 16 | return sources.map((s) => ({ |
| 17 | scopeId: s.scopeId ? ScopeId.make(s.scopeId) : undefined, | 17 | id: s.id, |
| 18 | name: s.name, | 18 | scopeId: s.scopeId ? ScopeId.make(s.scopeId) : undefined, |
| 19 | kind: s.kind, | 19 | name: s.name, |
| 20 | url: s.url, | 20 | kind: s.kind, |
| 21 | runtime: s.runtime, | 21 | url: s.url, |
| 22 | canRemove: s.canRemove, | 22 | runtime: s.runtime, |
| 23 | canRefresh: s.canRefresh, | 23 | canRemove: s.canRemove, |
| 24 | canEdit: s.canEdit, | 24 | canRefresh: s.canRefresh, |
| 25 | })); | 25 | canEdit: s.canEdit, |
| 26 | })), | 26 | })); |
| 27 | }), | ||
| 28 | ), | ||
| 27 | ) | 29 | ) |
| 28 | .handle("remove", ({ params: path }) => | 30 | .handle("remove", ({ params: path }) => |
| 29 | capture(Effect.gen(function* () { | 31 | capture( |
| 30 | const executor = yield* ExecutorService; | 32 | Effect.gen(function* () { |
| 31 | yield* executor.sources.remove(path.sourceId); | 33 | const executor = yield* ExecutorService; |
| 32 | return { removed: true }; | 34 | yield* executor.sources.remove(path.sourceId); |
| 33 | })), | 35 | return { removed: true }; |
| 36 | }), | ||
| 37 | ), | ||
| 34 | ) | 38 | ) |
| 35 | .handle("refresh", ({ params: path }) => | 39 | .handle("refresh", ({ params: path }) => |
| 36 | capture(Effect.gen(function* () { | 40 | capture( |
| 37 | const executor = yield* ExecutorService; | 41 | Effect.gen(function* () { |
| 38 | yield* executor.sources.refresh(path.sourceId); | 42 | const executor = yield* ExecutorService; |
| 39 | return { refreshed: true }; | 43 | yield* executor.sources.refresh(path.sourceId); |
| 40 | })), | 44 | return { refreshed: true }; |
| 45 | }), | ||
| 46 | ), | ||
| 41 | ) | 47 | ) |
| 42 | .handle("tools", ({ params: path }) => | 48 | .handle("tools", ({ params: path }) => |
| 43 | capture(Effect.gen(function* () { | 49 | capture( |
| 44 | const executor = yield* ExecutorService; | 50 | Effect.gen(function* () { |
| 45 | // Source detail is a management view — include policy-blocked | 51 | const executor = yield* ExecutorService; |
| 46 | // tools so users can see and unblock them from the same place | 52 | // Source detail is a management view — include policy-blocked |
| 47 | // they review the source's other tools. Annotations are loaded | 53 | // tools so users can see and unblock them from the same place |
| 48 | // so the UI can show the plugin's default approval state for | 54 | // they review the source's other tools. Annotations are loaded |
| 49 | // tools that have no user policy override. | 55 | // so the UI can show the plugin's default approval state for |
| 50 | const tools = yield* executor.tools.list({ | 56 | // tools that have no user policy override. |
| 51 | sourceId: path.sourceId, | 57 | const tools = yield* executor.tools.list({ |
| 52 | includeAnnotations: true, | 58 | sourceId: path.sourceId, |
| 53 | includeBlocked: true, | 59 | includeAnnotations: true, |
| 54 | }); | 60 | includeBlocked: true, |
| 55 | return tools.map((t) => ({ | 61 | }); |
| 56 | id: ToolId.make(t.id), | 62 | return tools.map((t) => ({ |
| 57 | pluginId: t.pluginId, | 63 | id: ToolId.make(t.id), |
| 58 | sourceId: t.sourceId, | 64 | pluginId: t.pluginId, |
| 59 | name: t.name, | 65 | sourceId: t.sourceId, |
| 60 | description: t.description, | 66 | name: t.name, |
| 61 | mayElicit: t.annotations?.mayElicit, | 67 | description: t.description, |
| 62 | requiresApproval: t.annotations?.requiresApproval, | 68 | mayElicit: t.annotations?.mayElicit, |
| 63 | approvalDescription: t.annotations?.approvalDescription, | 69 | requiresApproval: t.annotations?.requiresApproval, |
| 64 | })); | 70 | approvalDescription: t.annotations?.approvalDescription, |
| 65 | })), | 71 | })); |
| 72 | }), | ||
| 73 | ), | ||
| 66 | ) | 74 | ) |
| 67 | .handle("detect", ({ payload }) => | 75 | .handle("detect", ({ payload }) => |
| 68 | capture(Effect.gen(function* () { | 76 | capture( |
| 69 | const executor = yield* ExecutorService; | 77 | Effect.gen(function* () { |
| 70 | const results = yield* executor.sources.detect(payload.url); | 78 | const executor = yield* ExecutorService; |
| 71 | return results.map((r) => ({ | 79 | const results = yield* executor.sources.detect(payload.url); |
| 72 | kind: r.kind, | 80 | return results.map((r) => ({ |
| 73 | confidence: r.confidence, | 81 | kind: r.kind, |
| 74 | endpoint: r.endpoint, | 82 | confidence: r.confidence, |
| 75 | name: r.name, | 83 | endpoint: r.endpoint, |
| 76 | namespace: r.namespace, | 84 | name: r.name, |
| 77 | })); | 85 | namespace: r.namespace, |
| 78 | })), | 86 | })); |
| 87 | }), | ||
| 88 | ), | ||
| 79 | ), | 89 | ), |
| 80 | ); | 90 | ); |
| @@ -9,35 +9,39 @@ import { capture } from "@executor-js/api"; · 8 unmodified lines | |||
| 9 | export const ToolsHandlers = HttpApiBuilder.group(ExecutorApi, "tools", (handlers) => | 9 | export const ToolsHandlers = HttpApiBuilder.group(ExecutorApi, "tools", (handlers) => |
| 10 | handlers | 10 | handlers |
| 11 | .handle("list", () => | 11 | .handle("list", () => |
| 12 | capture(Effect.gen(function* () { | 12 | capture( |
| 13 | const executor = yield* ExecutorService; | 13 | Effect.gen(function* () { |
| 14 | // Tools page is a management view — include policy-blocked tools | 14 | const executor = yield* ExecutorService; |
| 15 | // (so users can unblock them) and load annotations so the row can | 15 | // Tools page is a management view — include policy-blocked tools |
| 16 | // show the plugin's default approval state when no user rule | 16 | // (so users can unblock them) and load annotations so the row can |
| 17 | // matches. Mirrors the per-source `sources.tools` handler. | 17 | // show the plugin's default approval state when no user rule |
| 18 | const tools = yield* executor.tools.list({ | 18 | // matches. Mirrors the per-source `sources.tools` handler. |
| 19 | includeAnnotations: true, | 19 | const tools = yield* executor.tools.list({ |
| 20 | includeBlocked: true, | 20 | includeAnnotations: true, |
| 21 | }); | 21 | includeBlocked: true, |
| 22 | return tools.map((t) => ({ | 22 | }); |
| 23 | id: ToolId.make(t.id), | 23 | return tools.map((t) => ({ |
| 24 | pluginId: t.pluginId, | 24 | id: ToolId.make(t.id), |
| 25 | sourceId: t.sourceId, | 25 | pluginId: t.pluginId, |
| 26 | name: t.name, | 26 | sourceId: t.sourceId, |
| 27 | description: t.description, | 27 | name: t.name, |
| 28 | mayElicit: t.annotations?.mayElicit, | 28 | description: t.description, |
| 29 | requiresApproval: t.annotations?.requiresApproval, | 29 | mayElicit: t.annotations?.mayElicit, |
| 30 | })); | 30 | requiresApproval: t.annotations?.requiresApproval, |
| 31 | })), | 31 | })); |
| 32 | }), | ||
| 33 | ), | ||
| 32 | ) | 34 | ) |
| 33 | .handle("schema", ({ params: path }) => | 35 | .handle("schema", ({ params: path }) => |
| 34 | capture(Effect.gen(function* () { | 36 | capture( |
| 35 | const executor = yield* ExecutorService; | 37 | Effect.gen(function* () { |
| 36 | const schema = yield* executor.tools.schema(path.toolId); | 38 | const executor = yield* ExecutorService; |
| 37 | if (schema === null) { | 39 | const schema = yield* executor.tools.schema(path.toolId); |
| 38 | return yield* new ToolNotFoundError({ toolId: path.toolId }); | 40 | if (schema === null) { |
| 39 | } | 41 | return yield* new ToolNotFoundError({ toolId: path.toolId }); |
| 40 | return schema; | 42 | } |
| 41 | })), | 43 | return schema; |
| 44 | }), | ||
| 45 | ), | ||
| 42 | ), | 46 | ), |
| 43 | ); | 47 | ); |
| @@ -172,10 +172,12 @@ describe("runOAuthCallback", () => { · 171 unmodified lines | |||
| 172 | class DomainError extends Data.TaggedError("DomainError")<{ | 172 | class DomainError extends Data.TaggedError("DomainError")<{ |
| 173 | readonly message: string; | 173 | readonly message: string; |
| 174 | }> {} | 174 | }> {} |
| 175 | const isDomainError = Schema.is(Schema.Struct({ | 175 | const isDomainError = Schema.is( |
| 176 | _tag: Schema.Literal("DomainError"), | 176 | Schema.Struct({ |
| 177 | message: Schema.String, | 177 | _tag: Schema.Literal("DomainError"), |
| 178 | })); | 178 | message: Schema.String, |
| 179 | }), | ||
| 180 | ); | ||
| 179 | const html = await Effect.runPromise( | 181 | const html = await Effect.runPromise( |
| 180 | runOAuthCallback<GoogleAuth, DomainError, never>({ | 182 | runOAuthCallback<GoogleAuth, DomainError, never>({ |
| 181 | complete: () => Effect.fail(new DomainError({ message: "Code expired" })), | 183 | complete: () => Effect.fail(new DomainError({ message: "Code expired" })), |
| @@ -12,10 +12,7 @@ · 11 unmodified lines | |||
| 12 | 12 | ||
| 13 | import { Cause, Effect } from "effect"; | 13 | import { Cause, Effect } from "effect"; |
| 14 | 14 | ||
| 15 | import { | 15 | import { OAUTH_POPUP_MESSAGE_TYPE, type OAuthPopupResult } from "@executor-js/sdk"; |
| 16 | OAUTH_POPUP_MESSAGE_TYPE, | ||
| 17 | type OAuthPopupResult, | ||
| 18 | } from "@executor-js/sdk"; | ||
| 19 | 16 | ||
| 20 | export { OAUTH_POPUP_MESSAGE_TYPE, isOAuthPopupResult } from "@executor-js/sdk"; | 17 | export { OAUTH_POPUP_MESSAGE_TYPE, isOAuthPopupResult } from "@executor-js/sdk"; |
| 21 | export type { OAuthPopupResult } from "@executor-js/sdk"; | 18 | export type { OAuthPopupResult } from "@executor-js/sdk"; |
| @@ -36,9 +36,7 @@ const ProbePayload = Schema.Struct({ · 35 unmodified lines | |||
| 36 | const ProbeResponse = Schema.Struct({ | 36 | const ProbeResponse = Schema.Struct({ |
| 37 | resourceMetadata: Schema.NullOr(Schema.Record(Schema.String, Schema.Unknown)), | 37 | resourceMetadata: Schema.NullOr(Schema.Record(Schema.String, Schema.Unknown)), |
| 38 | resourceMetadataUrl: Schema.NullOr(Schema.String), | 38 | resourceMetadataUrl: Schema.NullOr(Schema.String), |
| 39 | authorizationServerMetadata: Schema.NullOr( | 39 | authorizationServerMetadata: Schema.NullOr(Schema.Record(Schema.String, Schema.Unknown)), |
| 40 | Schema.Record(Schema.String, Schema.Unknown), | ||
| 41 | ), | ||
| 42 | authorizationServerMetadataUrl: Schema.NullOr(Schema.String), | 40 | authorizationServerMetadataUrl: Schema.NullOr(Schema.String), |
| 43 | authorizationServerUrl: Schema.NullOr(Schema.String), | 41 | authorizationServerUrl: Schema.NullOr(Schema.String), |
| 44 | supportsDynamicRegistration: Schema.Boolean, | 42 | supportsDynamicRegistration: Schema.Boolean, |
| @@ -20,9 +20,7 @@ const makeRecorder = (traceId = "trace-xyz") => · 19 unmodified lines | |||
| 20 | ErrorCapture, | 20 | ErrorCapture, |
| 21 | ErrorCapture.of({ | 21 | ErrorCapture.of({ |
| 22 | captureException: (cause) => | 22 | captureException: (cause) => |
| 23 | Ref.update(seen, (prev) => [...prev, cause]).pipe( | 23 | Ref.update(seen, (prev) => [...prev, cause]).pipe(Effect.as(traceId)), |
| 24 | Effect.as(traceId), | ||
| 25 | ), | ||
| 26 | }), | 24 | }), |
| 27 | ); | 25 | ); |
| 28 | return { layer, seen }; | 26 | return { layer, seen }; |
| @@ -58,17 +56,19 @@ describe("capture", () => { · 29 unmodified lines | |||
| 58 | }), | 56 | }), |
| 59 | ); | 57 | ); |
| 60 | 58 | ||
| 61 | it.effect("UniqueViolationError dies (becomes a defect — plugins should catchTag before returning)", () => | 59 | it.effect( |
| 62 | Effect.gen(function* () { | 60 | "UniqueViolationError dies (becomes a defect — plugins should catchTag before returning)", |
| 63 | const err = new UniqueViolationError({ model: "thing" }); | 61 | () => |
| 64 | const exit = yield* Effect.exit(capture(Effect.fail(err))); | 62 | Effect.gen(function* () { |
| 65 | expect(Exit.isFailure(exit)).toBe(true); | 63 | const err = new UniqueViolationError({ model: "thing" }); |
| 66 | if (!Exit.isFailure(exit)) return; | 64 | const exit = yield* Effect.exit(capture(Effect.fail(err))); |
| 67 | const defect = Cause.findDefect(exit.cause); | 65 | expect(Exit.isFailure(exit)).toBe(true); |
| 68 | expect(Result.isSuccess(defect) ? defect.success : undefined).toBeInstanceOf( | 66 | if (!Exit.isFailure(exit)) return; |
| 69 | UniqueViolationError, | 67 | const defect = Cause.findDefect(exit.cause); |
| 70 | ); | 68 | expect(Result.isSuccess(defect) ? defect.success : undefined).toBeInstanceOf( |
| 71 | }), | 69 | UniqueViolationError, |
| 70 | ); | ||
| 71 | }), | ||
| 72 | ); | 72 | ); |
| 73 | 73 | ||
| 74 | it.effect("non-storage typed failures pass through unchanged", () => | 74 | it.effect("non-storage typed failures pass through unchanged", () => |
| @@ -76,10 +76,7 @@ describe("capture", () => { · 1 unmodified line | |||
| 76 | class DomainError { | 76 | class DomainError { |
| 77 | readonly _tag = "DomainError" as const; | 77 | readonly _tag = "DomainError" as const; |
| 78 | } | 78 | } |
| 79 | const eff = Effect.fail(new DomainError()) as Effect.Effect< | 79 | const eff = Effect.fail(new DomainError()) as Effect.Effect<never, DomainError>; |
| 80 | never, | ||
| 81 | DomainError | ||
| 82 | >; | ||
| 83 | const result = yield* Effect.flip(capture(eff)); | 80 | const result = yield* Effect.flip(capture(eff)); |
| 84 | expect(result).toBeInstanceOf(DomainError); | 81 | expect(result).toBeInstanceOf(DomainError); |
| 85 | }), | 82 | }), |
| @@ -51,19 +51,16 @@ export interface ErrorCaptureShape { · 50 unmodified lines | |||
| 51 | * can later look up. Implementations (Sentry, console, etc.) decide | 51 | * can later look up. Implementations (Sentry, console, etc.) decide |
| 52 | * how to persist it. | 52 | * how to persist it. |
| 53 | */ | 53 | */ |
| 54 | readonly captureException: ( | 54 | readonly captureException: (cause: Cause.Cause<unknown>) => Effect.Effect<string>; |
| 55 | cause: Cause.Cause<unknown>, | ||
| 56 | ) => Effect.Effect<string>; | ||
| 57 | } | 55 | } |
| 58 | 56 | ||
| 59 | export class ErrorCapture extends Context.Service<ErrorCapture, ErrorCaptureShape>()( | 57 | export class ErrorCapture extends Context.Service<ErrorCapture, ErrorCaptureShape>()( |
| 60 | "@executor-js/api/ErrorCapture", | 58 | "@executor-js/api/ErrorCapture", |
| 61 | ) { | 59 | ) { |
| 62 | /** No-op — used where capture isn't wired. Traces back as empty string. */ | 60 | /** No-op — used where capture isn't wired. Traces back as empty string. */ |
| 63 | static readonly NoOp: Layer.Layer<ErrorCapture> = Layer.succeed( | 61 | static readonly NoOp: Layer.Layer<ErrorCapture> = Layer.succeed(ErrorCapture, { |
| 64 | ErrorCapture, | 62 | captureException: () => Effect.succeed(""), |
| 65 | { captureException: () => Effect.succeed("") }, | 63 | }); |
| 66 | ); | ||
| 67 | } | 64 | } |
| 68 | 65 | ||
| 69 | // Resolve ErrorCapture with a no-op fallback. Keeps the caller's R channel | 66 | // Resolve ErrorCapture with a no-op fallback. Keeps the caller's R channel |
| @@ -71,9 +68,7 @@ export class ErrorCapture extends Context.Service<ErrorCapture, ErrorCaptureShap · 1 unmodified line | |||
| 71 | // typecheck; if it's there, we use it; if not, trace ids are empty. | 68 | // typecheck; if it's there, we use it; if not, trace ids are empty. |
| 72 | const resolveCapture = Effect.serviceOption(ErrorCapture).pipe( | 69 | const resolveCapture = Effect.serviceOption(ErrorCapture).pipe( |
| 73 | Effect.map((opt) => | 70 | Effect.map((opt) => |
| 74 | Option.isSome(opt) | 71 | Option.isSome(opt) ? opt.value : ({ captureException: () => Effect.succeed("") } as const), |
| 75 | ? opt.value | ||
| 76 | : ({ captureException: () => Effect.succeed("") } as const), | ||
| 77 | ), | 72 | ), |
| 78 | ); | 73 | ); |
| 79 | 74 | ||
| @@ -102,9 +97,7 @@ export const capture = <A, E, R>( · 22 unmodified lines | |||
| 102 | Effect.catchTag("StorageError", (err) => | 97 | Effect.catchTag("StorageError", (err) => |
| 103 | resolveCapture.pipe( | 98 | resolveCapture.pipe( |
| 104 | Effect.flatMap((c) => c.captureException(Cause.fail(err))), | 99 | Effect.flatMap((c) => c.captureException(Cause.fail(err))), |
| 105 | Effect.flatMap((traceId) => | 100 | Effect.flatMap((traceId) => Effect.fail(new InternalError({ traceId }))), |
| 106 | Effect.fail(new InternalError({ traceId })), | ||
| 107 | ), | ||
| 108 | ), | 101 | ), |
| 109 | ), | 102 | ), |
| 110 | ) as Effect.Effect<A, Exclude<E, StorageFailure> | InternalError, R>; | 103 | ) as Effect.Effect<A, Exclude<E, StorageFailure> | InternalError, R>; |
| @@ -131,10 +124,8 @@ export const captureEngineError = <A, R>( · 20 unmodified lines | |||
| 131 | ? Effect.fail(err) | 124 | ? Effect.fail(err) |
| 132 | : resolveCapture.pipe( | 125 | : resolveCapture.pipe( |
| 133 | Effect.flatMap((c) => c.captureException(Cause.fail(err))), | 126 | Effect.flatMap((c) => c.captureException(Cause.fail(err))), |
| 134 | Effect.flatMap((traceId) => | 127 | Effect.flatMap((traceId) => Effect.fail(new InternalError({ traceId }))), |
| 135 | Effect.fail(new InternalError({ traceId })), | 128 | ), |
| 136 | ), | ||
| 137 | ), | ||
| 138 | ), | 129 | ), |
| 139 | ); | 130 | ); |
| 140 | 131 | ||
| @@ -160,20 +151,18 @@ export class ObservabilityMiddleware extends HttpApiMiddleware.Service<Observabi · 19 unmodified lines | |||
| 160 | export const observabilityMiddleware = <Id extends string, Groups extends HttpApiGroup.Any>( | 151 | export const observabilityMiddleware = <Id extends string, Groups extends HttpApiGroup.Any>( |
| 161 | _api: HttpApi.HttpApi<Id, Groups>, | 152 | _api: HttpApi.HttpApi<Id, Groups>, |
| 162 | ): Layer.Layer<ObservabilityMiddleware> => | 153 | ): Layer.Layer<ObservabilityMiddleware> => |
| 163 | Layer.succeed( | 154 | Layer.succeed(ObservabilityMiddleware, (httpApp) => |
| 164 | ObservabilityMiddleware, | 155 | Effect.catchCause(httpApp, (cause) => |
| 165 | (httpApp) => | 156 | Effect.gen(function* () { |
| 166 | Effect.catchCause(httpApp, (cause) => | 157 | const defect = Cause.findDefect(cause); |
| 167 | Effect.gen(function* () { | 158 | if (Result.isFailure(defect)) { |
| 168 | const defect = Cause.findDefect(cause); | 159 | return yield* Effect.failCause(cause); |
| 169 | if (Result.isFailure(defect)) { | 160 | } |
| 170 | return yield* Effect.failCause(cause); | 161 | const c = yield* resolveCapture; |
| 171 | } | 162 | const traceId = yield* c.captureException(cause); |
| 172 | const c = yield* resolveCapture; | 163 | return HttpServerResponse.jsonUnsafe(new InternalError({ traceId }), { |
| 173 | const traceId = yield* c.captureException(cause); | 164 | status: 500, |
| 174 | return HttpServerResponse.jsonUnsafe(new InternalError({ traceId }), { | 165 | }); |
| 175 | status: 500, | 166 | }), |
| 176 | }); | 167 | ), |
| 177 | }), | ||
| 178 | ), | ||
| 179 | ); | 168 | ); |
| @@ -40,8 +40,9 @@ import { CoreExecutorApi } from "./api"; · 39 unmodified lines | |||
| 40 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 40 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 41 | type ExtractServiceId<S> = S extends Context.Service<infer Id, any> ? Id : never; | 41 | type ExtractServiceId<S> = S extends Context.Service<infer Id, any> ? Id : never; |
| 42 | 42 | ||
| 43 | export type PluginExtensionServices<TPlugins extends readonly AnyPlugin[]> = | 43 | export type PluginExtensionServices<TPlugins extends readonly AnyPlugin[]> = ExtractServiceId< |
| 44 | ExtractServiceId<NonNullable<TPlugins[number]["extensionService"]>>; | 44 | NonNullable<TPlugins[number]["extensionService"]> |
| 45 | >; | ||
| 45 | 46 | ||
| 46 | /** Extract the precise `HttpApiGroup` type carried by a plugin's | 47 | /** Extract the precise `HttpApiGroup` type carried by a plugin's |
| 47 | * `routes()` field. Plugins without a `routes()` field contribute | 48 | * `routes()` field. Plugins without a `routes()` field contribute |
| @@ -52,8 +53,9 @@ type ExtractPluginGroup<P> = P extends { readonly routes?: () => infer G } · 4 unmodified lines | |||
| 52 | 53 | ||
| 53 | /** Union of every plugin's contributed group — combined with the core | 54 | /** Union of every plugin's contributed group — combined with the core |
| 54 | * executor groups to type `composePluginApi(plugins)` precisely. */ | 55 | * executor groups to type `composePluginApi(plugins)` precisely. */ |
| 55 | export type PluginGroups<TPlugins extends readonly AnyPlugin[]> = | 56 | export type PluginGroups<TPlugins extends readonly AnyPlugin[]> = ExtractPluginGroup< |
| 56 | ExtractPluginGroup<TPlugins[number]>; | 57 | TPlugins[number] |
| 58 | >; | ||
| 57 | 59 | ||
| 58 | /** Group identities baked into `CoreExecutorApi` (tools, sources, secrets, | 60 | /** Group identities baked into `CoreExecutorApi` (tools, sources, secrets, |
| 59 | * …). Extracted via inference so adding a core group flows through | 61 | * …). Extracted via inference so adding a core group flows through |
| @@ -64,8 +66,10 @@ type CoreGroups = · 4 unmodified lines | |||
| 64 | 66 | ||
| 65 | /** Result of `composePluginApi(plugins)` — the core API extended with | 67 | /** Result of `composePluginApi(plugins)` — the core API extended with |
| 66 | * every plugin group from `TPlugins`. */ | 68 | * every plugin group from `TPlugins`. */ |
| 67 | export type ComposedExecutorApi<TPlugins extends readonly AnyPlugin[]> = | 69 | export type ComposedExecutorApi<TPlugins extends readonly AnyPlugin[]> = HttpApi.HttpApi< |
| 68 | HttpApi.HttpApi<"executor", CoreGroups | PluginGroups<TPlugins>>; | 70 | "executor", |
| 71 | CoreGroups | PluginGroups<TPlugins> | ||
| 72 | >; | ||
| 69 | 73 | ||
| 70 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 74 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 71 | type AnyLayer = Layer.Layer<any, any, any>; | 75 | type AnyLayer = Layer.Layer<any, any, any>; |
| @@ -73,18 +77,18 @@ type AnyLayer = Layer.Layer<any, any, any>; · 1 unmodified line | |||
| 73 | // Use the field accessor + NonNullable rather than `extends { handlers: ... }` | 77 | // Use the field accessor + NonNullable rather than `extends { handlers: ... }` |
| 74 | // because the spec marks `handlers` optional (`handlers?:`); the conditional | 78 | // because the spec marks `handlers` optional (`handlers?:`); the conditional |
| 75 | // form would fail the match because the field type includes `undefined`. | 79 | // form would fail the match because the field type includes `undefined`. |
| 76 | type ExtractHandlerLayer<P> = NonNullable< | 80 | type ExtractHandlerLayer<P> = |
| 77 | P extends { readonly handlers?: infer F } ? F : never | 81 | NonNullable<P extends { readonly handlers?: infer F } ? F : never> extends () => infer L |
| 78 | > extends () => infer L | 82 | ? L |
| 79 | ? L | 83 | : never; |
| 80 | : never; | ||
| 81 | 84 | ||
| 82 | // Compute the union of every plugin's handler-Layer type. Each plugin's | 85 | // Compute the union of every plugin's handler-Layer type. Each plugin's |
| 83 | // `handlers()` returns a specific `Layer<Group, never, ExtensionService>`; | 86 | // `handlers()` returns a specific `Layer<Group, never, ExtensionService>`; |
| 84 | // we union them so `Layer.mergeAll(...)`'s output type can be extracted | 87 | // we union them so `Layer.mergeAll(...)`'s output type can be extracted |
| 85 | // without erasing per-plugin requirements. | 88 | // without erasing per-plugin requirements. |
| 86 | type PluginHandlerLayers<TPlugins extends readonly AnyPlugin[]> = | 89 | type PluginHandlerLayers<TPlugins extends readonly AnyPlugin[]> = ExtractHandlerLayer< |
| 87 | ExtractHandlerLayer<TPlugins[number]>; | 90 | TPlugins[number] |
| 91 | >; | ||
| 88 | 92 | ||
| 89 | // Distribute over the union of handler layers to extract each channel | 93 | // Distribute over the union of handler layers to extract each channel |
| 90 | // individually, then re-pack into a single `Layer<UnionROut, UnionE, | 94 | // individually, then re-pack into a single `Layer<UnionROut, UnionE, |
| @@ -174,9 +178,7 @@ export const composePluginHandlers = <TPlugins extends readonly AnyPlugin[]>( · 83 unmodified lines | |||
| 174 | * `*ExtensionService` Tags) so the host's `HttpRouter.middleware` | 178 | * `*ExtensionService` Tags) so the host's `HttpRouter.middleware` |
| 175 | * recognises them as per-request requires. | 179 | * recognises them as per-request requires. |
| 176 | */ | 180 | */ |
| 177 | export const composePluginHandlerLayer = < | 181 | export const composePluginHandlerLayer = <TPlugins extends readonly AnyPlugin[]>( |
| 178 | TPlugins extends readonly AnyPlugin[], | ||
| 179 | >( | ||
| 180 | plugins: TPlugins, | 182 | plugins: TPlugins, |
| 181 | ): MergedHandlerLayer<TPlugins> => { | 183 | ): MergedHandlerLayer<TPlugins> => { |
| 182 | const layers = plugins.flatMap((p) => (p.handlers ? [p.handlers()] : [])); | 184 | const layers = plugins.flatMap((p) => (p.handlers ? [p.handlers()] : [])); |
| @@ -48,12 +48,8 @@ const SetSecretPayload = Schema.Struct({ · 47 unmodified lines | |||
| 48 | // --------------------------------------------------------------------------- | 48 | // --------------------------------------------------------------------------- |
| 49 | 49 | ||
| 50 | const SecretNotFound = SecretNotFoundError.annotate({ httpApiStatus: 404 }); | 50 | const SecretNotFound = SecretNotFoundError.annotate({ httpApiStatus: 404 }); |
| 51 | const SecretResolution = SecretResolutionError.annotate( | 51 | const SecretResolution = SecretResolutionError.annotate({ httpApiStatus: 500 }); |
| 52 | { httpApiStatus: 500 }, | 52 | const SecretOwnedByConnection = SecretOwnedByConnectionError.annotate({ httpApiStatus: 409 }); |
| 53 | ); | ||
| 54 | const SecretOwnedByConnection = SecretOwnedByConnectionError.annotate( | ||
| 55 | { httpApiStatus: 409 }, | ||
| 56 | ); | ||
| 57 | const SecretInUse = SecretInUseError.annotate({ httpApiStatus: 409 }); | 53 | const SecretInUse = SecretInUseError.annotate({ httpApiStatus: 409 }); |
| 58 | 54 | ||
| 59 | // --------------------------------------------------------------------------- | 55 | // --------------------------------------------------------------------------- |
| @@ -1,10 +1,6 @@ | |||
| 1 | import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"; | 1 | import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"; |
| 2 | import { Schema } from "effect"; | 2 | import { Schema } from "effect"; |
| 3 | import { | 3 | import { ScopeId, SourceRemovalNotAllowedError, ToolId } from "@executor-js/sdk"; |
| 4 | ScopeId, | ||
| 5 | SourceRemovalNotAllowedError, | ||
| 6 | ToolId, | ||
| 7 | } from "@executor-js/sdk"; | ||
| 8 | 4 | ||
| 9 | import { InternalError } from "../observability"; | 5 | import { InternalError } from "../observability"; |
| 10 | 6 | ||
| @@ -68,9 +64,7 @@ const DetectResultResponse = Schema.Struct({ · 57 unmodified lines | |||
| 68 | // Error schemas with HTTP status annotations | 64 | // Error schemas with HTTP status annotations |
| 69 | // --------------------------------------------------------------------------- | 65 | // --------------------------------------------------------------------------- |
| 70 | 66 | ||
| 71 | const SourceRemovalNotAllowed = SourceRemovalNotAllowedError.annotate( | 67 | const SourceRemovalNotAllowed = SourceRemovalNotAllowedError.annotate({ httpApiStatus: 409 }); |
| 72 | { httpApiStatus: 409 }, | ||
| 73 | ); | ||
| 74 | 68 | ||
| 75 | // --------------------------------------------------------------------------- | 69 | // --------------------------------------------------------------------------- |
| 76 | // Group | 70 | // Group |
| @@ -31,9 +31,7 @@ const ToolSchemaResponse = Schema.Struct({ · 30 unmodified lines | |||
| 31 | id: ToolId, | 31 | id: ToolId, |
| 32 | inputTypeScript: Schema.optional(Schema.String), | 32 | inputTypeScript: Schema.optional(Schema.String), |
| 33 | outputTypeScript: Schema.optional(Schema.String), | 33 | outputTypeScript: Schema.optional(Schema.String), |
| 34 | typeScriptDefinitions: Schema.optional( | 34 | typeScriptDefinitions: Schema.optional(Schema.Record(Schema.String, Schema.String)), |
| 35 | Schema.Record(Schema.String, Schema.String), | ||
| 36 | ), | ||
| 37 | inputSchema: Schema.optional(Schema.Unknown), | 35 | inputSchema: Schema.optional(Schema.Unknown), |
| 38 | outputSchema: Schema.optional(Schema.Unknown), | 36 | outputSchema: Schema.optional(Schema.Unknown), |
| 39 | }); | 37 | }); |
| @@ -12,13 +12,13 @@ · 11 unmodified lines | |||
| 12 | "url": "git+https://github.com/RhysSullivan/executor.git", | 12 | "url": "git+https://github.com/RhysSullivan/executor.git", |
| 13 | "directory": "packages/core/cli" | 13 | "directory": "packages/core/cli" |
| 14 | }, | 14 | }, |
| 15 | "type": "module", | ||
| 16 | "bin": { | 15 | "bin": { |
| 17 | "executor": "./dist/index.js" | 16 | "executor": "./dist/index.js" |
| 18 | }, | 17 | }, |
| 19 | "files": [ | 18 | "files": [ |
| 20 | "dist" | 19 | "dist" |
| 21 | ], | 20 | ], |
| 21 | "type": "module", | ||
| 22 | "exports": { | 22 | "exports": { |
| 23 | ".": "./src/index.ts" | 23 | ".": "./src/index.ts" |
| 24 | }, | 24 | }, |
| @@ -6,11 +6,7 @@ import { collectSchemas } from "@executor-js/sdk/core"; · 5 unmodified lines | |||
| 6 | import { getConfig } from "../utils/get-config.js"; | 6 | import { getConfig } from "../utils/get-config.js"; |
| 7 | import { generateDrizzleSchema } from "../generators/drizzle.js"; | 7 | import { generateDrizzleSchema } from "../generators/drizzle.js"; |
| 8 | 8 | ||
| 9 | async function generateAction(opts: { | 9 | async function generateAction(opts: { cwd: string; config?: string; output?: string }) { |
| 10 | cwd: string; | ||
| 11 | config?: string; | ||
| 12 | output?: string; | ||
| 13 | }) { | ||
| 14 | const cwd = path.resolve(opts.cwd); | 10 | const cwd = path.resolve(opts.cwd); |
| 15 | if (!existsSync(cwd)) { | 11 | if (!existsSync(cwd)) { |
| 16 | console.error(`The directory "${cwd}" does not exist.`); | 12 | console.error(`The directory "${cwd}" does not exist.`); |
| @@ -55,17 +51,7 @@ async function generateAction(opts: { · 38 unmodified lines | |||
| 55 | 51 | ||
| 56 | export const generate = new Command("generate") | 52 | export const generate = new Command("generate") |
| 57 | .description("Generate a drizzle schema file from the executor config") | 53 | .description("Generate a drizzle schema file from the executor config") |
| 58 | .option( | 54 | .option("-c, --cwd <cwd>", "the working directory", process.cwd()) |
| 59 | "-c, --cwd <cwd>", | 55 | .option("--config <config>", "path to the executor config file") |
| 60 | "the working directory", | 56 | .option("--output <output>", "output file path for the generated schema") |
| 61 | process.cwd(), | ||
| 62 | ) | ||
| 63 | .option( | ||
| 64 | "--config <config>", | ||
| 65 | "path to the executor config file", | ||
| 66 | ) | ||
| 67 | .option( | ||
| 68 | "--output <output>", | ||
| 69 | "output file path for the generated schema", | ||
| 70 | ) | ||
| 71 | .action(generateAction); | 57 | .action(generateAction); |
| @@ -15,14 +15,9 @@ import type { SchemaGenerator } from "./types.js"; · 14 unmodified lines | |||
| 15 | 15 | ||
| 16 | type Dialect = "pg" | "sqlite" | "mysql"; | 16 | type Dialect = "pg" | "sqlite" | "mysql"; |
| 17 | 17 | ||
| 18 | const getModelName = (key: string, def: DBSchema[string]): string => | 18 | const getModelName = (key: string, def: DBSchema[string]): string => def.modelName ?? key; |
| 19 | def.modelName ?? key; | 19 | |
| 20 | 20 | const getType = (name: string, field: DBFieldAttribute, dialect: Dialect): string => { | |
| 21 | const getType = ( | ||
| 22 | name: string, | ||
| 23 | field: DBFieldAttribute, | ||
| 24 | dialect: Dialect, | ||
| 25 | ): string => { | ||
| 26 | if (field.references?.field === "id") { | 21 | if (field.references?.field === "id") { |
| 27 | return `text('${name}')`; | 22 | return `text('${name}')`; |
| 28 | } | 23 | } |
| @@ -39,9 +34,7 @@ const getType = ( · 10 unmodified lines | |||
| 39 | }[dialect]; | 34 | }[dialect]; |
| 40 | } | 35 | } |
| 41 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: generator rejects invalid schema input through the SchemaGenerator promise API | 36 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: generator rejects invalid schema input through the SchemaGenerator promise API |
| 42 | throw new TypeError( | 37 | throw new TypeError(`Invalid field type for field ${name}`); |
| 43 | `Invalid field type for field ${name}`, | ||
| 44 | ); | ||
| 45 | } | 38 | } |
| 46 | 39 | ||
| 47 | const typeMap: Record<string, Record<Dialect, string>> = { | 40 | const typeMap: Record<string, Record<Dialect, string>> = { |
| @@ -65,12 +58,8 @@ const getType = ( · 17 unmodified lines | |||
| 65 | }, | 58 | }, |
| 66 | number: { | 59 | number: { |
| 67 | sqlite: `integer('${name}')`, | 60 | sqlite: `integer('${name}')`, |
| 68 | pg: field.bigint | 61 | pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`, |
| 69 | ? `bigint('${name}', { mode: 'number' })` | 62 | mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`, |
| 70 | : `integer('${name}')`, | ||
| 71 | mysql: field.bigint | ||
| 72 | ? `bigint('${name}', { mode: 'number' })` | ||
| 73 | : `int('${name}')`, | ||
| 74 | }, | 63 | }, |
| 75 | date: { | 64 | date: { |
| 76 | sqlite: `integer('${name}', { mode: 'timestamp_ms' })`, | 65 | sqlite: `integer('${name}', { mode: 'timestamp_ms' })`, |
| @@ -99,9 +88,7 @@ const getType = ( · 22 unmodified lines | |||
| 99 | const dbTypeMap = typeMap[type]; | 88 | const dbTypeMap = typeMap[type]; |
| 100 | if (!dbTypeMap) { | 89 | if (!dbTypeMap) { |
| 101 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: generator rejects unsupported schema input through the SchemaGenerator promise API | 90 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: generator rejects unsupported schema input through the SchemaGenerator promise API |
| 102 | throw new Error( | 91 | throw new Error(`Unsupported field type '${field.type}' for field '${name}'.`); |
| 103 | `Unsupported field type '${field.type}' for field '${name}'.`, | ||
| 104 | ); | ||
| 105 | } | 92 | } |
| 106 | return dbTypeMap[dialect]; | 93 | return dbTypeMap[dialect]; |
| 107 | }; | 94 | }; |
| @@ -110,11 +97,7 @@ const getType = ( · 2 unmodified lines | |||
| 110 | // Generator | 97 | // Generator |
| 111 | // --------------------------------------------------------------------------- | 98 | // --------------------------------------------------------------------------- |
| 112 | 99 | ||
| 113 | export const generateDrizzleSchema: SchemaGenerator = async ({ | 100 | export const generateDrizzleSchema: SchemaGenerator = async ({ schema, dialect, file }) => { |
| 114 | schema, | ||
| 115 | dialect, | ||
| 116 | file, | ||
| 117 | }) => { | ||
| 118 | const filePath = file || "./executor-schema.ts"; | 101 | const filePath = file || "./executor-schema.ts"; |
| 119 | const fileExist = existsSync(filePath); | 102 | const fileExist = existsSync(filePath); |
| 120 | 103 | ||
| @@ -159,69 +142,62 @@ export const generateDrizzleSchema: SchemaGenerator = async ({ · 38 unmodified lines | |||
| 159 | } | 142 | } |
| 160 | 143 | ||
| 161 | const fieldLines = Object.entries(fields) | 144 | const fieldLines = Object.entries(fields) |
| 162 | .filter(([fieldName]) => fieldName !== "id") | 145 | .filter(([fieldName]) => fieldName !== "id") |
| 163 | .map(([fieldName, attr]) => { | 146 | .map(([fieldName, attr]) => { |
| 164 | const physical = attr.fieldName ?? fieldName; | 147 | const physical = attr.fieldName ?? fieldName; |
| 165 | 148 | ||
| 166 | const isToolPolicyCompositeField = | 149 | const isToolPolicyCompositeField = |
| 167 | tableKey === "tool_policy" && | 150 | tableKey === "tool_policy" && (physical === "scope_id" || physical === "position"); |
| 168 | (physical === "scope_id" || physical === "position"); | 151 | |
| 169 | 152 | if (attr.index && !attr.unique && !isToolPolicyCompositeField) { | |
| 170 | if (attr.index && !attr.unique && !isToolPolicyCompositeField) { | 153 | extras.push({ |
| 171 | extras.push({ | 154 | kind: "index", |
| 172 | kind: "index", | 155 | name: `${tableKey}_${physical}_idx`, |
| 173 | name: `${tableKey}_${physical}_idx`, | 156 | on: physical, |
| 174 | on: physical, | 157 | }); |
| 175 | }); | 158 | } else if (attr.index && attr.unique) { |
| 176 | } else if (attr.index && attr.unique) { | 159 | extras.push({ |
| 177 | extras.push({ | 160 | kind: "uniqueIndex", |
| 178 | kind: "uniqueIndex", | 161 | name: `${tableKey}_${physical}_uidx`, |
| 179 | name: `${tableKey}_${physical}_uidx`, | 162 | on: physical, |
| 180 | on: physical, | 163 | }); |
| 181 | }); | 164 | } |
| 182 | } | 165 | |
| 166 | let col = getType(physical, attr, dialect); | ||
| 183 | 167 | ||
| 184 | let col = getType(physical, attr, dialect); | 168 | if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") { |
| 185 | 169 | if (typeof attr.defaultValue === "function") { | |
| 186 | if ( | 170 | if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) { |
| 187 | attr.defaultValue !== null && | 171 | if (dialect === "sqlite") { |
| 188 | typeof attr.defaultValue !== "undefined" | 172 | col += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`; |
| 189 | ) { | 173 | } else { |
| 190 | if (typeof attr.defaultValue === "function") { | 174 | col += `.defaultNow()`; |
| 191 | if ( | 175 | } |
| 192 | attr.type === "date" && | ||
| 193 | attr.defaultValue.toString().includes("new Date()") | ||
| 194 | ) { | ||
| 195 | if (dialect === "sqlite") { | ||
| 196 | col += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`; | ||
| 197 | } else { | ||
| 198 | col += `.defaultNow()`; | ||
| 199 | } | 176 | } |
| 177 | } else if (typeof attr.defaultValue === "string") { | ||
| 178 | col += `.default("${attr.defaultValue}")`; | ||
| 179 | } else { | ||
| 180 | col += `.default(${attr.defaultValue})`; | ||
| 200 | } | 181 | } |
| 201 | } else if (typeof attr.defaultValue === "string") { | ||
| 202 | col += `.default("${attr.defaultValue}")`; | ||
| 203 | } else { | ||
| 204 | col += `.default(${attr.defaultValue})`; | ||
| 205 | } | 182 | } |
| 206 | } | ||
| 207 | 183 | ||
| 208 | if (attr.onUpdate && attr.type === "date") { | 184 | if (attr.onUpdate && attr.type === "date") { |
| 209 | if (typeof attr.onUpdate === "function") { | 185 | if (typeof attr.onUpdate === "function") { |
| 210 | col += `.$onUpdate(${attr.onUpdate})`; | 186 | col += `.$onUpdate(${attr.onUpdate})`; |
| 187 | } | ||
| 211 | } | 188 | } |
| 212 | } | ||
| 213 | 189 | ||
| 214 | return `${physical}: ${col}${attr.required !== false ? ".notNull()" : ""}${ | 190 | return `${physical}: ${col}${attr.required !== false ? ".notNull()" : ""}${ |
| 215 | attr.unique ? ".unique()" : "" | 191 | attr.unique ? ".unique()" : "" |
| 216 | }${ | 192 | }${ |
| 217 | attr.references | 193 | attr.references |
| 218 | ? `.references(()=> ${attr.references.model}.${attr.references.field ?? "id"}, { onDelete: '${ | 194 | ? `.references(()=> ${attr.references.model}.${attr.references.field ?? "id"}, { onDelete: '${ |
| 219 | attr.references.onDelete || "cascade" | 195 | attr.references.onDelete || "cascade" |
| 220 | }' })` | 196 | }' })` |
| 221 | : "" | 197 | : "" |
| 222 | }`; | 198 | }`; |
| 223 | }) | 199 | }) |
| 224 | .join(",\n "); | 200 | .join(",\n "); |
| 225 | 201 | ||
| 226 | if (tableKey === "tool_policy") { | 202 | if (tableKey === "tool_policy") { |
| 227 | extras.push({ | 203 | extras.push({ |
| @@ -339,12 +315,7 @@ export const generateDrizzleSchema: SchemaGenerator = async ({ · 111 unmodified lines | |||
| 339 | const hasMany = manyRelations.length > 0; | 315 | const hasMany = manyRelations.length > 0; |
| 340 | 316 | ||
| 341 | if (hasOne || hasMany) { | 317 | if (hasOne || hasMany) { |
| 342 | const destructured = [ | 318 | const destructured = [hasOne ? "one" : "", hasMany ? "many" : ""].filter(Boolean).join(", "); |
| 343 | hasOne ? "one" : "", | ||
| 344 | hasMany ? "many" : "", | ||
| 345 | ] | ||
| 346 | .filter(Boolean) | ||
| 347 | .join(", "); | ||
| 348 | 319 | ||
| 349 | const body = [ | 320 | const body = [ |
| 350 | ...singleRelations | 321 | ...singleRelations |
| @@ -353,9 +324,7 @@ export const generateDrizzleSchema: SchemaGenerator = async ({ · 2 unmodified lines | |||
| 353 | (r) => | 324 | (r) => |
| 354 | ` ${r.key}: one(${r.model}, {\n fields: [${r.reference!.field}],\n references: [${r.reference!.references}],\n })`, | 325 | ` ${r.key}: one(${r.model}, {\n fields: [${r.reference!.field}],\n references: [${r.reference!.references}],\n })`, |
| 355 | ), | 326 | ), |
| 356 | ...manyRelations.map( | 327 | ...manyRelations.map(({ key, model }) => ` ${key}: many(${model})`), |
| 357 | ({ key, model }) => ` ${key}: many(${model})`, | ||
| 358 | ), | ||
| 359 | ].join(",\n"); | 328 | ].join(",\n"); |
| 360 | 329 | ||
| 361 | const block = `export const ${modelName}Relations = relations(${modelName}, ({ ${destructured} }) => ({ | 330 | const block = `export const ${modelName}Relations = relations(${modelName}, ({ ${destructured} }) => ({ |
| @@ -378,13 +347,7 @@ ${body} · 16 unmodified lines | |||
| 378 | // Import generation — only emit what's actually used | 347 | // Import generation — only emit what's actually used |
| 379 | // --------------------------------------------------------------------------- | 348 | // --------------------------------------------------------------------------- |
| 380 | 349 | ||
| 381 | function generateImport({ | 350 | function generateImport({ dialect, schema }: { dialect: Dialect; schema: DBSchema }) { |
| 382 | dialect, | ||
| 383 | schema, | ||
| 384 | }: { | ||
| 385 | dialect: Dialect; | ||
| 386 | schema: DBSchema; | ||
| 387 | }) { | ||
| 388 | const rootImports: string[] = []; | 351 | const rootImports: string[] = []; |
| 389 | const coreImports: string[] = []; | 352 | const coreImports: string[] = []; |
| 390 | 353 | ||
| @@ -403,10 +366,7 @@ function generateImport({ · 12 unmodified lines | |||
| 403 | if (field.bigint) hasBigint = true; | 366 | if (field.bigint) hasBigint = true; |
| 404 | if (field.type === "json") hasJson = true; | 367 | if (field.type === "json") hasJson = true; |
| 405 | if (field.type === "boolean") hasBoolean = true; | 368 | if (field.type === "boolean") hasBoolean = true; |
| 406 | if ( | 369 | if ((field.type === "number" && !field.bigint) || field.type === "number[]") { |
| 407 | (field.type === "number" && !field.bigint) || | ||
| 408 | field.type === "number[]" | ||
| 409 | ) { | ||
| 410 | hasNumber = true; | 370 | hasNumber = true; |
| 411 | } | 371 | } |
| 412 | if (field.type === "date") hasDate = true; | 372 | if (field.type === "date") hasDate = true; |
| @@ -482,9 +442,7 @@ function generateImport({ · 69 unmodified lines | |||
| 482 | // produce relation blocks (see relationsString generation). | 442 | // produce relation blocks (see relationsString generation). |
| 483 | if (hasReferences) rootImports.push("relations"); | 443 | if (hasReferences) rootImports.push("relations"); |
| 484 | 444 | ||
| 485 | const filteredCore = coreImports | 445 | const filteredCore = coreImports.map((x) => x.trim()).filter((x) => x !== ""); |
| 486 | .map((x) => x.trim()) | ||
| 487 | .filter((x) => x !== ""); | ||
| 488 | 446 | ||
| 489 | // Deduplicate | 447 | // Deduplicate |
| 490 | const uniqueCore = [...new Set(filteredCore)]; | 448 | const uniqueCore = [...new Set(filteredCore)]; |
| @@ -7,10 +7,7 @@ const generators: Record<string, SchemaGenerator> = { · 6 unmodified lines | |||
| 7 | drizzle: generateDrizzleSchema, | 7 | drizzle: generateDrizzleSchema, |
| 8 | }; | 8 | }; |
| 9 | 9 | ||
| 10 | export const generateSchema = ( | 10 | export const generateSchema = (adapter: string, ...args: Parameters<SchemaGenerator>) => { |
| 11 | adapter: string, | ||
| 12 | ...args: Parameters<SchemaGenerator> | ||
| 13 | ) => { | ||
| 14 | const generator = generators[adapter]; | 11 | const generator = generators[adapter]; |
| 15 | if (!generator) { | 12 | if (!generator) { |
| 16 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: synchronous CLI generator registry rejects unsupported adapter names | 13 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: synchronous CLI generator registry rejects unsupported adapter names |
| @@ -8,10 +8,5 @@ export default defineConfig({ · 7 unmodified lines | |||
| 8 | dts: false, | 8 | dts: false, |
| 9 | sourcemap: true, | 9 | sourcemap: true, |
| 10 | clean: true, | 10 | clean: true, |
| 11 | external: [ | 11 | external: [/^effect/, /^@effect\//, "commander", "jiti"], |
| 12 | /^effect/, | ||
| 13 | /^@effect\//, | ||
| 14 | "commander", | ||
| 15 | "jiti", | ||
| 16 | ], | ||
| 17 | }); | 12 | }); |
| @@ -25,8 +25,4 @@ export { · 24 unmodified lines | |||
| 25 | } from "./write"; | 25 | } from "./write"; |
| 26 | 26 | ||
| 27 | export type { ConfigFileSink, ConfigFileSinkOptions } from "./sink"; | 27 | export type { ConfigFileSink, ConfigFileSinkOptions } from "./sink"; |
| 28 | export { | 28 | export { makeFileConfigSink, headerToConfigValue, headersToConfigValues } from "./sink"; |
| 29 | makeFileConfigSink, | ||
| 30 | headerToConfigValue, | ||
| 31 | headersToConfigValues, | ||
| 32 | } from "./sink"; | ||
| @@ -25,9 +25,7 @@ interface FixturePlugin { · 24 unmodified lines | |||
| 25 | readonly __optionsReceived: Record<string, unknown> | null; | 25 | readonly __optionsReceived: Record<string, unknown> | null; |
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | const asFixturePlugins = ( | 28 | const asFixturePlugins = (plugins: readonly AnyPlugin[] | null): readonly FixturePlugin[] => { |
| 29 | plugins: readonly AnyPlugin[] | null, | ||
| 30 | ): readonly FixturePlugin[] => { | ||
| 31 | // Fixture servers return shapes wider than AnyPlugin (they carry | 29 | // Fixture servers return shapes wider than AnyPlugin (they carry |
| 32 | // __optionsReceived for test assertions); narrow once here. | 30 | // __optionsReceived for test assertions); narrow once here. |
| 33 | // oxlint-disable-next-line executor/no-double-cast | 31 | // oxlint-disable-next-line executor/no-double-cast |
| @@ -71,9 +69,7 @@ describe("loadPluginsFromJsonc", () => { · 37 unmodified lines | |||
| 71 | 69 | ||
| 72 | it("throws on JSONC parse errors", async () => { | 70 | it("throws on JSONC parse errors", async () => { |
| 73 | const file = writeJsonc("parse-error", `{ "plugins": [`); | 71 | const file = writeJsonc("parse-error", `{ "plugins": [`); |
| 74 | await expect(loadPluginsFromJsonc({ path: file })).rejects.toThrow( | 72 | await expect(loadPluginsFromJsonc({ path: file })).rejects.toThrow(/failed to parse/i); |
| 75 | /failed to parse/i, | ||
| 76 | ); | ||
| 77 | }); | 73 | }); |
| 78 | 74 | ||
| 79 | it("throws with a helpful message when the package can't be resolved", async () => { | 75 | it("throws with a helpful message when the package can't be resolved", async () => { |
| @@ -66,8 +66,7 @@ export interface LoadPluginsFromJsoncOptions { · 65 unmodified lines | |||
| 66 | */ | 66 | */ |
| 67 | export const loadPluginsFromJsonc = async ( | 67 | export const loadPluginsFromJsonc = async ( |
| 68 | options: LoadPluginsFromJsoncOptions, | 68 | options: LoadPluginsFromJsoncOptions, |
| 69 | ): Promise<readonly AnyPlugin[] | null> => | 69 | ): Promise<readonly AnyPlugin[] | null> => Effect.runPromise(loadPluginsFromJsoncEffect(options)); |
| 70 | Effect.runPromise(loadPluginsFromJsoncEffect(options)); | ||
| 71 | 70 | ||
| 72 | const loadPluginsFromJsoncEffect = ( | 71 | const loadPluginsFromJsoncEffect = ( |
| 73 | options: LoadPluginsFromJsoncOptions, | 72 | options: LoadPluginsFromJsoncOptions, |
| @@ -122,9 +121,7 @@ const loadPluginsFromJsoncEffect = ( · 48 unmodified lines | |||
| 122 | // require.resolve is anchored to the jsonc's directory so plugin | 121 | // require.resolve is anchored to the jsonc's directory so plugin |
| 123 | // packages resolve from the host app's `node_modules` regardless of | 122 | // packages resolve from the host app's `node_modules` regardless of |
| 124 | // CWD. | 123 | // CWD. |
| 125 | const require = createRequire( | 124 | const require = createRequire(isAbsolute(path) ? path : resolvePath(fromDir, "_anchor.js")); |
| 126 | isAbsolute(path) ? path : resolvePath(fromDir, "_anchor.js"), | ||
| 127 | ); | ||
| 128 | 125 | ||
| 129 | const loaded: AnyPlugin[] = []; | 126 | const loaded: AnyPlugin[] = []; |
| 130 | for (const entry of entries) { | 127 | for (const entry of entries) { |
| @@ -146,9 +143,7 @@ const loadPluginsFromJsoncEffect = ( · 15 unmodified lines | |||
| 146 | message: `[load-plugins] failed to import "${serverEntry}" from ${resolved}.`, | 143 | message: `[load-plugins] failed to import "${serverEntry}" from ${resolved}.`, |
| 147 | cause, | 144 | cause, |
| 148 | }), | 145 | }), |
| 149 | })) as | 146 | })) as { default?: LooseConfiguredPlugin } | LooseConfiguredPlugin; |
| 150 | | { default?: LooseConfiguredPlugin } | ||
| 151 | | LooseConfiguredPlugin; | ||
| 152 | const factory = ( | 147 | const factory = ( |
| 153 | typeof mod === "function" ? mod : (mod.default ?? null) | 148 | typeof mod === "function" ? mod : (mod.default ?? null) |
| 154 | ) as LooseConfiguredPlugin | null; | 149 | ) as LooseConfiguredPlugin | null; |
| @@ -22,9 +22,7 @@ import { addSourceToConfig, removeSourceFromConfig } from "./write"; · 21 unmodified lines | |||
| 22 | // refs) into the config file's `secret-public-ref:<id>` string form. | 22 | // refs) into the config file's `secret-public-ref:<id>` string form. |
| 23 | type PluginHeaderValue = string | { secretId: string; prefix?: string }; | 23 | type PluginHeaderValue = string | { secretId: string; prefix?: string }; |
| 24 | 24 | ||
| 25 | export const headerToConfigValue = ( | 25 | export const headerToConfigValue = (value: PluginHeaderValue): ConfigHeaderValue => { |
| 26 | value: PluginHeaderValue, | ||
| 27 | ): ConfigHeaderValue => { | ||
| 28 | if (typeof value === "string") return value; | 26 | if (typeof value === "string") return value; |
| 29 | const ref = `${SECRET_REF_PREFIX}${value.secretId}`; | 27 | const ref = `${SECRET_REF_PREFIX}${value.secretId}`; |
| 30 | return value.prefix ? { value: ref, prefix: value.prefix } : ref; | 28 | return value.prefix ? { value: ref, prefix: value.prefix } : ref; |
| @@ -55,9 +53,7 @@ const defaultOnError = (op: "upsert" | "remove", err: unknown): void => { · 24 unmodified lines | |||
| 55 | console.warn(`[config-sink] ${op} failed`, err); | 53 | console.warn(`[config-sink] ${op} failed`, err); |
| 56 | }; | 54 | }; |
| 57 | 55 | ||
| 58 | export const makeFileConfigSink = ( | 56 | export const makeFileConfigSink = (options: ConfigFileSinkOptions): ConfigFileSink => { |
| 59 | options: ConfigFileSinkOptions, | ||
| 60 | ): ConfigFileSink => { | ||
| 61 | const { path, fsLayer, onError = defaultOnError } = options; | 57 | const { path, fsLayer, onError = defaultOnError } = options; |
| 62 | 58 | ||
| 63 | return { | 59 | return { |
| @@ -56,54 +56,44 @@ const slackPlugin = definePlugin(() => ({ · 55 unmodified lines | |||
| 56 | })); | 56 | })); |
| 57 | 57 | ||
| 58 | describe("buildExecuteDescription", () => { | 58 | describe("buildExecuteDescription", () => { |
| 59 | it.effect( | 59 | it.effect("renders real source ids as namespaces (sorted) through the real executor flow", () => |
| 60 | "renders real source ids as namespaces (sorted) through the real executor flow", | 60 | Effect.gen(function* () { |
| 61 | () => | 61 | // Intentionally register in non-alphabetical order — the formatter |
| 62 | Effect.gen(function* () { | 62 | // is expected to sort by source id. |
| 63 | // Intentionally register in non-alphabetical order — the formatter | 63 | const executor = yield* createExecutor( |
| 64 | // is expected to sort by source id. | 64 | makeTestConfig({ plugins: [slackPlugin(), githubPlugin()] as const }), |
| 65 | const executor = yield* createExecutor( | 65 | ); |
| 66 | makeTestConfig({ plugins: [slackPlugin(), githubPlugin()] as const }), | ||
| 67 | ); | ||
| 68 | 66 | ||
| 69 | const description = yield* buildExecuteDescription(executor); | 67 | const description = yield* buildExecuteDescription(executor); |
| 70 | 68 | ||
| 71 | // Stable anchor from the workflow preamble. | 69 | // Stable anchor from the workflow preamble. |
| 72 | expect(description).toContain( | 70 | expect(description).toContain("Execute TypeScript in a sandboxed runtime"); |
| 73 | "Execute TypeScript in a sandboxed runtime", | 71 | // The namespaces section header. |
| 74 | ); | 72 | expect(description).toContain("## Available namespaces"); |
| 75 | // The namespaces section header. | 73 | // Each source renders with its ACTUAL id (not pluginId / name / UUID). |
| 76 | expect(description).toContain("## Available namespaces"); | 74 | expect(description).toContain("`github` — GitHub"); |
| 77 | // Each source renders with its ACTUAL id (not pluginId / name / UUID). | 75 | expect(description).toContain("`slack` — Slack Workspace"); |
| 78 | expect(description).toContain("`github` — GitHub"); | 76 | // And the plugin ids must NOT leak into the namespace list. |
| 79 | expect(description).toContain("`slack` — Slack Workspace"); | 77 | expect(description).not.toContain("`github-plugin`"); |
| 80 | // And the plugin ids must NOT leak into the namespace list. | 78 | expect(description).not.toContain("`slack-plugin`"); |
| 81 | expect(description).not.toContain("`github-plugin`"); | ||
| 82 | expect(description).not.toContain("`slack-plugin`"); | ||
| 83 | 79 | ||
| 84 | // Sort order: `github` before `slack`. | 80 | // Sort order: `github` before `slack`. |
| 85 | const githubIdx = description.indexOf("`github`"); | 81 | const githubIdx = description.indexOf("`github`"); |
| 86 | const slackIdx = description.indexOf("`slack`"); | 82 | const slackIdx = description.indexOf("`slack`"); |
| 87 | expect(githubIdx).toBeGreaterThan(-1); | 83 | expect(githubIdx).toBeGreaterThan(-1); |
| 88 | expect(slackIdx).toBeGreaterThan(-1); | 84 | expect(slackIdx).toBeGreaterThan(-1); |
| 89 | expect(githubIdx).toBeLessThan(slackIdx); | 85 | expect(githubIdx).toBeLessThan(slackIdx); |
| 90 | }), | 86 | }), |
| 91 | ); | 87 | ); |
| 92 | 88 | ||
| 93 | it.effect( | 89 | it.effect("omits the Available namespaces section when no plugins register sources", () => |
| 94 | "omits the Available namespaces section when no plugins register sources", | 90 | Effect.gen(function* () { |
| 95 | () => | 91 | const executor = yield* createExecutor(makeTestConfig({ plugins: [] as const })); |
| 96 | Effect.gen(function* () { | ||
| 97 | const executor = yield* createExecutor( | ||
| 98 | makeTestConfig({ plugins: [] as const }), | ||
| 99 | ); | ||
| 100 | 92 | ||
| 101 | const description = yield* buildExecuteDescription(executor); | 93 | const description = yield* buildExecuteDescription(executor); |
| 102 | 94 | ||
| 103 | expect(description).toContain( | 95 | expect(description).toContain("Execute TypeScript in a sandboxed runtime"); |
| 104 | "Execute TypeScript in a sandboxed runtime", | 96 | expect(description).not.toContain("## Available namespaces"); |
| 105 | ); | 97 | }), |
| 106 | expect(description).not.toContain("## Available namespaces"); | ||
| 107 | }), | ||
| 108 | ); | 98 | ); |
| 109 | }); | 99 | }); |
| @@ -10,13 +10,11 @@ import type { Executor, Source } from "@executor-js/sdk/core"; · 9 unmodified lines | |||
| 10 | */ | 10 | */ |
| 11 | export const buildExecuteDescription = (executor: Executor): Effect.Effect<string> => | 11 | export const buildExecuteDescription = (executor: Executor): Effect.Effect<string> => |
| 12 | Effect.gen(function* () { | 12 | Effect.gen(function* () { |
| 13 | const sources: readonly Source[] = yield* executor.sources | 13 | const sources: readonly Source[] = yield* executor.sources.list().pipe( |
| 14 | .list() | 14 | // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: ExecutionEngine.getDescription currently exposes no error channel; engine typed-error widening is covered separately |
| 15 | .pipe( | 15 | Effect.orDie, |
| 16 | // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: ExecutionEngine.getDescription currently exposes no error channel; engine typed-error widening is covered separately | 16 | Effect.withSpan("executor.sources.list"), |
| 17 | Effect.orDie, | 17 | ); |
| 18 | Effect.withSpan("executor.sources.list"), | ||
| 19 | ); | ||
| 20 | 18 | ||
| 21 | const description = yield* Effect.sync(() => formatDescription(sources)).pipe( | 19 | const description = yield* Effect.sync(() => formatDescription(sources)).pipe( |
| 22 | Effect.withSpan("schema.compile.description", { | 20 | Effect.withSpan("schema.compile.description", { |
| @@ -20,13 +20,11 @@ class FakeRuntimeError extends Data.TaggedError("FakeRuntimeError")<{ · 19 unmodified lines | |||
| 20 | }> {} | 20 | }> {} |
| 21 | 21 | ||
| 22 | const failingExecutor: CodeExecutor<FakeRuntimeError> = { | 22 | const failingExecutor: CodeExecutor<FakeRuntimeError> = { |
| 23 | execute: () => | 23 | execute: () => Effect.fail(new FakeRuntimeError({ message: "Unexpected token ':'" })), |
| 24 | Effect.fail(new FakeRuntimeError({ message: "Unexpected token ':'" })), | ||
| 25 | }; | 24 | }; |
| 26 | 25 | ||
| 27 | const succeedingExecutor: CodeExecutor<FakeRuntimeError> = { | 26 | const succeedingExecutor: CodeExecutor<FakeRuntimeError> = { |
| 28 | execute: () => | 27 | execute: () => Effect.succeed({ result: "ok", logs: [] } satisfies ExecuteResult), |
| 29 | Effect.succeed({ result: "ok", logs: [] } satisfies ExecuteResult), | ||
| 30 | }; | 28 | }; |
| 31 | 29 | ||
| 32 | const emptyPlugin = definePlugin(() => ({ | 30 | const emptyPlugin = definePlugin(() => ({ |
| @@ -30,13 +30,9 @@ import { · 29 unmodified lines | |||
| 30 | type ResumeResponse, | 30 | type ResumeResponse, |
| 31 | } from "./engine"; | 31 | } from "./engine"; |
| 32 | 32 | ||
| 33 | export type ElicitationHandler = ( | 33 | export type ElicitationHandler = (ctx: ElicitationContext) => Promise<ElicitationResponse>; |
| 34 | ctx: ElicitationContext, | ||
| 35 | ) => Promise<ElicitationResponse>; | ||
| 36 | 34 | ||
| 37 | export type ExecutionEngineConfig< | 35 | export type ExecutionEngineConfig<E extends Cause.YieldableError = CodeExecutionError> = { |
| 38 | E extends Cause.YieldableError = CodeExecutionError, | ||
| 39 | > = { | ||
| 40 | readonly executor: PromiseExecutor; | 36 | readonly executor: PromiseExecutor; |
| 41 | readonly codeExecutor: CodeExecutor<E>; | 37 | readonly codeExecutor: CodeExecutor<E>; |
| 42 | }; | 38 | }; |
| @@ -64,9 +60,7 @@ const fromPromise = <A>(try_: () => Promise<A>): Effect.Effect<A> => · 21 unmodified lines | |||
| 64 | type EffectInvokeOptions = Parameters<EffectExecutor["tools"]["invoke"]>[2]; | 60 | type EffectInvokeOptions = Parameters<EffectExecutor["tools"]["invoke"]>[2]; |
| 65 | type PromiseInvokeOptions = Parameters<PromiseExecutor["tools"]["invoke"]>[2]; | 61 | type PromiseInvokeOptions = Parameters<PromiseExecutor["tools"]["invoke"]>[2]; |
| 66 | 62 | ||
| 67 | const toPromiseInvokeOptions = ( | 63 | const toPromiseInvokeOptions = (options: EffectInvokeOptions): PromiseInvokeOptions => { |
| 68 | options: EffectInvokeOptions, | ||
| 69 | ): PromiseInvokeOptions => { | ||
| 70 | const onElicitation = options?.onElicitation; | 64 | const onElicitation = options?.onElicitation; |
| 71 | if (!onElicitation) return undefined; | 65 | if (!onElicitation) return undefined; |
| 72 | if (onElicitation === "accept-all") return { onElicitation }; | 66 | if (onElicitation === "accept-all") return { onElicitation }; |
| @@ -154,9 +148,7 @@ export const toPromiseExecutionEngine = <E extends Cause.YieldableError>( · 81 unmodified lines | |||
| 154 | getDescription: () => Effect.runPromise(engine.getDescription), | 148 | getDescription: () => Effect.runPromise(engine.getDescription), |
| 155 | }); | 149 | }); |
| 156 | 150 | ||
| 157 | export const createExecutionEngine = < | 151 | export const createExecutionEngine = <E extends Cause.YieldableError = CodeExecutionError>( |
| 158 | E extends Cause.YieldableError = CodeExecutionError, | ||
| 159 | >( | ||
| 160 | config: ExecutionEngineConfig<E>, | 152 | config: ExecutionEngineConfig<E>, |
| 161 | ): ExecutionEngine => | 153 | ): ExecutionEngine => |
| 162 | toPromiseExecutionEngine( | 154 | toPromiseExecutionEngine( |
| @@ -133,9 +133,7 @@ const errorPlugin = definePlugin(() => ({ · 132 unmodified lines | |||
| 133 | })); | 133 | })); |
| 134 | 134 | ||
| 135 | const makeSearchExecutor = () => | 135 | const makeSearchExecutor = () => |
| 136 | createExecutor( | 136 | createExecutor(makeTestConfig({ plugins: [githubPlugin(), crmPlugin()] as const })); |
| 137 | makeTestConfig({ plugins: [githubPlugin(), crmPlugin()] as const }), | ||
| 138 | ); | ||
| 139 | 137 | ||
| 140 | describe("tool discovery", () => { | 138 | describe("tool discovery", () => { |
| 141 | it.effect("ranks matches using ids, namespaces, camelCase names, and descriptions", () => | 139 | it.effect("ranks matches using ids, namespaces, camelCase names, and descriptions", () => |
| @@ -155,9 +153,7 @@ describe("tool discovery", () => { · 13 unmodified lines | |||
| 155 | 153 | ||
| 156 | const crmMatches = yield* searchTools(executor, "crm create contact", 5); | 154 | const crmMatches = yield* searchTools(executor, "crm create contact", 5); |
| 157 | expect(crmMatches.items[0]?.path).toBe("crm.createContact"); | 155 | expect(crmMatches.items[0]?.path).toBe("crm.createContact"); |
| 158 | expect(crmMatches.items[0]?.score ?? 0).toBeGreaterThan( | 156 | expect(crmMatches.items[0]?.score ?? 0).toBeGreaterThan(crmMatches.items[1]?.score ?? 0); |
| 159 | crmMatches.items[1]?.score ?? 0, | ||
| 160 | ); | ||
| 161 | }), | 157 | }), |
| 162 | ); | 158 | ); |
| 163 | 159 | ||
| @@ -218,9 +214,7 @@ describe("tool discovery", () => { · 54 unmodified lines | |||
| 218 | const githubOnly = yield* searchTools(executor, "list", 5, { | 214 | const githubOnly = yield* searchTools(executor, "list", 5, { |
| 219 | namespace: "github", | 215 | namespace: "github", |
| 220 | }); | 216 | }); |
| 221 | expect(githubOnly.items.map((match) => match.path)).toEqual([ | 217 | expect(githubOnly.items.map((match) => match.path)).toEqual(["github.listRepositoryIssues"]); |
| 222 | "github.listRepositoryIssues", | ||
| 223 | ]); | ||
| 224 | 218 | ||
| 225 | const crmOnly = yield* searchTools(executor, "list", 5, { | 219 | const crmOnly = yield* searchTools(executor, "list", 5, { |
| 226 | namespace: "crm", | 220 | namespace: "crm", |
| @@ -426,16 +420,12 @@ describe("tool discovery", () => { · 199 unmodified lines | |||
| 426 | 420 | ||
| 427 | it.effect("converts message-bearing tool error results into execution errors", () => | 421 | it.effect("converts message-bearing tool error results into execution errors", () => |
| 428 | Effect.gen(function* () { | 422 | Effect.gen(function* () { |
| 429 | const executor = yield* createExecutor( | 423 | const executor = yield* createExecutor(makeTestConfig({ plugins: [errorPlugin()] as const })); |
| 430 | makeTestConfig({ plugins: [errorPlugin()] as const }), | ||
| 431 | ); | ||
| 432 | const invoker = makeExecutorToolInvoker(executor, { | 424 | const invoker = makeExecutorToolInvoker(executor, { |
| 433 | invokeOptions: { onElicitation: acceptAll }, | 425 | invokeOptions: { onElicitation: acceptAll }, |
| 434 | }); | 426 | }); |
| 435 | 427 | ||
| 436 | const error = yield* Effect.flip( | 428 | const error = yield* Effect.flip(invoker.invoke({ path: "records.queryRows", args: {} })); |
| 437 | invoker.invoke({ path: "records.queryRows", args: {} }), | ||
| 438 | ); | ||
| 439 | 429 | ||
| 440 | expect(error).toEqual( | 430 | expect(error).toEqual( |
| 441 | expect.objectContaining({ | 431 | expect.objectContaining({ |
| @@ -552,10 +542,11 @@ describe("pause/resume with multiple elicitations", () => { · 110 unmodified lines | |||
| 552 | 542 | ||
| 553 | // `execution.fiber` is on `InternalPausedExecution`; the exported | 543 | // `execution.fiber` is on `InternalPausedExecution`; the exported |
| 554 | // `PausedExecution` type doesn't carry it. Cast to read. | 544 | // `PausedExecution` type doesn't carry it. Cast to read. |
| 555 | const pausedWithFiber = (value: unknown): { | 545 | const pausedWithFiber = ( |
| 546 | value: unknown, | ||
| 547 | ): { | ||
| 556 | readonly fiber: Fiber.Fiber<unknown, unknown>; | 548 | readonly fiber: Fiber.Fiber<unknown, unknown>; |
| 557 | } => | 549 | } => value as { readonly fiber: Fiber.Fiber<unknown, unknown> }; |
| 558 | value as { readonly fiber: Fiber.Fiber<unknown, unknown> }; | ||
| 559 | const sandboxFiber = pausedWithFiber(paused1.execution).fiber; | 550 | const sandboxFiber = pausedWithFiber(paused1.execution).fiber; |
| 560 | const exitProbe = await Effect.runPromise( | 551 | const exitProbe = await Effect.runPromise( |
| 561 | Effect.race( | 552 | Effect.race( |
| @@ -60,9 +60,7 @@ type ToolResultEnvelope = { · 59 unmodified lines | |||
| 60 | }; | 60 | }; |
| 61 | 61 | ||
| 62 | const isToolResultEnvelope = (value: unknown): value is ToolResultEnvelope => | 62 | const isToolResultEnvelope = (value: unknown): value is ToolResultEnvelope => |
| 63 | value !== null && | 63 | value !== null && typeof value === "object" && ("error" in value || "data" in value); |
| 64 | typeof value === "object" && | ||
| 65 | ("error" in value || "data" in value); | ||
| 66 | 64 | ||
| 67 | const hasToolResultError = ( | 65 | const hasToolResultError = ( |
| 68 | value: ToolResultEnvelope, | 66 | value: ToolResultEnvelope, |
| @@ -129,7 +127,11 @@ export const makeExecutorToolInvoker = ( · 60 unmodified lines | |||
| 129 | 127 | ||
| 130 | const isElicitationDeclinedError = ( | 128 | const isElicitationDeclinedError = ( |
| 131 | value: unknown, | 129 | value: unknown, |
| 132 | ): value is { readonly _tag: "ElicitationDeclinedError"; readonly toolId: string; readonly action: "cancel" | "decline" } => | 130 | ): value is { |
| 131 | readonly _tag: "ElicitationDeclinedError"; | ||
| 132 | readonly toolId: string; | ||
| 133 | readonly action: "cancel" | "decline"; | ||
| 134 | } => | ||
| 133 | Predicate.isTagged(value, "ElicitationDeclinedError") && | 135 | Predicate.isTagged(value, "ElicitationDeclinedError") && |
| 134 | value !== null && | 136 | value !== null && |
| 135 | typeof value === "object" && | 137 | typeof value === "object" && |
| @@ -83,35 +83,27 @@ interface MemorySecretsConfig { · 82 unmodified lines | |||
| 83 | 83 | ||
| 84 | // definePlugin takes a factory and returns a configured-plugin function | 84 | // definePlugin takes a factory and returns a configured-plugin function |
| 85 | // that the consumer calls with options. | 85 | // that the consumer calls with options. |
| 86 | export const memorySecretsPlugin = definePlugin( | 86 | export const memorySecretsPlugin = definePlugin((options?: MemorySecretsConfig) => { |
| 87 | (options?: MemorySecretsConfig) => { | 87 | const map = new Map<string, string>(Object.entries(options?.initial ?? {})); |
| 88 | const map = new Map<string, string>( | 88 | const provider: SecretProvider = { |
| 89 | Object.entries(options?.initial ?? {}), | 89 | key: "memory", |
| 90 | ); | 90 | writable: true, |
| 91 | const provider: SecretProvider = { | 91 | get: (id: string) => Effect.sync(() => map.get(id) ?? null), |
| 92 | key: "memory", | 92 | has: (id: string) => Effect.sync(() => map.has(id)), |
| 93 | writable: true, | 93 | set: (id: string, value: string) => Effect.sync(() => void map.set(id, value)), |
| 94 | get: (id: string) => Effect.sync(() => map.get(id) ?? null), | 94 | delete: (id: string) => Effect.sync(() => map.delete(id)), |
| 95 | has: (id: string) => Effect.sync(() => map.has(id)), | 95 | list: () => Effect.sync(() => Array.from(map.keys()).map((k) => ({ id: k, name: k }))), |
| 96 | set: (id: string, value: string) => | 96 | }; |
| 97 | Effect.sync(() => void map.set(id, value)), | 97 | |
| 98 | delete: (id: string) => Effect.sync(() => map.delete(id)), | 98 | return { |
| 99 | list: () => | 99 | id: "memorySecrets" as const, |
| 100 | Effect.sync(() => | 100 | storage: () => ({}), |
| 101 | Array.from(map.keys()).map((k) => ({ id: k, name: k })), | 101 | extension: () => ({ |
| 102 | ), | 102 | label: "in-memory secrets", |
| 103 | }; | 103 | }), |
| 104 | 104 | secretProviders: () => [provider], | |
| 105 | return { | 105 | }; |
| 106 | id: "memorySecrets" as const, | 106 | }); |
| 107 | storage: () => ({}), | ||
| 108 | extension: () => ({ | ||
| 109 | label: "in-memory secrets", | ||
| 110 | }), | ||
| 111 | secretProviders: () => [provider], | ||
| 112 | }; | ||
| 113 | }, | ||
| 114 | ); | ||
| 115 | 107 | ||
| 116 | // End users compose the executor exactly the same way as above — | 108 | // End users compose the executor exactly the same way as above — |
| 117 | // the plugin's extension is reachable as `executor.memorySecrets`. | 109 | // the plugin's extension is reachable as `executor.memorySecrets`. |
| @@ -53,8 +53,18 @@ · 52 unmodified lines | |||
| 53 | "dependencies": { | 53 | "dependencies": { |
| 54 | "@executor-js/storage-core": "workspace:*", | 54 | "@executor-js/storage-core": "workspace:*", |
| 55 | "effect": "catalog:", | 55 | "effect": "catalog:", |
| 56 | "oauth4webapi": "^3.8.5", | 56 | "fractional-indexing": "^3.2.0", |
| 57 | "fractional-indexing": "^3.2.0" | 57 | "oauth4webapi": "^3.8.5" |
| 58 | }, | ||
| 59 | "devDependencies": { | ||
| 60 | "@effect/atom-react": "catalog:", | ||
| 61 | "@effect/vitest": "catalog:", | ||
| 62 | "@types/node": "catalog:", | ||
| 63 | "@types/react": "catalog:", | ||
| 64 | "react": "catalog:", | ||
| 65 | "tsup": "catalog:", | ||
| 66 | "typescript": "catalog:", | ||
| 67 | "vitest": "catalog:" | ||
| 58 | }, | 68 | }, |
| 59 | "peerDependencies": { | 69 | "peerDependencies": { |
| 60 | "@effect/atom-react": "catalog:", | 70 | "@effect/atom-react": "catalog:", |
| @@ -67,15 +77,5 @@ · 6 unmodified lines | |||
| 67 | "react": { | 77 | "react": { |
| 68 | "optional": true | 78 | "optional": true |
| 69 | } | 79 | } |
| 70 | }, | ||
| 71 | "devDependencies": { | ||
| 72 | "@effect/atom-react": "catalog:", | ||
| 73 | "@effect/vitest": "catalog:", | ||
| 74 | "@types/node": "catalog:", | ||
| 75 | "@types/react": "catalog:", | ||
| 76 | "react": "catalog:", | ||
| 77 | "tsup": "catalog:", | ||
| 78 | "typescript": "catalog:", | ||
| 79 | "vitest": "catalog:" | ||
| 80 | } | 80 | } |
| 81 | } | 81 | } |
| @@ -75,9 +75,7 @@ describe("pluginBlobStore", () => { · 74 unmodified lines | |||
| 75 | Effect.gen(function* () { | 75 | Effect.gen(function* () { |
| 76 | const store = makeInMemoryBlobStore(); | 76 | const store = makeInMemoryBlobStore(); |
| 77 | const plugin = pluginBlobStore(store, ["inner", "outer"], "my-plugin"); | 77 | const plugin = pluginBlobStore(store, ["inner", "outer"], "my-plugin"); |
| 78 | const err = yield* plugin | 78 | const err = yield* plugin.put("k", "v", { scope: "not-in-stack" }).pipe(Effect.flip); |
| 79 | .put("k", "v", { scope: "not-in-stack" }) | ||
| 80 | .pipe(Effect.flip); | ||
| 81 | expect(err).toBeInstanceOf(StorageError); | 79 | expect(err).toBeInstanceOf(StorageError); |
| 82 | expect(err).toMatchObject({ | 80 | expect(err).toMatchObject({ |
| 83 | message: expect.stringContaining("not in the"), | 81 | message: expect.stringContaining("not in the"), |
| @@ -91,9 +89,7 @@ describe("pluginBlobStore", () => { · 7 unmodified lines | |||
| 91 | Effect.gen(function* () { | 89 | Effect.gen(function* () { |
| 92 | const store = makeInMemoryBlobStore(); | 90 | const store = makeInMemoryBlobStore(); |
| 93 | const plugin = pluginBlobStore(store, ["inner"], "my-plugin"); | 91 | const plugin = pluginBlobStore(store, ["inner"], "my-plugin"); |
| 94 | const err = yield* plugin | 92 | const err = yield* plugin.delete("k", { scope: "not-in-stack" }).pipe(Effect.flip); |
| 95 | .delete("k", { scope: "not-in-stack" }) | ||
| 96 | .pipe(Effect.flip); | ||
| 97 | expect(err).toBeInstanceOf(StorageError); | 93 | expect(err).toBeInstanceOf(StorageError); |
| 98 | }), | 94 | }), |
| 99 | ); | 95 | ); |
| @@ -21,10 +21,7 @@ import { Effect } from "effect"; · 20 unmodified lines | |||
| 21 | import { StorageError } from "@executor-js/storage-core"; | 21 | import { StorageError } from "@executor-js/storage-core"; |
| 22 | 22 | ||
| 23 | export interface BlobStore { | 23 | export interface BlobStore { |
| 24 | readonly get: ( | 24 | readonly get: (namespace: string, key: string) => Effect.Effect<string | null, StorageError>; |
| 25 | namespace: string, | ||
| 26 | key: string, | ||
| 27 | ) => Effect.Effect<string | null, StorageError>; | ||
| 28 | /** Multi-namespace lookup for a single key. Backends issue one query | 25 | /** Multi-namespace lookup for a single key. Backends issue one query |
| 29 | * (`WHERE namespace IN (...) AND key = ?`) and return the hits keyed | 26 | * (`WHERE namespace IN (...) AND key = ?`) and return the hits keyed |
| 30 | * by namespace — the caller applies its own precedence. Lets | 27 | * by namespace — the caller applies its own precedence. Lets |
| @@ -39,14 +36,8 @@ export interface BlobStore { · 8 unmodified lines | |||
| 39 | key: string, | 36 | key: string, |
| 40 | value: string, | 37 | value: string, |
| 41 | ) => Effect.Effect<void, StorageError>; | 38 | ) => Effect.Effect<void, StorageError>; |
| 42 | readonly delete: ( | 39 | readonly delete: (namespace: string, key: string) => Effect.Effect<void, StorageError>; |
| 43 | namespace: string, | 40 | readonly has: (namespace: string, key: string) => Effect.Effect<boolean, StorageError>; |
| 44 | key: string, | ||
| 45 | ) => Effect.Effect<void, StorageError>; | ||
| 46 | readonly has: ( | ||
| 47 | namespace: string, | ||
| 48 | key: string, | ||
| 49 | ) => Effect.Effect<boolean, StorageError>; | ||
| 50 | } | 41 | } |
| 51 | 42 | ||
| 52 | export interface PluginBlobStore { | 43 | export interface PluginBlobStore { |
| @@ -69,10 +60,7 @@ export interface PluginBlobStore { · 16 unmodified lines | |||
| 69 | readonly has: (key: string) => Effect.Effect<boolean, StorageError>; | 60 | readonly has: (key: string) => Effect.Effect<boolean, StorageError>; |
| 70 | } | 61 | } |
| 71 | 62 | ||
| 72 | const assertScope = ( | 63 | const assertScope = (scope: string, scopes: readonly string[]): Effect.Effect<void, StorageError> => |
| 73 | scope: string, | ||
| 74 | scopes: readonly string[], | ||
| 75 | ): Effect.Effect<void, StorageError> => | ||
| 76 | scopes.includes(scope) | 64 | scopes.includes(scope) |
| 77 | ? Effect.void | 65 | ? Effect.void |
| 78 | : Effect.fail( | 66 | : Effect.fail( |
| @@ -35,12 +35,7 @@ export * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; · 34 unmodified lines | |||
| 35 | export * as Atom from "effect/unstable/reactivity/Atom"; | 35 | export * as Atom from "effect/unstable/reactivity/Atom"; |
| 36 | export * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi"; | 36 | export * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi"; |
| 37 | 37 | ||
| 38 | export { | 38 | export { useAtomValue, useAtomSet, useAtomMount, useAtomRefresh } from "@effect/atom-react"; |
| 39 | useAtomValue, | ||
| 40 | useAtomSet, | ||
| 41 | useAtomMount, | ||
| 42 | useAtomRefresh, | ||
| 43 | } from "@effect/atom-react"; | ||
| 44 | 39 | ||
| 45 | // --------------------------------------------------------------------------- | 40 | // --------------------------------------------------------------------------- |
| 46 | // defineClientPlugin — declarative spec for the frontend half of a plugin. | 41 | // defineClientPlugin — declarative spec for the frontend half of a plugin. |
| @@ -219,14 +214,11 @@ export const createPluginAtomClient = < · 172 unmodified lines | |||
| 219 | const { baseUrl = "/api" } = options; | 214 | const { baseUrl = "/api" } = options; |
| 220 | const pluginId = group.identifier; | 215 | const pluginId = group.identifier; |
| 221 | const bundle = HttpApi.make(`plugin-${pluginId}`).add(group); | 216 | const bundle = HttpApi.make(`plugin-${pluginId}`).add(group); |
| 222 | return AtomHttpApi.Service<`Plugin_${G["identifier"]}Client`>()( | 217 | return AtomHttpApi.Service<`Plugin_${G["identifier"]}Client`>()(`Plugin_${pluginId}Client`, { |
| 223 | `Plugin_${pluginId}Client`, | 218 | api: bundle, |
| 224 | { | 219 | httpClient: FetchHttpClient.layer, |
| 225 | api: bundle, | 220 | baseUrl, |
| 226 | httpClient: FetchHttpClient.layer, | 221 | }); |
| 227 | baseUrl, | ||
| 228 | }, | ||
| 229 | ); | ||
| 230 | }; | 222 | }; |
| 231 | 223 | ||
| 232 | // --------------------------------------------------------------------------- | 224 | // --------------------------------------------------------------------------- |
| @@ -250,9 +242,7 @@ interface ExecutorPluginsContextValue { · 17 unmodified lines | |||
| 250 | readonly secretProviderPlugins: readonly SecretProviderPlugin[]; | 242 | readonly secretProviderPlugins: readonly SecretProviderPlugin[]; |
| 251 | } | 243 | } |
| 252 | 244 | ||
| 253 | const ExecutorPluginsContext = createContext< | 245 | const ExecutorPluginsContext = createContext<ExecutorPluginsContextValue | null>(null); |
| 254 | ExecutorPluginsContextValue | null | ||
| 255 | >(null); | ||
| 256 | ExecutorPluginsContext.displayName = "ExecutorPluginsContext"; | 246 | ExecutorPluginsContext.displayName = "ExecutorPluginsContext"; |
| 257 | 247 | ||
| 258 | export interface ExecutorPluginsProviderProps { | 248 | export interface ExecutorPluginsProviderProps { |
| @@ -286,9 +276,7 @@ const usePluginsCtx = (hookName: string): ExecutorPluginsContextValue => { · 27 unmodified lines | |||
| 286 | const ctx = useContext(ExecutorPluginsContext); | 276 | const ctx = useContext(ExecutorPluginsContext); |
| 287 | if (!ctx) { | 277 | if (!ctx) { |
| 288 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: React hook invariant | 278 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: React hook invariant |
| 289 | throw new Error( | 279 | throw new Error(`${hookName} must be called inside an <ExecutorPluginsProvider>.`); |
| 290 | `${hookName} must be called inside an <ExecutorPluginsProvider>.`, | ||
| 291 | ); | ||
| 292 | } | 280 | } |
| 293 | return ctx; | 281 | return ctx; |
| 294 | }; | 282 | }; |
| @@ -162,9 +162,7 @@ describe("connections", () => { · 161 unmodified lines | |||
| 162 | }), | 162 | }), |
| 163 | ) | 163 | ) |
| 164 | .pipe(Effect.flip); | 164 | .pipe(Effect.flip); |
| 165 | expect( | 165 | expect(Predicate.isTagged(err, "ConnectionProviderNotRegisteredError")).toBe(true); |
| 166 | Predicate.isTagged(err, "ConnectionProviderNotRegisteredError"), | ||
| 167 | ).toBe(true); | ||
| 168 | }), | 166 | }), |
| 169 | ); | 167 | ); |
| 170 | 168 | ||
| @@ -200,54 +198,52 @@ describe("connections", () => { · 29 unmodified lines | |||
| 200 | }), | 198 | }), |
| 201 | ); | 199 | ); |
| 202 | 200 | ||
| 203 | it.effect( | 201 | it.effect("secrets.list hides connection-owned secrets but surfaces bare ones", () => |
| 204 | "secrets.list hides connection-owned secrets but surfaces bare ones", | 202 | Effect.gen(function* () { |
| 205 | () => | 203 | const { provider } = makeConnectionProvider({ key: "spotify" }); |
| 206 | Effect.gen(function* () { | 204 | const executor = yield* createExecutor( |
| 207 | const { provider } = makeConnectionProvider({ key: "spotify" }); | 205 | makeTestConfig({ |
| 208 | const executor = yield* createExecutor( | 206 | plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, |
| 209 | makeTestConfig({ | 207 | }), |
| 210 | plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, | 208 | ); |
| 211 | }), | ||
| 212 | ); | ||
| 213 | 209 | ||
| 214 | yield* executor.secrets.set( | 210 | yield* executor.secrets.set( |
| 215 | new SetSecretInput({ | 211 | new SetSecretInput({ |
| 216 | id: sid("bare-api"), | 212 | id: sid("bare-api"), |
| 217 | scope: scpid("test-scope"), | 213 | scope: scpid("test-scope"), |
| 218 | name: "bare API key", | 214 | name: "bare API key", |
| 219 | value: "bare", | 215 | value: "bare", |
| 220 | }), | 216 | }), |
| 221 | ); | 217 | ); |
| 222 | 218 | ||
| 223 | yield* executor.connections.create( | 219 | yield* executor.connections.create( |
| 224 | new CreateConnectionInput({ | 220 | new CreateConnectionInput({ |
| 225 | id: cid("conn-1"), | 221 | id: cid("conn-1"), |
| 226 | scope: scpid("test-scope"), | 222 | scope: scpid("test-scope"), |
| 227 | provider: "spotify", | 223 | provider: "spotify", |
| 228 | identityLabel: null, | 224 | identityLabel: null, |
| 229 | accessToken: new TokenMaterial({ | 225 | accessToken: new TokenMaterial({ |
| 230 | secretId: sid("conn-1.access"), | 226 | secretId: sid("conn-1.access"), |
| 231 | name: "access", | 227 | name: "access", |
| 232 | value: "a", | 228 | value: "a", |
| 233 | }), | ||
| 234 | refreshToken: new TokenMaterial({ | ||
| 235 | secretId: sid("conn-1.refresh"), | ||
| 236 | name: "refresh", | ||
| 237 | value: "r", | ||
| 238 | }), | ||
| 239 | expiresAt: null, | ||
| 240 | oauthScope: null, | ||
| 241 | providerState: null, | ||
| 242 | }), | 229 | }), |
| 243 | ); | 230 | refreshToken: new TokenMaterial({ |
| 231 | secretId: sid("conn-1.refresh"), | ||
| 232 | name: "refresh", | ||
| 233 | value: "r", | ||
| 234 | }), | ||
| 235 | expiresAt: null, | ||
| 236 | oauthScope: null, | ||
| 237 | providerState: null, | ||
| 238 | }), | ||
| 239 | ); | ||
| 244 | 240 | ||
| 245 | const list = yield* executor.secrets.list(); | 241 | const list = yield* executor.secrets.list(); |
| 246 | const ids = list.map((s) => String(s.id)); | 242 | const ids = list.map((s) => String(s.id)); |
| 247 | expect(ids).toContain("bare-api"); | 243 | expect(ids).toContain("bare-api"); |
| 248 | expect(ids).not.toContain("conn-1.access"); | 244 | expect(ids).not.toContain("conn-1.access"); |
| 249 | expect(ids).not.toContain("conn-1.refresh"); | 245 | expect(ids).not.toContain("conn-1.refresh"); |
| 250 | }), | 246 | }), |
| 251 | ); | 247 | ); |
| 252 | 248 | ||
| 253 | it.effect( | 249 | it.effect( |
| @@ -279,72 +275,55 @@ describe("connections", () => { · 25 unmodified lines | |||
| 279 | }), | 275 | }), |
| 280 | ); | 276 | ); |
| 281 | 277 | ||
| 282 | const err = yield* executor.secrets | 278 | const err = yield* executor.secrets.remove("conn-1.access").pipe(Effect.flip); |
| 283 | .remove("conn-1.access") | 279 | expect(Predicate.isTagged(err, "SecretOwnedByConnectionError")).toBe(true); |
| 284 | .pipe(Effect.flip); | ||
| 285 | expect(Predicate.isTagged(err, "SecretOwnedByConnectionError")).toBe( | ||
| 286 | true, | ||
| 287 | ); | ||
| 288 | }), | 280 | }), |
| 289 | ); | 281 | ); |
| 290 | 282 | ||
| 291 | it.effect( | 283 | it.effect("connections.remove cascades through providers and deletes the core row", () => |
| 292 | "connections.remove cascades through providers and deletes the core row", | 284 | Effect.gen(function* () { |
| 293 | () => | 285 | const secretProvider = makeMemoryProvider(); |
| 294 | Effect.gen(function* () { | 286 | const { provider } = makeConnectionProvider({ key: "spotify" }); |
| 295 | const secretProvider = makeMemoryProvider(); | 287 | const executor = yield* createExecutor( |
| 296 | const { provider } = makeConnectionProvider({ key: "spotify" }); | 288 | makeTestConfig({ |
| 297 | const executor = yield* createExecutor( | 289 | plugins: [memorySecretsPlugin(secretProvider), connPlugin(provider)] as const, |
| 298 | makeTestConfig({ | 290 | }), |
| 299 | plugins: [ | 291 | ); |
| 300 | memorySecretsPlugin(secretProvider), | ||
| 301 | connPlugin(provider), | ||
| 302 | ] as const, | ||
| 303 | }), | ||
| 304 | ); | ||
| 305 | 292 | ||
| 306 | yield* executor.connections.create( | 293 | yield* executor.connections.create( |
| 307 | new CreateConnectionInput({ | 294 | new CreateConnectionInput({ |
| 308 | id: cid("conn-1"), | 295 | id: cid("conn-1"), |
| 309 | scope: scpid("test-scope"), | 296 | scope: scpid("test-scope"), |
| 310 | provider: "spotify", | 297 | provider: "spotify", |
| 311 | identityLabel: null, | 298 | identityLabel: null, |
| 312 | accessToken: new TokenMaterial({ | 299 | accessToken: new TokenMaterial({ |
| 313 | secretId: sid("conn-1.access"), | 300 | secretId: sid("conn-1.access"), |
| 314 | name: "access", | 301 | name: "access", |
| 315 | value: "access-v1", | 302 | value: "access-v1", |
| 316 | }), | ||
| 317 | refreshToken: new TokenMaterial({ | ||
| 318 | secretId: sid("conn-1.refresh"), | ||
| 319 | name: "refresh", | ||
| 320 | value: "refresh-v1", | ||
| 321 | }), | ||
| 322 | expiresAt: null, | ||
| 323 | oauthScope: null, | ||
| 324 | providerState: null, | ||
| 325 | }), | 303 | }), |
| 326 | ); | 304 | refreshToken: new TokenMaterial({ |
| 305 | secretId: sid("conn-1.refresh"), | ||
| 306 | name: "refresh", | ||
| 307 | value: "refresh-v1", | ||
| 308 | }), | ||
| 309 | expiresAt: null, | ||
| 310 | oauthScope: null, | ||
| 311 | providerState: null, | ||
| 312 | }), | ||
| 313 | ); | ||
| 327 | 314 | ||
| 328 | // Pre-check: backing provider holds the tokens. | 315 | // Pre-check: backing provider holds the tokens. |
| 329 | expect( | 316 | expect(yield* secretProvider.get!("conn-1.access", "test-scope")).toBe("access-v1"); |
| 330 | yield* secretProvider.get!("conn-1.access", "test-scope"), | 317 | expect(yield* secretProvider.get!("conn-1.refresh", "test-scope")).toBe("refresh-v1"); |
| 331 | ).toBe("access-v1"); | 318 | |
| 332 | expect( | 319 | yield* executor.connections.remove("conn-1"); |
| 333 | yield* secretProvider.get!("conn-1.refresh", "test-scope"), | 320 | |
| 334 | ).toBe("refresh-v1"); | 321 | // Connection row gone. |
| 335 | 322 | expect(yield* executor.connections.get("conn-1")).toBeNull(); | |
| 336 | yield* executor.connections.remove("conn-1"); | 323 | // Backing secret values gone from the provider. |
| 337 | 324 | expect(yield* secretProvider.get!("conn-1.access", "test-scope")).toBeNull(); | |
| 338 | // Connection row gone. | 325 | expect(yield* secretProvider.get!("conn-1.refresh", "test-scope")).toBeNull(); |
| 339 | expect(yield* executor.connections.get("conn-1")).toBeNull(); | 326 | }), |
| 340 | // Backing secret values gone from the provider. | ||
| 341 | expect( | ||
| 342 | yield* secretProvider.get!("conn-1.access", "test-scope"), | ||
| 343 | ).toBeNull(); | ||
| 344 | expect( | ||
| 345 | yield* secretProvider.get!("conn-1.refresh", "test-scope"), | ||
| 346 | ).toBeNull(); | ||
| 347 | }), | ||
| 348 | ); | 327 | ); |
| 349 | 328 | ||
| 350 | it.effect("accessToken returns the stored value when not near expiry", () => | 329 | it.effect("accessToken returns the stored value when not near expiry", () => |
| @@ -407,10 +386,7 @@ describe("connections", () => { · 56 unmodified lines | |||
| 407 | }); | 386 | }); |
| 408 | const executor = yield* createExecutor( | 387 | const executor = yield* createExecutor( |
| 409 | makeTestConfig({ | 388 | makeTestConfig({ |
| 410 | plugins: [ | 389 | plugins: [memorySecretsPlugin(secretProvider), connPlugin(provider)] as const, |
| 411 | memorySecretsPlugin(secretProvider), | ||
| 412 | connPlugin(provider), | ||
| 413 | ] as const, | ||
| 414 | }), | 390 | }), |
| 415 | ); | 391 | ); |
| 416 | 392 | ||
| @@ -445,12 +421,10 @@ describe("connections", () => { · 28 unmodified lines | |||
| 445 | expect(calls[0]!.providerState).toEqual({ rotation: "fresh" }); | 421 | expect(calls[0]!.providerState).toEqual({ rotation: "fresh" }); |
| 446 | 422 | ||
| 447 | // Backing secrets got rewritten at the same ids. | 423 | // Backing secrets got rewritten at the same ids. |
| 448 | expect( | 424 | expect(yield* secretProvider.get!("conn-1.access", "test-scope")).toBe( |
| 449 | yield* secretProvider.get!("conn-1.access", "test-scope"), | 425 | "rotated-refresh-v1", |
| 450 | ).toBe("rotated-refresh-v1"); | 426 | ); |
| 451 | expect( | 427 | expect(yield* secretProvider.get!("conn-1.refresh", "test-scope")).toBe("refresh-v2"); |
| 452 | yield* secretProvider.get!("conn-1.refresh", "test-scope"), | ||
| 453 | ).toBe("refresh-v2"); | ||
| 454 | 428 | ||
| 455 | const got = yield* executor.connections.get("conn-1"); | 429 | const got = yield* executor.connections.get("conn-1"); |
| 456 | expect(got?.providerState).toEqual({ rotation: "bumped" }); | 430 | expect(got?.providerState).toEqual({ rotation: "bumped" }); |
| @@ -458,110 +432,105 @@ describe("connections", () => { · 1 unmodified line | |||
| 458 | }), | 432 | }), |
| 459 | ); | 433 | ); |
| 460 | 434 | ||
| 461 | it.effect( | 435 | it.effect("accessToken dedupes concurrent refreshes into a single provider call", () => |
| 462 | "accessToken dedupes concurrent refreshes into a single provider call", | 436 | Effect.gen(function* () { |
| 463 | () => | 437 | // A gated refresh provider. Every concurrent caller that lands |
| 464 | Effect.gen(function* () { | 438 | // inside the skew window must converge on the single pending |
| 465 | // A gated refresh provider. Every concurrent caller that lands | 439 | // refresh instead of hitting the token endpoint N times. The |
| 466 | // inside the skew window must converge on the single pending | 440 | // `entered` Deferred signals that the leader fiber is parked |
| 467 | // refresh instead of hitting the token endpoint N times. The | 441 | // inside `refresh`; we only release the `gate` once every |
| 468 | // `entered` Deferred signals that the leader fiber is parked | 442 | // caller has had a chance to register. |
| 469 | // inside `refresh`; we only release the `gate` once every | 443 | const gate = yield* Deferred.make<void>(); |
| 470 | // caller has had a chance to register. | 444 | const entered = yield* Deferred.make<void>(); |
| 471 | const gate = yield* Deferred.make<void>(); | 445 | const calls: ConnectionRefreshInput[] = []; |
| 472 | const entered = yield* Deferred.make<void>(); | 446 | let responseCounter = 0; |
| 473 | const calls: ConnectionRefreshInput[] = []; | 447 | const provider: ConnectionProvider = { |
| 474 | let responseCounter = 0; | 448 | key: "spotify", |
| 475 | const provider: ConnectionProvider = { | 449 | refresh: (input): Effect.Effect<ConnectionRefreshResult, ConnectionRefreshError> => |
| 476 | key: "spotify", | 450 | Effect.gen(function* () { |
| 477 | refresh: ( | 451 | calls.push(input); |
| 478 | input, | 452 | const n = ++responseCounter; |
| 479 | ): Effect.Effect<ConnectionRefreshResult, ConnectionRefreshError> => | 453 | yield* Deferred.succeed(entered, undefined as void); |
| 480 | Effect.gen(function* () { | 454 | // Block the leader inside `refresh` until the test |
| 481 | calls.push(input); | 455 | // releases the gate. Any other fiber that concurrently |
| 482 | const n = ++responseCounter; | 456 | // calls `accessToken` must observe the in-flight |
| 483 | yield* Deferred.succeed(entered, undefined as void); | 457 | // Deferred instead of entering this handler. |
| 484 | // Block the leader inside `refresh` until the test | 458 | yield* Deferred.await(gate); |
| 485 | // releases the gate. Any other fiber that concurrently | 459 | return { |
| 486 | // calls `accessToken` must observe the in-flight | 460 | accessToken: `rotated-${n}`, |
| 487 | // Deferred instead of entering this handler. | 461 | refreshToken: `refresh-${n}`, |
| 488 | yield* Deferred.await(gate); | 462 | expiresAt: Date.now() + 3_600_000, |
| 489 | return { | 463 | oauthScope: "user-read", |
| 490 | accessToken: `rotated-${n}`, | 464 | providerState: null, |
| 491 | refreshToken: `refresh-${n}`, | 465 | }; |
| 492 | expiresAt: Date.now() + 3_600_000, | 466 | }), |
| 493 | oauthScope: "user-read", | 467 | }; |
| 494 | providerState: null, | ||
| 495 | }; | ||
| 496 | }), | ||
| 497 | }; | ||
| 498 | 468 | ||
| 499 | const executor = yield* createExecutor( | 469 | const executor = yield* createExecutor( |
| 500 | makeTestConfig({ | 470 | makeTestConfig({ |
| 501 | plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, | 471 | plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, |
| 502 | }), | 472 | }), |
| 503 | ); | 473 | ); |
| 504 | 474 | ||
| 505 | yield* executor.connections.create( | 475 | yield* executor.connections.create( |
| 506 | new CreateConnectionInput({ | 476 | new CreateConnectionInput({ |
| 507 | id: cid("conn-1"), | 477 | id: cid("conn-1"), |
| 508 | scope: scpid("test-scope"), | 478 | scope: scpid("test-scope"), |
| 509 | provider: "spotify", | 479 | provider: "spotify", |
| 510 | identityLabel: null, | 480 | identityLabel: null, |
| 511 | accessToken: new TokenMaterial({ | 481 | accessToken: new TokenMaterial({ |
| 512 | secretId: sid("conn-1.access"), | 482 | secretId: sid("conn-1.access"), |
| 513 | name: "access", | 483 | name: "access", |
| 514 | value: "stale", | 484 | value: "stale", |
| 515 | }), | ||
| 516 | refreshToken: new TokenMaterial({ | ||
| 517 | secretId: sid("conn-1.refresh"), | ||
| 518 | name: "refresh", | ||
| 519 | value: "refresh-v1", | ||
| 520 | }), | ||
| 521 | // Expired so every caller enters the refresh branch. | ||
| 522 | expiresAt: Date.now() - 1_000, | ||
| 523 | oauthScope: null, | ||
| 524 | providerState: null, | ||
| 525 | }), | 485 | }), |
| 526 | ); | 486 | refreshToken: new TokenMaterial({ |
| 487 | secretId: sid("conn-1.refresh"), | ||
| 488 | name: "refresh", | ||
| 489 | value: "refresh-v1", | ||
| 490 | }), | ||
| 491 | // Expired so every caller enters the refresh branch. | ||
| 492 | expiresAt: Date.now() - 1_000, | ||
| 493 | oauthScope: null, | ||
| 494 | providerState: null, | ||
| 495 | }), | ||
| 496 | ); | ||
| 527 | 497 | ||
| 528 | // Kick off the leader first and wait for it to park inside | 498 | // Kick off the leader first and wait for it to park inside |
| 529 | // `refresh`. Any subsequent caller is guaranteed to see the | 499 | // `refresh`. Any subsequent caller is guaranteed to see the |
| 530 | // in-flight Deferred the leader just registered. | 500 | // in-flight Deferred the leader just registered. |
| 531 | const leaderFiber = yield* Effect.forkDetach( | 501 | const leaderFiber = yield* Effect.forkDetach(executor.connections.accessToken("conn-1"), { |
| 532 | executor.connections.accessToken("conn-1"), | 502 | startImmediately: true, |
| 533 | { startImmediately: true }, | 503 | }); |
| 534 | ); | 504 | yield* Deferred.await(entered); |
| 535 | yield* Deferred.await(entered); | ||
| 536 | 505 | ||
| 537 | const followerFibers = yield* Effect.forEach( | 506 | const followerFibers = yield* Effect.forEach( |
| 538 | [1, 2, 3, 4], | 507 | [1, 2, 3, 4], |
| 539 | () => | 508 | () => |
| 540 | Effect.forkDetach(executor.connections.accessToken("conn-1"), { | 509 | Effect.forkDetach(executor.connections.accessToken("conn-1"), { |
| 541 | startImmediately: true, | 510 | startImmediately: true, |
| 542 | }), | 511 | }), |
| 543 | { concurrency: "unbounded" }, | 512 | { concurrency: "unbounded" }, |
| 544 | ); | 513 | ); |
| 545 | yield* Effect.forEach([1, 2, 3, 4], () => Effect.yieldNow); | 514 | yield* Effect.forEach([1, 2, 3, 4], () => Effect.yieldNow); |
| 546 | 515 | ||
| 547 | // Every follower is queued on the leader's Deferred. Release | 516 | // Every follower is queued on the leader's Deferred. Release |
| 548 | // the gate — the leader resolves, waiters wake up with the | 517 | // the gate — the leader resolves, waiters wake up with the |
| 549 | // same token, no extra `refresh` is invoked. | 518 | // same token, no extra `refresh` is invoked. |
| 550 | yield* Deferred.succeed(gate, undefined as void); | 519 | yield* Deferred.succeed(gate, undefined as void); |
| 551 | 520 | ||
| 552 | const leaderResult = yield* Fiber.join(leaderFiber); | 521 | const leaderResult = yield* Fiber.join(leaderFiber); |
| 553 | const followerResults = yield* Effect.all( | 522 | const followerResults = yield* Effect.all( |
| 554 | followerFibers.map((f) => Fiber.await(f)), | 523 | followerFibers.map((f) => Fiber.await(f)), |
| 555 | { concurrency: "unbounded" }, | 524 | { concurrency: "unbounded" }, |
| 556 | ); | 525 | ); |
| 557 | expect(leaderResult).toBe("rotated-1"); | 526 | expect(leaderResult).toBe("rotated-1"); |
| 558 | for (const result of followerResults) { | 527 | for (const result of followerResults) { |
| 559 | expect(Exit.isSuccess(result)).toBe(true); | 528 | expect(Exit.isSuccess(result)).toBe(true); |
| 560 | if (!Exit.isSuccess(result)) continue; | 529 | if (!Exit.isSuccess(result)) continue; |
| 561 | expect(result.value).toBe("rotated-1"); | 530 | expect(result.value).toBe("rotated-1"); |
| 562 | } | 531 | } |
| 563 | expect(calls).toHaveLength(1); | 532 | expect(calls).toHaveLength(1); |
| 564 | }), | 533 | }), |
| 565 | ); | 534 | ); |
| 566 | 535 | ||
| 567 | it.effect( | 536 | it.effect( |
| @@ -609,12 +578,8 @@ describe("connections", () => { · 41 unmodified lines | |||
| 609 | }), | 578 | }), |
| 610 | ); | 579 | ); |
| 611 | 580 | ||
| 612 | const flipped = yield* executor.connections | 581 | const flipped = yield* executor.connections.accessToken("conn-1").pipe(Effect.flip); |
| 613 | .accessToken("conn-1") | 582 | expect(Predicate.isTagged(flipped, "ConnectionReauthRequiredError")).toBe(true); |
| 614 | .pipe(Effect.flip); | ||
| 615 | expect( | ||
| 616 | Predicate.isTagged(flipped, "ConnectionReauthRequiredError"), | ||
| 617 | ).toBe(true); | ||
| 618 | if (!Predicate.isTagged(flipped, "ConnectionReauthRequiredError")) { | 583 | if (!Predicate.isTagged(flipped, "ConnectionReauthRequiredError")) { |
| 619 | return; | 584 | return; |
| 620 | } | 585 | } |
| @@ -623,57 +588,53 @@ describe("connections", () => { · 2 unmodified lines | |||
| 623 | }), | 588 | }), |
| 624 | ); | 589 | ); |
| 625 | 590 | ||
| 626 | it.effect( | 591 | it.effect("accessToken preserves ConnectionRefreshError for non-reauth failures", () => |
| 627 | "accessToken preserves ConnectionRefreshError for non-reauth failures", | 592 | Effect.gen(function* () { |
| 628 | () => | 593 | // Transient failure path — the provider failed but not with a |
| 629 | Effect.gen(function* () { | 594 | // terminal RFC 6749 code. The SDK must keep it as-is so |
| 630 | // Transient failure path — the provider failed but not with a | 595 | // callers can tell "retry later" from "prompt for sign-in". |
| 631 | // terminal RFC 6749 code. The SDK must keep it as-is so | 596 | const provider: ConnectionProvider = { |
| 632 | // callers can tell "retry later" from "prompt for sign-in". | 597 | key: "spotify", |
| 633 | const provider: ConnectionProvider = { | 598 | refresh: (input) => |
| 634 | key: "spotify", | 599 | Effect.fail( |
| 635 | refresh: (input) => | 600 | new ConnectionRefreshError({ |
| 636 | Effect.fail( | 601 | connectionId: input.connectionId, |
| 637 | new ConnectionRefreshError({ | 602 | message: "network flake", |
| 638 | connectionId: input.connectionId, | 603 | }), |
| 639 | message: "network flake", | 604 | ), |
| 640 | }), | 605 | }; |
| 641 | ), | ||
| 642 | }; | ||
| 643 | 606 | ||
| 644 | const executor = yield* createExecutor( | 607 | const executor = yield* createExecutor( |
| 645 | makeTestConfig({ | 608 | makeTestConfig({ |
| 646 | plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, | 609 | plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, |
| 647 | }), | 610 | }), |
| 648 | ); | 611 | ); |
| 649 | 612 | ||
| 650 | yield* executor.connections.create( | 613 | yield* executor.connections.create( |
| 651 | new CreateConnectionInput({ | 614 | new CreateConnectionInput({ |
| 652 | id: cid("conn-1"), | 615 | id: cid("conn-1"), |
| 653 | scope: scpid("test-scope"), | 616 | scope: scpid("test-scope"), |
| 654 | provider: "spotify", | 617 | provider: "spotify", |
| 655 | identityLabel: null, | 618 | identityLabel: null, |
| 656 | accessToken: new TokenMaterial({ | 619 | accessToken: new TokenMaterial({ |
| 657 | secretId: sid("conn-1.access"), | 620 | secretId: sid("conn-1.access"), |
| 658 | name: "access", | 621 | name: "access", |
| 659 | value: "stale", | 622 | value: "stale", |
| 660 | }), | ||
| 661 | refreshToken: new TokenMaterial({ | ||
| 662 | secretId: sid("conn-1.refresh"), | ||
| 663 | name: "refresh", | ||
| 664 | value: "refresh-v1", | ||
| 665 | }), | ||
| 666 | expiresAt: Date.now() - 1_000, | ||
| 667 | oauthScope: null, | ||
| 668 | providerState: null, | ||
| 669 | }), | 623 | }), |
| 670 | ); | 624 | refreshToken: new TokenMaterial({ |
| 625 | secretId: sid("conn-1.refresh"), | ||
| 626 | name: "refresh", | ||
| 627 | value: "refresh-v1", | ||
| 628 | }), | ||
| 629 | expiresAt: Date.now() - 1_000, | ||
| 630 | oauthScope: null, | ||
| 631 | providerState: null, | ||
| 632 | }), | ||
| 633 | ); | ||
| 671 | 634 | ||
| 672 | const err = yield* executor.connections | 635 | const err = yield* executor.connections.accessToken("conn-1").pipe(Effect.flip); |
| 673 | .accessToken("conn-1") | 636 | expect(Predicate.isTagged(err, "ConnectionRefreshError")).toBe(true); |
| 674 | .pipe(Effect.flip); | 637 | }), |
| 675 | expect(Predicate.isTagged(err, "ConnectionRefreshError")).toBe(true); | ||
| 676 | }), | ||
| 677 | ); | 638 | ); |
| 678 | 639 | ||
| 679 | it.effect( | 640 | it.effect( |
| @@ -705,116 +666,97 @@ describe("connections", () => { · 25 unmodified lines | |||
| 705 | }), | 666 | }), |
| 706 | ); | 667 | ); |
| 707 | 668 | ||
| 708 | const err = yield* executor.connections | 669 | const err = yield* executor.connections.accessToken("conn-1").pipe(Effect.flip); |
| 709 | .accessToken("conn-1") | 670 | expect(Predicate.isTagged(err, "ConnectionRefreshNotSupportedError")).toBe(true); |
| 710 | .pipe(Effect.flip); | ||
| 711 | expect( | ||
| 712 | Predicate.isTagged(err, "ConnectionRefreshNotSupportedError"), | ||
| 713 | ).toBe(true); | ||
| 714 | }), | 671 | }), |
| 715 | ); | 672 | ); |
| 716 | 673 | ||
| 717 | it.effect( | 674 | it.effect("accessToken fails with ConnectionNotFoundError for an unknown id", () => |
| 718 | "accessToken fails with ConnectionNotFoundError for an unknown id", | 675 | Effect.gen(function* () { |
| 719 | () => | 676 | const executor = yield* createExecutor( |
| 720 | Effect.gen(function* () { | 677 | makeTestConfig({ |
| 721 | const executor = yield* createExecutor( | 678 | plugins: [memorySecretsPlugin()] as const, |
| 722 | makeTestConfig({ | 679 | }), |
| 723 | plugins: [memorySecretsPlugin()] as const, | 680 | ); |
| 724 | }), | ||
| 725 | ); | ||
| 726 | 681 | ||
| 727 | const err = yield* executor.connections | 682 | const err = yield* executor.connections.accessToken("does-not-exist").pipe(Effect.flip); |
| 728 | .accessToken("does-not-exist") | 683 | expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true); |
| 729 | .pipe(Effect.flip); | 684 | }), |
| 730 | expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true); | ||
| 731 | }), | ||
| 732 | ); | 685 | ); |
| 733 | 686 | ||
| 734 | it.effect( | 687 | it.effect("updateTokens writes new values but does not rotate secret ids", () => |
| 735 | "updateTokens writes new values but does not rotate secret ids", | 688 | Effect.gen(function* () { |
| 736 | () => | 689 | const secretProvider = makeMemoryProvider(); |
| 737 | Effect.gen(function* () { | 690 | const { provider } = makeConnectionProvider({ key: "spotify" }); |
| 738 | const secretProvider = makeMemoryProvider(); | 691 | const executor = yield* createExecutor( |
| 739 | const { provider } = makeConnectionProvider({ key: "spotify" }); | 692 | makeTestConfig({ |
| 740 | const executor = yield* createExecutor( | 693 | plugins: [memorySecretsPlugin(secretProvider), connPlugin(provider)] as const, |
| 741 | makeTestConfig({ | 694 | }), |
| 742 | plugins: [ | 695 | ); |
| 743 | memorySecretsPlugin(secretProvider), | ||
| 744 | connPlugin(provider), | ||
| 745 | ] as const, | ||
| 746 | }), | ||
| 747 | ); | ||
| 748 | 696 | ||
| 749 | yield* executor.connections.create( | 697 | yield* executor.connections.create( |
| 750 | new CreateConnectionInput({ | 698 | new CreateConnectionInput({ |
| 751 | id: cid("conn-1"), | 699 | id: cid("conn-1"), |
| 752 | scope: scpid("test-scope"), | 700 | scope: scpid("test-scope"), |
| 753 | provider: "spotify", | 701 | provider: "spotify", |
| 754 | identityLabel: null, | 702 | identityLabel: null, |
| 755 | accessToken: new TokenMaterial({ | 703 | accessToken: new TokenMaterial({ |
| 756 | secretId: sid("conn-1.access"), | 704 | secretId: sid("conn-1.access"), |
| 757 | name: "access", | 705 | name: "access", |
| 758 | value: "v1", | 706 | value: "v1", |
| 759 | }), | ||
| 760 | refreshToken: new TokenMaterial({ | ||
| 761 | secretId: sid("conn-1.refresh"), | ||
| 762 | name: "refresh", | ||
| 763 | value: "r1", | ||
| 764 | }), | ||
| 765 | expiresAt: null, | ||
| 766 | oauthScope: null, | ||
| 767 | providerState: null, | ||
| 768 | }), | 707 | }), |
| 769 | ); | 708 | refreshToken: new TokenMaterial({ |
| 770 | 709 | secretId: sid("conn-1.refresh"), | |
| 771 | const updated = yield* executor.connections.updateTokens( | 710 | name: "refresh", |
| 772 | new UpdateConnectionTokensInput({ | 711 | value: "r1", |
| 773 | id: cid("conn-1"), | ||
| 774 | accessToken: "v2", | ||
| 775 | refreshToken: "r2", | ||
| 776 | expiresAt: 1_700_000_000_000, | ||
| 777 | oauthScope: "new-scope", | ||
| 778 | providerState: { rotation: "next" }, | ||
| 779 | }), | 712 | }), |
| 780 | ); | 713 | expiresAt: null, |
| 714 | oauthScope: null, | ||
| 715 | providerState: null, | ||
| 716 | }), | ||
| 717 | ); | ||
| 781 | 718 | ||
| 782 | expect(updated.accessTokenSecretId).toBe(sid("conn-1.access")); | 719 | const updated = yield* executor.connections.updateTokens( |
| 783 | expect(updated.refreshTokenSecretId).toBe(sid("conn-1.refresh")); | 720 | new UpdateConnectionTokensInput({ |
| 784 | expect(updated.expiresAt).toBe(1_700_000_000_000); | 721 | id: cid("conn-1"), |
| 785 | expect(updated.oauthScope).toBe("new-scope"); | 722 | accessToken: "v2", |
| 786 | expect(updated.providerState).toEqual({ rotation: "next" }); | 723 | refreshToken: "r2", |
| 787 | 724 | expiresAt: 1_700_000_000_000, | |
| 788 | expect( | 725 | oauthScope: "new-scope", |
| 789 | yield* secretProvider.get!("conn-1.access", "test-scope"), | 726 | providerState: { rotation: "next" }, |
| 790 | ).toBe("v2"); | 727 | }), |
| 791 | expect( | 728 | ); |
| 792 | yield* secretProvider.get!("conn-1.refresh", "test-scope"), | 729 | |
| 793 | ).toBe("r2"); | 730 | expect(updated.accessTokenSecretId).toBe(sid("conn-1.access")); |
| 794 | }), | 731 | expect(updated.refreshTokenSecretId).toBe(sid("conn-1.refresh")); |
| 732 | expect(updated.expiresAt).toBe(1_700_000_000_000); | ||
| 733 | expect(updated.oauthScope).toBe("new-scope"); | ||
| 734 | expect(updated.providerState).toEqual({ rotation: "next" }); | ||
| 735 | |||
| 736 | expect(yield* secretProvider.get!("conn-1.access", "test-scope")).toBe("v2"); | ||
| 737 | expect(yield* secretProvider.get!("conn-1.refresh", "test-scope")).toBe("r2"); | ||
| 738 | }), | ||
| 795 | ); | 739 | ); |
| 796 | 740 | ||
| 797 | it.effect( | 741 | it.effect("updateTokens fails with ConnectionNotFoundError for unknown id", () => |
| 798 | "updateTokens fails with ConnectionNotFoundError for unknown id", | 742 | Effect.gen(function* () { |
| 799 | () => | 743 | const { provider } = makeConnectionProvider({ key: "spotify" }); |
| 800 | Effect.gen(function* () { | 744 | const executor = yield* createExecutor( |
| 801 | const { provider } = makeConnectionProvider({ key: "spotify" }); | 745 | makeTestConfig({ |
| 802 | const executor = yield* createExecutor( | 746 | plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, |
| 803 | makeTestConfig({ | 747 | }), |
| 804 | plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, | 748 | ); |
| 805 | }), | ||
| 806 | ); | ||
| 807 | 749 | ||
| 808 | const err = yield* executor.connections | 750 | const err = yield* executor.connections |
| 809 | .updateTokens( | 751 | .updateTokens( |
| 810 | new UpdateConnectionTokensInput({ | 752 | new UpdateConnectionTokensInput({ |
| 811 | id: cid("nope"), | 753 | id: cid("nope"), |
| 812 | accessToken: "x", | 754 | accessToken: "x", |
| 813 | }), | 755 | }), |
| 814 | ) | 756 | ) |
| 815 | .pipe(Effect.flip); | 757 | .pipe(Effect.flip); |
| 816 | expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true); | 758 | expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true); |
| 817 | }), | 759 | }), |
| 818 | ); | 760 | ); |
| 819 | 761 | ||
| 820 | it.effect("setIdentityLabel updates the label", () => | 762 | it.effect("setIdentityLabel updates the label", () => |
| @@ -850,21 +792,19 @@ describe("connections", () => { · 29 unmodified lines | |||
| 850 | }), | 792 | }), |
| 851 | ); | 793 | ); |
| 852 | 794 | ||
| 853 | it.effect( | 795 | it.effect("setIdentityLabel fails with ConnectionNotFoundError for unknown id", () => |
| 854 | "setIdentityLabel fails with ConnectionNotFoundError for unknown id", | 796 | Effect.gen(function* () { |
| 855 | () => | 797 | const executor = yield* createExecutor( |
| 856 | Effect.gen(function* () { | 798 | makeTestConfig({ |
| 857 | const executor = yield* createExecutor( | 799 | plugins: [memorySecretsPlugin()] as const, |
| 858 | makeTestConfig({ | 800 | }), |
| 859 | plugins: [memorySecretsPlugin()] as const, | 801 | ); |
| 860 | }), | ||
| 861 | ); | ||
| 862 | 802 | ||
| 863 | const err = yield* executor.connections | 803 | const err = yield* executor.connections |
| 864 | .setIdentityLabel("does-not-exist", "x") | 804 | .setIdentityLabel("does-not-exist", "x") |
| 865 | .pipe(Effect.flip); | 805 | .pipe(Effect.flip); |
| 866 | expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true); | 806 | expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true); |
| 867 | }), | 807 | }), |
| 868 | ); | 808 | ); |
| 869 | 809 | ||
| 870 | it.effect("providers() returns every registered connection provider key", () => | 810 | it.effect("providers() returns every registered connection provider key", () => |
| @@ -936,8 +876,7 @@ const makeLayeredConnExecutors = () => · 65 unmodified lines | |||
| 936 | describe("connections — multi-scope behaviour", () => { | 876 | describe("connections — multi-scope behaviour", () => { |
| 937 | it.effect("get picks the innermost-scope row when the same id exists at two scopes", () => | 877 | it.effect("get picks the innermost-scope row when the same id exists at two scopes", () => |
| 938 | Effect.gen(function* () { | 878 | Effect.gen(function* () { |
| 939 | const { execOuter, execInner, outerId, innerId } = | 879 | const { execOuter, execInner, outerId, innerId } = yield* makeLayeredConnExecutors(); |
| 940 | yield* makeLayeredConnExecutors(); | ||
| 941 | 880 | ||
| 942 | yield* execOuter.connections.create( | 881 | yield* execOuter.connections.create( |
| 943 | new CreateConnectionInput({ | 882 | new CreateConnectionInput({ |
| @@ -991,52 +930,49 @@ describe("connections — multi-scope behaviour", () => { · 47 unmodified lines | |||
| 991 | }), | 930 | }), |
| 992 | ); | 931 | ); |
| 993 | 932 | ||
| 994 | it.effect( | 933 | it.effect("remove at the inner scope does not wipe the outer-scope connection", () => |
| 995 | "remove at the inner scope does not wipe the outer-scope connection", | 934 | Effect.gen(function* () { |
| 996 | () => | 935 | const { execOuter, execInner, outerId, innerId } = yield* makeLayeredConnExecutors(); |
| 997 | Effect.gen(function* () { | ||
| 998 | const { execOuter, execInner, outerId, innerId } = | ||
| 999 | yield* makeLayeredConnExecutors(); | ||
| 1000 | 936 | ||
| 1001 | yield* execOuter.connections.create( | 937 | yield* execOuter.connections.create( |
| 1002 | new CreateConnectionInput({ | 938 | new CreateConnectionInput({ |
| 1003 | id: cid("shared"), | 939 | id: cid("shared"), |
| 1004 | scope: outerId, | 940 | scope: outerId, |
| 1005 | provider: "spotify", | 941 | provider: "spotify", |
| 1006 | identityLabel: "outer", | 942 | identityLabel: "outer", |
| 1007 | accessToken: new TokenMaterial({ | 943 | accessToken: new TokenMaterial({ |
| 1008 | secretId: sid("shared.access.outer"), | 944 | secretId: sid("shared.access.outer"), |
| 1009 | name: "access", | 945 | name: "access", |
| 1010 | value: "outer-access", | 946 | value: "outer-access", |
| 1011 | }), | ||
| 1012 | refreshToken: null, | ||
| 1013 | expiresAt: null, | ||
| 1014 | oauthScope: null, | ||
| 1015 | providerState: null, | ||
| 1016 | }), | 947 | }), |
| 1017 | ); | 948 | refreshToken: null, |
| 1018 | yield* execInner.connections.create( | 949 | expiresAt: null, |
| 1019 | new CreateConnectionInput({ | 950 | oauthScope: null, |
| 1020 | id: cid("shared"), | 951 | providerState: null, |
| 1021 | scope: innerId, | 952 | }), |
| 1022 | provider: "spotify", | 953 | ); |
| 1023 | identityLabel: "inner", | 954 | yield* execInner.connections.create( |
| 1024 | accessToken: new TokenMaterial({ | 955 | new CreateConnectionInput({ |
| 1025 | secretId: sid("shared.access.inner"), | 956 | id: cid("shared"), |
| 1026 | name: "access", | 957 | scope: innerId, |
| 1027 | value: "inner-access", | 958 | provider: "spotify", |
| 1028 | }), | 959 | identityLabel: "inner", |
| 1029 | refreshToken: null, | 960 | accessToken: new TokenMaterial({ |
| 1030 | expiresAt: null, | 961 | secretId: sid("shared.access.inner"), |
| 1031 | oauthScope: null, | 962 | name: "access", |
| 1032 | providerState: null, | 963 | value: "inner-access", |
| 1033 | }), | 964 | }), |
| 1034 | ); | 965 | refreshToken: null, |
| 966 | expiresAt: null, | ||
| 967 | oauthScope: null, | ||
| 968 | providerState: null, | ||
| 969 | }), | ||
| 970 | ); | ||
| 1035 | 971 | ||
| 1036 | yield* execInner.connections.remove("shared"); | 972 | yield* execInner.connections.remove("shared"); |
| 1037 | 973 | ||
| 1038 | const outerStill = yield* execOuter.connections.get("shared"); | 974 | const outerStill = yield* execOuter.connections.get("shared"); |
| 1039 | expect(outerStill?.identityLabel).toBe("outer"); | 975 | expect(outerStill?.identityLabel).toBe("outer"); |
| 1040 | }), | 976 | }), |
| 1041 | ); | 977 | ); |
| 1042 | }); | 978 | }); |
| @@ -87,9 +87,7 @@ export class CreateConnectionInput extends Schema.Class<CreateConnectionInput>( · 86 unmodified lines | |||
| 87 | // the SDK rethrows it from `ctx.connections.accessToken` callers. | 87 | // the SDK rethrows it from `ctx.connections.accessToken` callers. |
| 88 | // --------------------------------------------------------------------------- | 88 | // --------------------------------------------------------------------------- |
| 89 | 89 | ||
| 90 | export class ConnectionRefreshError extends Data.TaggedError( | 90 | export class ConnectionRefreshError extends Data.TaggedError("ConnectionRefreshError")<{ |
| 91 | "ConnectionRefreshError", | ||
| 92 | )<{ | ||
| 93 | readonly connectionId: ConnectionId; | 91 | readonly connectionId: ConnectionId; |
| 94 | readonly message: string; | 92 | readonly message: string; |
| 95 | /** | 93 | /** |
| @@ -10,10 +10,7 @@ · 9 unmodified lines | |||
| 10 | // dynamic (runtime-registered) rows. | 10 | // dynamic (runtime-registered) rows. |
| 11 | // --------------------------------------------------------------------------- | 11 | // --------------------------------------------------------------------------- |
| 12 | 12 | ||
| 13 | import type { | 13 | import type { DBSchema, InferDBFieldsOutput } from "@executor-js/storage-core"; |
| 14 | DBSchema, | ||
| 15 | InferDBFieldsOutput, | ||
| 16 | } from "@executor-js/storage-core"; | ||
| 17 | 14 | ||
| 18 | export const coreSchema = { | 15 | export const coreSchema = { |
| 19 | source: { | 16 | source: { |
| @@ -209,25 +206,18 @@ export type CoreSchema = typeof coreSchema; · 189 unmodified lines | |||
| 209 | export type SourceRow = InferDBFieldsOutput<CoreSchema["source"]["fields"]> & | 206 | export type SourceRow = InferDBFieldsOutput<CoreSchema["source"]["fields"]> & |
| 210 | Record<string, unknown>; | 207 | Record<string, unknown>; |
| 211 | 208 | ||
| 212 | export type ToolRow = InferDBFieldsOutput<CoreSchema["tool"]["fields"]> & | 209 | export type ToolRow = InferDBFieldsOutput<CoreSchema["tool"]["fields"]> & Record<string, unknown>; |
| 213 | Record<string, unknown>; | ||
| 214 | 210 | ||
| 215 | export type DefinitionRow = InferDBFieldsOutput< | 211 | export type DefinitionRow = InferDBFieldsOutput<CoreSchema["definition"]["fields"]> & |
| 216 | CoreSchema["definition"]["fields"] | ||
| 217 | > & | ||
| 218 | Record<string, unknown>; | 212 | Record<string, unknown>; |
| 219 | 213 | ||
| 220 | export type SecretRow = InferDBFieldsOutput<CoreSchema["secret"]["fields"]> & | 214 | export type SecretRow = InferDBFieldsOutput<CoreSchema["secret"]["fields"]> & |
| 221 | Record<string, unknown>; | 215 | Record<string, unknown>; |
| 222 | 216 | ||
| 223 | export type ConnectionRow = InferDBFieldsOutput< | 217 | export type ConnectionRow = InferDBFieldsOutput<CoreSchema["connection"]["fields"]> & |
| 224 | CoreSchema["connection"]["fields"] | ||
| 225 | > & | ||
| 226 | Record<string, unknown>; | 218 | Record<string, unknown>; |
| 227 | 219 | ||
| 228 | export type ToolPolicyRow = InferDBFieldsOutput< | 220 | export type ToolPolicyRow = InferDBFieldsOutput<CoreSchema["tool_policy"]["fields"]> & |
| 229 | CoreSchema["tool_policy"]["fields"] | ||
| 230 | > & | ||
| 231 | Record<string, unknown>; | 221 | Record<string, unknown>; |
| 232 | 222 | ||
| 233 | // --------------------------------------------------------------------------- | 223 | // --------------------------------------------------------------------------- |
| @@ -251,8 +241,7 @@ export const TOOL_POLICY_ACTIONS = [ · 17 unmodified lines | |||
| 251 | ] as const satisfies readonly ToolPolicyAction[]; | 241 | ] as const satisfies readonly ToolPolicyAction[]; |
| 252 | 242 | ||
| 253 | export const isToolPolicyAction = (value: unknown): value is ToolPolicyAction => | 243 | export const isToolPolicyAction = (value: unknown): value is ToolPolicyAction => |
| 254 | typeof value === "string" && | 244 | typeof value === "string" && (TOOL_POLICY_ACTIONS as readonly string[]).includes(value); |
| 255 | (TOOL_POLICY_ACTIONS as readonly string[]).includes(value); | ||
| 256 | 245 | ||
| 257 | // --------------------------------------------------------------------------- | 246 | // --------------------------------------------------------------------------- |
| 258 | // Tool annotations — default-policy metadata the executor consults | 247 | // Tool annotations — default-policy metadata the executor consults |
| @@ -43,8 +43,7 @@ import { Scope } from "./scope"; · 42 unmodified lines | |||
| 43 | // --------------------------------------------------------------------------- | 43 | // --------------------------------------------------------------------------- |
| 44 | 44 | ||
| 45 | const makeFailingAdapter = (failure: StorageFailure): DBAdapter => { | 45 | const makeFailingAdapter = (failure: StorageFailure): DBAdapter => { |
| 46 | const fail = () => | 46 | const fail = () => Effect.fail(failure) as Effect.Effect<never, StorageFailure, never>; |
| 47 | Effect.fail(failure) as Effect.Effect<never, StorageFailure, never>; | ||
| 48 | return { | 47 | return { |
| 49 | id: "failing", | 48 | id: "failing", |
| 50 | create: fail as DBAdapter["create"], | 49 | create: fail as DBAdapter["create"], |
| @@ -91,9 +90,7 @@ describe("typed-error edge model — SDK", () => { · 40 unmodified lines | |||
| 91 | message: "backend lost its mind", | 90 | message: "backend lost its mind", |
| 92 | cause: driverCause, | 91 | cause: driverCause, |
| 93 | }); | 92 | }); |
| 94 | const executor = yield* createExecutor( | 93 | const executor = yield* createExecutor(baseConfig(makeFailingAdapter(failure))); |
| 95 | baseConfig(makeFailingAdapter(failure)), | ||
| 96 | ); | ||
| 97 | 94 | ||
| 98 | const result = yield* executor.tools.list().pipe(Effect.flip); | 95 | const result = yield* executor.tools.list().pipe(Effect.flip); |
| 99 | expect(result).toBeInstanceOf(StorageError); | 96 | expect(result).toBeInstanceOf(StorageError); |
| @@ -140,9 +137,7 @@ describe("typed-error edge model — SDK", () => { · 40 unmodified lines | |||
| 140 | model: err.model ?? null, | 137 | model: err.model ?? null, |
| 141 | }), | 138 | }), |
| 142 | ), | 139 | ), |
| 143 | Effect.map((r) => | 140 | Effect.map((r) => ("caught" in r ? r : { caught: false as const, model: null })), |
| 144 | "caught" in r ? r : { caught: false as const, model: null }, | ||
| 145 | ), | ||
| 146 | ), | 141 | ), |
| 147 | }), | 142 | }), |
| 148 | extension: (ctx) => ({ | 143 | extension: (ctx) => ({ |
| @@ -172,9 +167,7 @@ describe("typed-error edge model — SDK", () => { · 23 unmodified lines | |||
| 172 | // type-check (R would leak) nor run (would crash at construction). | 167 | // type-check (R would leak) nor run (would crash at construction). |
| 173 | const executor = yield* createExecutor( | 168 | const executor = yield* createExecutor( |
| 174 | baseConfig( | 169 | baseConfig( |
| 175 | makeFailingAdapter( | 170 | makeFailingAdapter(new StorageError({ message: "doesn't matter", cause: undefined })), |
| 176 | new StorageError({ message: "doesn't matter", cause: undefined }), | ||
| 177 | ), | ||
| 178 | ), | 171 | ), |
| 179 | ); | 172 | ); |
| 180 | // Sanity: the executor surface exists and methods are callable. | 173 | // Sanity: the executor surface exists and methods are callable. |
| @@ -32,13 +32,10 @@ export class PluginNotLoadedError extends Schema.TaggedErrorClass<PluginNotLoade · 31 unmodified lines | |||
| 32 | * the plugin only declares static tools and this one's id matched | 32 | * the plugin only declares static tools and this one's id matched |
| 33 | * dynamically somehow. Shouldn't happen in practice; guards against | 33 | * dynamically somehow. Shouldn't happen in practice; guards against |
| 34 | * programmer error. */ | 34 | * programmer error. */ |
| 35 | export class NoHandlerError extends Schema.TaggedErrorClass<NoHandlerError>()( | 35 | export class NoHandlerError extends Schema.TaggedErrorClass<NoHandlerError>()("NoHandlerError", { |
| 36 | "NoHandlerError", | 36 | toolId: ToolId, |
| 37 | { | 37 | pluginId: Schema.String, |
| 38 | toolId: ToolId, | 38 | }) {} |
| 39 | pluginId: Schema.String, | ||
| 40 | }, | ||
| 41 | ) {} | ||
| 42 | 39 | ||
| 43 | /** Tool invocation was rejected because a workspace `tool_policy` rule | 40 | /** Tool invocation was rejected because a workspace `tool_policy` rule |
| 44 | * with `action: "block"` matched. `pattern` is the matched policy | 41 | * with `action: "block"` matched. `pattern` is the matched policy |
| @@ -7,11 +7,7 @@ import type { DBAdapter, Where } from "@executor-js/storage-core"; · 6 unmodified lines | |||
| 7 | import { makeInMemoryBlobStore } from "./blob"; | 7 | import { makeInMemoryBlobStore } from "./blob"; |
| 8 | import { CreateConnectionInput, TokenMaterial } from "./connections"; | 8 | import { CreateConnectionInput, TokenMaterial } from "./connections"; |
| 9 | import { collectSchemas, createExecutor } from "./executor"; | 9 | import { collectSchemas, createExecutor } from "./executor"; |
| 10 | import { | 10 | import { ElicitationResponse, FormElicitation, UrlElicitation } from "./elicitation"; |
| 11 | ElicitationResponse, | ||
| 12 | FormElicitation, | ||
| 13 | UrlElicitation, | ||
| 14 | } from "./elicitation"; | ||
| 15 | import { defineSchema, definePlugin } from "./plugin"; | 11 | import { defineSchema, definePlugin } from "./plugin"; |
| 16 | import { SetSecretInput } from "./secrets"; | 12 | import { SetSecretInput } from "./secrets"; |
| 17 | import { makeTestConfig } from "./testing"; | 13 | import { makeTestConfig } from "./testing"; |
| @@ -29,10 +25,7 @@ class TestPluginError extends Data.TaggedError("TestPluginError")<{ · 11 unmodified lines | |||
| 29 | readonly message: string; | 25 | readonly message: string; |
| 30 | }> {} | 26 | }> {} |
| 31 | 27 | ||
| 32 | const recordFindMany = ( | 28 | const recordFindMany = (adapter: DBAdapter, calls: FindManyCall[]): DBAdapter => ({ |
| 33 | adapter: DBAdapter, | ||
| 34 | calls: FindManyCall[], | ||
| 35 | ): DBAdapter => ({ | ||
| 36 | ...adapter, | 29 | ...adapter, |
| 37 | findMany: (data) => { | 30 | findMany: (data) => { |
| 38 | calls.push({ model: data.model, where: data.where }); | 31 | calls.push({ model: data.model, where: data.where }); |
| @@ -244,9 +237,7 @@ const opaqueRouteSecretsPlugin = definePlugin(() => ({ · 205 unmodified lines | |||
| 244 | describe("createExecutor", () => { | 237 | describe("createExecutor", () => { |
| 245 | it.effect("invokes a static tool via the in-memory pool", () => | 238 | it.effect("invokes a static tool via the in-memory pool", () => |
| 246 | Effect.gen(function* () { | 239 | Effect.gen(function* () { |
| 247 | const executor = yield* createExecutor( | 240 | const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); |
| 248 | makeTestConfig({ plugins: [testPlugin()] as const }), | ||
| 249 | ); | ||
| 250 | const result = yield* executor.tools.invoke( | 241 | const result = yield* executor.tools.invoke( |
| 251 | "test.control.echo", | 242 | "test.control.echo", |
| 252 | { text: "hi" }, | 243 | { text: "hi" }, |
| @@ -258,9 +249,7 @@ describe("createExecutor", () => { · 5 unmodified lines | |||
| 258 | 249 | ||
| 259 | it.effect("lists static tools alongside dynamic ones", () => | 250 | it.effect("lists static tools alongside dynamic ones", () => |
| 260 | Effect.gen(function* () { | 251 | Effect.gen(function* () { |
| 261 | const executor = yield* createExecutor( | 252 | const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); |
| 262 | makeTestConfig({ plugins: [testPlugin()] as const }), | ||
| 263 | ); | ||
| 264 | yield* executor.test.addThing("thing1", "hello"); | 253 | yield* executor.test.addThing("thing1", "hello"); |
| 265 | 254 | ||
| 266 | const tools = yield* executor.tools.list(); | 255 | const tools = yield* executor.tools.list(); |
| @@ -273,9 +262,7 @@ describe("createExecutor", () => { · 6 unmodified lines | |||
| 273 | 262 | ||
| 274 | it.effect("filters tools by query", () => | 263 | it.effect("filters tools by query", () => |
| 275 | Effect.gen(function* () { | 264 | Effect.gen(function* () { |
| 276 | const executor = yield* createExecutor( | 265 | const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); |
| 277 | makeTestConfig({ plugins: [testPlugin()] as const }), | ||
| 278 | ); | ||
| 279 | yield* executor.test.addThing("thing1", "hello"); | 266 | yield* executor.test.addThing("thing1", "hello"); |
| 280 | 267 | ||
| 281 | const tools = yield* executor.tools.list({ query: "echo" }); | 268 | const tools = yield* executor.tools.list({ query: "echo" }); |
| @@ -298,24 +285,17 @@ describe("createExecutor", () => { · 16 unmodified lines | |||
| 298 | findManyCalls.length = 0; | 285 | findManyCalls.length = 0; |
| 299 | const tools = yield* executor.tools.list({ sourceId: "thing1" }); | 286 | const tools = yield* executor.tools.list({ sourceId: "thing1" }); |
| 300 | 287 | ||
| 301 | expect(tools.map((t) => t.id).sort()).toEqual([ | 288 | expect(tools.map((t) => t.id).sort()).toEqual(["thing1.read", "thing1.write"]); |
| 302 | "thing1.read", | ||
| 303 | "thing1.write", | ||
| 304 | ]); | ||
| 305 | const toolRead = findManyCalls.find((call) => call.model === "tool"); | 289 | const toolRead = findManyCalls.find((call) => call.model === "tool"); |
| 306 | expect(toolRead?.where).toEqual( | 290 | expect(toolRead?.where).toEqual( |
| 307 | expect.arrayContaining([ | 291 | expect.arrayContaining([expect.objectContaining({ field: "source_id", value: "thing1" })]), |
| 308 | expect.objectContaining({ field: "source_id", value: "thing1" }), | ||
| 309 | ]), | ||
| 310 | ); | 292 | ); |
| 311 | }), | 293 | }), |
| 312 | ); | 294 | ); |
| 313 | 295 | ||
| 314 | it.effect("can list tools without resolving dynamic annotations", () => | 296 | it.effect("can list tools without resolving dynamic annotations", () => |
| 315 | Effect.gen(function* () { | 297 | Effect.gen(function* () { |
| 316 | const executor = yield* createExecutor( | 298 | const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); |
| 317 | makeTestConfig({ plugins: [testPlugin()] as const }), | ||
| 318 | ); | ||
| 319 | yield* executor.test.addThing("thing1", "hello"); | 299 | yield* executor.test.addThing("thing1", "hello"); |
| 320 | testAnnotationResolveCount = 0; | 300 | testAnnotationResolveCount = 0; |
| 321 | 301 | ||
| @@ -325,19 +305,14 @@ describe("createExecutor", () => { · 3 unmodified lines | |||
| 325 | }); | 305 | }); |
| 326 | 306 | ||
| 327 | expect(testAnnotationResolveCount).toBe(0); | 307 | expect(testAnnotationResolveCount).toBe(0); |
| 328 | expect(tools.map((t) => t.id).sort()).toEqual([ | 308 | expect(tools.map((t) => t.id).sort()).toEqual(["thing1.read", "thing1.write"]); |
| 329 | "thing1.read", | ||
| 330 | "thing1.write", | ||
| 331 | ]); | ||
| 332 | expect(tools.every((tool) => tool.annotations === undefined)).toBe(true); | 309 | expect(tools.every((tool) => tool.annotations === undefined)).toBe(true); |
| 333 | }), | 310 | }), |
| 334 | ); | 311 | ); |
| 335 | 312 | ||
| 336 | it.effect("invokes a dynamic tool through plugin.invokeTool", () => | 313 | it.effect("invokes a dynamic tool through plugin.invokeTool", () => |
| 337 | Effect.gen(function* () { | 314 | Effect.gen(function* () { |
| 338 | const executor = yield* createExecutor( | 315 | const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); |
| 339 | makeTestConfig({ plugins: [testPlugin()] as const }), | ||
| 340 | ); | ||
| 341 | yield* executor.test.addThing("thing1", "hello"); | 316 | yield* executor.test.addThing("thing1", "hello"); |
| 342 | 317 | ||
| 343 | const result = yield* executor.tools.invoke( | 318 | const result = yield* executor.tools.invoke( |
| @@ -351,9 +326,7 @@ describe("createExecutor", () => { · 7 unmodified lines | |||
| 351 | 326 | ||
| 352 | it.effect("enforces tool annotations before invoking", () => | 327 | it.effect("enforces tool annotations before invoking", () => |
| 353 | Effect.gen(function* () { | 328 | Effect.gen(function* () { |
| 354 | const executor = yield* createExecutor( | 329 | const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); |
| 355 | makeTestConfig({ plugins: [testPlugin()] as const }), | ||
| 356 | ); | ||
| 357 | yield* executor.test.addThing("thing1", "hello"); | 330 | yield* executor.test.addThing("thing1", "hello"); |
| 358 | 331 | ||
| 359 | // requiresApproval: true → declined → ElicitationDeclinedError | 332 | // requiresApproval: true → declined → ElicitationDeclinedError |
| @@ -362,8 +335,7 @@ describe("createExecutor", () => { · 2 unmodified lines | |||
| 362 | "thing1.write", | 335 | "thing1.write", |
| 363 | { value: "updated" }, | 336 | { value: "updated" }, |
| 364 | { | 337 | { |
| 365 | onElicitation: () => | 338 | onElicitation: () => Effect.succeed(new ElicitationResponse({ action: "decline" })), |
| 366 | Effect.succeed(new ElicitationResponse({ action: "decline" })), | ||
| 367 | }, | 339 | }, |
| 368 | ) | 340 | ) |
| 369 | .pipe(Effect.flip); | 341 | .pipe(Effect.flip); |
| @@ -381,9 +353,7 @@ describe("createExecutor", () => { · 11 unmodified lines | |||
| 381 | 353 | ||
| 382 | it.effect("sources.list unions static runtime sources and dynamic ones", () => | 354 | it.effect("sources.list unions static runtime sources and dynamic ones", () => |
| 383 | Effect.gen(function* () { | 355 | Effect.gen(function* () { |
| 384 | const executor = yield* createExecutor( | 356 | const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); |
| 385 | makeTestConfig({ plugins: [testPlugin()] as const }), | ||
| 386 | ); | ||
| 387 | yield* executor.test.addThing("thing1", "hello"); | 357 | yield* executor.test.addThing("thing1", "hello"); |
| 388 | 358 | ||
| 389 | const sources = yield* executor.sources.list(); | 359 | const sources = yield* executor.sources.list(); |
| @@ -444,12 +414,8 @@ describe("createExecutor", () => { · 54 unmodified lines | |||
| 444 | 414 | ||
| 445 | it.effect("rejects remove of a static source", () => | 415 | it.effect("rejects remove of a static source", () => |
| 446 | Effect.gen(function* () { | 416 | Effect.gen(function* () { |
| 447 | const executor = yield* createExecutor( | 417 | const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); |
| 448 | makeTestConfig({ plugins: [testPlugin()] as const }), | 418 | const err = yield* executor.sources.remove("test.control").pipe(Effect.flip); |
| 449 | ); | ||
| 450 | const err = yield* executor.sources | ||
| 451 | .remove("test.control") | ||
| 452 | .pipe(Effect.flip); | ||
| 453 | expect(Predicate.isTagged(err, "SourceRemovalNotAllowedError")).toBe(true); | 419 | expect(Predicate.isTagged(err, "SourceRemovalNotAllowedError")).toBe(true); |
| 454 | }), | 420 | }), |
| 455 | ); | 421 | ); |
| @@ -546,9 +512,7 @@ describe("createExecutor", () => { · 90 unmodified lines | |||
| 546 | 512 | ||
| 547 | it.effect("ctx.transaction commits all nested writes on success", () => | 513 | it.effect("ctx.transaction commits all nested writes on success", () => |
| 548 | Effect.gen(function* () { | 514 | Effect.gen(function* () { |
| 549 | const executor = yield* createExecutor( | 515 | const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); |
| 550 | makeTestConfig({ plugins: [testPlugin()] as const }), | ||
| 551 | ); | ||
| 552 | // addThing wraps storage + sources.register in ctx.transaction. | 516 | // addThing wraps storage + sources.register in ctx.transaction. |
| 553 | yield* executor.test.addThing("thing1", "hello"); | 517 | yield* executor.test.addThing("thing1", "hello"); |
| 554 | 518 | ||
| @@ -560,13 +524,9 @@ describe("createExecutor", () => { · 5 unmodified lines | |||
| 560 | expect(tools).toContain("thing1.write"); | 524 | expect(tools).toContain("thing1.write"); |
| 561 | 525 | ||
| 562 | // plugin storage row committed too | 526 | // plugin storage row committed too |
| 563 | expect( | 527 | expect(yield* executor.tools.invoke("thing1.read", {}, { onElicitation: "accept-all" })).toBe( |
| 564 | yield* executor.tools.invoke( | 528 | "hello", |
| 565 | "thing1.read", | 529 | ); |
| 566 | {}, | ||
| 567 | { onElicitation: "accept-all" }, | ||
| 568 | ), | ||
| 569 | ).toBe("hello"); | ||
| 570 | }), | 530 | }), |
| 571 | ); | 531 | ); |
| 572 | 532 | ||
| @@ -612,9 +572,7 @@ describe("createExecutor", () => { · 39 unmodified lines | |||
| 612 | makeTestConfig({ plugins: [rollbackPlugin()] as const }), | 572 | makeTestConfig({ plugins: [rollbackPlugin()] as const }), |
| 613 | ); | 573 | ); |
| 614 | 574 | ||
| 615 | const result = yield* executor.rollback | 575 | const result = yield* executor.rollback.doFailingTx().pipe(Effect.result); |
| 616 | .doFailingTx() | ||
| 617 | .pipe(Effect.result); | ||
| 618 | expect(Result.isFailure(result)).toBe(true); | 576 | expect(Result.isFailure(result)).toBe(true); |
| 619 | 577 | ||
| 620 | // Plugin storage row must not persist. | 578 | // Plugin storage row must not persist. |
| @@ -684,18 +642,12 @@ describe("createExecutor", () => { · 63 unmodified lines | |||
| 684 | }), | 642 | }), |
| 685 | ); | 643 | ); |
| 686 | 644 | ||
| 687 | const leaked = yield* executor.secrets | 645 | const leaked = yield* executor.secrets.get("conn-owned.access_token").pipe(Effect.flip); |
| 688 | .get("conn-owned.access_token") | 646 | expect(Predicate.isTagged(leaked, "SecretOwnedByConnectionError")).toBe(true); |
| 689 | .pipe(Effect.flip); | ||
| 690 | expect(Predicate.isTagged(leaked, "SecretOwnedByConnectionError")).toBe( | ||
| 691 | true, | ||
| 692 | ); | ||
| 693 | 647 | ||
| 694 | const status = yield* executor.secrets.status("conn-owned.access_token"); | 648 | const status = yield* executor.secrets.status("conn-owned.access_token"); |
| 695 | expect(status).toBe("missing"); | 649 | expect(status).toBe("missing"); |
| 696 | const visibleIds = (yield* executor.secrets.list()).map( | 650 | const visibleIds = (yield* executor.secrets.list()).map((s) => String(s.id)); |
| 697 | (s) => String(s.id), | ||
| 698 | ); | ||
| 699 | expect(visibleIds).not.toContain("conn-owned.access_token"); | 651 | expect(visibleIds).not.toContain("conn-owned.access_token"); |
| 700 | 652 | ||
| 701 | const token = yield* executor.connections.accessToken("conn-owned"); | 653 | const token = yield* executor.connections.accessToken("conn-owned"); |
| @@ -764,9 +716,7 @@ describe("createExecutor", () => { · 62 unmodified lines | |||
| 764 | close: () => Effect.sync(() => void closed++), | 716 | close: () => Effect.sync(() => void closed++), |
| 765 | })); | 717 | })); |
| 766 | 718 | ||
| 767 | const executor = yield* createExecutor( | 719 | const executor = yield* createExecutor(makeTestConfig({ plugins: [closeable()] as const })); |
| 768 | makeTestConfig({ plugins: [closeable()] as const }), | ||
| 769 | ); | ||
| 770 | 720 | ||
| 771 | yield* executor.close(); | 721 | yield* executor.close(); |
| 772 | expect(closed).toBe(1); | 722 | expect(closed).toBe(1); |
| @@ -812,18 +762,14 @@ describe("createExecutor", () => { · 39 unmodified lines | |||
| 812 | ], | 762 | ], |
| 813 | })); | 763 | })); |
| 814 | 764 | ||
| 815 | const executor = yield* createExecutor( | 765 | const executor = yield* createExecutor(makeTestConfig({ plugins: [loginPlugin()] as const })); |
| 816 | makeTestConfig({ plugins: [loginPlugin()] as const }), | ||
| 817 | ); | ||
| 818 | 766 | ||
| 819 | const result = yield* executor.tools.invoke( | 767 | const result = yield* executor.tools.invoke( |
| 820 | "login.ctl.signIn", | 768 | "login.ctl.signIn", |
| 821 | {}, | 769 | {}, |
| 822 | { | 770 | { |
| 823 | onElicitation: (ctx) => { | 771 | onElicitation: (ctx) => { |
| 824 | expect(Predicate.isTagged(ctx.request, "FormElicitation")).toBe( | 772 | expect(Predicate.isTagged(ctx.request, "FormElicitation")).toBe(true); |
| 825 | true, | ||
| 826 | ); | ||
| 827 | return Effect.succeed( | 773 | return Effect.succeed( |
| 828 | new ElicitationResponse({ | 774 | new ElicitationResponse({ |
| 829 | action: "accept", | 775 | action: "accept", |
| @@ -872,18 +818,14 @@ describe("createExecutor", () => { · 42 unmodified lines | |||
| 872 | ], | 818 | ], |
| 873 | })); | 819 | })); |
| 874 | 820 | ||
| 875 | const executor = yield* createExecutor( | 821 | const executor = yield* createExecutor(makeTestConfig({ plugins: [oauthPlugin()] as const })); |
| 876 | makeTestConfig({ plugins: [oauthPlugin()] as const }), | ||
| 877 | ); | ||
| 878 | 822 | ||
| 879 | const result = yield* executor.tools.invoke( | 823 | const result = yield* executor.tools.invoke( |
| 880 | "oauth.ctl.connect", | 824 | "oauth.ctl.connect", |
| 881 | {}, | 825 | {}, |
| 882 | { | 826 | { |
| 883 | onElicitation: (ctx) => { | 827 | onElicitation: (ctx) => { |
| 884 | expect(Predicate.isTagged(ctx.request, "UrlElicitation")).toBe( | 828 | expect(Predicate.isTagged(ctx.request, "UrlElicitation")).toBe(true); |
| 885 | true, | ||
| 886 | ); | ||
| 887 | return Effect.succeed( | 829 | return Effect.succeed( |
| 888 | new ElicitationResponse({ | 830 | new ElicitationResponse({ |
| 889 | action: "accept", | 831 | action: "accept", |
| @@ -932,9 +874,7 @@ describe("createExecutor", () => { · 42 unmodified lines | |||
| 932 | ], | 874 | ], |
| 933 | })); | 875 | })); |
| 934 | 876 | ||
| 935 | const executor = yield* createExecutor( | 877 | const executor = yield* createExecutor(makeTestConfig({ plugins: [elicitOnly()] as const })); |
| 936 | makeTestConfig({ plugins: [elicitOnly()] as const }), | ||
| 937 | ); | ||
| 938 | 878 | ||
| 939 | const action = yield* executor.tools.invoke( | 879 | const action = yield* executor.tools.invoke( |
| 940 | "elicit.ctl.ask", | 880 | "elicit.ctl.ask", |
| @@ -985,10 +925,7 @@ describe("createExecutor", () => { · 44 unmodified lines | |||
| 985 | }), | 925 | }), |
| 986 | ); | 926 | ); |
| 987 | 927 | ||
| 988 | const result = yield* executor.rotate.rotate( | 928 | const result = yield* executor.rotate.rotate("DB_PASSWORD", "correct-horse-battery-staple"); |
| 989 | "DB_PASSWORD", | ||
| 990 | "correct-horse-battery-staple", | ||
| 991 | ); | ||
| 992 | expect(result).toEqual({ | 929 | expect(result).toEqual({ |
| 993 | oldValue: "hunter2", | 930 | oldValue: "hunter2", |
| 994 | newValue: "correct-horse-battery-staple", | 931 | newValue: "correct-horse-battery-staple", |
| @@ -1348,8 +1285,7 @@ describe("cross-scope write preservation (SDK)", () => { · 353 unmodified lines | |||
| 1348 | "secrets.set at the inner scope does not wipe an outer-scope row with the same id", | 1285 | "secrets.set at the inner scope does not wipe an outer-scope row with the same id", |
| 1349 | () => | 1286 | () => |
| 1350 | Effect.gen(function* () { | 1287 | Effect.gen(function* () { |
| 1351 | const { execOuter, execInner, outerId, innerId } = | 1288 | const { execOuter, execInner, outerId, innerId } = yield* makeLayeredExecutors(); |
| 1352 | yield* makeLayeredExecutors(); | ||
| 1353 | 1289 | ||
| 1354 | // Admin-equivalent writes the org-wide secret at the outer scope. | 1290 | // Admin-equivalent writes the org-wide secret at the outer scope. |
| 1355 | yield* execInner.secrets.set( | 1291 | yield* execInner.secrets.set( |
| @@ -1384,9 +1320,7 @@ describe("cross-scope write preservation (SDK)", () => { · 28 unmodified lines | |||
| 1384 | // are the shared, observable source of truth. | 1320 | // are the shared, observable source of truth. |
| 1385 | const outerRefs = yield* execOuter.secrets.list(); | 1321 | const outerRefs = yield* execOuter.secrets.list(); |
| 1386 | expect(outerRefs.map((r) => r.id)).toContain("api-token"); | 1322 | expect(outerRefs.map((r) => r.id)).toContain("api-token"); |
| 1387 | expect(outerRefs.find((r) => r.id === "api-token")?.scopeId).toBe( | 1323 | expect(outerRefs.find((r) => r.id === "api-token")?.scopeId).toBe(outerId); |
| 1388 | outerId, | ||
| 1389 | ); | ||
| 1390 | 1324 | ||
| 1391 | // Inner executor's list is de-duplicated by id (innermost wins), | 1325 | // Inner executor's list is de-duplicated by id (innermost wins), |
| 1392 | // so we only expect one ref for `api-token` — pinned at the | 1326 | // so we only expect one ref for `api-token` — pinned at the |
| @@ -1530,40 +1464,35 @@ const makeMarkerExecutors = () => · 137 unmodified lines | |||
| 1530 | }); | 1464 | }); |
| 1531 | 1465 | ||
| 1532 | describe("cross-scope read precedence + remove isolation (SDK)", () => { | 1466 | describe("cross-scope read precedence + remove isolation (SDK)", () => { |
| 1533 | it.effect( | 1467 | it.effect("secrets.remove at the inner scope does not wipe outer-scope row with same id", () => |
| 1534 | "secrets.remove at the inner scope does not wipe outer-scope row with same id", | 1468 | Effect.gen(function* () { |
| 1535 | () => | 1469 | const { execOuter, execInner, outerId, innerId } = yield* makeLayeredExecutors(); |
| 1536 | Effect.gen(function* () { | ||
| 1537 | const { execOuter, execInner, outerId, innerId } = | ||
| 1538 | yield* makeLayeredExecutors(); | ||
| 1539 | 1470 | ||
| 1540 | yield* execInner.secrets.set( | 1471 | yield* execInner.secrets.set( |
| 1541 | new SetSecretInput({ | 1472 | new SetSecretInput({ |
| 1542 | id: SecretId.make("api-token"), | 1473 | id: SecretId.make("api-token"), |
| 1543 | scope: outerId, | 1474 | scope: outerId, |
| 1544 | name: "Org default", | 1475 | name: "Org default", |
| 1545 | value: "org-default", | 1476 | value: "org-default", |
| 1546 | }), | 1477 | }), |
| 1547 | ); | 1478 | ); |
| 1548 | yield* execInner.secrets.set( | 1479 | yield* execInner.secrets.set( |
| 1549 | new SetSecretInput({ | 1480 | new SetSecretInput({ |
| 1550 | id: SecretId.make("api-token"), | 1481 | id: SecretId.make("api-token"), |
| 1551 | scope: innerId, | 1482 | scope: innerId, |
| 1552 | name: "Personal override", | 1483 | name: "Personal override", |
| 1553 | value: "personal-override", | 1484 | value: "personal-override", |
| 1554 | }), | 1485 | }), |
| 1555 | ); | 1486 | ); |
| 1556 | 1487 | ||
| 1557 | // Inner caller removes — should only drop the inner override. | 1488 | // Inner caller removes — should only drop the inner override. |
| 1558 | yield* execInner.secrets.remove("api-token"); | 1489 | yield* execInner.secrets.remove("api-token"); |
| 1559 | 1490 | ||
| 1560 | // Outer-only executor must still see its org-scope row. | 1491 | // Outer-only executor must still see its org-scope row. |
| 1561 | const outerRefs = yield* execOuter.secrets.list(); | 1492 | const outerRefs = yield* execOuter.secrets.list(); |
| 1562 | expect(outerRefs.map((r) => r.id)).toContain("api-token"); | 1493 | expect(outerRefs.map((r) => r.id)).toContain("api-token"); |
| 1563 | expect(outerRefs.find((r) => r.id === "api-token")?.scopeId).toBe( | 1494 | expect(outerRefs.find((r) => r.id === "api-token")?.scopeId).toBe(outerId); |
| 1564 | outerId, | 1495 | }), |
| 1565 | ); | ||
| 1566 | }), | ||
| 1567 | ); | 1496 | ); |
| 1568 | 1497 | ||
| 1569 | it.effect( | 1498 | it.effect( |
| @@ -1584,33 +1513,30 @@ describe("cross-scope read precedence + remove isolation (SDK)", () => { · 14 unmodified lines | |||
| 1584 | }), | 1513 | }), |
| 1585 | ); | 1514 | ); |
| 1586 | 1515 | ||
| 1587 | it.effect( | 1516 | it.effect("ctx.core.sources.unregister at the inner scope does not wipe outer-scope row", () => |
| 1588 | "ctx.core.sources.unregister at the inner scope does not wipe outer-scope row", | 1517 | Effect.gen(function* () { |
| 1589 | () => | 1518 | const { execOuter, execInner } = yield* makeLayeredExecutors(); |
| 1590 | Effect.gen(function* () { | ||
| 1591 | const { execOuter, execInner } = yield* makeLayeredExecutors(); | ||
| 1592 | 1519 | ||
| 1593 | yield* execOuter.tenant.addSource("shared"); | 1520 | yield* execOuter.tenant.addSource("shared"); |
| 1594 | yield* execInner.tenant.addSource("shared"); | 1521 | yield* execInner.tenant.addSource("shared"); |
| 1595 | 1522 | ||
| 1596 | // Plugin-owned unregister path (ctx.core.sources.unregister) fires | 1523 | // Plugin-owned unregister path (ctx.core.sources.unregister) fires |
| 1597 | // via a dedicated extension method. We drive it by calling | 1524 | // via a dedicated extension method. We drive it by calling |
| 1598 | // `sources.remove` — which routes through the same deleteSourceById | 1525 | // `sources.remove` — which routes through the same deleteSourceById |
| 1599 | // helper — but the real regression is the findOne-before-delete | 1526 | // helper — but the real regression is the findOne-before-delete |
| 1600 | // picking the wrong scope's row. The outer row must survive. | 1527 | // picking the wrong scope's row. The outer row must survive. |
| 1601 | yield* execInner.sources.remove("shared"); | 1528 | yield* execInner.sources.remove("shared"); |
| 1602 | 1529 | ||
| 1603 | const outerSources = yield* execOuter.sources.list(); | 1530 | const outerSources = yield* execOuter.sources.list(); |
| 1604 | expect(outerSources.filter((s) => s.id === "shared")).toHaveLength(1); | 1531 | expect(outerSources.filter((s) => s.id === "shared")).toHaveLength(1); |
| 1605 | }), | 1532 | }), |
| 1606 | ); | 1533 | ); |
| 1607 | 1534 | ||
| 1608 | it.effect( | 1535 | it.effect( |
| 1609 | "tools.invoke picks the innermost tool when the same tool id exists at two scopes", | 1536 | "tools.invoke picks the innermost tool when the same tool id exists at two scopes", |
| 1610 | () => | 1537 | () => |
| 1611 | Effect.gen(function* () { | 1538 | Effect.gen(function* () { |
| 1612 | const { execOuter, execInner, outerId, innerId } = | 1539 | const { execOuter, execInner, outerId, innerId } = yield* makeMarkerExecutors(); |
| 1613 | yield* makeMarkerExecutors(); | ||
| 1614 | 1540 | ||
| 1615 | yield* execOuter.marker.register("shared", "outer"); | 1541 | yield* execOuter.marker.register("shared", "outer"); |
| 1616 | yield* execInner.marker.register("shared", "inner"); | 1542 | yield* execInner.marker.register("shared", "inner"); |
| @@ -1637,153 +1563,141 @@ describe("cross-scope read precedence + remove isolation (SDK)", () => { · 20 unmodified lines | |||
| 1637 | }), | 1563 | }), |
| 1638 | ); | 1564 | ); |
| 1639 | 1565 | ||
| 1640 | it.effect( | 1566 | it.effect("tools.schema — innermost shadow returns the inner description", () => |
| 1641 | "tools.schema — innermost shadow returns the inner description", | 1567 | Effect.gen(function* () { |
| 1642 | () => | 1568 | const { execOuter, execInner } = yield* makeMarkerExecutors(); |
| 1643 | Effect.gen(function* () { | ||
| 1644 | const { execOuter, execInner } = yield* makeMarkerExecutors(); | ||
| 1645 | 1569 | ||
| 1646 | yield* execOuter.marker.register("shared", "outer-desc"); | 1570 | yield* execOuter.marker.register("shared", "outer-desc"); |
| 1647 | yield* execInner.marker.register("shared", "inner-desc"); | 1571 | yield* execInner.marker.register("shared", "inner-desc"); |
| 1648 | 1572 | ||
| 1649 | const schema = yield* execInner.tools.schema("shared.t"); | 1573 | const schema = yield* execInner.tools.schema("shared.t"); |
| 1650 | expect(schema?.description).toBe("inner-desc"); | 1574 | expect(schema?.description).toBe("inner-desc"); |
| 1651 | }), | 1575 | }), |
| 1652 | ); | 1576 | ); |
| 1653 | 1577 | ||
| 1654 | it.effect( | 1578 | it.effect("tools.list dedupes by id, keeping the innermost row", () => |
| 1655 | "tools.list dedupes by id, keeping the innermost row", | 1579 | Effect.gen(function* () { |
| 1656 | () => | 1580 | const { execOuter, execInner } = yield* makeMarkerExecutors(); |
| 1657 | Effect.gen(function* () { | ||
| 1658 | const { execOuter, execInner } = yield* makeMarkerExecutors(); | ||
| 1659 | 1581 | ||
| 1660 | yield* execOuter.marker.register("shared", "outer-desc"); | 1582 | yield* execOuter.marker.register("shared", "outer-desc"); |
| 1661 | yield* execInner.marker.register("shared", "inner-desc"); | 1583 | yield* execInner.marker.register("shared", "inner-desc"); |
| 1662 | 1584 | ||
| 1663 | const tools = yield* execInner.tools.list(); | 1585 | const tools = yield* execInner.tools.list(); |
| 1664 | const shared = tools.filter((t) => t.id === "shared.t"); | 1586 | const shared = tools.filter((t) => t.id === "shared.t"); |
| 1665 | expect(shared).toHaveLength(1); | 1587 | expect(shared).toHaveLength(1); |
| 1666 | expect(shared[0]?.description).toBe("inner-desc"); | 1588 | expect(shared[0]?.description).toBe("inner-desc"); |
| 1667 | }), | 1589 | }), |
| 1668 | ); | 1590 | ); |
| 1669 | 1591 | ||
| 1670 | it.effect( | 1592 | it.effect("sources.list dedupes by id, keeping the innermost row", () => |
| 1671 | "sources.list dedupes by id, keeping the innermost row", | 1593 | Effect.gen(function* () { |
| 1672 | () => | 1594 | const { execOuter, execInner } = yield* makeMarkerExecutors(); |
| 1673 | Effect.gen(function* () { | ||
| 1674 | const { execOuter, execInner } = yield* makeMarkerExecutors(); | ||
| 1675 | 1595 | ||
| 1676 | yield* execOuter.marker.register("shared", "outer-name"); | 1596 | yield* execOuter.marker.register("shared", "outer-name"); |
| 1677 | yield* execInner.marker.register("shared", "inner-name"); | 1597 | yield* execInner.marker.register("shared", "inner-name"); |
| 1678 | 1598 | ||
| 1679 | const sources = yield* execInner.sources.list(); | 1599 | const sources = yield* execInner.sources.list(); |
| 1680 | const shared = sources.filter((s) => s.id === "shared"); | 1600 | const shared = sources.filter((s) => s.id === "shared"); |
| 1681 | expect(shared).toHaveLength(1); | 1601 | expect(shared).toHaveLength(1); |
| 1682 | expect(shared[0]?.name).toBe("inner-name"); | 1602 | expect(shared[0]?.name).toBe("inner-name"); |
| 1683 | }), | 1603 | }), |
| 1684 | ); | 1604 | ); |
| 1685 | 1605 | ||
| 1686 | it.effect( | 1606 | it.effect("tools.definitions dedupes by (source_id, name), keeping the innermost row", () => |
| 1687 | "tools.definitions dedupes by (source_id, name), keeping the innermost row", | 1607 | Effect.gen(function* () { |
| 1688 | () => | 1608 | const { execOuter, execInner } = yield* makeLayeredExecutors(); |
| 1689 | Effect.gen(function* () { | 1609 | |
| 1690 | const { execOuter, execInner } = yield* makeLayeredExecutors(); | 1610 | // Register inner first, outer second. Without precedence-aware |
| 1691 | 1611 | // dedup, a naive "iterate rows, last-one-wins" map would end up | |
| 1692 | // Register inner first, outer second. Without precedence-aware | 1612 | // keyed to the outer description just because outer was inserted |
| 1693 | // dedup, a naive "iterate rows, last-one-wins" map would end up | 1613 | // into the store last. |
| 1694 | // keyed to the outer description just because outer was inserted | 1614 | yield* execInner["tenant-defs"].addDefs("S", { |
| 1695 | // into the store last. | 1615 | Shared: { type: "string", description: "inner" }, |
| 1696 | yield* execInner["tenant-defs"].addDefs("S", { | 1616 | }); |
| 1697 | Shared: { type: "string", description: "inner" }, | 1617 | yield* execOuter["tenant-defs"].addDefs("S", { |
| 1698 | }); | 1618 | Shared: { type: "string", description: "outer" }, |
| 1699 | yield* execOuter["tenant-defs"].addDefs("S", { | 1619 | }); |
| 1700 | Shared: { type: "string", description: "outer" }, | ||
| 1701 | }); | ||
| 1702 | 1620 | ||
| 1703 | const defs = yield* execInner.tools.definitions(); | 1621 | const defs = yield* execInner.tools.definitions(); |
| 1704 | const shared = defs.S?.Shared as { description?: string } | undefined; | 1622 | const shared = defs.S?.Shared as { description?: string } | undefined; |
| 1705 | expect(shared?.description).toBe("inner"); | 1623 | expect(shared?.description).toBe("inner"); |
| 1706 | }), | 1624 | }), |
| 1707 | ); | 1625 | ); |
| 1708 | 1626 | ||
| 1709 | it.effect( | 1627 | it.effect("tools.schema attaches innermost $defs when shadowed across scopes", () => |
| 1710 | "tools.schema attaches innermost $defs when shadowed across scopes", | 1628 | Effect.gen(function* () { |
| 1711 | () => | 1629 | // Source id "S" with a tool that references $defs/Shared, plus a |
| 1712 | Effect.gen(function* () { | 1630 | // definition "Shared" registered at both scopes. Schema's attached |
| 1713 | // Source id "S" with a tool that references $defs/Shared, plus a | 1631 | // $defs should come from the inner scope. |
| 1714 | // definition "Shared" registered at both scopes. Schema's attached | 1632 | const plugins = [ |
| 1715 | // $defs should come from the inner scope. | 1633 | tenantPlugin(), |
| 1716 | const plugins = [ | 1634 | tenantPluginWithDefs(), |
| 1717 | tenantPlugin(), | 1635 | definePlugin(() => ({ |
| 1718 | tenantPluginWithDefs(), | 1636 | id: "ref" as const, |
| 1719 | definePlugin(() => ({ | 1637 | storage: () => ({}), |
| 1720 | id: "ref" as const, | 1638 | extension: (ctx) => ({ |
| 1721 | storage: () => ({}), | 1639 | register: (sourceId: string) => |
| 1722 | extension: (ctx) => ({ | 1640 | ctx.transaction( |
| 1723 | register: (sourceId: string) => | 1641 | ctx.core.sources.register({ |
| 1724 | ctx.transaction( | 1642 | id: sourceId, |
| 1725 | ctx.core.sources.register({ | 1643 | scope: ctx.scopes[0]!.id, |
| 1726 | id: sourceId, | 1644 | kind: "ref", |
| 1727 | scope: ctx.scopes[0]!.id, | 1645 | name: sourceId, |
| 1728 | kind: "ref", | 1646 | canRemove: true, |
| 1729 | name: sourceId, | 1647 | tools: [ |
| 1730 | canRemove: true, | 1648 | { |
| 1731 | tools: [ | 1649 | name: "use", |
| 1732 | { | 1650 | description: "uses $defs/Shared", |
| 1733 | name: "use", | 1651 | inputSchema: { |
| 1734 | description: "uses $defs/Shared", | 1652 | type: "object", |
| 1735 | inputSchema: { | 1653 | properties: { x: { $ref: "#/$defs/Shared" } }, |
| 1736 | type: "object", | ||
| 1737 | properties: { x: { $ref: "#/$defs/Shared" } }, | ||
| 1738 | }, | ||
| 1739 | }, | 1654 | }, |
| 1740 | ], | 1655 | }, |
| 1741 | }), | 1656 | ], |
| 1742 | ), | 1657 | }), |
| 1743 | }), | 1658 | ), |
| 1744 | }))(), | 1659 | }), |
| 1745 | ] as const; | 1660 | }))(), |
| 1746 | const schema = collectSchemas(plugins); | 1661 | ] as const; |
| 1747 | const adapter = makeMemoryAdapter({ schema }); | 1662 | const schema = collectSchemas(plugins); |
| 1748 | const blobs = makeInMemoryBlobStore(); | 1663 | const adapter = makeMemoryAdapter({ schema }); |
| 1749 | 1664 | const blobs = makeInMemoryBlobStore(); | |
| 1750 | const outerId = ScopeId.make("org"); | ||
| 1751 | const innerId = ScopeId.make("user-org:u1:org"); | ||
| 1752 | const execOuter = yield* createExecutor({ | ||
| 1753 | scopes: [ | ||
| 1754 | new Scope({ id: outerId, name: "outer", createdAt: new Date() }), | ||
| 1755 | ], | ||
| 1756 | adapter, | ||
| 1757 | blobs, | ||
| 1758 | plugins, | ||
| 1759 | onElicitation: "accept-all", | ||
| 1760 | }); | ||
| 1761 | const execInner = yield* createExecutor({ | ||
| 1762 | scopes: [ | ||
| 1763 | new Scope({ id: innerId, name: "inner", createdAt: new Date() }), | ||
| 1764 | new Scope({ id: outerId, name: "outer", createdAt: new Date() }), | ||
| 1765 | ], | ||
| 1766 | adapter, | ||
| 1767 | blobs, | ||
| 1768 | plugins, | ||
| 1769 | onElicitation: "accept-all", | ||
| 1770 | }); | ||
| 1771 | 1665 | ||
| 1772 | yield* execOuter.ref.register("S"); | 1666 | const outerId = ScopeId.make("org"); |
| 1773 | yield* execInner.ref.register("S"); | 1667 | const innerId = ScopeId.make("user-org:u1:org"); |
| 1668 | const execOuter = yield* createExecutor({ | ||
| 1669 | scopes: [new Scope({ id: outerId, name: "outer", createdAt: new Date() })], | ||
| 1670 | adapter, | ||
| 1671 | blobs, | ||
| 1672 | plugins, | ||
| 1673 | onElicitation: "accept-all", | ||
| 1674 | }); | ||
| 1675 | const execInner = yield* createExecutor({ | ||
| 1676 | scopes: [ | ||
| 1677 | new Scope({ id: innerId, name: "inner", createdAt: new Date() }), | ||
| 1678 | new Scope({ id: outerId, name: "outer", createdAt: new Date() }), | ||
| 1679 | ], | ||
| 1680 | adapter, | ||
| 1681 | blobs, | ||
| 1682 | plugins, | ||
| 1683 | onElicitation: "accept-all", | ||
| 1684 | }); | ||
| 1774 | 1685 | ||
| 1775 | yield* execInner["tenant-defs"].addDefs("S", { | 1686 | yield* execOuter.ref.register("S"); |
| 1776 | Shared: { type: "string", description: "inner" }, | 1687 | yield* execInner.ref.register("S"); |
| 1777 | }); | ||
| 1778 | yield* execOuter["tenant-defs"].addDefs("S", { | ||
| 1779 | Shared: { type: "string", description: "outer" }, | ||
| 1780 | }); | ||
| 1781 | 1688 | ||
| 1782 | const view = yield* execInner.tools.schema("S.use"); | 1689 | yield* execInner["tenant-defs"].addDefs("S", { |
| 1783 | const input = view?.inputSchema as | 1690 | Shared: { type: "string", description: "inner" }, |
| 1784 | | { $defs?: { Shared?: { description?: string } } } | 1691 | }); |
| 1785 | | undefined; | 1692 | yield* execOuter["tenant-defs"].addDefs("S", { |
| 1786 | expect(input?.$defs?.Shared?.description).toBe("inner"); | 1693 | Shared: { type: "string", description: "outer" }, |
| 1787 | }), | 1694 | }); |
| 1695 | |||
| 1696 | const view = yield* execInner.tools.schema("S.use"); | ||
| 1697 | const input = view?.inputSchema as | ||
| 1698 | | { $defs?: { Shared?: { description?: string } } } | ||
| 1699 | | undefined; | ||
| 1700 | expect(input?.$defs?.Shared?.description).toBe("inner"); | ||
| 1701 | }), | ||
| 1788 | ); | 1702 | ); |
| 1789 | }); | 1703 | }); |
| @@ -121,11 +121,7 @@ export { · 120 unmodified lines | |||
| 121 | } from "./secret-backed-value"; | 121 | } from "./secret-backed-value"; |
| 122 | 122 | ||
| 123 | // Usage tracking — secret/connection refs across plugins | 123 | // Usage tracking — secret/connection refs across plugins |
| 124 | export { | 124 | export { Usage, type UsagesForSecretInput, type UsagesForConnectionInput } from "./usages"; |
| 125 | Usage, | ||
| 126 | type UsagesForSecretInput, | ||
| 127 | type UsagesForConnectionInput, | ||
| 128 | } from "./usages"; | ||
| 129 | 125 | ||
| 130 | // Connections | 126 | // Connections |
| 131 | export { | 127 | export { |
| @@ -15,23 +15,19 @@ const DcrRequestBody = Schema.Struct({ · 14 unmodified lines | |||
| 15 | redirect_uris: Schema.Array(Schema.String), | 15 | redirect_uris: Schema.Array(Schema.String), |
| 16 | token_endpoint_auth_method: Schema.String, | 16 | token_endpoint_auth_method: Schema.String, |
| 17 | }); | 17 | }); |
| 18 | const decodeDcrRequestBody = Schema.decodeUnknownSync( | 18 | const decodeDcrRequestBody = Schema.decodeUnknownSync(Schema.fromJsonString(DcrRequestBody)); |
| 19 | Schema.fromJsonString(DcrRequestBody), | ||
| 20 | ); | ||
| 21 | 19 | ||
| 22 | const installFetchRouter = ( | 20 | const installFetchRouter = ( |
| 23 | handlers: readonly { match: (url: string) => boolean; handle: Handler }[], | 21 | handlers: readonly { match: (url: string) => boolean; handle: Handler }[], |
| 24 | ): { calls: Array<{ url: string; init: RequestInit }> } => { | 22 | ): { calls: Array<{ url: string; init: RequestInit }> } => { |
| 25 | const calls: Array<{ url: string; init: RequestInit }> = []; | 23 | const calls: Array<{ url: string; init: RequestInit }> = []; |
| 26 | globalThis.fetch = vi | 24 | globalThis.fetch = vi.fn().mockImplementation(async (url: string, init: RequestInit = {}) => { |
| 27 | .fn() | 25 | calls.push({ url, init }); |
| 28 | .mockImplementation(async (url: string, init: RequestInit = {}) => { | 26 | for (const h of handlers) { |
| 29 | calls.push({ url, init }); | 27 | if (h.match(url)) return h.handle(url, init); |
| 30 | for (const h of handlers) { | 28 | } |
| 31 | if (h.match(url)) return h.handle(url, init); | 29 | return new Response(null, { status: 404 }); |
| 32 | } | 30 | }) as typeof fetch; |
| 33 | return new Response(null, { status: 404 }); | ||
| 34 | }) as typeof fetch; | ||
| 35 | return { calls }; | 31 | return { calls }; |
| 36 | }; | 32 | }; |
| 37 | 33 | ||
| @@ -45,14 +41,11 @@ describe("discoverProtectedResourceMetadata", () => { · 7 unmodified lines | |||
| 45 | it("fetches RFC 9728 well-known metadata on the resource's origin", async () => { | 41 | it("fetches RFC 9728 well-known metadata on the resource's origin", async () => { |
| 46 | installFetchRouter([ | 42 | installFetchRouter([ |
| 47 | { | 43 | { |
| 48 | match: (u) => | 44 | match: (u) => u === "https://api.example.com/.well-known/oauth-protected-resource/graphql", |
| 49 | u === | ||
| 50 | "https://api.example.com/.well-known/oauth-protected-resource/graphql", | ||
| 51 | handle: () => new Response(null, { status: 404 }), | 45 | handle: () => new Response(null, { status: 404 }), |
| 52 | }, | 46 | }, |
| 53 | { | 47 | { |
| 54 | match: (u) => | 48 | match: (u) => u === "https://api.example.com/.well-known/oauth-protected-resource", |
| 55 | u === "https://api.example.com/.well-known/oauth-protected-resource", | ||
| 56 | handle: () => | 49 | handle: () => |
| 57 | new Response( | 50 | new Response( |
| 58 | JSON.stringify({ | 51 | JSON.stringify({ |
| @@ -69,18 +62,14 @@ describe("discoverProtectedResourceMetadata", () => { · 10 unmodified lines | |||
| 69 | discoverProtectedResourceMetadata("https://api.example.com/graphql"), | 62 | discoverProtectedResourceMetadata("https://api.example.com/graphql"), |
| 70 | ); | 63 | ); |
| 71 | expect(result).not.toBeNull(); | 64 | expect(result).not.toBeNull(); |
| 72 | expect(result!.metadata.authorization_servers?.[0]).toBe( | 65 | expect(result!.metadata.authorization_servers?.[0]).toBe("https://api.example.com"); |
| 73 | "https://api.example.com", | ||
| 74 | ); | ||
| 75 | expect(result!.metadataUrl).toBe( | 66 | expect(result!.metadataUrl).toBe( |
| 76 | "https://api.example.com/.well-known/oauth-protected-resource", | 67 | "https://api.example.com/.well-known/oauth-protected-resource", |
| 77 | ); | 68 | ); |
| 78 | }); | 69 | }); |
| 79 | 70 | ||
| 80 | it("returns null when every well-known candidate 404s", async () => { | 71 | it("returns null when every well-known candidate 404s", async () => { |
| 81 | installFetchRouter([ | 72 | installFetchRouter([{ match: () => true, handle: () => new Response(null, { status: 404 }) }]); |
| 82 | { match: () => true, handle: () => new Response(null, { status: 404 }) }, | ||
| 83 | ]); | ||
| 84 | 73 | ||
| 85 | const result = await Effect.runPromise( | 74 | const result = await Effect.runPromise( |
| 86 | discoverProtectedResourceMetadata("https://api.example.com/graphql"), | 75 | discoverProtectedResourceMetadata("https://api.example.com/graphql"), |
| @@ -117,13 +106,11 @@ describe("discoverAuthorizationServerMetadata", () => { · 30 unmodified lines | |||
| 117 | it("falls back to openid-configuration when oauth-authorization-server is absent", async () => { | 106 | it("falls back to openid-configuration when oauth-authorization-server is absent", async () => { |
| 118 | installFetchRouter([ | 107 | installFetchRouter([ |
| 119 | { | 108 | { |
| 120 | match: (u) => | 109 | match: (u) => u === "https://as.example.com/.well-known/oauth-authorization-server", |
| 121 | u === "https://as.example.com/.well-known/oauth-authorization-server", | ||
| 122 | handle: () => new Response(null, { status: 404 }), | 110 | handle: () => new Response(null, { status: 404 }), |
| 123 | }, | 111 | }, |
| 124 | { | 112 | { |
| 125 | match: (u) => | 113 | match: (u) => u === "https://as.example.com/.well-known/openid-configuration", |
| 126 | u === "https://as.example.com/.well-known/openid-configuration", | ||
| 127 | handle: () => | 114 | handle: () => |
| 128 | new Response( | 115 | new Response( |
| 129 | JSON.stringify({ | 116 | JSON.stringify({ |
| @@ -142,9 +129,7 @@ describe("discoverAuthorizationServerMetadata", () => { · 12 unmodified lines | |||
| 142 | discoverAuthorizationServerMetadata("https://as.example.com"), | 129 | discoverAuthorizationServerMetadata("https://as.example.com"), |
| 143 | ); | 130 | ); |
| 144 | expect(result).not.toBeNull(); | 131 | expect(result).not.toBeNull(); |
| 145 | expect(result!.metadata.token_endpoint).toBe( | 132 | expect(result!.metadata.token_endpoint).toBe("https://as.example.com/token"); |
| 146 | "https://as.example.com/token", | ||
| 147 | ); | ||
| 148 | expect(result!.metadataUrl.endsWith("openid-configuration")).toBe(true); | 133 | expect(result!.metadataUrl.endsWith("openid-configuration")).toBe(true); |
| 149 | }); | 134 | }); |
| 150 | 135 | ||
| @@ -159,9 +144,7 @@ describe("discoverAuthorizationServerMetadata", () => { · 8 unmodified lines | |||
| 159 | }), | 144 | }), |
| 160 | }, | 145 | }, |
| 161 | ]); | 146 | ]); |
| 162 | const exit = await Effect.runPromiseExit( | 147 | const exit = await Effect.runPromiseExit(discoverAuthorizationServerMetadata("https://as")); |
| 163 | discoverAuthorizationServerMetadata("https://as"), | ||
| 164 | ); | ||
| 165 | expect(Exit.isFailure(exit)).toBe(true); | 148 | expect(Exit.isFailure(exit)).toBe(true); |
| 166 | }); | 149 | }); |
| 167 | }); | 150 | }); |
| @@ -260,11 +243,13 @@ describe("registerDynamicClient", () => { · 92 unmodified lines | |||
| 260 | if (!Exit.isFailure(exit)) return; | 243 | if (!Exit.isFailure(exit)) return; |
| 261 | const reason = exit.cause.reasons.find(Cause.isFailReason); | 244 | const reason = exit.cause.reasons.find(Cause.isFailReason); |
| 262 | const error = reason?.error; | 245 | const error = reason?.error; |
| 263 | expect(error).toEqual(expect.objectContaining({ | 246 | expect(error).toEqual( |
| 264 | _tag: "OAuthDiscoveryError", | 247 | expect.objectContaining({ |
| 265 | status: 400, | 248 | _tag: "OAuthDiscoveryError", |
| 266 | message: expect.stringMatching(/invalid_client_metadata/), | 249 | status: 400, |
| 267 | })); | 250 | message: expect.stringMatching(/invalid_client_metadata/), |
| 251 | }), | ||
| 252 | ); | ||
| 268 | }); | 253 | }); |
| 269 | }); | 254 | }); |
| 270 | 255 | ||
| @@ -279,14 +264,11 @@ describe("beginDynamicAuthorization", () => { · 8 unmodified lines | |||
| 279 | installFetchRouter([ | 264 | installFetchRouter([ |
| 280 | { | 265 | { |
| 281 | match: (u) => | 266 | match: (u) => |
| 282 | u === | 267 | u === "https://backboard.railway.com/.well-known/oauth-protected-resource/graphql/v2", |
| 283 | "https://backboard.railway.com/.well-known/oauth-protected-resource/graphql/v2", | ||
| 284 | handle: () => new Response(null, { status: 404 }), | 268 | handle: () => new Response(null, { status: 404 }), |
| 285 | }, | 269 | }, |
| 286 | { | 270 | { |
| 287 | match: (u) => | 271 | match: (u) => u === "https://backboard.railway.com/.well-known/oauth-protected-resource", |
| 288 | u === | ||
| 289 | "https://backboard.railway.com/.well-known/oauth-protected-resource", | ||
| 290 | handle: () => | 272 | handle: () => |
| 291 | new Response( | 273 | new Response( |
| 292 | JSON.stringify({ | 274 | JSON.stringify({ |
| @@ -304,18 +286,14 @@ describe("beginDynamicAuthorization", () => { · 11 unmodified lines | |||
| 304 | ), | 286 | ), |
| 305 | }, | 287 | }, |
| 306 | { | 288 | { |
| 307 | match: (u) => | 289 | match: (u) => u === "https://backboard.railway.com/.well-known/oauth-authorization-server", |
| 308 | u === | ||
| 309 | "https://backboard.railway.com/.well-known/oauth-authorization-server", | ||
| 310 | handle: () => | 290 | handle: () => |
| 311 | new Response( | 291 | new Response( |
| 312 | JSON.stringify({ | 292 | JSON.stringify({ |
| 313 | issuer: "https://backboard.railway.com", | 293 | issuer: "https://backboard.railway.com", |
| 314 | authorization_endpoint: | 294 | authorization_endpoint: "https://backboard.railway.com/oauth/auth", |
| 315 | "https://backboard.railway.com/oauth/auth", | ||
| 316 | token_endpoint: "https://backboard.railway.com/oauth/token", | 295 | token_endpoint: "https://backboard.railway.com/oauth/token", |
| 317 | registration_endpoint: | 296 | registration_endpoint: "https://backboard.railway.com/oauth/register", |
| 318 | "https://backboard.railway.com/oauth/register", | ||
| 319 | scopes_supported: [ | 297 | scopes_supported: [ |
| 320 | "openid", | 298 | "openid", |
| 321 | "profile", | 299 | "profile", |
| @@ -356,27 +334,19 @@ describe("beginDynamicAuthorization", () => { · 34 unmodified lines | |||
| 356 | ); | 334 | ); |
| 357 | 335 | ||
| 358 | const url = new URL(result.authorizationUrl); | 336 | const url = new URL(result.authorizationUrl); |
| 359 | expect(url.origin + url.pathname).toBe( | 337 | expect(url.origin + url.pathname).toBe("https://backboard.railway.com/oauth/auth"); |
| 360 | "https://backboard.railway.com/oauth/auth", | ||
| 361 | ); | ||
| 362 | expect(url.searchParams.get("client_id")).toBe("dyn-client-42"); | 338 | expect(url.searchParams.get("client_id")).toBe("dyn-client-42"); |
| 363 | expect(url.searchParams.get("redirect_uri")).toBe( | 339 | expect(url.searchParams.get("redirect_uri")).toBe("https://app.example/cb"); |
| 364 | "https://app.example/cb", | ||
| 365 | ); | ||
| 366 | expect(url.searchParams.get("response_type")).toBe("code"); | 340 | expect(url.searchParams.get("response_type")).toBe("code"); |
| 367 | expect(url.searchParams.get("code_challenge_method")).toBe("S256"); | 341 | expect(url.searchParams.get("code_challenge_method")).toBe("S256"); |
| 368 | expect(url.searchParams.get("state")).toBe("state-xyz"); | 342 | expect(url.searchParams.get("state")).toBe("state-xyz"); |
| 369 | expect(result.codeVerifier.length).toBeGreaterThanOrEqual(43); | 343 | expect(result.codeVerifier.length).toBeGreaterThanOrEqual(43); |
| 370 | expect(result.state.authorizationServerUrl).toBe( | 344 | expect(result.state.authorizationServerUrl).toBe("https://backboard.railway.com"); |
| 371 | "https://backboard.railway.com", | ||
| 372 | ); | ||
| 373 | expect(result.state.authorizationServerMetadata.token_endpoint).toBe( | 345 | expect(result.state.authorizationServerMetadata.token_endpoint).toBe( |
| 374 | "https://backboard.railway.com/oauth/token", | 346 | "https://backboard.railway.com/oauth/token", |
| 375 | ); | 347 | ); |
| 376 | expect(result.state.clientInformation.client_id).toBe("dyn-client-42"); | 348 | expect(result.state.clientInformation.client_id).toBe("dyn-client-42"); |
| 377 | expect(result.state.resourceMetadata?.resource).toBe( | 349 | expect(result.state.resourceMetadata?.resource).toBe("https://backboard.railway.com"); |
| 378 | "https://backboard.railway.com", | ||
| 379 | ); | ||
| 380 | }); | 350 | }); |
| 381 | 351 | ||
| 382 | it("skips discovery + DCR when previousState is provided", async () => { | 352 | it("skips discovery + DCR when previousState is provided", async () => { |
| @@ -422,8 +392,7 @@ describe("beginDynamicAuthorization", () => { · 39 unmodified lines | |||
| 422 | new Response( | 392 | new Response( |
| 423 | JSON.stringify({ | 393 | JSON.stringify({ |
| 424 | issuer: "https://legacy.example.com", | 394 | issuer: "https://legacy.example.com", |
| 425 | authorization_endpoint: | 395 | authorization_endpoint: "https://legacy.example.com/authorize", |
| 426 | "https://legacy.example.com/authorize", | ||
| 427 | token_endpoint: "https://legacy.example.com/token", | 396 | token_endpoint: "https://legacy.example.com/token", |
| 428 | code_challenge_methods_supported: ["plain"], | 397 | code_challenge_methods_supported: ["plain"], |
| 429 | response_types_supported: ["code"], | 398 | response_types_supported: ["code"], |
| @@ -454,8 +423,7 @@ describe("beginDynamicAuthorization", () => { · 24 unmodified lines | |||
| 454 | new Response( | 423 | new Response( |
| 455 | JSON.stringify({ | 424 | JSON.stringify({ |
| 456 | issuer: "https://static.example.com", | 425 | issuer: "https://static.example.com", |
| 457 | authorization_endpoint: | 426 | authorization_endpoint: "https://static.example.com/authorize", |
| 458 | "https://static.example.com/authorize", | ||
| 459 | token_endpoint: "https://static.example.com/token", | 427 | token_endpoint: "https://static.example.com/token", |
| 460 | code_challenge_methods_supported: ["S256"], | 428 | code_challenge_methods_supported: ["S256"], |
| 461 | response_types_supported: ["code"], | 429 | response_types_supported: ["code"], |
| @@ -109,7 +109,10 @@ describe("buildAuthorizationUrl", () => { · 108 unmodified lines | |||
| 109 | 109 | ||
| 110 | it("preserves pre-existing query params on the authorization URL", () => { | 110 | it("preserves pre-existing query params on the authorization URL", () => { |
| 111 | const url = new URL( | 111 | const url = new URL( |
| 112 | buildAuthorizationUrl({ ...baseInput, authorizationUrl: "https://example.com/auth?tenant=acme" }), | 112 | buildAuthorizationUrl({ |
| 113 | ...baseInput, | ||
| 114 | authorizationUrl: "https://example.com/auth?tenant=acme", | ||
| 115 | }), | ||
| 113 | ); | 116 | ); |
| 114 | expect(url.searchParams.get("tenant")).toBe("acme"); | 117 | expect(url.searchParams.get("tenant")).toBe("acme"); |
| 115 | expect(url.searchParams.get("client_id")).toBe("client-123"); | 118 | expect(url.searchParams.get("client_id")).toBe("client-123"); |
| @@ -124,12 +127,10 @@ type FetchArgs = { url: string; init: RequestInit }; · 8 unmodified lines | |||
| 124 | 127 | ||
| 125 | const captureFetch = (response: Response): { calls: FetchArgs[] } => { | 128 | const captureFetch = (response: Response): { calls: FetchArgs[] } => { |
| 126 | const calls: FetchArgs[] = []; | 129 | const calls: FetchArgs[] = []; |
| 127 | globalThis.fetch = vi | 130 | globalThis.fetch = vi.fn().mockImplementation(async (url: string, init: RequestInit) => { |
| 128 | .fn() | 131 | calls.push({ url, init }); |
| 129 | .mockImplementation(async (url: string, init: RequestInit) => { | 132 | return response; |
| 130 | calls.push({ url, init }); | 133 | }) as typeof fetch; |
| 131 | return response; | ||
| 132 | }) as typeof fetch; | ||
| 133 | return { calls }; | 134 | return { calls }; |
| 134 | }; | 135 | }; |
| 135 | 136 | ||
| @@ -138,10 +139,8 @@ const originalFetch = globalThis.fetch; · 2 unmodified lines | |||
| 138 | const jwtPart = (value: unknown): string => | 139 | const jwtPart = (value: unknown): string => |
| 139 | Buffer.from(JSON.stringify(value)).toString("base64url"); | 140 | Buffer.from(JSON.stringify(value)).toString("base64url"); |
| 140 | 141 | ||
| 141 | const unsignedJwt = ( | 142 | const unsignedJwt = (claims: Record<string, unknown>, alg = "RS256"): string => |
| 142 | claims: Record<string, unknown>, | 143 | `${jwtPart({ alg, typ: "JWT" })}.${jwtPart(claims)}.sig`; |
| 143 | alg = "RS256", | ||
| 144 | ): string => `${jwtPart({ alg, typ: "JWT" })}.${jwtPart(claims)}.sig`; | ||
| 145 | 144 | ||
| 146 | describe("exchangeAuthorizationCode", () => { | 145 | describe("exchangeAuthorizationCode", () => { |
| 147 | afterEach(() => { | 146 | afterEach(() => { |
| @@ -61,8 +61,7 @@ export const OAUTH2_DEFAULT_TIMEOUT_MS = 20_000; · 60 unmodified lines | |||
| 61 | // PKCE (RFC 7636) — straight delegation to `oauth4webapi` | 61 | // PKCE (RFC 7636) — straight delegation to `oauth4webapi` |
| 62 | // --------------------------------------------------------------------------- | 62 | // --------------------------------------------------------------------------- |
| 63 | 63 | ||
| 64 | export const createPkceCodeVerifier = (): string => | 64 | export const createPkceCodeVerifier = (): string => oauth.generateRandomCodeVerifier(); |
| 65 | oauth.generateRandomCodeVerifier(); | ||
| 66 | 65 | ||
| 67 | export const createPkceCodeChallenge = (verifier: string): Promise<string> => | 66 | export const createPkceCodeChallenge = (verifier: string): Promise<string> => |
| 68 | oauth.calculatePKCECodeChallenge(verifier); | 67 | oauth.calculatePKCECodeChallenge(verifier); |
| @@ -165,9 +164,7 @@ const asFromTokenUrlAndIssuer = ( · 96 unmodified lines | |||
| 165 | return options.idTokenSigningAlgValuesSupported | 164 | return options.idTokenSigningAlgValuesSupported |
| 166 | ? { | 165 | ? { |
| 167 | ...withIssuer, | 166 | ...withIssuer, |
| 168 | id_token_signing_alg_values_supported: [ | 167 | id_token_signing_alg_values_supported: [...options.idTokenSigningAlgValuesSupported], |
| 169 | ...options.idTokenSigningAlgValuesSupported, | ||
| 170 | ], | ||
| 171 | } | 168 | } |
| 172 | : withIssuer; | 169 | : withIssuer; |
| 173 | }; | 170 | }; |
| @@ -194,9 +191,7 @@ const oauth4webapiRequestOptions = ( · 20 unmodified lines | |||
| 194 | signal: AbortSignal.timeout(timeoutMs ?? OAUTH2_DEFAULT_TIMEOUT_MS), | 191 | signal: AbortSignal.timeout(timeoutMs ?? OAUTH2_DEFAULT_TIMEOUT_MS), |
| 195 | }; | 192 | }; |
| 196 | if (isLoopbackHttpUrl(targetUrl)) { | 193 | if (isLoopbackHttpUrl(targetUrl)) { |
| 197 | (options as { [oauth.allowInsecureRequests]?: boolean })[ | 194 | (options as { [oauth.allowInsecureRequests]?: boolean })[oauth.allowInsecureRequests] = true; |
| 198 | oauth.allowInsecureRequests | ||
| 199 | ] = true; | ||
| 200 | } | 195 | } |
| 201 | return options; | 196 | return options; |
| 202 | }; | 197 | }; |
| @@ -211,9 +206,7 @@ const pickClientAuth = ( · 8 unmodified lines | |||
| 211 | : oauth.ClientSecretPost(clientSecret); | 206 | : oauth.ClientSecretPost(clientSecret); |
| 212 | }; | 207 | }; |
| 213 | 208 | ||
| 214 | const tokenResponseFrom = ( | 209 | const tokenResponseFrom = (r: oauth.TokenEndpointResponse): OAuth2TokenResponse => ({ |
| 215 | r: oauth.TokenEndpointResponse, | ||
| 216 | ): OAuth2TokenResponse => ({ | ||
| 217 | access_token: r.access_token, | 210 | access_token: r.access_token, |
| 218 | token_type: r.token_type, | 211 | token_type: r.token_type, |
| 219 | refresh_token: r.refresh_token, | 212 | refresh_token: r.refresh_token, |
| @@ -227,15 +220,14 @@ const tokenResponseFrom = ( · 7 unmodified lines | |||
| 227 | // its claims against the AS metadata and rejects mismatches we don't care | 220 | // its claims against the AS metadata and rejects mismatches we don't care |
| 228 | // about. Strip the field before delegation. | 221 | // about. Strip the field before delegation. |
| 229 | const stripIdToken = async (response: Response): Promise<Response> => { | 222 | const stripIdToken = async (response: Response): Promise<Response> => { |
| 230 | const body = await response.clone().json().then( | 223 | const body = await response |
| 231 | (value: unknown) => value, | 224 | .clone() |
| 232 | () => null, | 225 | .json() |
| 233 | ); | 226 | .then( |
| 234 | if ( | 227 | (value: unknown) => value, |
| 235 | !body || | 228 | () => null, |
| 236 | typeof body !== "object" || | 229 | ); |
| 237 | !("id_token" in (body as Record<string, unknown>)) | 230 | if (!body || typeof body !== "object" || !("id_token" in (body as Record<string, unknown>))) { |
| 238 | ) { | ||
| 239 | return response; | 231 | return response; |
| 240 | } | 232 | } |
| 241 | const { id_token: _ignored, ...rest } = body as Record<string, unknown>; | 233 | const { id_token: _ignored, ...rest } = body as Record<string, unknown>; |
| @@ -252,11 +244,7 @@ const processTokenEndpointResponse = async ( · 10 unmodified lines | |||
| 252 | response: Response, | 244 | response: Response, |
| 253 | ): Promise<OAuth2TokenResponse> => | 245 | ): Promise<OAuth2TokenResponse> => |
| 254 | tokenResponseFrom( | 246 | tokenResponseFrom( |
| 255 | await oauth.processGenericTokenEndpointResponse( | 247 | await oauth.processGenericTokenEndpointResponse(as, client, await stripIdToken(response)), |
| 256 | as, | ||
| 257 | client, | ||
| 258 | await stripIdToken(response), | ||
| 259 | ), | ||
| 260 | ); | 248 | ); |
| 261 | 249 | ||
| 262 | // --------------------------------------------------------------------------- | 250 | // --------------------------------------------------------------------------- |
| @@ -282,14 +270,10 @@ export const exchangeAuthorizationCode = ( · 19 unmodified lines | |||
| 282 | Effect.tryPromise({ | 270 | Effect.tryPromise({ |
| 283 | try: async () => { | 271 | try: async () => { |
| 284 | const as = asFromTokenUrlAndIssuer(input.tokenUrl, input.issuerUrl, { | 272 | const as = asFromTokenUrlAndIssuer(input.tokenUrl, input.issuerUrl, { |
| 285 | idTokenSigningAlgValuesSupported: | 273 | idTokenSigningAlgValuesSupported: input.idTokenSigningAlgValuesSupported, |
| 286 | input.idTokenSigningAlgValuesSupported, | ||
| 287 | }); | 274 | }); |
| 288 | const client: oauth.Client = { client_id: input.clientId }; | 275 | const client: oauth.Client = { client_id: input.clientId }; |
| 289 | const clientAuth = pickClientAuth( | 276 | const clientAuth = pickClientAuth(input.clientSecret, input.clientAuth ?? "body"); |
| 290 | input.clientSecret, | ||
| 291 | input.clientAuth ?? "body", | ||
| 292 | ); | ||
| 293 | // `authorizationCodeGrantRequest` requires its `callbackParameters` | 277 | // `authorizationCodeGrantRequest` requires its `callbackParameters` |
| 294 | // to have been returned from `validateAuthResponse`. Our public API | 278 | // to have been returned from `validateAuthResponse`. Our public API |
| 295 | // takes the `code` directly (the UI already validated `state` by | 279 | // takes the `code` directly (the UI already validated `state` by |
| @@ -334,10 +318,7 @@ export const exchangeClientCredentials = ( · 38 unmodified lines | |||
| 334 | try: async () => { | 318 | try: async () => { |
| 335 | const as = asFromTokenUrl(input.tokenUrl); | 319 | const as = asFromTokenUrl(input.tokenUrl); |
| 336 | const client: oauth.Client = { client_id: input.clientId }; | 320 | const client: oauth.Client = { client_id: input.clientId }; |
| 337 | const clientAuth = pickClientAuth( | 321 | const clientAuth = pickClientAuth(input.clientSecret, input.clientAuth ?? "body"); |
| 338 | input.clientSecret, | ||
| 339 | input.clientAuth ?? "body", | ||
| 340 | ); | ||
| 341 | const params = new URLSearchParams(); | 322 | const params = new URLSearchParams(); |
| 342 | if (input.scopes && input.scopes.length > 0) { | 323 | if (input.scopes && input.scopes.length > 0) { |
| 343 | params.set("scope", input.scopes.join(input.scopeSeparator ?? " ")); | 324 | params.set("scope", input.scopes.join(input.scopeSeparator ?? " ")); |
| @@ -349,11 +330,7 @@ export const exchangeClientCredentials = ( · 5 unmodified lines | |||
| 349 | params, | 330 | params, |
| 350 | oauth4webapiRequestOptions(input.tokenUrl, input.timeoutMs), | 331 | oauth4webapiRequestOptions(input.tokenUrl, input.timeoutMs), |
| 351 | ); | 332 | ); |
| 352 | const result = await oauth.processClientCredentialsResponse( | 333 | const result = await oauth.processClientCredentialsResponse(as, client, response); |
| 353 | as, | ||
| 354 | client, | ||
| 355 | response, | ||
| 356 | ); | ||
| 357 | return tokenResponseFrom(result); | 334 | return tokenResponseFrom(result); |
| 358 | }, | 335 | }, |
| 359 | catch: toOAuth2Error, | 336 | catch: toOAuth2Error, |
| @@ -382,14 +359,10 @@ export const refreshAccessToken = ( · 22 unmodified lines | |||
| 382 | Effect.tryPromise({ | 359 | Effect.tryPromise({ |
| 383 | try: async () => { | 360 | try: async () => { |
| 384 | const as = asFromTokenUrlAndIssuer(input.tokenUrl, input.issuerUrl, { | 361 | const as = asFromTokenUrlAndIssuer(input.tokenUrl, input.issuerUrl, { |
| 385 | idTokenSigningAlgValuesSupported: | 362 | idTokenSigningAlgValuesSupported: input.idTokenSigningAlgValuesSupported, |
| 386 | input.idTokenSigningAlgValuesSupported, | ||
| 387 | }); | 363 | }); |
| 388 | const client: oauth.Client = { client_id: input.clientId }; | 364 | const client: oauth.Client = { client_id: input.clientId }; |
| 389 | const clientAuth = pickClientAuth( | 365 | const clientAuth = pickClientAuth(input.clientSecret, input.clientAuth ?? "body"); |
| 390 | input.clientSecret, | ||
| 391 | input.clientAuth ?? "body", | ||
| 392 | ); | ||
| 393 | const additionalParameters = | 366 | const additionalParameters = |
| 394 | input.scopes && input.scopes.length > 0 | 367 | input.scopes && input.scopes.length > 0 |
| 395 | ? new URLSearchParams({ | 368 | ? new URLSearchParams({ |
| @@ -22,9 +22,7 @@ export type OAuthPopupResult<TAuth> = · 21 unmodified lines | |||
| 22 | readonly error: string; | 22 | readonly error: string; |
| 23 | }; | 23 | }; |
| 24 | 24 | ||
| 25 | export const isOAuthPopupResult = <TAuth>( | 25 | export const isOAuthPopupResult = <TAuth>(value: unknown): value is OAuthPopupResult<TAuth> => |
| 26 | value: unknown, | ||
| 27 | ): value is OAuthPopupResult<TAuth> => | ||
| 28 | typeof value === "object" && | 26 | typeof value === "object" && |
| 29 | value !== null && | 27 | value !== null && |
| 30 | (value as { type?: unknown }).type === OAUTH_POPUP_MESSAGE_TYPE; | 28 | (value as { type?: unknown }).type === OAUTH_POPUP_MESSAGE_TYPE; |
| @@ -69,15 +69,12 @@ export const OAuthAuthorizationCodeStrategy = Schema.Struct({ · 68 unmodified lines | |||
| 69 | scopeSeparator: Schema.optional(Schema.String), | 69 | scopeSeparator: Schema.optional(Schema.String), |
| 70 | /** Provider-specific params injected at authorization URL build time | 70 | /** Provider-specific params injected at authorization URL build time |
| 71 | * (Google's `access_type=offline`, `prompt=consent`, ...). */ | 71 | * (Google's `access_type=offline`, `prompt=consent`, ...). */ |
| 72 | extraAuthorizationParams: Schema.optional( | 72 | extraAuthorizationParams: Schema.optional(Schema.Record(Schema.String, Schema.String)), |
| 73 | Schema.Record(Schema.String, Schema.String), | ||
| 74 | ), | ||
| 75 | /** `"body"` (default) sends client creds in the form body; `"basic"` | 73 | /** `"body"` (default) sends client creds in the form body; `"basic"` |
| 76 | * uses HTTP Basic auth. Stripe-style servers require basic. */ | 74 | * uses HTTP Basic auth. Stripe-style servers require basic. */ |
| 77 | clientAuth: Schema.optional(Schema.Literals(["body", "basic"])), | 75 | clientAuth: Schema.optional(Schema.Literals(["body", "basic"])), |
| 78 | }); | 76 | }); |
| 79 | export type OAuthAuthorizationCodeStrategy = | 77 | export type OAuthAuthorizationCodeStrategy = typeof OAuthAuthorizationCodeStrategy.Type; |
| 80 | typeof OAuthAuthorizationCodeStrategy.Type; | ||
| 81 | 78 | ||
| 82 | /** RFC 6749 §4.4 client credentials — no user redirect, no PKCE. Used | 79 | /** RFC 6749 §4.4 client credentials — no user redirect, no PKCE. Used |
| 83 | * for server-to-server integrations where the plugin has both | 80 | * for server-to-server integrations where the plugin has both |
| @@ -92,8 +89,7 @@ export const OAuthClientCredentialsStrategy = Schema.Struct({ · 8 unmodified lines | |||
| 92 | scopeSeparator: Schema.optional(Schema.String), | 89 | scopeSeparator: Schema.optional(Schema.String), |
| 93 | clientAuth: Schema.optional(Schema.Literals(["body", "basic"])), | 90 | clientAuth: Schema.optional(Schema.Literals(["body", "basic"])), |
| 94 | }); | 91 | }); |
| 95 | export type OAuthClientCredentialsStrategy = | 92 | export type OAuthClientCredentialsStrategy = typeof OAuthClientCredentialsStrategy.Type; |
| 96 | typeof OAuthClientCredentialsStrategy.Type; | ||
| 97 | 93 | ||
| 98 | /** Tagged union of every start-time strategy shape. A new strategy (e.g. | 94 | /** Tagged union of every start-time strategy shape. A new strategy (e.g. |
| 99 | * device-code) is added here; the service's start/complete routes on | 95 | * device-code) is added here; the service's start/complete routes on |
| @@ -121,9 +117,7 @@ export const OAuthProviderState = Schema.Union([ · 21 unmodified lines | |||
| 121 | issuerUrl: Schema.optional(Schema.NullOr(Schema.String)), | 117 | issuerUrl: Schema.optional(Schema.NullOr(Schema.String)), |
| 122 | authorizationServerUrl: Schema.optional(Schema.NullOr(Schema.String)), | 118 | authorizationServerUrl: Schema.optional(Schema.NullOr(Schema.String)), |
| 123 | authorizationServerMetadataUrl: Schema.NullOr(Schema.String), | 119 | authorizationServerMetadataUrl: Schema.NullOr(Schema.String), |
| 124 | idTokenSigningAlgValuesSupported: Schema.optional( | 120 | idTokenSigningAlgValuesSupported: Schema.optional(Schema.Array(Schema.String)), |
| 125 | Schema.Array(Schema.String), | ||
| 126 | ), | ||
| 127 | /** DCR-minted client_id. Embedded inline (not a secret) — DCR | 121 | /** DCR-minted client_id. Embedded inline (not a secret) — DCR |
| 128 | * clients are public-ish by design; the secret part (if the AS | 122 | * clients are public-ish by design; the secret part (if the AS |
| 129 | * issued one) is a separate secret row. */ | 123 | * issued one) is a separate secret row. */ |
| @@ -266,21 +260,15 @@ export interface OAuthCompleteResult { · 136 unmodified lines | |||
| 266 | // capable plugin group `.addError(OAuthStartError)` etc. and the HTTP | 260 | // capable plugin group `.addError(OAuthStartError)` etc. and the HTTP |
| 267 | // edge renders them with the annotated status. | 261 | // edge renders them with the annotated status. |
| 268 | 262 | ||
| 269 | export class OAuthProbeError extends Schema.TaggedErrorClass<OAuthProbeError>()( | 263 | export class OAuthProbeError extends Schema.TaggedErrorClass<OAuthProbeError>()("OAuthProbeError", { |
| 270 | "OAuthProbeError", | 264 | message: Schema.String, |
| 271 | { | 265 | }) { |
| 272 | message: Schema.String, | ||
| 273 | }, | ||
| 274 | ) { | ||
| 275 | static annotations = { httpApiStatus: 400 }; | 266 | static annotations = { httpApiStatus: 400 }; |
| 276 | } | 267 | } |
| 277 | 268 | ||
| 278 | export class OAuthStartError extends Schema.TaggedErrorClass<OAuthStartError>()( | 269 | export class OAuthStartError extends Schema.TaggedErrorClass<OAuthStartError>()("OAuthStartError", { |
| 279 | "OAuthStartError", | 270 | message: Schema.String, |
| 280 | { | 271 | }) { |
| 281 | message: Schema.String, | ||
| 282 | }, | ||
| 283 | ) { | ||
| 284 | static annotations = { httpApiStatus: 400 }; | 272 | static annotations = { httpApiStatus: 400 }; |
| 285 | } | 273 | } |
| 286 | 274 | ||
| @@ -312,9 +300,7 @@ export class OAuthSessionNotFoundError extends Schema.TaggedErrorClass<OAuthSess · 25 unmodified lines | |||
| 312 | // --------------------------------------------------------------------------- | 300 | // --------------------------------------------------------------------------- |
| 313 | 301 | ||
| 314 | export interface OAuthService { | 302 | export interface OAuthService { |
| 315 | readonly probe: ( | 303 | readonly probe: (input: OAuthProbeInput) => Effect.Effect<OAuthProbeResult, OAuthProbeError>; |
| 316 | input: OAuthProbeInput, | ||
| 317 | ) => Effect.Effect<OAuthProbeResult, OAuthProbeError>; | ||
| 318 | readonly start: ( | 304 | readonly start: ( |
| 319 | input: OAuthStartInput, | 305 | input: OAuthStartInput, |
| 320 | ) => Effect.Effect<OAuthStartResult, OAuthStartError | StorageFailure>; | 306 | ) => Effect.Effect<OAuthStartResult, OAuthStartError | StorageFailure>; |
| @@ -326,10 +312,7 @@ export interface OAuthService { · 5 unmodified lines | |||
| 326 | >; | 312 | >; |
| 327 | /** Drop an in-flight session without completing — used when the | 313 | /** Drop an in-flight session without completing — used when the |
| 328 | * user cancels the popup or the source is deleted mid-onboarding. */ | 314 | * user cancels the popup or the source is deleted mid-onboarding. */ |
| 329 | readonly cancel: ( | 315 | readonly cancel: (sessionId: string, tokenScope?: string) => Effect.Effect<void, StorageFailure>; |
| 330 | sessionId: string, | ||
| 331 | tokenScope?: string, | ||
| 332 | ) => Effect.Effect<void, StorageFailure>; | ||
| 333 | } | 316 | } |
| 334 | 317 | ||
| 335 | // --------------------------------------------------------------------------- | 318 | // --------------------------------------------------------------------------- |
| @@ -1,11 +1,6 @@ | |||
| 1 | import type { Context, Effect, Layer } from "effect"; | 1 | import type { Context, Effect, Layer } from "effect"; |
| 2 | import type { HttpApiGroup } from "effect/unstable/httpapi"; | 2 | import type { HttpApiGroup } from "effect/unstable/httpapi"; |
| 3 | import type { | 3 | import type { DBAdapter, DBSchema, StorageFailure, TypedAdapter } from "@executor-js/storage-core"; |
| 4 | DBAdapter, | ||
| 5 | DBSchema, | ||
| 6 | StorageFailure, | ||
| 7 | TypedAdapter, | ||
| 8 | } from "@executor-js/storage-core"; | ||
| 9 | 4 | ||
| 10 | import type { PluginBlobStore } from "./blob"; | 5 | import type { PluginBlobStore } from "./blob"; |
| 11 | import type { | 6 | import type { |
| @@ -15,12 +10,7 @@ import type { · 3 unmodified lines | |||
| 15 | CreateConnectionInput, | 10 | CreateConnectionInput, |
| 16 | UpdateConnectionTokensInput, | 11 | UpdateConnectionTokensInput, |
| 17 | } from "./connections"; | 12 | } from "./connections"; |
| 18 | import type { | 13 | import type { DefinitionsInput, SourceInput, ToolAnnotations, ToolRow } from "./core-schema"; |
| 19 | DefinitionsInput, | ||
| 20 | SourceInput, | ||
| 21 | ToolAnnotations, | ||
| 22 | ToolRow, | ||
| 23 | } from "./core-schema"; | ||
| 24 | import type { SourceDetectionResult } from "./types"; | 14 | import type { SourceDetectionResult } from "./types"; |
| 25 | import type { | 15 | import type { |
| 26 | ElicitationDeclinedError, | 16 | ElicitationDeclinedError, |
| @@ -70,9 +60,7 @@ export interface StorageDeps<TSchema extends DBSchema | undefined = undefined> { · 43 unmodified lines | |||
| 70 | * `@executor-js/api` `withCapture`) is the one place that | 60 | * `@executor-js/api` `withCapture`) is the one place that |
| 71 | * translates it to the opaque `InternalError({ traceId })`. | 61 | * translates it to the opaque `InternalError({ traceId })`. |
| 72 | */ | 62 | */ |
| 73 | readonly adapter: TSchema extends DBSchema | 63 | readonly adapter: TSchema extends DBSchema ? TypedAdapter<TSchema, StorageFailure> : DBAdapter; |
| 74 | ? TypedAdapter<TSchema, StorageFailure> | ||
| 75 | : DBAdapter; | ||
| 76 | readonly blobs: PluginBlobStore; | 64 | readonly blobs: PluginBlobStore; |
| 77 | } | 65 | } |
| 78 | 66 | ||
| @@ -114,12 +102,8 @@ export interface PluginCtx<TStore = unknown> { · 35 unmodified lines | |||
| 114 | 102 | ||
| 115 | readonly core: { | 103 | readonly core: { |
| 116 | readonly sources: { | 104 | readonly sources: { |
| 117 | readonly register: ( | 105 | readonly register: (input: SourceInput) => Effect.Effect<void, StorageFailure>; |
| 118 | input: SourceInput, | 106 | readonly unregister: (sourceId: string) => Effect.Effect<void, StorageFailure>; |
| 119 | ) => Effect.Effect<void, StorageFailure>; | ||
| 120 | readonly unregister: ( | ||
| 121 | sourceId: string, | ||
| 122 | ) => Effect.Effect<void, StorageFailure>; | ||
| 123 | readonly update: (input: { | 107 | readonly update: (input: { |
| 124 | readonly id: string; | 108 | readonly id: string; |
| 125 | readonly scope: string; | 109 | readonly scope: string; |
| @@ -134,9 +118,7 @@ export interface PluginCtx<TStore = unknown> { · 8 unmodified lines | |||
| 134 | * same `ctx.transaction` as `sources.register` for atomicity. | 118 | * same `ctx.transaction` as `sources.register` for atomicity. |
| 135 | * Replaces any existing defs for the given sourceId. */ | 119 | * Replaces any existing defs for the given sourceId. */ |
| 136 | readonly definitions: { | 120 | readonly definitions: { |
| 137 | readonly register: ( | 121 | readonly register: (input: DefinitionsInput) => Effect.Effect<void, StorageFailure>; |
| 138 | input: DefinitionsInput, | ||
| 139 | ) => Effect.Effect<void, StorageFailure>; | ||
| 140 | }; | 122 | }; |
| 141 | }; | 123 | }; |
| 142 | 124 | ||
| @@ -157,9 +139,7 @@ export interface PluginCtx<TStore = unknown> { · 14 unmodified lines | |||
| 157 | * `executor.secrets.set` on the host surface, but OAuth2 refresh | 139 | * `executor.secrets.set` on the host surface, but OAuth2 refresh |
| 158 | * and one-shot token capture from plugin-owned flows need it here | 140 | * and one-shot token capture from plugin-owned flows need it here |
| 159 | * too. Same routing rules as the host-level setter. */ | 141 | * too. Same routing rules as the host-level setter. */ |
| 160 | readonly set: ( | 142 | readonly set: (input: SetSecretInput) => Effect.Effect<SecretRef, StorageFailure>; |
| 161 | input: SetSecretInput, | ||
| 162 | ) => Effect.Effect<SecretRef, StorageFailure>; | ||
| 163 | /** Delete a secret from its pinned provider and the core table. | 143 | /** Delete a secret from its pinned provider and the core table. |
| 164 | * Rejects with `SecretOwnedByConnectionError` if the row is owned | 144 | * Rejects with `SecretOwnedByConnectionError` if the row is owned |
| 165 | * by a connection — callers must go through `connections.remove` | 145 | * by a connection — callers must go through `connections.remove` |
| @@ -168,10 +148,7 @@ export interface PluginCtx<TStore = unknown> { · 2 unmodified lines | |||
| 168 | * the user to detach the listed sources first. */ | 148 | * the user to detach the listed sources first. */ |
| 169 | readonly remove: ( | 149 | readonly remove: ( |
| 170 | id: string, | 150 | id: string, |
| 171 | ) => Effect.Effect< | 151 | ) => Effect.Effect<void, SecretOwnedByConnectionError | SecretInUseError | StorageFailure>; |
| 172 | void, | ||
| 173 | SecretOwnedByConnectionError | SecretInUseError | StorageFailure | ||
| 174 | >; | ||
| 175 | }; | 152 | }; |
| 176 | 153 | ||
| 177 | /** Connections — product-level sign-in state. Owns backing secret | 154 | /** Connections — product-level sign-in state. Owns backing secret |
| @@ -180,22 +157,14 @@ export interface PluginCtx<TStore = unknown> { · 2 unmodified lines | |||
| 180 | * fresh token (the SDK handles refresh via the registered provider | 157 | * fresh token (the SDK handles refresh via the registered provider |
| 181 | * keyed by `connection.provider`). */ | 158 | * keyed by `connection.provider`). */ |
| 182 | readonly connections: { | 159 | readonly connections: { |
| 183 | readonly get: ( | 160 | readonly get: (id: string) => Effect.Effect<ConnectionRef | null, StorageFailure>; |
| 184 | id: string, | ||
| 185 | ) => Effect.Effect<ConnectionRef | null, StorageFailure>; | ||
| 186 | readonly list: () => Effect.Effect<readonly ConnectionRef[], StorageFailure>; | 161 | readonly list: () => Effect.Effect<readonly ConnectionRef[], StorageFailure>; |
| 187 | readonly create: ( | 162 | readonly create: ( |
| 188 | input: CreateConnectionInput, | 163 | input: CreateConnectionInput, |
| 189 | ) => Effect.Effect< | 164 | ) => Effect.Effect<ConnectionRef, ConnectionProviderNotRegisteredError | StorageFailure>; |
| 190 | ConnectionRef, | ||
| 191 | ConnectionProviderNotRegisteredError | StorageFailure | ||
| 192 | >; | ||
| 193 | readonly updateTokens: ( | 165 | readonly updateTokens: ( |
| 194 | input: UpdateConnectionTokensInput, | 166 | input: UpdateConnectionTokensInput, |
| 195 | ) => Effect.Effect< | 167 | ) => Effect.Effect<ConnectionRef, ConnectionNotFoundError | StorageFailure>; |
| 196 | ConnectionRef, | ||
| 197 | ConnectionNotFoundError | StorageFailure | ||
| 198 | >; | ||
| 199 | readonly setIdentityLabel: ( | 168 | readonly setIdentityLabel: ( |
| 200 | id: string, | 169 | id: string, |
| 201 | label: string | null, | 170 | label: string | null, |
| @@ -217,9 +186,7 @@ export interface PluginCtx<TStore = unknown> { · 15 unmodified lines | |||
| 217 | /** Refuses with `ConnectionInUseError` if any plugin reports the | 186 | /** Refuses with `ConnectionInUseError` if any plugin reports the |
| 218 | * connection as in use. Caller surfaces the `usages` list to the | 187 | * connection as in use. Caller surfaces the `usages` list to the |
| 219 | * user. */ | 188 | * user. */ |
| 220 | readonly remove: ( | 189 | readonly remove: (id: string) => Effect.Effect<void, ConnectionInUseError | StorageFailure>; |
| 221 | id: string, | ||
| 222 | ) => Effect.Effect<void, ConnectionInUseError | StorageFailure>; | ||
| 223 | }; | 190 | }; |
| 224 | 191 | ||
| 225 | /** Shared OAuth service. Plugins use this to probe/start/complete OAuth | 192 | /** Shared OAuth service. Plugins use this to probe/start/complete OAuth |
| @@ -230,9 +197,7 @@ export interface PluginCtx<TStore = unknown> { · 4 unmodified lines | |||
| 230 | * adapter's transaction method. Use this in extension methods that | 197 | * adapter's transaction method. Use this in extension methods that |
| 231 | * need atomicity across plugin storage writes AND core source/tool | 198 | * need atomicity across plugin storage writes AND core source/tool |
| 232 | * registration. */ | 199 | * registration. */ |
| 233 | readonly transaction: <A, E>( | 200 | readonly transaction: <A, E>(effect: Effect.Effect<A, E>) => Effect.Effect<A, E | StorageFailure>; |
| 234 | effect: Effect.Effect<A, E>, | ||
| 235 | ) => Effect.Effect<A, E | StorageFailure>; | ||
| 236 | } | 201 | } |
| 237 | 202 | ||
| 238 | // --------------------------------------------------------------------------- | 203 | // --------------------------------------------------------------------------- |
| @@ -265,9 +230,7 @@ export interface StaticToolDecl<TStore = unknown> { · 26 unmodified lines | |||
| 265 | * Inline because static tools have no plugin storage to resolve from; | 230 | * Inline because static tools have no plugin storage to resolve from; |
| 266 | * the plugin author literally writes this at definition time. */ | 231 | * the plugin author literally writes this at definition time. */ |
| 267 | readonly annotations?: ToolAnnotations; | 232 | readonly annotations?: ToolAnnotations; |
| 268 | readonly handler: ( | 233 | readonly handler: (input: StaticToolHandlerInput<TStore>) => Effect.Effect<unknown, unknown>; |
| 269 | input: StaticToolHandlerInput<TStore>, | ||
| 270 | ) => Effect.Effect<unknown, unknown>; | ||
| 271 | } | 234 | } |
| 272 | 235 | ||
| 273 | export interface StaticSourceDecl<TStore = unknown> { | 236 | export interface StaticSourceDecl<TStore = unknown> { |
| @@ -388,9 +351,7 @@ export interface PluginSpec< · 114 unmodified lines | |||
| 388 | * Handlers close over `self` via the closure, so a control tool | 351 | * Handlers close over `self` via the closure, so a control tool |
| 389 | * that delegates to the plugin's real API is a one-liner: | 352 | * that delegates to the plugin's real API is a one-liner: |
| 390 | * `({ args }) => self.addSpec(args)`. */ | 353 | * `({ args }) => self.addSpec(args)`. */ |
| 391 | readonly staticSources?: ( | 354 | readonly staticSources?: (self: NoInfer<TExtension>) => readonly StaticSourceDecl<TStore>[]; |
| 392 | self: NoInfer<TExtension>, | ||
| 393 | ) => readonly StaticSourceDecl<TStore>[]; | ||
| 394 | 355 | ||
| 395 | /** HttpApiGroup contributed by this plugin. Composed into the host's | 356 | /** HttpApiGroup contributed by this plugin. Composed into the host's |
| 396 | * `HttpApi` via the `addGroup` helper at runtime. The host mounts | 357 | * `HttpApi` via the `addGroup` helper at runtime. The host mounts |
| @@ -446,9 +407,7 @@ export interface PluginSpec< · 49 unmodified lines | |||
| 446 | * map doesn't have the toolId. The plugin reads its own enrichment | 407 | * map doesn't have the toolId. The plugin reads its own enrichment |
| 447 | * via `ctx.storage` and returns the result. Optional — plugins with | 408 | * via `ctx.storage` and returns the result. Optional — plugins with |
| 448 | * only static tools can omit it. */ | 409 | * only static tools can omit it. */ |
| 449 | readonly invokeTool?: ( | 410 | readonly invokeTool?: (input: InvokeToolInput<TStore>) => Effect.Effect<unknown, unknown>; |
| 450 | input: InvokeToolInput<TStore>, | ||
| 451 | ) => Effect.Effect<unknown, unknown>; | ||
| 452 | 411 | ||
| 453 | /** Bulk resolve annotations (requiresApproval, approvalDescription, | 412 | /** Bulk resolve annotations (requiresApproval, approvalDescription, |
| 454 | * mayElicit) for a set of tool rows under a single source. Called | 413 | * mayElicit) for a set of tool rows under a single source. Called |
| @@ -497,13 +456,9 @@ export interface PluginSpec< · 42 unmodified lines | |||
| 497 | * by this plugin. Plugin-side cleanup only; the executor deletes | 456 | * by this plugin. Plugin-side cleanup only; the executor deletes |
| 498 | * the core source/tool rows after this callback returns, inside | 457 | * the core source/tool rows after this callback returns, inside |
| 499 | * the same transaction. */ | 458 | * the same transaction. */ |
| 500 | readonly removeSource?: ( | 459 | readonly removeSource?: (input: SourceLifecycleInput<TStore>) => Effect.Effect<void, unknown>; |
| 501 | input: SourceLifecycleInput<TStore>, | ||
| 502 | ) => Effect.Effect<void, unknown>; | ||
| 503 | 460 | ||
| 504 | readonly refreshSource?: ( | 461 | readonly refreshSource?: (input: SourceLifecycleInput<TStore>) => Effect.Effect<void, unknown>; |
| 505 | input: SourceLifecycleInput<TStore>, | ||
| 506 | ) => Effect.Effect<void, unknown>; | ||
| 507 | 462 | ||
| 508 | /** URL autodetection hook. When the user pastes a URL in the | 463 | /** URL autodetection hook. When the user pastes a URL in the |
| 509 | * onboarding UI, `executor.sources.detect(url)` fans out to every | 464 | * onboarding UI, `executor.sources.detect(url)` fans out to every |
| @@ -526,9 +481,7 @@ export interface PluginSpec< · 16 unmodified lines | |||
| 526 | readonly secretProviders?: | 481 | readonly secretProviders?: |
| 527 | | readonly SecretProvider[] | 482 | | readonly SecretProvider[] |
| 528 | | ((ctx: PluginCtx<TStore>) => readonly SecretProvider[]) | 483 | | ((ctx: PluginCtx<TStore>) => readonly SecretProvider[]) |
| 529 | | (( | 484 | | ((ctx: PluginCtx<TStore>) => Effect.Effect<readonly SecretProvider[]>); |
| 530 | ctx: PluginCtx<TStore>, | ||
| 531 | ) => Effect.Effect<readonly SecretProvider[]>); | ||
| 532 | 485 | ||
| 533 | /** Connection providers contributed by this plugin. Same registration | 486 | /** Connection providers contributed by this plugin. Same registration |
| 534 | * shape as `secretProviders`. Each provider's `key` is what | 487 | * shape as `secretProviders`. Each provider's `key` is what |
| @@ -538,9 +491,7 @@ export interface PluginSpec< · 3 unmodified lines | |||
| 538 | readonly connectionProviders?: | 491 | readonly connectionProviders?: |
| 539 | | readonly ConnectionProvider[] | 492 | | readonly ConnectionProvider[] |
| 540 | | ((ctx: PluginCtx<TStore>) => readonly ConnectionProvider[]) | 493 | | ((ctx: PluginCtx<TStore>) => readonly ConnectionProvider[]) |
| 541 | | (( | 494 | | ((ctx: PluginCtx<TStore>) => Effect.Effect<readonly ConnectionProvider[]>); |
| 542 | ctx: PluginCtx<TStore>, | ||
| 543 | ) => Effect.Effect<readonly ConnectionProvider[]>); | ||
| 544 | 495 | ||
| 545 | readonly close?: () => Effect.Effect<void, unknown>; | 496 | readonly close?: () => Effect.Effect<void, unknown>; |
| 546 | } | 497 | } |
| @@ -558,15 +509,7 @@ export interface Plugin< · 11 unmodified lines | |||
| 558 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 509 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 559 | THandlersLayer extends Layer.Layer<any, any, any> = any, | 510 | THandlersLayer extends Layer.Layer<any, any, any> = any, |
| 560 | TGroup extends HttpApiGroup.Any = HttpApiGroup.Any, | 511 | TGroup extends HttpApiGroup.Any = HttpApiGroup.Any, |
| 561 | > extends PluginSpec< | 512 | > extends PluginSpec<TId, TExtension, TStore, TSchema, TExtensionService, THandlersLayer, TGroup> {} |
| 562 | TId, | ||
| 563 | TExtension, | ||
| 564 | TStore, | ||
| 565 | TSchema, | ||
| 566 | TExtensionService, | ||
| 567 | THandlersLayer, | ||
| 568 | TGroup | ||
| 569 | > {} | ||
| 570 | 513 | ||
| 571 | // --------------------------------------------------------------------------- | 514 | // --------------------------------------------------------------------------- |
| 572 | // definePlugin — factory-returning-spec. Options from the author factory | 515 | // definePlugin — factory-returning-spec. Options from the author factory |
| @@ -589,15 +532,7 @@ export type ConfiguredPlugin< · 16 unmodified lines | |||
| 589 | options?: TOptions & { | 532 | options?: TOptions & { |
| 590 | readonly storage?: (deps: StorageDeps<TSchema>) => TStore; | 533 | readonly storage?: (deps: StorageDeps<TSchema>) => TStore; |
| 591 | }, | 534 | }, |
| 592 | ) => Plugin< | 535 | ) => Plugin<TId, TExtension, TStore, TSchema, TExtensionService, THandlersLayer, TGroup>; |
| 593 | TId, | ||
| 594 | TExtension, | ||
| 595 | TStore, | ||
| 596 | TSchema, | ||
| 597 | TExtensionService, | ||
| 598 | THandlersLayer, | ||
| 599 | TGroup | ||
| 600 | >; | ||
| 601 | 536 | ||
| 602 | // eslint-disable-next-line @typescript-eslint/ban-types | 537 | // eslint-disable-next-line @typescript-eslint/ban-types |
| 603 | export function definePlugin< | 538 | export function definePlugin< |
| @@ -614,15 +549,7 @@ export function definePlugin< · 10 unmodified lines | |||
| 614 | >( | 549 | >( |
| 615 | authorFactory: ( | 550 | authorFactory: ( |
| 616 | options?: TOptions, | 551 | options?: TOptions, |
| 617 | ) => PluginSpec< | 552 | ) => PluginSpec<TId, TExtension, TStore, TSchema, TExtensionService, THandlersLayer, TGroup>, |
| 618 | TId, | ||
| 619 | TExtension, | ||
| 620 | TStore, | ||
| 621 | TSchema, | ||
| 622 | TExtensionService, | ||
| 623 | THandlersLayer, | ||
| 624 | TGroup | ||
| 625 | >, | ||
| 626 | ): ConfiguredPlugin< | 553 | ): ConfiguredPlugin< |
| 627 | TId, | 554 | TId, |
| 628 | TExtension, | 555 | TExtension, |
| @@ -643,9 +570,7 @@ export function definePlugin< · 14 unmodified lines | |||
| 643 | } = options ?? {}; | 570 | } = options ?? {}; |
| 644 | 571 | ||
| 645 | const hasAuthorOptions = Object.keys(rest).length > 0; | 572 | const hasAuthorOptions = Object.keys(rest).length > 0; |
| 646 | const spec = authorFactory( | 573 | const spec = authorFactory(hasAuthorOptions ? (rest as TOptions) : undefined); |
| 647 | hasAuthorOptions ? (rest as TOptions) : undefined, | ||
| 648 | ); | ||
| 649 | 574 | ||
| 650 | return { | 575 | return { |
| 651 | ...spec, | 576 | ...spec, |
| @@ -664,12 +589,7 @@ export function definePlugin< · 12 unmodified lines | |||
| 664 | export type AnyPlugin = Plugin<string>; | 589 | export type AnyPlugin = Plugin<string>; |
| 665 | 590 | ||
| 666 | export type PluginExtensions<TPlugins extends readonly AnyPlugin[]> = { | 591 | export type PluginExtensions<TPlugins extends readonly AnyPlugin[]> = { |
| 667 | readonly [P in TPlugins[number] as P["id"]]: P extends Plugin< | 592 | readonly [P in TPlugins[number] as P["id"]]: P extends Plugin<string, infer TExt> ? TExt : never; |
| 668 | string, | ||
| 669 | infer TExt | ||
| 670 | > | ||
| 671 | ? TExt | ||
| 672 | : never; | ||
| 673 | }; | 593 | }; |
| 674 | 594 | ||
| 675 | /** Lightweight projection of a secret entry as returned by `ctx.secrets.list`. */ | 595 | /** Lightweight projection of a secret entry as returned by `ctx.secrets.list`. */ |
| @@ -5,10 +5,7 @@ import { generateKeyBetween } from "fractional-indexing"; · 4 unmodified lines | |||
| 5 | import type { ToolPolicyRow } from "./core-schema"; | 5 | import type { ToolPolicyRow } from "./core-schema"; |
| 6 | import { PolicyId } from "./ids"; | 6 | import { PolicyId } from "./ids"; |
| 7 | import { createExecutor } from "./executor"; | 7 | import { createExecutor } from "./executor"; |
| 8 | import { | 8 | import { ElicitationResponse, type ElicitationHandler } from "./elicitation"; |
| 9 | ElicitationResponse, | ||
| 10 | type ElicitationHandler, | ||
| 11 | } from "./elicitation"; | ||
| 12 | import { | 9 | import { |
| 13 | effectivePolicyFromSorted, | 10 | effectivePolicyFromSorted, |
| 14 | isValidPattern, | 11 | isValidPattern, |
| @@ -149,13 +146,8 @@ describe("resolveToolPolicy", () => { · 134 unmodified lines | |||
| 149 | ROW("outer", "vercel.*", "block", "a0", "org"), | 146 | ROW("outer", "vercel.*", "block", "a0", "org"), |
| 150 | ROW("inner", "vercel.dns.create", "approve", "a0", "user"), | 147 | ROW("inner", "vercel.dns.create", "approve", "a0", "user"), |
| 151 | ]; | 148 | ]; |
| 152 | const rank = (row: Pick<ToolPolicyRow, "scope_id">) => | 149 | const rank = (row: Pick<ToolPolicyRow, "scope_id">) => (row.scope_id === "user" ? 0 : 1); |
| 153 | row.scope_id === "user" ? 0 : 1; | 150 | const result = resolveToolPolicy("vercel.dns.create", policies, rank); |
| 154 | const result = resolveToolPolicy( | ||
| 155 | "vercel.dns.create", | ||
| 156 | policies, | ||
| 157 | rank, | ||
| 158 | ); | ||
| 159 | expect(result?.action).toBe("approve"); | 151 | expect(result?.action).toBe("approve"); |
| 160 | expect(result?.policyId).toBe("inner"); | 152 | expect(result?.policyId).toBe("inner"); |
| 161 | }); | 153 | }); |
| @@ -167,18 +159,12 @@ describe("resolveToolPolicy", () => { · 5 unmodified lines | |||
| 167 | // refetch and reorder math sees colliding neighbor keys. | 159 | // refetch and reorder math sees colliding neighbor keys. |
| 168 | const a = resolveToolPolicy( | 160 | const a = resolveToolPolicy( |
| 169 | "vercel.dns.create", | 161 | "vercel.dns.create", |
| 170 | [ | 162 | [ROW("z", "vercel.dns.*", "block", "a0"), ROW("a", "vercel.dns.*", "approve", "a0")], |
| 171 | ROW("z", "vercel.dns.*", "block", "a0"), | ||
| 172 | ROW("a", "vercel.dns.*", "approve", "a0"), | ||
| 173 | ], | ||
| 174 | flatRank, | 163 | flatRank, |
| 175 | ); | 164 | ); |
| 176 | const b = resolveToolPolicy( | 165 | const b = resolveToolPolicy( |
| 177 | "vercel.dns.create", | 166 | "vercel.dns.create", |
| 178 | [ | 167 | [ROW("a", "vercel.dns.*", "approve", "a0"), ROW("z", "vercel.dns.*", "block", "a0")], |
| 179 | ROW("a", "vercel.dns.*", "approve", "a0"), | ||
| 180 | ROW("z", "vercel.dns.*", "block", "a0"), | ||
| 181 | ], | ||
| 182 | flatRank, | 168 | flatRank, |
| 183 | ); | 169 | ); |
| 184 | // Same input rows in different order, same winner — id "a" sorts | 170 | // Same input rows in different order, same winner — id "a" sorts |
| @@ -189,11 +175,11 @@ describe("resolveToolPolicy", () => { · 4 unmodified lines | |||
| 189 | }); | 175 | }); |
| 190 | 176 | ||
| 191 | describe("effectivePolicyFromSorted", () => { | 177 | describe("effectivePolicyFromSorted", () => { |
| 192 | const POL = ( | 178 | const POL = (id: string, pattern: string, action: "approve" | "require_approval" | "block") => ({ |
| 193 | id: string, | 179 | id: PolicyId.make(id), |
| 194 | pattern: string, | 180 | pattern, |
| 195 | action: "approve" | "require_approval" | "block", | 181 | action, |
| 196 | ) => ({ id: PolicyId.make(id), pattern, action }); | 182 | }); |
| 197 | 183 | ||
| 198 | it("returns user policy when one matches", () => { | 184 | it("returns user policy when one matches", () => { |
| 199 | const result = effectivePolicyFromSorted( | 185 | const result = effectivePolicyFromSorted( |
| @@ -218,9 +204,10 @@ describe("effectivePolicyFromSorted", () => { · 18 unmodified lines | |||
| 218 | }); | 204 | }); |
| 219 | 205 | ||
| 220 | it("falls back to plugin default approve when annotation is false/undefined", () => { | 206 | it("falls back to plugin default approve when annotation is false/undefined", () => { |
| 221 | expect( | 207 | expect(effectivePolicyFromSorted("vercel.dns.create", [], false)).toEqual({ |
| 222 | effectivePolicyFromSorted("vercel.dns.create", [], false), | 208 | action: "approve", |
| 223 | ).toEqual({ action: "approve", source: "plugin-default" }); | 209 | source: "plugin-default", |
| 210 | }); | ||
| 224 | expect(effectivePolicyFromSorted("vercel.dns.create", [])).toEqual({ | 211 | expect(effectivePolicyFromSorted("vercel.dns.create", [])).toEqual({ |
| 225 | action: "approve", | 212 | action: "approve", |
| 226 | source: "plugin-default", | 213 | source: "plugin-default", |
| @@ -342,10 +329,7 @@ describe("executor.policies", () => { · 115 unmodified lines | |||
| 342 | expect(second.position < first.position).toBe(true); | 329 | expect(second.position < first.position).toBe(true); |
| 343 | 330 | ||
| 344 | const rules = yield* executor.policies.list(); | 331 | const rules = yield* executor.policies.list(); |
| 345 | expect(rules.map((r) => r.pattern)).toEqual([ | 332 | expect(rules.map((r) => r.pattern)).toEqual(["vercel.delete", "vercel.*"]); |
| 346 | "vercel.delete", | ||
| 347 | "vercel.*", | ||
| 348 | ]); | ||
| 349 | }), | 333 | }), |
| 350 | ); | 334 | ); |
| 351 | 335 | ||
| @@ -425,34 +409,27 @@ describe("executor.policies", () => { · 73 unmodified lines | |||
| 425 | }), | 409 | }), |
| 426 | ); | 410 | ); |
| 427 | 411 | ||
| 428 | it.effect( | 412 | it.effect("consecutive creates produce strictly increasing-precedence keys", () => |
| 429 | "consecutive creates produce strictly increasing-precedence keys", | 413 | Effect.gen(function* () { |
| 430 | () => | 414 | const executor = yield* setupExecutor(); |
| 431 | Effect.gen(function* () { | 415 | const created: string[] = []; |
| 432 | const executor = yield* setupExecutor(); | 416 | for (const pattern of ["a.*", "b.*", "c.*", "d.*"]) { |
| 433 | const created: string[] = []; | 417 | const row = yield* executor.policies.create({ |
| 434 | for (const pattern of ["a.*", "b.*", "c.*", "d.*"]) { | 418 | scope: "test-scope", |
| 435 | const row = yield* executor.policies.create({ | 419 | pattern, |
| 436 | scope: "test-scope", | 420 | action: "approve", |
| 437 | pattern, | 421 | }); |
| 438 | action: "approve", | 422 | created.push(row.position); |
| 439 | }); | 423 | } |
| 440 | created.push(row.position); | 424 | // Each new key sorts strictly above the previous (lower lex |
| 441 | } | 425 | // order = higher precedence). No collisions. |
| 442 | // Each new key sorts strictly above the previous (lower lex | 426 | for (let i = 1; i < created.length; i++) { |
| 443 | // order = higher precedence). No collisions. | 427 | expect(created[i]! < created[i - 1]!).toBe(true); |
| 444 | for (let i = 1; i < created.length; i++) { | 428 | } |
| 445 | expect(created[i]! < created[i - 1]!).toBe(true); | 429 | // List order matches insertion-reverse. |
| 446 | } | 430 | const rules = yield* executor.policies.list(); |
| 447 | // List order matches insertion-reverse. | 431 | expect(rules.map((r) => r.pattern)).toEqual(["d.*", "c.*", "b.*", "a.*"]); |
| 448 | const rules = yield* executor.policies.list(); | 432 | }), |
| 449 | expect(rules.map((r) => r.pattern)).toEqual([ | ||
| 450 | "d.*", | ||
| 451 | "c.*", | ||
| 452 | "b.*", | ||
| 453 | "a.*", | ||
| 454 | ]); | ||
| 455 | }), | ||
| 456 | ); | 433 | ); |
| 457 | 434 | ||
| 458 | it.effect("remove deletes the row", () => | 435 | it.effect("remove deletes the row", () => |
| @@ -521,14 +498,10 @@ describe("blocked tools", () => { · 62 unmodified lines | |||
| 521 | pattern: "vercel.*", | 498 | pattern: "vercel.*", |
| 522 | action: "block", | 499 | action: "block", |
| 523 | }); | 500 | }); |
| 524 | const result = yield* Effect.result( | 501 | const result = yield* Effect.result(executor.tools.invoke("vercel.delete", {})); |
| 525 | executor.tools.invoke("vercel.delete", {}), | ||
| 526 | ); | ||
| 527 | expect(Result.isFailure(result)).toBe(true); | 502 | expect(Result.isFailure(result)).toBe(true); |
| 528 | if (!Result.isFailure(result)) return; | 503 | if (!Result.isFailure(result)) return; |
| 529 | expect(Predicate.isTagged("ToolBlockedError")(result.failure)).toBe( | 504 | expect(Predicate.isTagged("ToolBlockedError")(result.failure)).toBe(true); |
| 530 | true, | ||
| 531 | ); | ||
| 532 | if (!Predicate.isTagged("ToolBlockedError")(result.failure)) return; | 505 | if (!Predicate.isTagged("ToolBlockedError")(result.failure)) return; |
| 533 | expect(result.failure.pattern).toBe("vercel.*"); | 506 | expect(result.failure.pattern).toBe("vercel.*"); |
| 534 | }), | 507 | }), |
| @@ -545,9 +518,13 @@ describe("approve / require_approval interaction with annotations", () => { · 10 unmodified lines | |||
| 545 | action: "approve", | 518 | action: "approve", |
| 546 | }); | 519 | }); |
| 547 | const calls = { count: 0 }; | 520 | const calls = { count: 0 }; |
| 548 | const result = yield* executor.tools.invoke("vercel.delete", {}, { | 521 | const result = yield* executor.tools.invoke( |
| 549 | onElicitation: recordingHandler(calls), | 522 | "vercel.delete", |
| 550 | }); | 523 | {}, |
| 524 | { | ||
| 525 | onElicitation: recordingHandler(calls), | ||
| 526 | }, | ||
| 527 | ); | ||
| 551 | expect(calls.count).toBe(0); | 528 | expect(calls.count).toBe(0); |
| 552 | expect(result).toEqual({ ran: "vercel.delete" }); | 529 | expect(result).toEqual({ ran: "vercel.delete" }); |
| 553 | }), | 530 | }), |
| @@ -562,9 +539,13 @@ describe("approve / require_approval interaction with annotations", () => { · 8 unmodified lines | |||
| 562 | action: "require_approval", | 539 | action: "require_approval", |
| 563 | }); | 540 | }); |
| 564 | const calls = { count: 0 }; | 541 | const calls = { count: 0 }; |
| 565 | yield* executor.tools.invoke("vercel.deploy", {}, { | 542 | yield* executor.tools.invoke( |
| 566 | onElicitation: recordingHandler(calls), | 543 | "vercel.deploy", |
| 567 | }); | 544 | {}, |
| 545 | { | ||
| 546 | onElicitation: recordingHandler(calls), | ||
| 547 | }, | ||
| 548 | ); | ||
| 568 | expect(calls.count).toBe(1); | 549 | expect(calls.count).toBe(1); |
| 569 | }), | 550 | }), |
| 570 | ); | 551 | ); |
| @@ -578,15 +559,17 @@ describe("approve / require_approval interaction with annotations", () => { · 7 unmodified lines | |||
| 578 | action: "require_approval", | 559 | action: "require_approval", |
| 579 | }); | 560 | }); |
| 580 | const result = yield* Effect.result( | 561 | const result = yield* Effect.result( |
| 581 | executor.tools.invoke("vercel.deploy", {}, { | 562 | executor.tools.invoke( |
| 582 | onElicitation: decliningHandler, | 563 | "vercel.deploy", |
| 583 | }), | 564 | {}, |
| 565 | { | ||
| 566 | onElicitation: decliningHandler, | ||
| 567 | }, | ||
| 568 | ), | ||
| 584 | ); | 569 | ); |
| 585 | expect(Result.isFailure(result)).toBe(true); | 570 | expect(Result.isFailure(result)).toBe(true); |
| 586 | if (!Result.isFailure(result)) return; | 571 | if (!Result.isFailure(result)) return; |
| 587 | expect( | 572 | expect(Predicate.isTagged("ElicitationDeclinedError")(result.failure)).toBe(true); |
| 588 | Predicate.isTagged("ElicitationDeclinedError")(result.failure), | ||
| 589 | ).toBe(true); | ||
| 590 | }), | 573 | }), |
| 591 | ); | 574 | ); |
| 592 | 575 | ||
| @@ -597,16 +580,24 @@ describe("approve / require_approval interaction with annotations", () => { · 4 unmodified lines | |||
| 597 | // marks any tool whose name contains "delete" as requiring | 580 | // marks any tool whose name contains "delete" as requiring |
| 598 | // approval, so the prompt should fire. | 581 | // approval, so the prompt should fire. |
| 599 | const calls = { count: 0 }; | 582 | const calls = { count: 0 }; |
| 600 | yield* executor.tools.invoke("vercel.delete", {}, { | 583 | yield* executor.tools.invoke( |
| 601 | onElicitation: recordingHandler(calls), | 584 | "vercel.delete", |
| 602 | }); | 585 | {}, |
| 586 | { | ||
| 587 | onElicitation: recordingHandler(calls), | ||
| 588 | }, | ||
| 589 | ); | ||
| 603 | expect(calls.count).toBe(1); | 590 | expect(calls.count).toBe(1); |
| 604 | 591 | ||
| 605 | // vercel.deploy has no plugin-required approval and no policy, | 592 | // vercel.deploy has no plugin-required approval and no policy, |
| 606 | // so no prompt. | 593 | // so no prompt. |
| 607 | yield* executor.tools.invoke("vercel.deploy", {}, { | 594 | yield* executor.tools.invoke( |
| 608 | onElicitation: recordingHandler(calls), | 595 | "vercel.deploy", |
| 609 | }); | 596 | {}, |
| 597 | { | ||
| 598 | onElicitation: recordingHandler(calls), | ||
| 599 | }, | ||
| 600 | ); | ||
| 610 | expect(calls.count).toBe(1); | 601 | expect(calls.count).toBe(1); |
| 611 | }), | 602 | }), |
| 612 | ); | 603 | ); |
| @@ -184,9 +184,7 @@ export const resolveToolPolicy = ( · 183 unmodified lines | |||
| 184 | // policies in evaluation order from the API. | 184 | // policies in evaluation order from the API. |
| 185 | // --------------------------------------------------------------------------- | 185 | // --------------------------------------------------------------------------- |
| 186 | 186 | ||
| 187 | const liftPlugin = ( | 187 | const liftPlugin = (defaultRequiresApproval: boolean | undefined): EffectivePolicy => |
| 188 | defaultRequiresApproval: boolean | undefined, | ||
| 189 | ): EffectivePolicy => | ||
| 190 | defaultRequiresApproval | 188 | defaultRequiresApproval |
| 191 | ? { action: "require_approval", source: "plugin-default" } | 189 | ? { action: "require_approval", source: "plugin-default" } |
| 192 | : { action: "approve", source: "plugin-default" }; | 190 | : { action: "approve", source: "plugin-default" }; |
| @@ -245,8 +243,4 @@ export const rowToToolPolicy = (row: ToolPolicyRow): ToolPolicy => ({ · 52 unmodified lines | |||
| 245 | // inputs with effect/Schema. | 243 | // inputs with effect/Schema. |
| 246 | // --------------------------------------------------------------------------- | 244 | // --------------------------------------------------------------------------- |
| 247 | 245 | ||
| 248 | export const ToolPolicyActionSchema = Schema.Literals([ | 246 | export const ToolPolicyActionSchema = Schema.Literals(["approve", "require_approval", "block"]); |
| 249 | "approve", | ||
| 250 | "require_approval", | ||
| 251 | "block", | ||
| 252 | ]); | ||
| @@ -40,25 +40,24 @@ import { Scope } from "./scope"; · 39 unmodified lines | |||
| 40 | // off a returned ref still gets the branded type for use as an opaque token. | 40 | // off a returned ref still gets the branded type for use as an opaque token. |
| 41 | // --------------------------------------------------------------------------- | 41 | // --------------------------------------------------------------------------- |
| 42 | 42 | ||
| 43 | type Unbrand<T> = T extends Brand.Brand<string> | 43 | type Unbrand<T> = |
| 44 | ? string | 44 | T extends Brand.Brand<string> |
| 45 | : T extends readonly (infer U)[] | 45 | ? string |
| 46 | ? readonly Unbrand<U>[] | 46 | : T extends readonly (infer U)[] |
| 47 | : T extends ReadonlyMap<infer K, infer V> | 47 | ? readonly Unbrand<U>[] |
| 48 | ? ReadonlyMap<Unbrand<K>, Unbrand<V>> | 48 | : T extends ReadonlyMap<infer K, infer V> |
| 49 | : T extends ReadonlySet<infer U> | 49 | ? ReadonlyMap<Unbrand<K>, Unbrand<V>> |
| 50 | ? ReadonlySet<Unbrand<U>> | 50 | : T extends ReadonlySet<infer U> |
| 51 | : T extends Date | 51 | ? ReadonlySet<Unbrand<U>> |
| 52 | ? T | 52 | : T extends Date |
| 53 | : T extends (...args: infer A) => infer R | 53 | ? T |
| 54 | ? (...args: { [I in keyof A]: Unbrand<A[I]> }) => Unbrand<R> | 54 | : T extends (...args: infer A) => infer R |
| 55 | : T extends object | 55 | ? (...args: { [I in keyof A]: Unbrand<A[I]> }) => Unbrand<R> |
| 56 | ? { readonly [K in keyof T]: Unbrand<T[K]> } | 56 | : T extends object |
| 57 | : T; | 57 | ? { readonly [K in keyof T]: Unbrand<T[K]> } |
| 58 | 58 | : T; | |
| 59 | export type Promisified<T> = T extends ( | 59 | |
| 60 | ...args: infer A | 60 | export type Promisified<T> = T extends (...args: infer A) => Effect.Effect<infer R, infer _E> |
| 61 | ) => Effect.Effect<infer R, infer _E> | ||
| 62 | ? (...args: { [I in keyof A]: Unbrand<A[I]> }) => Promise<R> | 61 | ? (...args: { [I in keyof A]: Unbrand<A[I]> }) => Promise<R> |
| 63 | : T extends readonly unknown[] | 62 | : T extends readonly unknown[] |
| 64 | ? T | 63 | ? T |
| @@ -103,10 +102,7 @@ const isPlainObject = (v: unknown): v is Record<string | symbol, unknown> => · 38 unmodified lines | |||
| 103 | const promisifyDeep = <T>(value: T): Promisified<T> => { | 102 | const promisifyDeep = <T>(value: T): Promisified<T> => { |
| 104 | if (typeof value === "function") { | 103 | if (typeof value === "function") { |
| 105 | return ((...args: unknown[]) => { | 104 | return ((...args: unknown[]) => { |
| 106 | const result = (value as (...a: unknown[]) => unknown).apply( | 105 | const result = (value as (...a: unknown[]) => unknown).apply(undefined, args); |
| 107 | undefined, | ||
| 108 | args, | ||
| 109 | ); | ||
| 110 | if (Effect.isEffect(result)) { | 106 | if (Effect.isEffect(result)) { |
| 111 | return Effect.runPromise(result as Effect.Effect<unknown, unknown>); | 107 | return Effect.runPromise(result as Effect.Effect<unknown, unknown>); |
| 112 | } | 108 | } |
| @@ -121,10 +117,7 @@ const promisifyDeep = <T>(value: T): Promisified<T> => { · 8 unmodified lines | |||
| 121 | const v = Reflect.get(target, prop, receiver); | 117 | const v = Reflect.get(target, prop, receiver); |
| 122 | if (typeof v === "function") { | 118 | if (typeof v === "function") { |
| 123 | return (...args: unknown[]) => { | 119 | return (...args: unknown[]) => { |
| 124 | const result = (v as (...a: unknown[]) => unknown).apply( | 120 | const result = (v as (...a: unknown[]) => unknown).apply(target, args); |
| 125 | target, | ||
| 126 | args, | ||
| 127 | ); | ||
| 128 | if (Effect.isEffect(result)) { | 121 | if (Effect.isEffect(result)) { |
| 129 | return Effect.runPromise(result as Effect.Effect<unknown, unknown>); | 122 | return Effect.runPromise(result as Effect.Effect<unknown, unknown>); |
| 130 | } | 123 | } |
| @@ -143,9 +136,7 @@ const promisifyDeep = <T>(value: T): Promisified<T> => { · 12 unmodified lines | |||
| 143 | // construct an executor with just `{ plugins: [...] }`. | 136 | // construct an executor with just `{ plugins: [...] }`. |
| 144 | // --------------------------------------------------------------------------- | 137 | // --------------------------------------------------------------------------- |
| 145 | 138 | ||
| 146 | export const createExecutor = async < | 139 | export const createExecutor = async <const TPlugins extends readonly AnyPlugin[] = []>( |
| 147 | const TPlugins extends readonly AnyPlugin[] = [], | ||
| 148 | >( | ||
| 149 | config: ExecutorConfig<TPlugins>, | 140 | config: ExecutorConfig<TPlugins>, |
| 150 | ): Promise<Executor<TPlugins>> => { | 141 | ): Promise<Executor<TPlugins>> => { |
| 151 | const plugins = (config?.plugins ?? []) as TPlugins; | 142 | const plugins = (config?.plugins ?? []) as TPlugins; |
| @@ -183,9 +174,7 @@ export const createExecutor = async < · 31 unmodified lines | |||
| 183 | // get the tagged error as the rejected value. See | 174 | // get the tagged error as the rejected value. See |
| 184 | // notes/promise-sdk-typed-errors.md for the planned `runPromiseExit` | 175 | // notes/promise-sdk-typed-errors.md for the planned `runPromiseExit` |
| 185 | // rewrite that exposes the full error union to consumers. | 176 | // rewrite that exposes the full error union to consumers. |
| 186 | const effectExecutor = await Effect.runPromise( | 177 | const effectExecutor = await Effect.runPromise(createEffectExecutor(effectConfig)); |
| 187 | createEffectExecutor(effectConfig), | ||
| 188 | ); | ||
| 189 | 178 | ||
| 190 | return promisifyDeep(effectExecutor) as Executor<TPlugins>; | 179 | return promisifyDeep(effectExecutor) as Executor<TPlugins>; |
| 191 | }; | 180 | }; |
| @@ -33,8 +33,7 @@ const echoPlugin = definePlugin(() => ({ · 32 unmodified lines | |||
| 33 | }, | 33 | }, |
| 34 | ], | 34 | ], |
| 35 | extension: () => ({ | 35 | extension: () => ({ |
| 36 | greet: (name: string) => | 36 | greet: (name: string) => Effect.succeed(`hello, ${name}`) as Effect.Effect<string, never>, |
| 37 | Effect.succeed(`hello, ${name}`) as Effect.Effect<string, never>, | ||
| 38 | }), | 37 | }), |
| 39 | })); | 38 | })); |
| 40 | 39 | ||
| @@ -110,8 +109,7 @@ describe("promise/createExecutor", () => { · 69 unmodified lines | |||
| 110 | "ap.ctl.go", | 109 | "ap.ctl.go", |
| 111 | {}, | 110 | {}, |
| 112 | { | 111 | { |
| 113 | onElicitation: () => | 112 | onElicitation: () => Effect.succeed({ action: "decline" as const }) as any, |
| 114 | Effect.succeed({ action: "decline" as const }) as any, | ||
| 115 | }, | 113 | }, |
| 116 | ), | 114 | ), |
| 117 | ).rejects.toMatchObject({ | 115 | ).rejects.toMatchObject({ |
| @@ -51,11 +51,7 @@ export { · 50 unmodified lines | |||
| 51 | 51 | ||
| 52 | // File-config helper for the CLI. Plain typed-object factory with no | 52 | // File-config helper for the CLI. Plain typed-object factory with no |
| 53 | // Effect in its signature, so it's safe to live on the Promise surface. | 53 | // Effect in its signature, so it's safe to live on the Promise surface. |
| 54 | export { | 54 | export { defineExecutorConfig, type ExecutorCliConfig, type ExecutorDialect } from "./config"; |
| 55 | defineExecutorConfig, | ||
| 56 | type ExecutorCliConfig, | ||
| 57 | type ExecutorDialect, | ||
| 58 | } from "./config"; | ||
| 59 | 55 | ||
| 60 | // Error tags — Promise callers handle these via .catch(). | 56 | // Error tags — Promise callers handle these via .catch(). |
| 61 | export { | 57 | export { |
| @@ -398,4 +398,3 @@ export const buildToolTypeScriptPreview = (input: { · 397 unmodified lines | |||
| 398 | : {}), | 398 | : {}), |
| 399 | }; | 399 | }; |
| 400 | }; | 400 | }; |
| 401 | |||
| @@ -179,16 +179,12 @@ describe("scopeAdapter — read isolation", () => { · 178 unmodified lines | |||
| 179 | it.effect("caller-supplied scope_id filter is stripped (can't bypass isolation)", () => | 179 | it.effect("caller-supplied scope_id filter is stripped (can't bypass isolation)", () => |
| 180 | Effect.gen(function* () { | 180 | Effect.gen(function* () { |
| 181 | const inner = makeMemoryAdapter({ schema }); | 181 | const inner = makeMemoryAdapter({ schema }); |
| 182 | yield* typedAdapter<typeof schema>( | 182 | yield* typedAdapter<typeof schema>(scopeAdapter(inner, { scopes: ["c"] }, schema)).create({ |
| 183 | scopeAdapter(inner, { scopes: ["c"] }, schema), | ||
| 184 | ).create({ | ||
| 185 | model: "thing", | 183 | model: "thing", |
| 186 | data: { id: "t-hidden", scope_id: "c", value: "secret" }, | 184 | data: { id: "t-hidden", scope_id: "c", value: "secret" }, |
| 187 | }); | 185 | }); |
| 188 | 186 | ||
| 189 | const reader = typedAdapter<typeof schema>( | 187 | const reader = typedAdapter<typeof schema>(scopeAdapter(inner, { scopes: ["a"] }, schema)); |
| 190 | scopeAdapter(inner, { scopes: ["a"] }, schema), | ||
| 191 | ); | ||
| 192 | // Attempt to bypass by explicitly filtering for scope_id "c". | 188 | // Attempt to bypass by explicitly filtering for scope_id "c". |
| 193 | const rows = yield* reader.findMany({ | 189 | const rows = yield* reader.findMany({ |
| 194 | model: "thing", | 190 | model: "thing", |
| @@ -49,10 +49,7 @@ const collectScopedModels = (schema: DBSchema): Set<string> => { · 48 unmodified lines | |||
| 49 | return out; | 49 | return out; |
| 50 | }; | 50 | }; |
| 51 | 51 | ||
| 52 | const withScopeRead = ( | 52 | const withScopeRead = (where: readonly Where[] | undefined, ctx: ScopeContext): Where[] => { |
| 53 | where: readonly Where[] | undefined, | ||
| 54 | ctx: ScopeContext, | ||
| 55 | ): Where[] => { | ||
| 56 | const base = (where ?? []).filter((w) => w.field !== SCOPE_FIELD); | 53 | const base = (where ?? []).filter((w) => w.field !== SCOPE_FIELD); |
| 57 | const callerScope = (where ?? []).find((w) => w.field === SCOPE_FIELD); | 54 | const callerScope = (where ?? []).find((w) => w.field === SCOPE_FIELD); |
| 58 | 55 | ||
| @@ -123,11 +120,7 @@ const wrapTxMethods = ( · 64 unmodified lines | |||
| 123 | create: (data) => | 120 | create: (data) => |
| 124 | isScoped(data.model) | 121 | isScoped(data.model) |
| 125 | ? Effect.flatMap( | 122 | ? Effect.flatMap( |
| 126 | assertScopedWrite( | 123 | assertScopedWrite(data.model, data.data as Record<string, unknown>, ctx), |
| 127 | data.model, | ||
| 128 | data.data as Record<string, unknown>, | ||
| 129 | ctx, | ||
| 130 | ), | ||
| 131 | () => inner.create(data), | 124 | () => inner.create(data), |
| 132 | ) | 125 | ) |
| 133 | : inner.create(data), | 126 | : inner.create(data), |
| @@ -136,11 +129,7 @@ const wrapTxMethods = ( · 2 unmodified lines | |||
| 136 | ? Effect.flatMap( | 129 | ? Effect.flatMap( |
| 137 | Effect.all( | 130 | Effect.all( |
| 138 | data.data.map((row) => | 131 | data.data.map((row) => |
| 139 | assertScopedWrite( | 132 | assertScopedWrite(data.model, row as Record<string, unknown>, ctx), |
| 140 | data.model, | ||
| 141 | row as Record<string, unknown>, | ||
| 142 | ctx, | ||
| 143 | ), | ||
| 144 | ), | 133 | ), |
| 145 | ), | 134 | ), |
| 146 | () => inner.createMany(data), | 135 | () => inner.createMany(data), |
| @@ -168,11 +157,7 @@ const wrapTxMethods = ( · 21 unmodified lines | |||
| 168 | // rows you can read. That's sufficient for isolation; we | 157 | // rows you can read. That's sufficient for isolation; we |
| 169 | // don't need to force-stamp on update. | 158 | // don't need to force-stamp on update. |
| 170 | (data.update as Record<string, unknown>)[SCOPE_FIELD] !== undefined | 159 | (data.update as Record<string, unknown>)[SCOPE_FIELD] !== undefined |
| 171 | ? assertScopedWrite( | 160 | ? assertScopedWrite(data.model, data.update as Record<string, unknown>, ctx) |
| 172 | data.model, | ||
| 173 | data.update as Record<string, unknown>, | ||
| 174 | ctx, | ||
| 175 | ) | ||
| 176 | : Effect.void, | 161 | : Effect.void, |
| 177 | () => | 162 | () => |
| 178 | inner.update({ | 163 | inner.update({ |
| @@ -185,11 +170,7 @@ const wrapTxMethods = ( · 6 unmodified lines | |||
| 185 | isScoped(data.model) | 170 | isScoped(data.model) |
| 186 | ? Effect.flatMap( | 171 | ? Effect.flatMap( |
| 187 | (data.update as Record<string, unknown>)[SCOPE_FIELD] !== undefined | 172 | (data.update as Record<string, unknown>)[SCOPE_FIELD] !== undefined |
| 188 | ? assertScopedWrite( | 173 | ? assertScopedWrite(data.model, data.update as Record<string, unknown>, ctx) |
| 189 | data.model, | ||
| 190 | data.update as Record<string, unknown>, | ||
| 191 | ctx, | ||
| 192 | ) | ||
| 193 | : Effect.void, | 174 | : Effect.void, |
| 194 | () => | 175 | () => |
| 195 | inner.updateMany({ | 176 | inner.updateMany({ |
| @@ -209,22 +190,14 @@ const wrapTxMethods = ( · 13 unmodified lines | |||
| 209 | }; | 190 | }; |
| 210 | }; | 191 | }; |
| 211 | 192 | ||
| 212 | export const scopeAdapter = ( | 193 | export const scopeAdapter = (inner: DBAdapter, ctx: ScopeContext, schema: DBSchema): DBAdapter => { |
| 213 | inner: DBAdapter, | ||
| 214 | ctx: ScopeContext, | ||
| 215 | schema: DBSchema, | ||
| 216 | ): DBAdapter => { | ||
| 217 | const scopedModels = collectScopedModels(schema); | 194 | const scopedModels = collectScopedModels(schema); |
| 218 | const tx = wrapTxMethods(inner, ctx, scopedModels); | 195 | const tx = wrapTxMethods(inner, ctx, scopedModels); |
| 219 | return { | 196 | return { |
| 220 | ...tx, | 197 | ...tx, |
| 221 | transaction: (callback) => | 198 | transaction: (callback) => |
| 222 | inner.transaction((rawTrx) => { | 199 | inner.transaction((rawTrx) => { |
| 223 | const scopedTrx: DBTransactionAdapter = wrapTxMethods( | 200 | const scopedTrx: DBTransactionAdapter = wrapTxMethods(rawTrx, ctx, scopedModels); |
| 224 | rawTrx, | ||
| 225 | ctx, | ||
| 226 | scopedModels, | ||
| 227 | ); | ||
| 228 | return callback(scopedTrx); | 201 | return callback(scopedTrx); |
| 229 | }), | 202 | }), |
| 230 | createSchema: inner.createSchema, | 203 | createSchema: inner.createSchema, |
| @@ -30,31 +30,18 @@ export interface SecretProvider { · 29 unmodified lines | |||
| 30 | * failed, etc.) surface as `StorageFailure` — the executor treats | 30 | * failed, etc.) surface as `StorageFailure` — the executor treats |
| 31 | * a provider call the same as a DB call; `StorageError` is captured | 31 | * a provider call the same as a DB call; `StorageError` is captured |
| 32 | * at the HTTP edge to `InternalError`, `UniqueViolationError` dies. */ | 32 | * at the HTTP edge to `InternalError`, `UniqueViolationError` dies. */ |
| 33 | readonly get: ( | 33 | readonly get: (id: string, scope: string) => Effect.Effect<string | null, StorageFailure>; |
| 34 | id: string, | ||
| 35 | scope: string, | ||
| 36 | ) => Effect.Effect<string | null, StorageFailure>; | ||
| 37 | /** Check whether a provider has a backing value without returning it. | 34 | /** Check whether a provider has a backing value without returning it. |
| 38 | * Providers that can answer this cheaply should implement it so | 35 | * Providers that can answer this cheaply should implement it so |
| 39 | * stale core routing rows don't appear as selectable secrets. */ | 36 | * stale core routing rows don't appear as selectable secrets. */ |
| 40 | readonly has?: ( | 37 | readonly has?: (id: string, scope: string) => Effect.Effect<boolean, StorageFailure>; |
| 41 | id: string, | ||
| 42 | scope: string, | ||
| 43 | ) => Effect.Effect<boolean, StorageFailure>; | ||
| 44 | /** Set a secret value at a named scope. Only called on writable | 38 | /** Set a secret value at a named scope. Only called on writable |
| 45 | * providers. Providers that partition by scope use this arg to | 39 | * providers. Providers that partition by scope use this arg to |
| 46 | * decide where to write; flat providers ignore it. */ | 40 | * decide where to write; flat providers ignore it. */ |
| 47 | readonly set?: ( | 41 | readonly set?: (id: string, value: string, scope: string) => Effect.Effect<void, StorageFailure>; |
| 48 | id: string, | ||
| 49 | value: string, | ||
| 50 | scope: string, | ||
| 51 | ) => Effect.Effect<void, StorageFailure>; | ||
| 52 | /** Delete a secret at a named scope. Only called on writable providers. | 42 | /** Delete a secret at a named scope. Only called on writable providers. |
| 53 | * Returns true if something was deleted. */ | 43 | * Returns true if something was deleted. */ |
| 54 | readonly delete?: ( | 44 | readonly delete?: (id: string, scope: string) => Effect.Effect<boolean, StorageFailure>; |
| 55 | id: string, | ||
| 56 | scope: string, | ||
| 57 | ) => Effect.Effect<boolean, StorageFailure>; | ||
| 58 | /** Enumerate known secret entries. Optional — not all backends can | 45 | /** Enumerate known secret entries. Optional — not all backends can |
| 59 | * enumerate (env-backed providers, for example). */ | 46 | * enumerate (env-backed providers, for example). */ |
| 60 | readonly list?: () => Effect.Effect< | 47 | readonly list?: () => Effect.Effect< |
| @@ -90,9 +77,7 @@ export class SecretRef extends Schema.Class<SecretRef>("SecretRef")({ · 29 unmodified lines | |||
| 90 | // OAuth token exchange writes to the innermost per-user scope. | 77 | // OAuth token exchange writes to the innermost per-user scope. |
| 91 | // --------------------------------------------------------------------------- | 78 | // --------------------------------------------------------------------------- |
| 92 | 79 | ||
| 93 | export class SetSecretInput extends Schema.Class<SetSecretInput>( | 80 | export class SetSecretInput extends Schema.Class<SetSecretInput>("SetSecretInput")({ |
| 94 | "SetSecretInput", | ||
| 95 | )({ | ||
| 96 | id: SecretId, | 81 | id: SecretId, |
| 97 | /** Scope id to own this secret. Must be one of the executor's | 82 | /** Scope id to own this secret. Must be one of the executor's |
| 98 | * configured scopes. */ | 83 | * configured scopes. */ |
| @@ -16,21 +16,18 @@ import { Scope } from "./scope"; · 15 unmodified lines | |||
| 16 | // need multi-scope behavior can pass `scopes` explicitly. | 16 | // need multi-scope behavior can pass `scopes` explicitly. |
| 17 | // --------------------------------------------------------------------------- | 17 | // --------------------------------------------------------------------------- |
| 18 | 18 | ||
| 19 | export const makeTestConfig = < | 19 | export const makeTestConfig = <const TPlugins extends readonly AnyPlugin[] = []>(options?: { |
| 20 | const TPlugins extends readonly AnyPlugin[] = [], | ||
| 21 | >(options?: { | ||
| 22 | readonly scopeName?: string; | 20 | readonly scopeName?: string; |
| 23 | readonly scopes?: readonly Scope[]; | 21 | readonly scopes?: readonly Scope[]; |
| 24 | readonly plugins?: TPlugins; | 22 | readonly plugins?: TPlugins; |
| 25 | }): ExecutorConfig<TPlugins> => { | 23 | }): ExecutorConfig<TPlugins> => { |
| 26 | const scopes = | 24 | const scopes = options?.scopes ?? [ |
| 27 | options?.scopes ?? [ | 25 | new Scope({ |
| 28 | new Scope({ | 26 | id: ScopeId.make("test-scope"), |
| 29 | id: ScopeId.make("test-scope"), | 27 | name: options?.scopeName ?? "test", |
| 30 | name: options?.scopeName ?? "test", | 28 | createdAt: new Date(), |
| 31 | createdAt: new Date(), | 29 | }), |
| 32 | }), | 30 | ]; |
| 33 | ]; | ||
| 34 | 31 | ||
| 35 | const schema = collectSchemas(options?.plugins ?? []); | 32 | const schema = collectSchemas(options?.plugins ?? []); |
| 36 | 33 | ||
| @@ -65,9 +65,7 @@ export class ToolSchema extends Schema.Class<ToolSchema>("ToolSchema")({ · 64 unmodified lines | |||
| 65 | outputSchema: Schema.optional(Schema.Unknown), | 65 | outputSchema: Schema.optional(Schema.Unknown), |
| 66 | inputTypeScript: Schema.optional(Schema.String), | 66 | inputTypeScript: Schema.optional(Schema.String), |
| 67 | outputTypeScript: Schema.optional(Schema.String), | 67 | outputTypeScript: Schema.optional(Schema.String), |
| 68 | typeScriptDefinitions: Schema.optional( | 68 | typeScriptDefinitions: Schema.optional(Schema.Record(Schema.String, Schema.String)), |
| 69 | Schema.Record(Schema.String, Schema.String), | ||
| 70 | ), | ||
| 71 | }) {} | 69 | }) {} |
| 72 | 70 | ||
| 73 | // --------------------------------------------------------------------------- | 71 | // --------------------------------------------------------------------------- |
| @@ -6,26 +6,27 @@ Modifications were made to remove authentication-specific types and to · 5 unmodified lines | |||
| 6 | adapt the interfaces to the executor project. | 6 | adapt the interfaces to the executor project. |
| 7 | 7 | ||
| 8 | Vendored files: | 8 | Vendored files: |
| 9 | - src/adapter.ts — ported from packages/core/src/db/adapter/index.ts | 9 | |
| 10 | and types.ts (DBAdapter interface, Where DSL, CustomAdapter, | 10 | - src/adapter.ts — ported from packages/core/src/db/adapter/index.ts |
| 11 | DBAdapterFactoryConfig). Promise → Effect conversion, auth-specific | 11 | and types.ts (DBAdapter interface, Where DSL, CustomAdapter, |
| 12 | model names stripped. | 12 | DBAdapterFactoryConfig). Promise → Effect conversion, auth-specific |
| 13 | - src/schema.ts — ported from packages/core/src/db/type.ts | 13 | model names stripped. |
| 14 | (DBSchema, DBFieldAttribute, InferDB*). | 14 | - src/schema.ts — ported from packages/core/src/db/type.ts |
| 15 | - src/factory.ts — ported from packages/core/src/db/adapter/factory.ts | 15 | (DBSchema, DBFieldAttribute, InferDB\*). |
| 16 | (createAdapterFactory). Promise → Effect. Stripped auth-specific | 16 | - src/factory.ts — ported from packages/core/src/db/adapter/factory.ts |
| 17 | concerns: BetterAuthOptions generic, numeric serial ids, joins, | 17 | (createAdapterFactory). Promise → Effect. Stripped auth-specific |
| 18 | telemetry spans, logger, plural model resolution. Matches our | 18 | concerns: BetterAuthOptions generic, numeric serial ids, joins, |
| 19 | simpler CustomAdapter + DBAdapterFactoryConfig shape. | 19 | telemetry spans, logger, plural model resolution. Matches our |
| 20 | - src/testing/memory.ts — ported from | 20 | simpler CustomAdapter + DBAdapterFactoryConfig shape. |
| 21 | packages/memory-adapter/src/memory-adapter.ts (memoryAdapter). | 21 | - src/testing/memory.ts — ported from |
| 22 | Promise → Effect. Piped through our createAdapter. | 22 | packages/memory-adapter/src/memory-adapter.ts (memoryAdapter). |
| 23 | - storage-drizzle/src/adapter.ts — ported from | 23 | Promise → Effect. Piped through our createAdapter. |
| 24 | packages/drizzle-adapter/src/drizzle-adapter.ts (drizzleAdapter). | 24 | - storage-drizzle/src/adapter.ts — ported from |
| 25 | Promise → Effect. Takes an explicit tables map instead of reading | 25 | packages/drizzle-adapter/src/drizzle-adapter.ts (drizzleAdapter). |
| 26 | db._.fullSchema. Piped through our createAdapter. | 26 | Promise → Effect. Takes an explicit tables map instead of reading |
| 27 | 27 | db.\_.fullSchema. Piped through our createAdapter. | |
| 28 | ------------------------------------------------------------------------------- | 28 | |
| 29 | --- | ||
| 29 | 30 | ||
| 30 | The MIT License (MIT) | 31 | The MIT License (MIT) |
| 31 | Copyright (c) 2024 - present, Bereket Engida | 32 | Copyright (c) 2024 - present, Bereket Engida |
| @@ -42,11 +42,7 @@ Or grab typed query helpers for an existing schema — purely a type-level · 41 unmodified lines | |||
| 42 | view, no runtime cost: | 42 | view, no runtime cost: |
| 43 | 43 | ||
| 44 | ```ts | 44 | ```ts |
| 45 | import { | 45 | import { typedAdapter, type DBAdapter, type DBSchema } from "@executor-js/storage-core"; |
| 46 | typedAdapter, | ||
| 47 | type DBAdapter, | ||
| 48 | type DBSchema, | ||
| 49 | } from "@executor-js/storage-core"; | ||
| 50 | 46 | ||
| 51 | const schema = { | 47 | const schema = { |
| 52 | secrets: { | 48 | secrets: { |
| @@ -53,6 +53,12 @@ · 52 unmodified lines | |||
| 53 | "@standard-schema/spec": "^1.0.0", | 53 | "@standard-schema/spec": "^1.0.0", |
| 54 | "effect": "catalog:" | 54 | "effect": "catalog:" |
| 55 | }, | 55 | }, |
| 56 | "devDependencies": { | ||
| 57 | "@effect/vitest": "catalog:", | ||
| 58 | "tsup": "catalog:", | ||
| 59 | "typescript": "catalog:", | ||
| 60 | "vitest": "catalog:" | ||
| 61 | }, | ||
| 56 | "peerDependencies": { | 62 | "peerDependencies": { |
| 57 | "@effect/vitest": "catalog:", | 63 | "@effect/vitest": "catalog:", |
| 58 | "vitest": "catalog:" | 64 | "vitest": "catalog:" |
| @@ -64,11 +70,5 @@ · 5 unmodified lines | |||
| 64 | "vitest": { | 70 | "vitest": { |
| 65 | "optional": true | 71 | "optional": true |
| 66 | } | 72 | } |
| 67 | }, | ||
| 68 | "devDependencies": { | ||
| 69 | "@effect/vitest": "catalog:", | ||
| 70 | "tsup": "catalog:", | ||
| 71 | "typescript": "catalog:", | ||
| 72 | "vitest": "catalog:" | ||
| 73 | } | 73 | } |
| 74 | } | 74 | } |
| @@ -192,9 +192,7 @@ export type DBAdapter = { · 191 unmodified lines | |||
| 192 | * "does my adapter support JSON?") without having to thread the config | 192 | * "does my adapter support JSON?") without having to thread the config |
| 193 | * manually. Populated by `createAdapter`. | 193 | * manually. Populated by `createAdapter`. |
| 194 | */ | 194 | */ |
| 195 | options?: | 195 | options?: ({ adapterConfig: DBAdapterFactoryConfig } & CustomAdapter["options"]) | undefined; |
| 196 | | ({ adapterConfig: DBAdapterFactoryConfig } & CustomAdapter["options"]) | ||
| 197 | | undefined; | ||
| 198 | }; | 196 | }; |
| 199 | 197 | ||
| 200 | // --------------------------------------------------------------------------- | 198 | // --------------------------------------------------------------------------- |
| @@ -252,10 +250,7 @@ export interface CustomAdapter { · 51 unmodified lines | |||
| 252 | join?: JoinConfig | undefined; | 250 | join?: JoinConfig | undefined; |
| 253 | }) => Effect.Effect<T[], StorageFailure>; | 251 | }) => Effect.Effect<T[], StorageFailure>; |
| 254 | 252 | ||
| 255 | delete: (data: { | 253 | delete: (data: { model: string; where: CleanedWhere[] }) => Effect.Effect<void, StorageFailure>; |
| 256 | model: string; | ||
| 257 | where: CleanedWhere[]; | ||
| 258 | }) => Effect.Effect<void, StorageFailure>; | ||
| 259 | 254 | ||
| 260 | deleteMany: (data: { | 255 | deleteMany: (data: { |
| 261 | model: string; | 256 | model: string; |
| @@ -35,8 +35,6 @@ export class StorageError extends Data.TaggedError("StorageError")<{ · 34 unmodified lines | |||
| 35 | * driver message) stays internal because plugin code rarely needs it | 35 | * driver message) stays internal because plugin code rarely needs it |
| 36 | * and surfacing it leaks backend specifics. | 36 | * and surfacing it leaks backend specifics. |
| 37 | */ | 37 | */ |
| 38 | export class UniqueViolationError extends Data.TaggedError( | 38 | export class UniqueViolationError extends Data.TaggedError("UniqueViolationError")<{ |
| 39 | "UniqueViolationError", | ||
| 40 | )<{ | ||
| 41 | readonly model?: string; | 39 | readonly model?: string; |
| 42 | }> {} | 40 | }> {} |
| @@ -11,10 +11,7 @@ import { Effect } from "effect"; · 10 unmodified lines | |||
| 11 | 11 | ||
| 12 | import type { DBAdapter } from "./adapter"; | 12 | import type { DBAdapter } from "./adapter"; |
| 13 | import { makeMemoryAdapter } from "./testing/memory"; | 13 | import { makeMemoryAdapter } from "./testing/memory"; |
| 14 | import { | 14 | import { conformanceSchema, runAdapterConformance } from "./testing/conformance"; |
| 15 | conformanceSchema, | ||
| 16 | runAdapterConformance, | ||
| 17 | } from "./testing/conformance"; | ||
| 18 | 15 | ||
| 19 | const withAdapter = <A, E>( | 16 | const withAdapter = <A, E>( |
| 20 | fn: (adapter: DBAdapter) => Effect.Effect<A, E>, | 17 | fn: (adapter: DBAdapter) => Effect.Effect<A, E>, |
| @@ -41,16 +41,13 @@ export type InferDBValueType<T extends DBFieldType> = T extends "string" · 40 unmodified lines | |||
| 41 | ? T[number] | 41 | ? T[number] |
| 42 | : never; | 42 | : never; |
| 43 | 43 | ||
| 44 | export type InferDBFieldOutput<T extends DBFieldAttribute> = | 44 | export type InferDBFieldOutput<T extends DBFieldAttribute> = T["returned"] extends false |
| 45 | T["returned"] extends false | 45 | ? never |
| 46 | ? never | 46 | : T["required"] extends false |
| 47 | : T["required"] extends false | 47 | ? InferDBValueType<T["type"]> | undefined | null |
| 48 | ? InferDBValueType<T["type"]> | undefined | null | 48 | : InferDBValueType<T["type"]>; |
| 49 | : InferDBValueType<T["type"]>; | ||
| 50 | 49 | ||
| 51 | export type InferDBFieldInput<T extends DBFieldAttribute> = InferDBValueType< | 50 | export type InferDBFieldInput<T extends DBFieldAttribute> = InferDBValueType<T["type"]>; |
| 52 | T["type"] | ||
| 53 | >; | ||
| 54 | 51 | ||
| 55 | export type InferDBFieldsInput<Field> = | 52 | export type InferDBFieldsInput<Field> = |
| 56 | Field extends Record<infer Key, DBFieldAttribute> | 53 | Field extends Record<infer Key, DBFieldAttribute> |
| @@ -70,19 +67,13 @@ export type InferDBFieldsInput<Field> = · 13 unmodified lines | |||
| 70 | } | 67 | } |
| 71 | : {}; | 68 | : {}; |
| 72 | 69 | ||
| 73 | export type InferDBFieldsOutput< | 70 | export type InferDBFieldsOutput<Fields extends Record<string, DBFieldAttribute>> = |
| 74 | Fields extends Record<string, DBFieldAttribute>, | ||
| 75 | > = | ||
| 76 | Fields extends Record<infer Key, DBFieldAttribute> | 71 | Fields extends Record<infer Key, DBFieldAttribute> |
| 77 | ? { | 72 | ? { |
| 78 | [K in Key as Fields[K]["returned"] extends false | 73 | [K in Key as Fields[K]["returned"] extends false |
| 79 | ? never | 74 | ? never |
| 80 | : Fields[K]["required"] extends false | 75 | : Fields[K]["required"] extends false |
| 81 | ? Fields[K]["defaultValue"] extends | 76 | ? Fields[K]["defaultValue"] extends boolean | string | number | Date |
| 82 | | boolean | ||
| 83 | | string | ||
| 84 | | number | ||
| 85 | | Date | ||
| 86 | ? K | 77 | ? K |
| 87 | : never | 78 | : never |
| 88 | : K]: InferDBFieldOutput<Fields[K]>; | 79 | : K]: InferDBFieldOutput<Fields[K]>; |
| @@ -90,11 +81,7 @@ export type InferDBFieldsOutput< · 1 unmodified line | |||
| 90 | [K in Key as Fields[K]["returned"] extends false | 81 | [K in Key as Fields[K]["returned"] extends false |
| 91 | ? never | 82 | ? never |
| 92 | : Fields[K]["required"] extends false | 83 | : Fields[K]["required"] extends false |
| 93 | ? Fields[K]["defaultValue"] extends | 84 | ? Fields[K]["defaultValue"] extends boolean | string | number | Date |
| 94 | | boolean | ||
| 95 | | string | ||
| 96 | | number | ||
| 97 | | Date | ||
| 98 | ? never | 85 | ? never |
| 99 | : K | 86 | : K |
| 100 | : never]?: InferDBFieldOutput<Fields[K]> | null; | 87 | : never]?: InferDBFieldOutput<Fields[K]> | null; |
| @@ -131,12 +118,7 @@ export type DBFieldAttributeConfig = { · 30 unmodified lines | |||
| 131 | model: string; | 118 | model: string; |
| 132 | field: string; | 119 | field: string; |
| 133 | /** @default "cascade" */ | 120 | /** @default "cascade" */ |
| 134 | onDelete?: | 121 | onDelete?: "no action" | "restrict" | "cascade" | "set null" | "set default"; |
| 135 | | "no action" | ||
| 136 | | "restrict" | ||
| 137 | | "cascade" | ||
| 138 | | "set null" | ||
| 139 | | "set default"; | ||
| 140 | } | 122 | } |
| 141 | | undefined; | 123 | | undefined; |
| 142 | unique?: boolean | undefined; | 124 | unique?: boolean | undefined; |
| @@ -18,20 +18,12 @@ · 17 unmodified lines | |||
| 18 | import type { Effect } from "effect"; | 18 | import type { Effect } from "effect"; |
| 19 | 19 | ||
| 20 | import type { DBAdapter, StorageFailure, Where } from "./adapter"; | 20 | import type { DBAdapter, StorageFailure, Where } from "./adapter"; |
| 21 | import type { | 21 | import type { DBSchema, InferDBFieldsInput, InferDBFieldsOutput } from "./schema"; |
| 22 | DBSchema, | ||
| 23 | InferDBFieldsInput, | ||
| 24 | InferDBFieldsOutput, | ||
| 25 | } from "./schema"; | ||
| 26 | 22 | ||
| 27 | type RowInput<S extends DBSchema, M extends keyof S> = InferDBFieldsInput< | 23 | type RowInput<S extends DBSchema, M extends keyof S> = InferDBFieldsInput<S[M]["fields"]> & |
| 28 | S[M]["fields"] | ||
| 29 | > & | ||
| 30 | Record<string, unknown>; | 24 | Record<string, unknown>; |
| 31 | 25 | ||
| 32 | type RowOutput<S extends DBSchema, M extends keyof S> = InferDBFieldsOutput< | 26 | type RowOutput<S extends DBSchema, M extends keyof S> = InferDBFieldsOutput<S[M]["fields"]> & |
| 33 | S[M]["fields"] | ||
| 34 | > & | ||
| 35 | Record<string, unknown>; | 27 | Record<string, unknown>; |
| 36 | 28 | ||
| 37 | /** | 29 | /** |
| @@ -103,9 +95,7 @@ export interface TypedAdapter<S extends DBSchema, E = StorageFailure> { · 65 unmodified lines | |||
| 103 | * this is a typed re-export of the adapter's methods. Pass the schema as | 95 | * this is a typed re-export of the adapter's methods. Pass the schema as |
| 104 | * a type parameter; the adapter argument is the normal untyped one. | 96 | * a type parameter; the adapter argument is the normal untyped one. |
| 105 | */ | 97 | */ |
| 106 | export const typedAdapter = <S extends DBSchema>( | 98 | export const typedAdapter = <S extends DBSchema>(adapter: DBAdapter): TypedAdapter<S> => ({ |
| 107 | adapter: DBAdapter, | ||
| 108 | ): TypedAdapter<S> => ({ | ||
| 109 | raw: adapter, | 99 | raw: adapter, |
| 110 | create: adapter.create as TypedAdapter<S>["create"], | 100 | create: adapter.create as TypedAdapter<S>["create"], |
| 111 | createMany: adapter.createMany as TypedAdapter<S>["createMany"], | 101 | createMany: adapter.createMany as TypedAdapter<S>["createMany"], |
| @@ -10,8 +10,7 @@ import { StorageError, UniqueViolationError } from "@executor-js/storage-core"; · 9 unmodified lines | |||
| 10 | 10 | ||
| 11 | import { isTransientStorageError } from "./adapter"; | 11 | import { isTransientStorageError } from "./adapter"; |
| 12 | 12 | ||
| 13 | const transientMsg = | 13 | const transientMsg = "[storage-drizzle] findOne select failed: Network connection lost."; |
| 14 | "[storage-drizzle] findOne select failed: Network connection lost."; | ||
| 15 | 14 | ||
| 16 | describe("isTransientStorageError", () => { | 15 | describe("isTransientStorageError", () => { |
| 17 | it.for([ | 16 | it.for([ |
| @@ -20,9 +19,7 @@ describe("isTransientStorageError", () => { · 2 unmodified lines | |||
| 20 | "[storage-drizzle] insert returning failed: ECONNRESET", | 19 | "[storage-drizzle] insert returning failed: ECONNRESET", |
| 21 | "[storage-drizzle] findMany select failed: Connection terminated unexpectedly", | 20 | "[storage-drizzle] findMany select failed: Connection terminated unexpectedly", |
| 22 | ])("returns true for %s", (message) => { | 21 | ])("returns true for %s", (message) => { |
| 23 | expect( | 22 | expect(isTransientStorageError(new StorageError({ message, cause: null }))).toBe(true); |
| 24 | isTransientStorageError(new StorageError({ message, cause: null })), | ||
| 25 | ).toBe(true); | ||
| 26 | }); | 23 | }); |
| 27 | 24 | ||
| 28 | it("returns false for unique violation", () => { | 25 | it("returns false for unique violation", () => { |
| @@ -52,9 +49,7 @@ describe("transient retry policy", () => { · 23 unmodified lines | |||
| 52 | const result = yield* Effect.suspend(() => { | 49 | const result = yield* Effect.suspend(() => { |
| 53 | calls++; | 50 | calls++; |
| 54 | if (calls < 3) { | 51 | if (calls < 3) { |
| 55 | return Effect.fail( | 52 | return Effect.fail(new StorageError({ message: transientMsg, cause: null })); |
| 56 | new StorageError({ message: transientMsg, cause: null }), | ||
| 57 | ); | ||
| 58 | } | 53 | } |
| 59 | return Effect.succeed("ok"); | 54 | return Effect.succeed("ok"); |
| 60 | }).pipe(Effect.retry(retryPolicy)); | 55 | }).pipe(Effect.retry(retryPolicy)); |
| @@ -70,9 +65,7 @@ describe("transient retry policy", () => { · 9 unmodified lines | |||
| 70 | const exit = yield* Effect.exit( | 65 | const exit = yield* Effect.exit( |
| 71 | Effect.suspend(() => { | 66 | Effect.suspend(() => { |
| 72 | calls++; | 67 | calls++; |
| 73 | return Effect.fail( | 68 | return Effect.fail(new StorageError({ message: transientMsg, cause: null })); |
| 74 | new StorageError({ message: transientMsg, cause: null }), | ||
| 75 | ); | ||
| 76 | }).pipe(Effect.retry(retryPolicy)), | 69 | }).pipe(Effect.retry(retryPolicy)), |
| 77 | ); | 70 | ); |
| 78 | 71 | ||
| @@ -28,16 +28,12 @@ export interface MakeSqliteAdapterOptions { · 27 unmodified lines | |||
| 28 | readonly generateId?: () => string; | 28 | readonly generateId?: () => string; |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | export const makeSqliteAdapter = ( | 31 | export const makeSqliteAdapter = (options: MakeSqliteAdapterOptions): DBAdapter => |
| 32 | options: MakeSqliteAdapterOptions, | ||
| 33 | ): DBAdapter => | ||
| 34 | drizzleAdapter({ | 32 | drizzleAdapter({ |
| 35 | db: options.db, | 33 | db: options.db, |
| 36 | schema: options.schema, | 34 | schema: options.schema, |
| 37 | provider: "sqlite", | 35 | provider: "sqlite", |
| 38 | adapterId: options.adapterId ?? "sqlite", | 36 | adapterId: options.adapterId ?? "sqlite", |
| 39 | supportsTransaction: true, | 37 | supportsTransaction: true, |
| 40 | customIdGenerator: options.generateId | 38 | customIdGenerator: options.generateId ? () => options.generateId!() : undefined, |
| 41 | ? () => options.generateId!() | ||
| 42 | : undefined, | ||
| 43 | }); | 39 | }); |
| @@ -41,21 +41,14 @@ const wrapErr = · 40 unmodified lines | |||
| 41 | cause, | 41 | cause, |
| 42 | }); | 42 | }); |
| 43 | 43 | ||
| 44 | export const makeSqliteBlobStore = ( | 44 | export const makeSqliteBlobStore = (options: MakeSqliteBlobStoreOptions): BlobStore => ({ |
| 45 | options: MakeSqliteBlobStoreOptions, | ||
| 46 | ): BlobStore => ({ | ||
| 47 | get: (namespace, key) => | 45 | get: (namespace, key) => |
| 48 | Effect.try({ | 46 | Effect.try({ |
| 49 | try: () => | 47 | try: () => |
| 50 | options.db | 48 | options.db |
| 51 | .select({ value: blobTable.value }) | 49 | .select({ value: blobTable.value }) |
| 52 | .from(blobTable) | 50 | .from(blobTable) |
| 53 | .where( | 51 | .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key))) |
| 54 | and( | ||
| 55 | eq(blobTable.namespace, namespace), | ||
| 56 | eq(blobTable.key, key), | ||
| 57 | ), | ||
| 58 | ) | ||
| 59 | .limit(1) | 52 | .limit(1) |
| 60 | .all() as ReadonlyArray<{ value: string }>, | 53 | .all() as ReadonlyArray<{ value: string }>, |
| 61 | catch: wrapErr("get"), | 54 | catch: wrapErr("get"), |
| @@ -72,12 +65,7 @@ export const makeSqliteBlobStore = ( · 10 unmodified lines | |||
| 72 | value: blobTable.value, | 65 | value: blobTable.value, |
| 73 | }) | 66 | }) |
| 74 | .from(blobTable) | 67 | .from(blobTable) |
| 75 | .where( | 68 | .where(and(inArray(blobTable.namespace, [...namespaces]), eq(blobTable.key, key))) |
| 76 | and( | ||
| 77 | inArray(blobTable.namespace, [...namespaces]), | ||
| 78 | eq(blobTable.key, key), | ||
| 79 | ), | ||
| 80 | ) | ||
| 81 | .all() as ReadonlyArray<{ namespace: string; value: string }>, | 69 | .all() as ReadonlyArray<{ namespace: string; value: string }>, |
| 82 | catch: wrapErr("getMany"), | 70 | catch: wrapErr("getMany"), |
| 83 | }).pipe( | 71 | }).pipe( |
| @@ -107,12 +95,7 @@ export const makeSqliteBlobStore = ( · 23 unmodified lines | |||
| 107 | try: () => | 95 | try: () => |
| 108 | options.db | 96 | options.db |
| 109 | .delete(blobTable) | 97 | .delete(blobTable) |
| 110 | .where( | 98 | .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key))) |
| 111 | and( | ||
| 112 | eq(blobTable.namespace, namespace), | ||
| 113 | eq(blobTable.key, key), | ||
| 114 | ), | ||
| 115 | ) | ||
| 116 | .run(), | 99 | .run(), |
| 117 | catch: wrapErr("delete"), | 100 | catch: wrapErr("delete"), |
| 118 | }).pipe(Effect.asVoid), | 101 | }).pipe(Effect.asVoid), |
| @@ -123,12 +106,7 @@ export const makeSqliteBlobStore = ( · 4 unmodified lines | |||
| 123 | options.db | 106 | options.db |
| 124 | .select({ one: drizzleSql<number>`1`.as("one") }) | 107 | .select({ one: drizzleSql<number>`1`.as("one") }) |
| 125 | .from(blobTable) | 108 | .from(blobTable) |
| 126 | .where( | 109 | .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key))) |
| 127 | and( | ||
| 128 | eq(blobTable.namespace, namespace), | ||
| 129 | eq(blobTable.key, key), | ||
| 130 | ), | ||
| 131 | ) | ||
| 132 | .limit(1) | 110 | .limit(1) |
| 133 | .all() as ReadonlyArray<{ one: number }>, | 111 | .all() as ReadonlyArray<{ one: number }>, |
| 134 | catch: wrapErr("has"), | 112 | catch: wrapErr("has"), |
| @@ -2,19 +2,10 @@ import { Effect } from "effect"; · 1 unmodified line | |||
| 2 | import Database from "better-sqlite3"; | 2 | import Database from "better-sqlite3"; |
| 3 | import { drizzle } from "drizzle-orm/better-sqlite3"; | 3 | import { drizzle } from "drizzle-orm/better-sqlite3"; |
| 4 | import { relations, sql } from "drizzle-orm"; | 4 | import { relations, sql } from "drizzle-orm"; |
| 5 | import { | 5 | import { sqliteTable, text, real, integer, getTableConfig } from "drizzle-orm/sqlite-core"; |
| 6 | sqliteTable, | ||
| 7 | text, | ||
| 8 | real, | ||
| 9 | integer, | ||
| 10 | getTableConfig, | ||
| 11 | } from "drizzle-orm/sqlite-core"; | ||
| 12 | 6 | ||
| 13 | import type { DBAdapter } from "@executor-js/storage-core"; | 7 | import type { DBAdapter } from "@executor-js/storage-core"; |
| 14 | import { | 8 | import { conformanceSchema, runAdapterConformance } from "@executor-js/storage-core/testing"; |
| 15 | conformanceSchema, | ||
| 16 | runAdapterConformance, | ||
| 17 | } from "@executor-js/storage-core/testing"; | ||
| 18 | 9 | ||
| 19 | import { makeSqliteAdapter } from "./index"; | 10 | import { makeSqliteAdapter } from "./index"; |
| 20 | 11 | ||
| @@ -91,11 +82,7 @@ const bootstrapTables = ( · 70 unmodified lines | |||
| 91 | if (col.isUnique) parts.push("UNIQUE"); | 82 | if (col.isUnique) parts.push("UNIQUE"); |
| 92 | return parts.join(" "); | 83 | return parts.join(" "); |
| 93 | }); | 84 | }); |
| 94 | db.run( | 85 | db.run(sql.raw(`CREATE TABLE IF NOT EXISTS "${config.name}" (${cols.join(", ")})`)); |
| 95 | sql.raw( | ||
| 96 | `CREATE TABLE IF NOT EXISTS "${config.name}" (${cols.join(", ")})`, | ||
| 97 | ), | ||
| 98 | ); | ||
| 99 | } catch { | 86 | } catch { |
| 100 | // Not a table (e.g. relations) — skip | 87 | // Not a table (e.g. relations) — skip |
| 101 | } | 88 | } |
| @@ -25,16 +25,12 @@ export interface MakePostgresAdapterOptions { · 24 unmodified lines | |||
| 25 | readonly generateId?: () => string; | 25 | readonly generateId?: () => string; |
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | export const makePostgresAdapter = ( | 28 | export const makePostgresAdapter = (options: MakePostgresAdapterOptions): DBAdapter => |
| 29 | options: MakePostgresAdapterOptions, | ||
| 30 | ): DBAdapter => | ||
| 31 | drizzleAdapter({ | 29 | drizzleAdapter({ |
| 32 | db: options.db, | 30 | db: options.db, |
| 33 | schema: options.schema, | 31 | schema: options.schema, |
| 34 | provider: "pg", | 32 | provider: "pg", |
| 35 | adapterId: options.adapterId ?? "postgres", | 33 | adapterId: options.adapterId ?? "postgres", |
| 36 | supportsTransaction: true, | 34 | supportsTransaction: true, |
| 37 | customIdGenerator: options.generateId | 35 | customIdGenerator: options.generateId ? () => options.generateId!() : undefined, |
| 38 | ? () => options.generateId!() | ||
| 39 | : undefined, | ||
| 40 | }); | 36 | }); |
| @@ -42,21 +42,14 @@ const wrapErr = · 41 unmodified lines | |||
| 42 | cause, | 42 | cause, |
| 43 | }); | 43 | }); |
| 44 | 44 | ||
| 45 | export const makePostgresBlobStore = ( | 45 | export const makePostgresBlobStore = (options: MakePostgresBlobStoreOptions): BlobStore => ({ |
| 46 | options: MakePostgresBlobStoreOptions, | ||
| 47 | ): BlobStore => ({ | ||
| 48 | get: (namespace, key) => | 46 | get: (namespace, key) => |
| 49 | Effect.tryPromise({ | 47 | Effect.tryPromise({ |
| 50 | try: async () => { | 48 | try: async () => { |
| 51 | const rows = await options.db | 49 | const rows = await options.db |
| 52 | .select({ value: blobTable.value }) | 50 | .select({ value: blobTable.value }) |
| 53 | .from(blobTable) | 51 | .from(blobTable) |
| 54 | .where( | 52 | .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key))) |
| 55 | and( | ||
| 56 | eq(blobTable.namespace, namespace), | ||
| 57 | eq(blobTable.key, key), | ||
| 58 | ), | ||
| 59 | ) | ||
| 60 | .limit(1); | 53 | .limit(1); |
| 61 | return rows[0]?.value ?? null; | 54 | return rows[0]?.value ?? null; |
| 62 | }, | 55 | }, |
| @@ -73,12 +66,7 @@ export const makePostgresBlobStore = ( · 10 unmodified lines | |||
| 73 | value: blobTable.value, | 66 | value: blobTable.value, |
| 74 | }) | 67 | }) |
| 75 | .from(blobTable) | 68 | .from(blobTable) |
| 76 | .where( | 69 | .where(and(inArray(blobTable.namespace, [...namespaces]), eq(blobTable.key, key))); |
| 77 | and( | ||
| 78 | inArray(blobTable.namespace, [...namespaces]), | ||
| 79 | eq(blobTable.key, key), | ||
| 80 | ), | ||
| 81 | ); | ||
| 82 | const out = new Map<string, string>(); | 70 | const out = new Map<string, string>(); |
| 83 | for (const row of rows) out.set(row.namespace, row.value); | 71 | for (const row of rows) out.set(row.namespace, row.value); |
| 84 | return out as ReadonlyMap<string, string>; | 72 | return out as ReadonlyMap<string, string>; |
| @@ -103,12 +91,7 @@ export const makePostgresBlobStore = ( · 18 unmodified lines | |||
| 103 | try: async () => { | 91 | try: async () => { |
| 104 | await options.db | 92 | await options.db |
| 105 | .delete(blobTable) | 93 | .delete(blobTable) |
| 106 | .where( | 94 | .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key))); |
| 107 | and( | ||
| 108 | eq(blobTable.namespace, namespace), | ||
| 109 | eq(blobTable.key, key), | ||
| 110 | ), | ||
| 111 | ); | ||
| 112 | }, | 95 | }, |
| 113 | catch: wrapErr("delete"), | 96 | catch: wrapErr("delete"), |
| 114 | }), | 97 | }), |
| @@ -118,12 +101,7 @@ export const makePostgresBlobStore = ( · 3 unmodified lines | |||
| 118 | const rows = await options.db | 101 | const rows = await options.db |
| 119 | .select({ key: blobTable.key }) | 102 | .select({ key: blobTable.key }) |
| 120 | .from(blobTable) | 103 | .from(blobTable) |
| 121 | .where( | 104 | .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key))) |
| 122 | and( | ||
| 123 | eq(blobTable.namespace, namespace), | ||
| 124 | eq(blobTable.key, key), | ||
| 125 | ), | ||
| 126 | ) | ||
| 127 | .limit(1); | 105 | .limit(1); |
| 128 | return rows.length > 0; | 106 | return rows.length > 0; |
| 129 | }, | 107 | }, |
| @@ -22,18 +22,13 @@ import { · 21 unmodified lines | |||
| 22 | } from "drizzle-orm/pg-core"; | 22 | } from "drizzle-orm/pg-core"; |
| 23 | 23 | ||
| 24 | import type { DBAdapter, StorageFailure } from "@executor-js/storage-core"; | 24 | import type { DBAdapter, StorageFailure } from "@executor-js/storage-core"; |
| 25 | import { | 25 | import { conformanceSchema, runAdapterConformance } from "@executor-js/storage-core/testing"; |
| 26 | conformanceSchema, | ||
| 27 | runAdapterConformance, | ||
| 28 | } from "@executor-js/storage-core/testing"; | ||
| 29 | 26 | ||
| 30 | import { makePostgresAdapter } from "./index"; | 27 | import { makePostgresAdapter } from "./index"; |
| 31 | 28 | ||
| 32 | const url = "postgresql://postgres:postgres@127.0.0.1:5435/postgres"; | 29 | const url = "postgresql://postgres:postgres@127.0.0.1:5435/postgres"; |
| 33 | 30 | ||
| 34 | class PostgresTestDatabaseError extends Data.TaggedError( | 31 | class PostgresTestDatabaseError extends Data.TaggedError("PostgresTestDatabaseError")<{ |
| 35 | "PostgresTestDatabaseError", | ||
| 36 | )<{ | ||
| 37 | readonly message: string; | 32 | readonly message: string; |
| 38 | readonly cause: unknown; | 33 | readonly cause: unknown; |
| 39 | }> {} | 34 | }> {} |
| @@ -153,7 +148,7 @@ const resetTables = Effect.gen(function* () { · 113 unmodified lines | |||
| 153 | try: () => | 148 | try: () => |
| 154 | sql`DROP TABLE IF EXISTS "source", "tag", "source_tag", "with_defaults", "blob" CASCADE`.then( | 149 | sql`DROP TABLE IF EXISTS "source", "tag", "source_tag", "with_defaults", "blob" CASCADE`.then( |
| 155 | () => undefined, | 150 | () => undefined, |
| 156 | ), | 151 | ), |
| 157 | catch: (cause) => | 152 | catch: (cause) => |
| 158 | new PostgresTestDatabaseError({ | 153 | new PostgresTestDatabaseError({ |
| 159 | message: "failed to reset postgres conformance tables", | 154 | message: "failed to reset postgres conformance tables", |
| @@ -5,9 +5,6 @@ · 4 unmodified lines | |||
| 5 | // wrapper around @executor-js/storage-drizzle. | 5 | // wrapper around @executor-js/storage-drizzle. |
| 6 | // --------------------------------------------------------------------------- | 6 | // --------------------------------------------------------------------------- |
| 7 | 7 | ||
| 8 | export { | 8 | export { makePostgresAdapter, type MakePostgresAdapterOptions } from "./adapter"; |
| 9 | makePostgresAdapter, | ||
| 10 | type MakePostgresAdapterOptions, | ||
| 11 | } from "./adapter"; | ||
| 12 | 9 | ||
| 13 | export { makePostgresBlobStore, blobTable } from "./blob-store"; | 10 | export { makePostgresBlobStore, blobTable } from "./blob-store"; |
| @@ -44,9 +44,6 @@ · 43 unmodified lines | |||
| 44 | "jiti": "^2.6.1", | 44 | "jiti": "^2.6.1", |
| 45 | "jsonc-parser": "^3.3.1" | 45 | "jsonc-parser": "^3.3.1" |
| 46 | }, | 46 | }, |
| 47 | "peerDependencies": { | ||
| 48 | "vite": "catalog:" | ||
| 49 | }, | ||
| 50 | "devDependencies": { | 47 | "devDependencies": { |
| 51 | "@effect/vitest": "catalog:", | 48 | "@effect/vitest": "catalog:", |
| 52 | "@types/node": "catalog:", | 49 | "@types/node": "catalog:", |
| @@ -54,5 +51,8 @@ · 1 unmodified line | |||
| 54 | "typescript": "catalog:", | 51 | "typescript": "catalog:", |
| 55 | "vite": "catalog:", | 52 | "vite": "catalog:", |
| 56 | "vitest": "catalog:" | 53 | "vitest": "catalog:" |
| 54 | }, | ||
| 55 | "peerDependencies": { | ||
| 56 | "vite": "catalog:" | ||
| 57 | } | 57 | } |
| 58 | } | 58 | } |
| @@ -56,10 +56,7 @@ const readJsoncPlugins = (path: string): readonly string[] => { · 55 unmodified lines | |||
| 56 | } | 56 | } |
| 57 | }; | 57 | }; |
| 58 | 58 | ||
| 59 | const tryResolveClient = ( | 59 | const tryResolveClient = (packageName: string, fromDir: string): string | null => { |
| 60 | packageName: string, | ||
| 61 | fromDir: string, | ||
| 62 | ): string | null => { | ||
| 63 | const require = createRequire(resolvePath(fromDir, "_anchor.js")); | 60 | const require = createRequire(resolvePath(fromDir, "_anchor.js")); |
| 64 | try { | 61 | try { |
| 65 | return require.resolve(`${packageName}/client`); | 62 | return require.resolve(`${packageName}/client`); |
| @@ -83,9 +80,7 @@ interface ExecutorVitePluginOptions { · 17 unmodified lines | |||
| 83 | readonly jsoncPath?: string; | 80 | readonly jsoncPath?: string; |
| 84 | } | 81 | } |
| 85 | 82 | ||
| 86 | export default function executorVitePlugin( | 83 | export default function executorVitePlugin(options: ExecutorVitePluginOptions = {}): Plugin { |
| 87 | options: ExecutorVitePluginOptions = {}, | ||
| 88 | ): Plugin { | ||
| 89 | let projectRoot: string = process.cwd(); | 84 | let projectRoot: string = process.cwd(); |
| 90 | let resolvedConfigPath: string | null = null; | 85 | let resolvedConfigPath: string | null = null; |
| 91 | let resolvedJsoncPath: string | null = null; | 86 | let resolvedJsoncPath: string | null = null; |
| @@ -93,13 +88,9 @@ export default function executorVitePlugin( · 1 unmodified line | |||
| 93 | 88 | ||
| 94 | const resolveConfigPath = (): string | null => { | 89 | const resolveConfigPath = (): string | null => { |
| 95 | if (resolvedConfigPath) return resolvedConfigPath; | 90 | if (resolvedConfigPath) return resolvedConfigPath; |
| 96 | const candidates = options.configPath | 91 | const candidates = options.configPath ? [options.configPath] : DEFAULT_CONFIG_CANDIDATES; |
| 97 | ? [options.configPath] | ||
| 98 | : DEFAULT_CONFIG_CANDIDATES; | ||
| 99 | for (const candidate of candidates) { | 92 | for (const candidate of candidates) { |
| 100 | const abs = isAbsolute(candidate) | 93 | const abs = isAbsolute(candidate) ? candidate : resolvePath(projectRoot, candidate); |
| 101 | ? candidate | ||
| 102 | : resolvePath(projectRoot, candidate); | ||
| 103 | if (existsSync(abs)) { | 94 | if (existsSync(abs)) { |
| 104 | resolvedConfigPath = abs; | 95 | resolvedConfigPath = abs; |
| 105 | return abs; | 96 | return abs; |
| @@ -110,13 +101,9 @@ export default function executorVitePlugin( · 4 unmodified lines | |||
| 110 | 101 | ||
| 111 | const resolveJsoncPath = (): string | null => { | 102 | const resolveJsoncPath = (): string | null => { |
| 112 | if (resolvedJsoncPath) return resolvedJsoncPath; | 103 | if (resolvedJsoncPath) return resolvedJsoncPath; |
| 113 | const candidates = options.jsoncPath | 104 | const candidates = options.jsoncPath ? [options.jsoncPath] : DEFAULT_JSONC_CANDIDATES; |
| 114 | ? [options.jsoncPath] | ||
| 115 | : DEFAULT_JSONC_CANDIDATES; | ||
| 116 | for (const candidate of candidates) { | 105 | for (const candidate of candidates) { |
| 117 | const abs = isAbsolute(candidate) | 106 | const abs = isAbsolute(candidate) ? candidate : resolvePath(projectRoot, candidate); |
| 118 | ? candidate | ||
| 119 | : resolvePath(projectRoot, candidate); | ||
| 120 | if (existsSync(abs)) { | 107 | if (existsSync(abs)) { |
| 121 | resolvedJsoncPath = abs; | 108 | resolvedJsoncPath = abs; |
| 122 | return abs; | 109 | return abs; |
| @@ -212,8 +199,7 @@ export default function executorVitePlugin( · 89 unmodified lines | |||
| 212 | } | 199 | } |
| 213 | 200 | ||
| 214 | cachedSource = | 201 | cachedSource = |
| 215 | `${lines.join("\n")}\n` + | 202 | `${lines.join("\n")}\n` + `export const plugins = [${exportExpressions.join(", ")}];\n`; |
| 216 | `export const plugins = [${exportExpressions.join(", ")}];\n`; | ||
| 217 | return cachedSource; | 203 | return cachedSource; |
| 218 | }; | 204 | }; |
| 219 | 205 | ||
| @@ -235,8 +221,7 @@ export default function executorVitePlugin( · 15 unmodified lines | |||
| 235 | const configPath = resolveConfigPath(); | 221 | const configPath = resolveConfigPath(); |
| 236 | const jsoncPath = resolveJsoncPath(); | 222 | const jsoncPath = resolveJsoncPath(); |
| 237 | const isWatched = | 223 | const isWatched = |
| 238 | (configPath && ctx.file === configPath) || | 224 | (configPath && ctx.file === configPath) || (jsoncPath && ctx.file === jsoncPath); |
| 239 | (jsoncPath && ctx.file === jsoncPath); | ||
| 240 | if (!isWatched) return undefined; | 225 | if (!isWatched) return undefined; |
| 241 | cachedSource = null; | 226 | cachedSource = null; |
| 242 | const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_ID); | 227 | const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_ID); |
| @@ -11,39 +11,41 @@ const cliEntry = resolve(repoRoot, "apps/cli/src/main.ts"); · 10 unmodified lines | |||
| 11 | const testScope = resolve(repoRoot, "apps/local"); | 11 | const testScope = resolve(repoRoot, "apps/local"); |
| 12 | 12 | ||
| 13 | describe("MCP stdio integration", () => { | 13 | describe("MCP stdio integration", () => { |
| 14 | it.effect("execute tool returns result over stdio transport", () => | 14 | it.effect( |
| 15 | Effect.gen(function* () { | 15 | "execute tool returns result over stdio transport", |
| 16 | // Fresh temp dir so the test doesn't migrate against the developer's | 16 | () => |
| 17 | // real ~/.executor/data.db. | 17 | Effect.gen(function* () { |
| 18 | const dataDir = mkdtempSync(join(tmpdir(), "executor-mcp-test-")); | 18 | // Fresh temp dir so the test doesn't migrate against the developer's |
| 19 | 19 | // real ~/.executor/data.db. | |
| 20 | const transport = new StdioClientTransport({ | 20 | const dataDir = mkdtempSync(join(tmpdir(), "executor-mcp-test-")); |
| 21 | command: "bun", | 21 | |
| 22 | args: ["run", cliEntry, "mcp", "--scope", testScope], | 22 | const transport = new StdioClientTransport({ |
| 23 | env: { ...process.env, EXECUTOR_DATA_DIR: dataDir }, | 23 | command: "bun", |
| 24 | }); | 24 | args: ["run", cliEntry, "mcp", "--scope", testScope], |
| 25 | 25 | env: { ...process.env, EXECUTOR_DATA_DIR: dataDir }, | |
| 26 | const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} }); | 26 | }); |
| 27 | 27 | ||
| 28 | yield* Effect.acquireRelease( | 28 | const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} }); |
| 29 | Effect.promise(() => client.connect(transport)), | 29 | |
| 30 | () => Effect.promise(() => transport.close()), | 30 | yield* Effect.acquireRelease( |
| 31 | ); | 31 | Effect.promise(() => client.connect(transport)), |
| 32 | 32 | () => Effect.promise(() => transport.close()), | |
| 33 | const { tools } = yield* Effect.promise(() => client.listTools()); | 33 | ); |
| 34 | expect(tools.map((t) => t.name)).toContain("execute"); | 34 | |
| 35 | 35 | const { tools } = yield* Effect.promise(() => client.listTools()); | |
| 36 | const result = yield* Effect.promise(() => | 36 | expect(tools.map((t) => t.name)).toContain("execute"); |
| 37 | client.callTool({ | 37 | |
| 38 | name: "execute", | 38 | const result = yield* Effect.promise(() => |
| 39 | arguments: { code: "return 2+2" }, | 39 | client.callTool({ |
| 40 | }), | 40 | name: "execute", |
| 41 | ); | 41 | arguments: { code: "return 2+2" }, |
| 42 | 42 | }), | |
| 43 | const text = (result.content as Array<{ type: string; text: string }>)[0]?.text; | 43 | ); |
| 44 | expect(text).toContain("4"); | 44 | |
| 45 | expect(result.isError).toBeFalsy(); | 45 | const text = (result.content as Array<{ type: string; text: string }>)[0]?.text; |
| 46 | }).pipe(Effect.scoped), | 46 | expect(text).toContain("4"); |
| 47 | expect(result.isError).toBeFalsy(); | ||
| 48 | }).pipe(Effect.scoped), | ||
| 47 | { timeout: 30_000 }, | 49 | { timeout: 30_000 }, |
| 48 | ); | 50 | ); |
| 49 | }); | 51 | }); |
| @@ -17,11 +17,7 @@ npm install @executor-js/codemode-core · 16 unmodified lines | |||
| 17 | Implement a runtime that satisfies `CodeExecutor`: | 17 | Implement a runtime that satisfies `CodeExecutor`: |
| 18 | 18 | ||
| 19 | ```ts | 19 | ```ts |
| 20 | import type { | 20 | import type { CodeExecutor, ExecuteResult, SandboxToolInvoker } from "@executor-js/codemode-core"; |
| 21 | CodeExecutor, | ||
| 22 | ExecuteResult, | ||
| 23 | SandboxToolInvoker, | ||
| 24 | } from "@executor-js/codemode-core"; | ||
| 25 | import { Effect } from "effect"; | 21 | import { Effect } from "effect"; |
| 26 | 22 | ||
| 27 | export const makeMyRuntime = (): CodeExecutor => ({ | 23 | export const makeMyRuntime = (): CodeExecutor => ({ |
| @@ -57,7 +57,9 @@ describe("recoverExecutionBody", () => { · 56 unmodified lines | |||
| 57 | }); | 57 | }); |
| 58 | 58 | ||
| 59 | it("strips markdown code fences", () => { | 59 | it("strips markdown code fences", () => { |
| 60 | expect(recoverExecutionBody("```js\nasync () => 42\n```")).toBe(wrapCallableBody("async () => 42")); | 60 | expect(recoverExecutionBody("```js\nasync () => 42\n```")).toBe( |
| 61 | wrapCallableBody("async () => 42"), | ||
| 62 | ); | ||
| 61 | }); | 63 | }); |
| 62 | 64 | ||
| 63 | it("strips typescript fences", () => { | 65 | it("strips typescript fences", () => { |
| @@ -121,13 +123,7 @@ describe("recoverExecutionBody", () => { · 57 unmodified lines | |||
| 121 | }); | 123 | }); |
| 122 | 124 | ||
| 123 | it("extracts code from prose wrapped fences", () => { | 125 | it("extracts code from prose wrapped fences", () => { |
| 124 | const code = [ | 126 | const code = ["Sure, here's the code:", "", "```ts", "async () => 42", "```"].join("\n"); |
| 125 | "Sure, here's the code:", | ||
| 126 | "", | ||
| 127 | "```ts", | ||
| 128 | "async () => 42", | ||
| 129 | "```", | ||
| 130 | ].join("\n"); | ||
| 131 | 127 | ||
| 132 | expect(recoverExecutionBody(code)).toBe(wrapCallableBody("async () => 42")); | 128 | expect(recoverExecutionBody(code)).toBe(wrapCallableBody("async () => 42")); |
| 133 | }); | 129 | }); |
| @@ -142,10 +138,7 @@ describe("recoverExecutionBody", () => { · 8 unmodified lines | |||
| 142 | }); | 138 | }); |
| 143 | 139 | ||
| 144 | it("handles block comments before export default", () => { | 140 | it("handles block comments before export default", () => { |
| 145 | const code = [ | 141 | const code = ["/* generated by the model */", "export default async () => 42"].join("\n"); |
| 146 | "/* generated by the model */", | ||
| 147 | "export default async () => 42", | ||
| 148 | ].join("\n"); | ||
| 149 | 142 | ||
| 150 | expect(recoverExecutionBody(code)).toBe(wrapCallableBody("async () => 42")); | 143 | expect(recoverExecutionBody(code)).toBe(wrapCallableBody("async () => 42")); |
| 151 | }); | 144 | }); |
| @@ -199,7 +192,7 @@ describe("recoverExecutionBody", () => { · 47 unmodified lines | |||
| 199 | " return { count: matches.length, top };", | 192 | " return { count: matches.length, top };", |
| 200 | "} catch (error) {", | 193 | "} catch (error) {", |
| 201 | " return {", | 194 | " return {", |
| 202 | ' error: error instanceof Error ? error.message : String(error),', | 195 | " error: error instanceof Error ? error.message : String(error),", |
| 203 | " query,", | 196 | " query,", |
| 204 | " };", | 197 | " };", |
| 205 | "}", | 198 | "}", |
| @@ -1,8 +1,7 @@ | |||
| 1 | import { parse } from "@babel/parser"; | 1 | import { parse } from "@babel/parser"; |
| 2 | 2 | ||
| 3 | const FENCED_CODE_BLOCK = /```(?:[^\n`]*)?\s*\n([\s\S]*?)```/i; | 3 | const FENCED_CODE_BLOCK = /```(?:[^\n`]*)?\s*\n([\s\S]*?)```/i; |
| 4 | const FUNCTION_DECLARATION = | 4 | const FUNCTION_DECLARATION = /^(?:async\s+)?function(?:\s+([a-zA-Z_$][a-zA-Z0-9_$]*))?\s*\(/; |
| 5 | /^(?:async\s+)?function(?:\s+([a-zA-Z_$][a-zA-Z0-9_$]*))?\s*\(/; | ||
| 6 | const CALLABLE_ERROR = "Code must evaluate to a function"; | 5 | const CALLABLE_ERROR = "Code must evaluate to a function"; |
| 7 | 6 | ||
| 8 | const extractCandidateSource = (code: string): string => { | 7 | const extractCandidateSource = (code: string): string => { |
| @@ -47,7 +46,9 @@ const unwrapExpression = (expression: { type: string; expression?: unknown }): u · 38 unmodified lines | |||
| 47 | case "TSTypeAssertion": | 46 | case "TSTypeAssertion": |
| 48 | case "TSNonNullExpression": | 47 | case "TSNonNullExpression": |
| 49 | case "TSInstantiationExpression": | 48 | case "TSInstantiationExpression": |
| 50 | return expression.expression ? unwrapExpression(expression.expression as { type: string }) : expression; | 49 | return expression.expression |
| 50 | ? unwrapExpression(expression.expression as { type: string }) | ||
| 51 | : expression; | ||
| 51 | default: | 52 | default: |
| 52 | return expression; | 53 | return expression; |
| 53 | } | 54 | } |
| @@ -76,11 +77,11 @@ const renderExportDefaultBody = ( · 22 unmodified lines | |||
| 76 | }; | 77 | }; |
| 77 | 78 | ||
| 78 | type ExportDefaultDeclarationNode = { | 79 | type ExportDefaultDeclarationNode = { |
| 79 | type: string; | 80 | type: string; |
| 80 | start?: number | null; | 81 | start?: number | null; |
| 81 | end?: number | null; | 82 | end?: number | null; |
| 82 | id?: { name?: string | null } | null; | 83 | id?: { name?: string | null } | null; |
| 83 | expression?: unknown; | 84 | expression?: unknown; |
| 84 | }; | 85 | }; |
| 85 | 86 | ||
| 86 | const renderParsedBody = (source: string): string => { | 87 | const renderParsedBody = (source: string): string => { |
| @@ -99,10 +100,13 @@ const renderParsedBody = (source: string): string => { · 12 unmodified lines | |||
| 99 | 100 | ||
| 100 | switch (statement.type) { | 101 | switch (statement.type) { |
| 101 | case "ExpressionStatement": { | 102 | case "ExpressionStatement": { |
| 102 | const expression = unwrapExpression(statement.expression as { type: string; expression?: unknown }) as { | 103 | const expression = unwrapExpression( |
| 104 | statement.expression as { type: string; expression?: unknown }, | ||
| 105 | ) as { | ||
| 103 | type?: string; | 106 | type?: string; |
| 104 | }; | 107 | }; |
| 105 | return expression?.type === "ArrowFunctionExpression" || expression?.type === "FunctionExpression" | 108 | return expression?.type === "ArrowFunctionExpression" || |
| 109 | expression?.type === "FunctionExpression" | ||
| 106 | ? wrapCallableBody(source) | 110 | ? wrapCallableBody(source) |
| 107 | : source; | 111 | : source; |
| 108 | } | 112 | } |
| @@ -6,7 +6,7 @@ describe("stripTypeScript", () => { · 5 unmodified lines | |||
| 6 | it("removes variable type annotations", () => { | 6 | it("removes variable type annotations", () => { |
| 7 | const out = stripTypeScript('const x: string = "hello"; return x;'); | 7 | const out = stripTypeScript('const x: string = "hello"; return x;'); |
| 8 | expect(out).not.toContain(": string"); | 8 | expect(out).not.toContain(": string"); |
| 9 | expect(out).toContain('const x'); | 9 | expect(out).toContain("const x"); |
| 10 | expect(out).toContain('"hello"'); | 10 | expect(out).toContain('"hello"'); |
| 11 | }); | 11 | }); |
| 12 | 12 | ||
| @@ -30,7 +30,9 @@ describe("stripTypeScript", () => { · 17 unmodified lines | |||
| 30 | }); | 30 | }); |
| 31 | 31 | ||
| 32 | it("removes interface declarations", () => { | 32 | it("removes interface declarations", () => { |
| 33 | const out = stripTypeScript("interface User { name: string; } const u = { name: 'a' }; return u;"); | 33 | const out = stripTypeScript( |
| 34 | "interface User { name: string; } const u = { name: 'a' }; return u;", | ||
| 35 | ); | ||
| 34 | expect(out).not.toContain("interface User"); | 36 | expect(out).not.toContain("interface User"); |
| 35 | expect(out).not.toContain(": string"); | 37 | expect(out).not.toContain(": string"); |
| 36 | expect(() => new Function(out)).not.toThrow(); | 38 | expect(() => new Function(out)).not.toThrow(); |
| @@ -167,23 +167,25 @@ describe.skipIf(!isDenoAvailable())("runtime-deno-subprocess", () => { · 166 unmodified lines | |||
| 167 | ); | 167 | ); |
| 168 | 168 | ||
| 169 | // Skipped in CI and on Windows — outbound HTTPS may be blocked by firewall/policy | 169 | // Skipped in CI and on Windows — outbound HTTPS may be blocked by firewall/policy |
| 170 | it.effect.skipIf(process.env["CI"] === "true" || process.platform === "win32")("network access can be allowed via permissions", () => | 170 | it.effect.skipIf(process.env["CI"] === "true" || process.platform === "win32")( |
| 171 | Effect.gen(function* () { | 171 | "network access can be allowed via permissions", |
| 172 | const executor = makeDenoSubprocessExecutor({ | 172 | () => |
| 173 | permissions: { | 173 | Effect.gen(function* () { |
| 174 | allowNet: true, | 174 | const executor = makeDenoSubprocessExecutor({ |
| 175 | }, | 175 | permissions: { |
| 176 | }); | 176 | allowNet: true, |
| 177 | const toolInvoker = makeTestInvoker({}); | 177 | }, |
| 178 | 178 | }); | |
| 179 | const output = yield* executor.execute( | 179 | const toolInvoker = makeTestInvoker({}); |
| 180 | ['const res = await fetch("https://example.com");', "return res.status;"].join("\n"), | 180 | |
| 181 | toolInvoker, | 181 | const output = yield* executor.execute( |
| 182 | ); | 182 | ['const res = await fetch("https://example.com");', "return res.status;"].join("\n"), |
| 183 | 183 | toolInvoker, | |
| 184 | expect(output.result).toBe(200); | 184 | ); |
| 185 | expect(output.error).toBeUndefined(); | 185 | |
| 186 | }), | 186 | expect(output.result).toBe(200); |
| 187 | expect(output.error).toBeUndefined(); | ||
| 188 | }), | ||
| 187 | ); | 189 | ); |
| 188 | 190 | ||
| 189 | it.effect("multiple sequential tool calls work correctly", () => | 191 | it.effect("multiple sequential tool calls work correctly", () => |
| @@ -92,9 +92,7 @@ const defaultDenoExecutable = (): string => { · 91 unmodified lines | |||
| 92 | const isWindows = process.platform === "win32"; | 92 | const isWindows = process.platform === "win32"; |
| 93 | const home = (process.env.HOME || process.env.USERPROFILE)?.trim(); | 93 | const home = (process.env.HOME || process.env.USERPROFILE)?.trim(); |
| 94 | if (home) { | 94 | if (home) { |
| 95 | const installedPath = isWindows | 95 | const installedPath = isWindows ? `${home}\\.deno\\bin\\deno.exe` : `${home}/.deno/bin/deno`; |
| 96 | ? `${home}\\.deno\\bin\\deno.exe` | ||
| 97 | : `${home}/.deno/bin/deno`; | ||
| 98 | const result = spawnSync(installedPath, ["--version"], { | 96 | const result = spawnSync(installedPath, ["--version"], { |
| 99 | stdio: "ignore", | 97 | stdio: "ignore", |
| 100 | timeout: 5000, | 98 | timeout: 5000, |
| @@ -33,11 +33,11 @@ const engine = createExecutionEngine({ · 32 unmodified lines | |||
| 33 | 33 | ||
| 34 | ### Options | 34 | ### Options |
| 35 | 35 | ||
| 36 | | Option | Default | Description | | 36 | | Option | Default | Description | |
| 37 | | ------------------- | ---------- | -------------------------------------------- | | 37 | | ------------------- | ---------- | --------------------------------- | |
| 38 | | `timeoutMs` | `5_000` | Max wall-clock time per execution | | 38 | | `timeoutMs` | `5_000` | Max wall-clock time per execution | |
| 39 | | `memoryLimitBytes` | `64 * 1MB` | Max memory the VM can allocate | | 39 | | `memoryLimitBytes` | `64 * 1MB` | Max memory the VM can allocate | |
| 40 | | `maxStackSizeBytes` | `1 * 1MB` | Max call-stack depth | | 40 | | `maxStackSizeBytes` | `1 * 1MB` | Max call-stack depth | |
| 41 | 41 | ||
| 42 | ### Swapping the QuickJS build | 42 | ### Swapping the QuickJS build |
| 43 | 43 | ||
| @@ -52,14 +52,6 @@ · 51 unmodified lines | |||
| 52 | "dependencies": { | 52 | "dependencies": { |
| 53 | "@executor-js/sdk": "workspace:*" | 53 | "@executor-js/sdk": "workspace:*" |
| 54 | }, | 54 | }, |
| 55 | "peerDependencies": { | ||
| 56 | "react": "catalog:" | ||
| 57 | }, | ||
| 58 | "peerDependenciesMeta": { | ||
| 59 | "react": { | ||
| 60 | "optional": true | ||
| 61 | } | ||
| 62 | }, | ||
| 63 | "devDependencies": { | 55 | "devDependencies": { |
| 64 | "@effect/vitest": "catalog:", | 56 | "@effect/vitest": "catalog:", |
| 65 | "@types/node": "catalog:", | 57 | "@types/node": "catalog:", |
| @@ -69,5 +61,13 @@ · 3 unmodified lines | |||
| 69 | "tsup": "catalog:", | 61 | "tsup": "catalog:", |
| 70 | "typescript": "catalog:", | 62 | "typescript": "catalog:", |
| 71 | "vitest": "catalog:" | 63 | "vitest": "catalog:" |
| 64 | }, | ||
| 65 | "peerDependencies": { | ||
| 66 | "react": "catalog:" | ||
| 67 | }, | ||
| 68 | "peerDependenciesMeta": { | ||
| 69 | "react": { | ||
| 70 | "optional": true | ||
| 71 | } | ||
| 72 | } | 72 | } |
| 73 | } | 73 | } |
| @@ -11,11 +11,7 @@ · 10 unmodified lines | |||
| 11 | // --------------------------------------------------------------------------- | 11 | // --------------------------------------------------------------------------- |
| 12 | 12 | ||
| 13 | import { useState } from "react"; | 13 | import { useState } from "react"; |
| 14 | import { | 14 | import { defineClientPlugin, createPluginAtomClient, useAtomSet } from "@executor-js/sdk/client"; |
| 15 | defineClientPlugin, | ||
| 16 | createPluginAtomClient, | ||
| 17 | useAtomSet, | ||
| 18 | } from "@executor-js/sdk/client"; | ||
| 19 | 15 | ||
| 20 | import { ExampleApi } from "./shared"; | 16 | import { ExampleApi } from "./shared"; |
| 21 | 17 | ||
| @@ -68,9 +64,7 @@ function ExamplePage() { · 46 unmodified lines | |||
| 68 | </button> | 64 | </button> |
| 69 | </div> | 65 | </div> |
| 70 | {result && ( | 66 | {result && ( |
| 71 | <pre style={{ marginTop: "1rem", padding: "0.5rem", background: "#f5f5f5" }}> | 67 | <pre style={{ marginTop: "1rem", padding: "0.5rem", background: "#f5f5f5" }}>{result}</pre> |
| 72 | {result} | ||
| 73 | </pre> | ||
| 74 | )} | 68 | )} |
| 75 | </div> | 69 | </div> |
| 76 | ); | 70 | ); |
| @@ -11,13 +11,7 @@ · 10 unmodified lines | |||
| 11 | // React and other browser-only deps live in `./client` — never here. | 11 | // React and other browser-only deps live in `./client` — never here. |
| 12 | // --------------------------------------------------------------------------- | 12 | // --------------------------------------------------------------------------- |
| 13 | 13 | ||
| 14 | import { | 14 | import { Context, definePlugin, Effect, HttpApi, HttpApiBuilder } from "@executor-js/sdk"; |
| 15 | Context, | ||
| 16 | definePlugin, | ||
| 17 | Effect, | ||
| 18 | HttpApi, | ||
| 19 | HttpApiBuilder, | ||
| 20 | } from "@executor-js/sdk"; | ||
| 21 | 15 | ||
| 22 | import { ExampleApi } from "./shared"; | 16 | import { ExampleApi } from "./shared"; |
| 23 | 17 | ||
| @@ -53,9 +53,7 @@ const WebHandler = Effect.acquireRelease( · 52 unmodified lines | |||
| 53 | Layer.provide(observabilityMiddleware(Api)), | 53 | Layer.provide(observabilityMiddleware(Api)), |
| 54 | Layer.provide(UnusedExecutor), | 54 | Layer.provide(UnusedExecutor), |
| 55 | Layer.provide(UnusedExecutionEngine), | 55 | Layer.provide(UnusedExecutionEngine), |
| 56 | Layer.provide( | 56 | Layer.provide(Layer.succeed(GoogleDiscoveryExtensionService, failingExtension)), |
| 57 | Layer.succeed(GoogleDiscoveryExtensionService, failingExtension), | ||
| 58 | ), | ||
| 59 | Layer.provideMerge(HttpServer.layerServices), | 57 | Layer.provideMerge(HttpServer.layerServices), |
| 60 | Layer.provideMerge(Layer.succeed(HttpRouter.RouterConfig)({ maxParamLength: 1000 })), | 58 | Layer.provideMerge(Layer.succeed(HttpRouter.RouterConfig)({ maxParamLength: 1000 })), |
| 61 | ), | 59 | ), |
| @@ -98,9 +96,7 @@ describe("GoogleDiscoveryHandlers", () => { · 36 unmodified lines | |||
| 98 | Layer.provide(observabilityMiddleware(Api)), | 96 | Layer.provide(observabilityMiddleware(Api)), |
| 99 | Layer.provide(UnusedExecutor), | 97 | Layer.provide(UnusedExecutor), |
| 100 | Layer.provide(UnusedExecutionEngine), | 98 | Layer.provide(UnusedExecutionEngine), |
| 101 | Layer.provide( | 99 | Layer.provide(Layer.succeed(GoogleDiscoveryExtensionService, extension)), |
| 102 | Layer.succeed(GoogleDiscoveryExtensionService, extension), | ||
| 103 | ), | ||
| 104 | Layer.provideMerge(HttpServer.layerServices), | 100 | Layer.provideMerge(HttpServer.layerServices), |
| 105 | Layer.provideMerge(Layer.succeed(HttpRouter.RouterConfig)({ maxParamLength: 1000 })), | 101 | Layer.provideMerge(Layer.succeed(HttpRouter.RouterConfig)({ maxParamLength: 1000 })), |
| 106 | ), | 102 | ), |
| @@ -130,27 +126,25 @@ describe("GoogleDiscoveryHandlers", () => { · 23 unmodified lines | |||
| 130 | }), | 126 | }), |
| 131 | ); | 127 | ); |
| 132 | 128 | ||
| 133 | it.effect( | 129 | it.effect("defect-returning methods produce an opaque InternalError, no leakage", () => |
| 134 | "defect-returning methods produce an opaque InternalError, no leakage", | 130 | Effect.gen(function* () { |
| 135 | () => | 131 | const web = yield* WebHandler; |
| 136 | Effect.gen(function* () { | 132 | const response = yield* Effect.promise(() => |
| 137 | const web = yield* WebHandler; | 133 | web.handler( |
| 138 | const response = yield* Effect.promise(() => | 134 | new Request("http://localhost/scopes/scope_1/google-discovery/probe", { |
| 139 | web.handler( | 135 | method: "POST", |
| 140 | new Request("http://localhost/scopes/scope_1/google-discovery/probe", { | 136 | headers: { "content-type": "application/json" }, |
| 141 | method: "POST", | 137 | body: JSON.stringify({ |
| 142 | headers: { "content-type": "application/json" }, | 138 | discoveryUrl: "https://example.googleapis.com/$discovery/rest?version=v1", |
| 143 | body: JSON.stringify({ | ||
| 144 | discoveryUrl: "https://example.googleapis.com/$discovery/rest?version=v1", | ||
| 145 | }), | ||
| 146 | }), | 139 | }), |
| 147 | HandlerContext, | 140 | }), |
| 148 | ), | 141 | HandlerContext, |
| 149 | ); | 142 | ), |
| 143 | ); | ||
| 150 | 144 | ||
| 151 | expect(response.status).toBe(500); | 145 | expect(response.status).toBe(500); |
| 152 | const body = yield* Effect.promise(() => response.text()); | 146 | const body = yield* Effect.promise(() => response.text()); |
| 153 | expect(body).not.toContain("Not implemented"); | 147 | expect(body).not.toContain("Not implemented"); |
| 154 | }), | 148 | }), |
| 155 | ); | 149 | ); |
| 156 | }); | 150 | }); |
| @@ -2,10 +2,7 @@ import { HttpApiBuilder } from "effect/unstable/httpapi"; · 1 unmodified line | |||
| 2 | import { Context, Effect } from "effect"; | 2 | import { Context, Effect } from "effect"; |
| 3 | 3 | ||
| 4 | import { addGroup, capture } from "@executor-js/api"; | 4 | import { addGroup, capture } from "@executor-js/api"; |
| 5 | import type { | 5 | import type { GoogleDiscoveryAddSourceInput, GoogleDiscoveryPluginExtension } from "../sdk/plugin"; |
| 6 | GoogleDiscoveryAddSourceInput, | ||
| 7 | GoogleDiscoveryPluginExtension, | ||
| 8 | } from "../sdk/plugin"; | ||
| 9 | import { GoogleDiscoveryStoredSourceSchema } from "../sdk/stored-source"; | 6 | import { GoogleDiscoveryStoredSourceSchema } from "../sdk/stored-source"; |
| 10 | import { GoogleDiscoveryGroup } from "./group"; | 7 | import { GoogleDiscoveryGroup } from "./group"; |
| 11 | 8 | ||
| @@ -20,7 +17,9 @@ import { GoogleDiscoveryGroup } from "./group"; · 8 unmodified lines | |||
| 20 | // `.addError(InternalError)` on the group — no per-handler translation. | 17 | // `.addError(InternalError)` on the group — no per-handler translation. |
| 21 | // --------------------------------------------------------------------------- | 18 | // --------------------------------------------------------------------------- |
| 22 | 19 | ||
| 23 | export class GoogleDiscoveryExtensionService extends Context.Service<GoogleDiscoveryExtensionService, GoogleDiscoveryPluginExtension | 20 | export class GoogleDiscoveryExtensionService extends Context.Service< |
| 21 | GoogleDiscoveryExtensionService, | ||
| 22 | GoogleDiscoveryPluginExtension | ||
| 24 | >()("GoogleDiscoveryExtensionService") {} | 23 | >()("GoogleDiscoveryExtensionService") {} |
| 25 | 24 | ||
| 26 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
| @@ -2,7 +2,6 @@ import { createPluginAtomClient } from "@executor-js/sdk/client"; · 1 unmodified line | |||
| 2 | import { getBaseUrl } from "@executor-js/react/api/base-url"; | 2 | import { getBaseUrl } from "@executor-js/react/api/base-url"; |
| 3 | import { GoogleDiscoveryGroup } from "../api/group"; | 3 | import { GoogleDiscoveryGroup } from "../api/group"; |
| 4 | 4 | ||
| 5 | export const GoogleDiscoveryClient = createPluginAtomClient( | 5 | export const GoogleDiscoveryClient = createPluginAtomClient(GoogleDiscoveryGroup, { |
| 6 | GoogleDiscoveryGroup, | 6 | baseUrl: getBaseUrl(), |
| 7 | { baseUrl: getBaseUrl() }, | 7 | }); |
| 8 | ); | ||
| @@ -1,6 +1,3 @@ | |||
| 1 | export { googleDiscoverySourcePlugin } from "./source-plugin"; | 1 | export { googleDiscoverySourcePlugin } from "./source-plugin"; |
| 2 | export { GoogleDiscoveryClient } from "./client"; | 2 | export { GoogleDiscoveryClient } from "./client"; |
| 3 | export { | 3 | export { addGoogleDiscoverySource, probeGoogleDiscovery } from "./atoms"; |
| 4 | addGoogleDiscoverySource, | ||
| 5 | probeGoogleDiscovery, | ||
| 6 | } from "./atoms"; | ||
| @@ -22,9 +22,7 @@ const LowercaseText = Schema.String.pipe( · 21 unmodified lines | |||
| 22 | ); | 22 | ); |
| 23 | const TextOption = Schema.OptionFromOptional(TrimmedString).pipe( | 23 | const TextOption = Schema.OptionFromOptional(TrimmedString).pipe( |
| 24 | Schema.decode({ | 24 | Schema.decode({ |
| 25 | decode: SchemaGetter.transform((value) => | 25 | decode: SchemaGetter.transform((value) => Option.filter(value, (text) => text.length > 0)), |
| 26 | Option.filter(value, (text) => text.length > 0), | ||
| 27 | ), | ||
| 28 | encode: SchemaGetter.transform((value) => value), | 26 | encode: SchemaGetter.transform((value) => value), |
| 29 | }), | 27 | }), |
| 30 | Schema.withDecodingDefaultType(Effect.succeed(Option.none())), | 28 | Schema.withDecodingDefaultType(Effect.succeed(Option.none())), |
| @@ -48,9 +46,7 @@ const DiscoveryParameterLocationInput = Schema.optional( · 17 unmodified lines | |||
| 48 | const DiscoveryDefaultValue = Schema.Union([Schema.String, Schema.Number, Schema.Boolean]); | 46 | const DiscoveryDefaultValue = Schema.Union([Schema.String, Schema.Number, Schema.Boolean]); |
| 49 | 47 | ||
| 50 | const DiscoverySchemaModel = Schema.Struct({ | 48 | const DiscoverySchemaModel = Schema.Struct({ |
| 51 | type: Schema.optional( | 49 | type: Schema.optional(LowercaseText), |
| 52 | LowercaseText, | ||
| 53 | ), | ||
| 54 | description: TextOption, | 50 | description: TextOption, |
| 55 | properties: UnknownRecordWithDefault, | 51 | properties: UnknownRecordWithDefault, |
| 56 | items: Schema.optional(Schema.Unknown), | 52 | items: Schema.optional(Schema.Unknown), |
| @@ -65,9 +61,7 @@ const DiscoverySchemaModel = Schema.Struct({ · 8 unmodified lines | |||
| 65 | type DiscoverySchema = typeof DiscoverySchemaModel.Type; | 61 | type DiscoverySchema = typeof DiscoverySchemaModel.Type; |
| 66 | 62 | ||
| 67 | const DiscoveryParameterModel = Schema.Struct({ | 63 | const DiscoveryParameterModel = Schema.Struct({ |
| 68 | type: Schema.optional( | 64 | type: Schema.optional(LowercaseText), |
| 69 | LowercaseText, | ||
| 70 | ), | ||
| 71 | description: TextOption, | 65 | description: TextOption, |
| 72 | properties: UnknownRecordWithDefault, | 66 | properties: UnknownRecordWithDefault, |
| 73 | items: Schema.optional(Schema.Unknown), | 67 | items: Schema.optional(Schema.Unknown), |
| @@ -446,7 +440,9 @@ const manifestMethodFromMethod = (input: { · 372 unmodified lines | |||
| 446 | hasBody: requestRef !== undefined, | 440 | hasBody: requestRef !== undefined, |
| 447 | }), | 441 | }), |
| 448 | inputSchema: Option.fromNullishOr(buildInputSchema({ parameters, requestRef })), | 442 | inputSchema: Option.fromNullishOr(buildInputSchema({ parameters, requestRef })), |
| 449 | outputSchema: Option.fromNullishOr(responseRef ? { $ref: schemaRef(responseRef) } : undefined), | 443 | outputSchema: Option.fromNullishOr( |
| 444 | responseRef ? { $ref: schemaRef(responseRef) } : undefined, | ||
| 445 | ), | ||
| 450 | scopes: method.scopes ?? [], | 446 | scopes: method.scopes ?? [], |
| 451 | }); | 447 | }); |
| 452 | }); | 448 | }); |
| @@ -497,20 +493,24 @@ export const extractGoogleDiscoveryManifest = Effect.fn("GoogleDiscovery.extract · 44 unmodified lines | |||
| 497 | ), | 493 | ), |
| 498 | ); | 494 | ); |
| 499 | 495 | ||
| 500 | const topLevelMethods = yield* Effect.forEach(Object.values(document.methods ?? {}), (rawMethod) => | 496 | const topLevelMethods = yield* Effect.forEach( |
| 501 | manifestMethodFromMethod({ | 497 | Object.values(document.methods ?? {}), |
| 502 | service, | 498 | (rawMethod) => |
| 503 | rawMethod, | 499 | manifestMethodFromMethod({ |
| 504 | globalParameters: document.parameters ?? {}, | 500 | service, |
| 505 | }), | 501 | rawMethod, |
| 502 | globalParameters: document.parameters ?? {}, | ||
| 503 | }), | ||
| 506 | ); | 504 | ); |
| 507 | 505 | ||
| 508 | const nestedMethods = yield* Effect.forEach(Object.values(document.resources ?? {}), (rawResource) => | 506 | const nestedMethods = yield* Effect.forEach( |
| 509 | collectMethods({ | 507 | Object.values(document.resources ?? {}), |
| 510 | service, | 508 | (rawResource) => |
| 511 | rawResource, | 509 | collectMethods({ |
| 512 | globalParameters: document.parameters ?? {}, | 510 | service, |
| 513 | }), | 511 | rawResource, |
| 512 | globalParameters: document.parameters ?? {}, | ||
| 513 | }), | ||
| 514 | ); | 514 | ); |
| 515 | 515 | ||
| 516 | return new GoogleDiscoveryManifest({ | 516 | return new GoogleDiscoveryManifest({ |
| @@ -3,10 +3,7 @@ import { Schema } from "effect"; · 2 unmodified lines | |||
| 3 | import { ScopeId } from "@executor-js/sdk/core"; | 3 | import { ScopeId } from "@executor-js/sdk/core"; |
| 4 | import { InternalError } from "@executor-js/api"; | 4 | import { InternalError } from "@executor-js/api"; |
| 5 | 5 | ||
| 6 | import { | 6 | import { GraphqlIntrospectionError, GraphqlExtractionError } from "../sdk/errors"; |
| 7 | GraphqlIntrospectionError, | ||
| 8 | GraphqlExtractionError, | ||
| 9 | } from "../sdk/errors"; | ||
| 10 | import { GraphqlSourceAuth, HeaderValue } from "../sdk/types"; | 7 | import { GraphqlSourceAuth, HeaderValue } from "../sdk/types"; |
| 11 | 8 | ||
| 12 | // StoredGraphqlSource shape as an HTTP response schema. Kept local to the | 9 | // StoredGraphqlSource shape as an HTTP response schema. Kept local to the |
| @@ -72,12 +69,8 @@ const AddSourceResponse = Schema.Struct({ · 59 unmodified lines | |||
| 72 | // Errors with HTTP status | 69 | // Errors with HTTP status |
| 73 | // --------------------------------------------------------------------------- | 70 | // --------------------------------------------------------------------------- |
| 74 | 71 | ||
| 75 | const IntrospectionError = GraphqlIntrospectionError.annotate( | 72 | const IntrospectionError = GraphqlIntrospectionError.annotate({ httpApiStatus: 400 }); |
| 76 | { httpApiStatus: 400 }, | 73 | const ExtractionError = GraphqlExtractionError.annotate({ httpApiStatus: 400 }); |
| 77 | ); | ||
| 78 | const ExtractionError = GraphqlExtractionError.annotate( | ||
| 79 | { httpApiStatus: 400 }, | ||
| 80 | ); | ||
| 81 | 74 | ||
| 82 | // --------------------------------------------------------------------------- | 75 | // --------------------------------------------------------------------------- |
| 83 | // Group | 76 | // Group |
| @@ -119,6 +112,6 @@ export const GraphqlGroup = HttpApiGroup.make("graphql") · 35 unmodified lines | |||
| 119 | success: UpdateSourceResponse, | 112 | success: UpdateSourceResponse, |
| 120 | error: GraphqlErrors, | 113 | error: GraphqlErrors, |
| 121 | }), | 114 | }), |
| 122 | ) | 115 | ); |
| 123 | // Plugin domain errors carry their own HTTP status (4xx); | 116 | // Plugin domain errors carry their own HTTP status (4xx); |
| 124 | // `InternalError` is the shared opaque 500 translated at the HTTP edge. | 117 | // `InternalError` is the shared opaque 500 translated at the HTTP edge. |
| @@ -2,11 +2,7 @@ import { HttpApiBuilder } from "effect/unstable/httpapi"; · 1 unmodified line | |||
| 2 | import { Context, Effect } from "effect"; | 2 | import { Context, Effect } from "effect"; |
| 3 | 3 | ||
| 4 | import { addGroup, capture } from "@executor-js/api"; | 4 | import { addGroup, capture } from "@executor-js/api"; |
| 5 | import type { | 5 | import type { GraphqlPluginExtension, HeaderValue, GraphqlUpdateSourceInput } from "../sdk/plugin"; |
| 6 | GraphqlPluginExtension, | ||
| 7 | HeaderValue, | ||
| 8 | GraphqlUpdateSourceInput, | ||
| 9 | } from "../sdk/plugin"; | ||
| 10 | import { GraphqlGroup } from "./group"; | 6 | import { GraphqlGroup } from "./group"; |
| 11 | 7 | ||
| 12 | // --------------------------------------------------------------------------- | 8 | // --------------------------------------------------------------------------- |
| @@ -20,7 +16,10 @@ import { GraphqlGroup } from "./group"; · 7 unmodified lines | |||
| 20 | // `.addError(InternalError)` on the group — no per-handler translation. | 16 | // `.addError(InternalError)` on the group — no per-handler translation. |
| 21 | // --------------------------------------------------------------------------- | 17 | // --------------------------------------------------------------------------- |
| 22 | 18 | ||
| 23 | export class GraphqlExtensionService extends Context.Service<GraphqlExtensionService, GraphqlPluginExtension>()("GraphqlExtensionService") {} | 19 | export class GraphqlExtensionService extends Context.Service< |
| 20 | GraphqlExtensionService, | ||
| 21 | GraphqlPluginExtension | ||
| 22 | >()("GraphqlExtensionService") {} | ||
| 24 | 23 | ||
| 25 | // --------------------------------------------------------------------------- | 24 | // --------------------------------------------------------------------------- |
| 26 | // Composed API — core + graphql group | 25 | // Composed API — core + graphql group |
| @@ -38,61 +37,50 @@ const ExecutorApiWithGraphql = addGroup(GraphqlGroup); · 11 unmodified lines | |||
| 38 | // by the API-level observability middleware. | 37 | // by the API-level observability middleware. |
| 39 | // --------------------------------------------------------------------------- | 38 | // --------------------------------------------------------------------------- |
| 40 | 39 | ||
| 41 | export const GraphqlHandlers = HttpApiBuilder.group( | 40 | export const GraphqlHandlers = HttpApiBuilder.group(ExecutorApiWithGraphql, "graphql", (handlers) => |
| 42 | ExecutorApiWithGraphql, | 41 | handlers |
| 43 | "graphql", | 42 | .handle("addSource", ({ params: path, payload }) => |
| 44 | (handlers) => | 43 | capture( |
| 45 | handlers | 44 | Effect.gen(function* () { |
| 46 | .handle("addSource", ({ params: path, payload }) => | 45 | const ext = yield* GraphqlExtensionService; |
| 47 | capture( | 46 | const result = yield* ext.addSource({ |
| 48 | Effect.gen(function* () { | 47 | endpoint: payload.endpoint, |
| 49 | const ext = yield* GraphqlExtensionService; | 48 | scope: path.scopeId, |
| 50 | const result = yield* ext.addSource({ | 49 | name: payload.name, |
| 51 | endpoint: payload.endpoint, | 50 | introspectionJson: payload.introspectionJson, |
| 52 | scope: path.scopeId, | 51 | namespace: payload.namespace, |
| 53 | name: payload.name, | 52 | headers: payload.headers as Record<string, HeaderValue> | undefined, |
| 54 | introspectionJson: payload.introspectionJson, | 53 | queryParams: payload.queryParams as Record<string, HeaderValue> | undefined, |
| 55 | namespace: payload.namespace, | 54 | auth: payload.auth, |
| 56 | headers: payload.headers as | 55 | }); |
| 57 | | Record<string, HeaderValue> | 56 | return { |
| 58 | | undefined, | 57 | toolCount: result.toolCount, |
| 59 | queryParams: payload.queryParams as | 58 | namespace: result.namespace, |
| 60 | | Record<string, HeaderValue> | 59 | }; |
| 61 | | undefined, | 60 | }), |
| 62 | auth: payload.auth, | ||
| 63 | }); | ||
| 64 | return { | ||
| 65 | toolCount: result.toolCount, | ||
| 66 | namespace: result.namespace, | ||
| 67 | }; | ||
| 68 | }), | ||
| 69 | ), | ||
| 70 | ) | ||
| 71 | .handle("getSource", ({ params: path }) => | ||
| 72 | capture( | ||
| 73 | Effect.gen(function* () { | ||
| 74 | const ext = yield* GraphqlExtensionService; | ||
| 75 | return yield* ext.getSource(path.namespace, path.scopeId); | ||
| 76 | }), | ||
| 77 | ), | ||
| 78 | ) | ||
| 79 | .handle("updateSource", ({ params: path, payload }) => | ||
| 80 | capture( | ||
| 81 | Effect.gen(function* () { | ||
| 82 | const ext = yield* GraphqlExtensionService; | ||
| 83 | yield* ext.updateSource(path.namespace, path.scopeId, { | ||
| 84 | name: payload.name, | ||
| 85 | endpoint: payload.endpoint, | ||
| 86 | headers: payload.headers as | ||
| 87 | | Record<string, HeaderValue> | ||
| 88 | | undefined, | ||
| 89 | queryParams: payload.queryParams as | ||
| 90 | | Record<string, HeaderValue> | ||
| 91 | | undefined, | ||
| 92 | auth: payload.auth, | ||
| 93 | } as GraphqlUpdateSourceInput); | ||
| 94 | return { updated: true }; | ||
| 95 | }), | ||
| 96 | ), | ||
| 97 | ), | 61 | ), |
| 62 | ) | ||
| 63 | .handle("getSource", ({ params: path }) => | ||
| 64 | capture( | ||
| 65 | Effect.gen(function* () { | ||
| 66 | const ext = yield* GraphqlExtensionService; | ||
| 67 | return yield* ext.getSource(path.namespace, path.scopeId); | ||
| 68 | }), | ||
| 69 | ), | ||
| 70 | ) | ||
| 71 | .handle("updateSource", ({ params: path, payload }) => | ||
| 72 | capture( | ||
| 73 | Effect.gen(function* () { | ||
| 74 | const ext = yield* GraphqlExtensionService; | ||
| 75 | yield* ext.updateSource(path.namespace, path.scopeId, { | ||
| 76 | name: payload.name, | ||
| 77 | endpoint: payload.endpoint, | ||
| 78 | headers: payload.headers as Record<string, HeaderValue> | undefined, | ||
| 79 | queryParams: payload.queryParams as Record<string, HeaderValue> | undefined, | ||
| 80 | auth: payload.auth, | ||
| 81 | } as GraphqlUpdateSourceInput); | ||
| 82 | return { updated: true }; | ||
| 83 | }), | ||
| 84 | ), | ||
| 85 | ), | ||
| 98 | ); | 86 | ); |
| @@ -25,7 +25,8 @@ export default function GraphqlSignInButton(props: { sourceId: string }) { · 24 unmodified lines | |||
| 25 | popupName: "graphql-oauth", | 25 | popupName: "graphql-oauth", |
| 26 | }); | 26 | }); |
| 27 | 27 | ||
| 28 | const source = AsyncResult.isSuccess(sourceResult) && sourceResult.value ? sourceResult.value : null; | 28 | const source = |
| 29 | AsyncResult.isSuccess(sourceResult) && sourceResult.value ? sourceResult.value : null; | ||
| 29 | const oauth2 = source?.auth.kind === "oauth2" ? source.auth : null; | 30 | const oauth2 = source?.auth.kind === "oauth2" ? source.auth : null; |
| 30 | const connections = AsyncResult.isSuccess(connectionsResult) ? connectionsResult.value : null; | 31 | const connections = AsyncResult.isSuccess(connectionsResult) ? connectionsResult.value : null; |
| 31 | const isConnected = | 32 | const isConnected = |
| @@ -3,5 +3,4 @@ import { · 2 unmodified lines | |||
| 3 | type HttpCredentialsState, | 3 | type HttpCredentialsState, |
| 4 | } from "@executor-js/react/plugins/http-credentials"; | 4 | } from "@executor-js/react/plugins/http-credentials"; |
| 5 | 5 | ||
| 6 | export const initialGraphqlCredentials = (): HttpCredentialsState => | 6 | export const initialGraphqlCredentials = (): HttpCredentialsState => emptyHttpCredentials(); |
| 7 | emptyHttpCredentials(); | ||
| @@ -226,9 +226,7 @@ export const introspect = Effect.fn("GraphQL.introspect")(function* ( · 225 unmodified lines | |||
| 226 | } | 226 | } |
| 227 | 227 | ||
| 228 | const raw = yield* response.json.pipe( | 228 | const raw = yield* response.json.pipe( |
| 229 | Effect.tapCause((cause) => | 229 | Effect.tapCause((cause) => Effect.logError("graphql introspection JSON parse failed", cause)), |
| 230 | Effect.logError("graphql introspection JSON parse failed", cause), | ||
| 231 | ), | ||
| 232 | Effect.mapError( | 230 | Effect.mapError( |
| 233 | () => | 231 | () => |
| 234 | new GraphqlIntrospectionError({ | 232 | new GraphqlIntrospectionError({ |
| @@ -20,8 +20,7 @@ export const resolveHeaders = ( · 19 unmodified lines | |||
| 20 | ); | 20 | ); |
| 21 | return resolveSecretBackedMap({ | 21 | return resolveSecretBackedMap({ |
| 22 | values: headers, | 22 | values: headers, |
| 23 | getSecret: (secretId) => | 23 | getSecret: (secretId) => secrets.get(secretId).pipe(Effect.catch(() => Effect.succeed(null))), |
| 24 | secrets.get(secretId).pipe(Effect.catch(() => Effect.succeed(null))), | ||
| 25 | missing: "drop", | 24 | missing: "drop", |
| 26 | onMissing: (name) => | 25 | onMissing: (name) => |
| 27 | new GraphqlInvocationError({ | 26 | new GraphqlInvocationError({ |
| @@ -64,15 +64,13 @@ export type QueryParamValue = typeof QueryParamValue.Type; · 63 unmodified lines | |||
| 64 | // Source auth | 64 | // Source auth |
| 65 | // --------------------------------------------------------------------------- | 65 | // --------------------------------------------------------------------------- |
| 66 | 66 | ||
| 67 | export const GraphqlSourceAuth = Schema.Union( | 67 | export const GraphqlSourceAuth = Schema.Union([ |
| 68 | [ | 68 | Schema.Struct({ kind: Schema.Literal("none") }), |
| 69 | Schema.Struct({ kind: Schema.Literal("none") }), | 69 | Schema.Struct({ |
| 70 | Schema.Struct({ | 70 | kind: Schema.Literal("oauth2"), |
| 71 | kind: Schema.Literal("oauth2"), | 71 | connectionId: Schema.String, |
| 72 | connectionId: Schema.String, | 72 | }), |
| 73 | }), | 73 | ]); |
| 74 | ], | ||
| 75 | ); | ||
| 76 | export type GraphqlSourceAuth = typeof GraphqlSourceAuth.Type; | 74 | export type GraphqlSourceAuth = typeof GraphqlSourceAuth.Type; |
| 77 | 75 | ||
| 78 | export class InvocationConfig extends Schema.Class<InvocationConfig>("InvocationConfig")({ | 76 | export class InvocationConfig extends Schema.Class<InvocationConfig>("InvocationConfig")({ |
| @@ -62,7 +62,9 @@ describe("keychain plugin", () => { · 61 unmodified lines | |||
| 62 | const resolved = yield* executor.secrets.get(testId); | 62 | const resolved = yield* executor.secrets.get(testId); |
| 63 | expect(resolved).toBe("keychain-test-value"); | 63 | expect(resolved).toBe("keychain-test-value"); |
| 64 | }).pipe( | 64 | }).pipe( |
| 65 | Effect.ensuring(executor.secrets.remove(testId).pipe(Effect.orElseSucceed(() => undefined))), | 65 | Effect.ensuring( |
| 66 | executor.secrets.remove(testId).pipe(Effect.orElseSucceed(() => undefined)), | ||
| 67 | ), | ||
| 66 | ); | 68 | ); |
| 67 | }), | 69 | }), |
| 68 | ); | 70 | ); |
| @@ -1,10 +1,6 @@ | |||
| 1 | import { Effect } from "effect"; | 1 | import { Effect } from "effect"; |
| 2 | 2 | ||
| 3 | import { | 3 | import { definePlugin, type PluginCtx, type SecretProvider } from "@executor-js/sdk/core"; |
| 4 | definePlugin, | ||
| 5 | type PluginCtx, | ||
| 6 | type SecretProvider, | ||
| 7 | } from "@executor-js/sdk/core"; | ||
| 8 | 4 | ||
| 9 | import { | 5 | import { |
| 10 | deletePassword, | 6 | deletePassword, |
| @@ -59,8 +55,7 @@ export type KeychainExtension = ReturnType<typeof makeKeychainExtension>; · 48 unmodified lines | |||
| 59 | const scopedServiceName = ( | 55 | const scopedServiceName = ( |
| 60 | ctx: PluginCtx<unknown>, | 56 | ctx: PluginCtx<unknown>, |
| 61 | options: KeychainPluginConfig | undefined, | 57 | options: KeychainPluginConfig | undefined, |
| 62 | ): string => | 58 | ): string => `${resolveServiceName(options?.serviceName)}/${ctx.scopes[0]!.id}`; |
| 63 | `${resolveServiceName(options?.serviceName)}/${ctx.scopes[0]!.id}`; | ||
| 64 | 59 | ||
| 65 | const makeKeychainExtension = ( | 60 | const makeKeychainExtension = ( |
| 66 | ctx: PluginCtx<unknown>, | 61 | ctx: PluginCtx<unknown>, |
| @@ -83,34 +78,26 @@ const makeKeychainExtension = ( · 16 unmodified lines | |||
| 83 | }; | 78 | }; |
| 84 | }; | 79 | }; |
| 85 | 80 | ||
| 86 | export const keychainPlugin = definePlugin( | 81 | export const keychainPlugin = definePlugin((options?: KeychainPluginConfig) => ({ |
| 87 | (options?: KeychainPluginConfig) => ({ | 82 | id: "keychain" as const, |
| 88 | id: "keychain" as const, | 83 | storage: () => ({}), |
| 89 | storage: () => ({}), | 84 | |
| 90 | 85 | extension: (ctx): KeychainExtension => makeKeychainExtension(ctx, options), | |
| 91 | extension: (ctx): KeychainExtension => makeKeychainExtension(ctx, options), | 86 | |
| 92 | 87 | secretProviders: (ctx): Effect.Effect<readonly SecretProvider[]> => | |
| 93 | secretProviders: (ctx): Effect.Effect<readonly SecretProvider[]> => | 88 | Effect.gen(function* () { |
| 94 | Effect.gen(function* () { | 89 | const serviceName = scopedServiceName(ctx, options); |
| 95 | const serviceName = scopedServiceName(ctx, options); | 90 | const reachable = yield* setPassword(serviceName, PROBE_ACCOUNT, PROBE_VALUE).pipe( |
| 96 | const reachable = yield* setPassword( | 91 | Effect.andThen( |
| 97 | serviceName, | 92 | deletePassword(serviceName, PROBE_ACCOUNT).pipe(Effect.catch(() => Effect.void)), |
| 98 | PROBE_ACCOUNT, | 93 | ), |
| 99 | PROBE_VALUE, | 94 | Effect.as(true), |
| 100 | ).pipe( | 95 | Effect.catch(() => |
| 101 | Effect.andThen( | 96 | Effect.logWarning("keychain unavailable, skipping provider registration").pipe( |
| 102 | deletePassword(serviceName, PROBE_ACCOUNT).pipe( | 97 | Effect.as(false), |
| 103 | Effect.catch(() => Effect.void), | ||
| 104 | ), | ||
| 105 | ), | ||
| 106 | Effect.as(true), | ||
| 107 | Effect.catch(() => | ||
| 108 | Effect.logWarning( | ||
| 109 | "keychain unavailable, skipping provider registration", | ||
| 110 | ).pipe(Effect.as(false)), | ||
| 111 | ), | 98 | ), |
| 112 | ); | 99 | ), |
| 113 | return reachable ? [makeKeychainProvider(serviceName)] : []; | 100 | ); |
| 114 | }), | 101 | return reachable ? [makeKeychainProvider(serviceName)] : []; |
| 115 | }), | 102 | }), |
| 116 | ); | 103 | })); |
| @@ -179,14 +179,13 @@ export const McpGroup = HttpApiGroup.make("mcp") · 178 unmodified lines | |||
| 179 | success: UpdateSourceResponse, | 179 | success: UpdateSourceResponse, |
| 180 | error: [InternalError, McpConnectionError, McpToolDiscoveryError], | 180 | error: [InternalError, McpConnectionError, McpToolDiscoveryError], |
| 181 | }), | 181 | }), |
| 182 | ) | 182 | ); |
| 183 | // Errors declared once at the group level — every endpoint inherits. | 183 | // Errors declared once at the group level — every endpoint inherits. |
| 184 | // Plugin domain errors carry their own HttpApiSchema status (4xx); | 184 | // Plugin domain errors carry their own HttpApiSchema status (4xx); |
| 185 | // `InternalError` is the shared opaque 500 translated at the HTTP | 185 | // `InternalError` is the shared opaque 500 translated at the HTTP |
| 186 | // edge by `withCapture`. We only list errors an MCP *group* | 186 | // edge by `withCapture`. We only list errors an MCP *group* |
| 187 | // endpoint can surface: `McpInvocationError` is thrown inside | 187 | // endpoint can surface: `McpInvocationError` is thrown inside |
| 188 | // `invokeTool` which is reached via the core `tools.invoke` | 188 | // `invokeTool` which is reached via the core `tools.invoke` |
| 189 | // endpoint, not any MCP-group endpoint, so it doesn't belong here. | 189 | // endpoint, not any MCP-group endpoint, so it doesn't belong here. |
| 190 | // OAuth errors live on the shared `/oauth/*` group in `@executor-js/api` | 190 | // OAuth errors live on the shared `/oauth/*` group in `@executor-js/api` |
| 191 | // now — the MCP group only declares its own plugin-domain errors. | 191 | // now — the MCP group only declares its own plugin-domain errors. |
| 192 | ; | ||
| @@ -69,25 +69,23 @@ const McpConnectionErrorResponse = Schema.Struct({ · 68 unmodified lines | |||
| 69 | }); | 69 | }); |
| 70 | 70 | ||
| 71 | describe("McpHandlers", () => { | 71 | describe("McpHandlers", () => { |
| 72 | it.effect( | 72 | it.effect("defect-returning methods produce an opaque InternalError, no leakage", () => |
| 73 | "defect-returning methods produce an opaque InternalError, no leakage", | 73 | Effect.gen(function* () { |
| 74 | () => | 74 | const web = yield* WebHandler; |
| 75 | Effect.gen(function* () { | 75 | const response = yield* Effect.promise(() => |
| 76 | const web = yield* WebHandler; | 76 | (web.handler as (request: Request) => Promise<Response>)( |
| 77 | const response = yield* Effect.promise(() => | 77 | new Request("http://localhost/scopes/scope_1/mcp/probe", { |
| 78 | (web.handler as (request: Request) => Promise<Response>)( | 78 | method: "POST", |
| 79 | new Request("http://localhost/scopes/scope_1/mcp/probe", { | 79 | headers: { "content-type": "application/json" }, |
| 80 | method: "POST", | 80 | body: JSON.stringify({ endpoint: "https://example.com/mcp" }), |
| 81 | headers: { "content-type": "application/json" }, | 81 | }), |
| 82 | body: JSON.stringify({ endpoint: "https://example.com/mcp" }), | 82 | ), |
| 83 | }), | 83 | ); |
| 84 | ), | ||
| 85 | ); | ||
| 86 | 84 | ||
| 87 | expect(response.status).toBe(500); | 85 | expect(response.status).toBe(500); |
| 88 | const body = yield* Effect.promise(() => response.text()); | 86 | const body = yield* Effect.promise(() => response.text()); |
| 89 | expect(body).not.toContain("Not implemented"); | 87 | expect(body).not.toContain("Not implemented"); |
| 90 | }), | 88 | }), |
| 91 | ); | 89 | ); |
| 92 | 90 | ||
| 93 | it.effect("domain MCP connection errors are encoded as 400 responses", () => | 91 | it.effect("domain MCP connection errors are encoded as 400 responses", () => |
| @@ -20,8 +20,9 @@ import { McpGroup } from "./group"; · 19 unmodified lines | |||
| 20 | // (no `Captured<>` inversion). | 20 | // (no `Captured<>` inversion). |
| 21 | // --------------------------------------------------------------------------- | 21 | // --------------------------------------------------------------------------- |
| 22 | 22 | ||
| 23 | export class McpExtensionService extends Context.Service<McpExtensionService, McpPluginExtension | 23 | export class McpExtensionService extends Context.Service<McpExtensionService, McpPluginExtension>()( |
| 24 | >()("McpExtensionService") {} | 24 | "McpExtensionService", |
| 25 | ) {} | ||
| 25 | 26 | ||
| 26 | // --------------------------------------------------------------------------- | 27 | // --------------------------------------------------------------------------- |
| 27 | // Composed API | 28 | // Composed API |
| @@ -1,9 +1,4 @@ | |||
| 1 | export { mcpSourcePlugin, createMcpSourcePlugin } from "./source-plugin"; | 1 | export { mcpSourcePlugin, createMcpSourcePlugin } from "./source-plugin"; |
| 2 | export type { McpSourcePluginOptions } from "./source-plugin"; | 2 | export type { McpSourcePluginOptions } from "./source-plugin"; |
| 3 | export { McpClient } from "./client"; | 3 | export { McpClient } from "./client"; |
| 4 | export { | 4 | export { probeMcpEndpoint, addMcpSource, removeMcpSource, refreshMcpSource } from "./atoms"; |
| 5 | probeMcpEndpoint, | ||
| 6 | addMcpSource, | ||
| 7 | removeMcpSource, | ||
| 8 | refreshMcpSource, | ||
| 9 | } from "./atoms"; | ||
| @@ -23,9 +23,7 @@ export interface McpSourcePluginOptions { · 22 unmodified lines | |||
| 23 | readonly allowStdio?: boolean; | 23 | readonly allowStdio?: boolean; |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | export const createMcpSourcePlugin = ( | 26 | export const createMcpSourcePlugin = (options?: McpSourcePluginOptions): SourcePlugin => { |
| 27 | options?: McpSourcePluginOptions, | ||
| 28 | ): SourcePlugin => { | ||
| 29 | const allowStdio = options?.allowStdio ?? false; | 27 | const allowStdio = options?.allowStdio ?? false; |
| 30 | 28 | ||
| 31 | const AddWithFlag: ComponentType<AddProps> = (props) => ( | 29 | const AddWithFlag: ComponentType<AddProps> = (props) => ( |
| @@ -163,11 +163,7 @@ describe("MCP connection pooling (regression)", () => { · 162 unmodified lines | |||
| 163 | // First invoke is necessarily a cache miss — it cold-handshakes the | 163 | // First invoke is necessarily a cache miss — it cold-handshakes the |
| 164 | // pooled connection. Every subsequent invoke must hit the cache and | 164 | // pooled connection. Every subsequent invoke must hit the cache and |
| 165 | // produce zero new MCP server sessions. | 165 | // produce zero new MCP server sessions. |
| 166 | yield* executor.tools.invoke( | 166 | yield* executor.tools.invoke(echo.id, { value: "1" }, { onElicitation: "accept-all" }); |
| 167 | echo.id, | ||
| 168 | { value: "1" }, | ||
| 169 | { onElicitation: "accept-all" }, | ||
| 170 | ); | ||
| 171 | const sessionsAfterFirstInvoke = server.sessionCount(); | 167 | const sessionsAfterFirstInvoke = server.sessionCount(); |
| 172 | expect(sessionsAfterFirstInvoke).toBe(sessionsAfterAddSource + 1); | 168 | expect(sessionsAfterFirstInvoke).toBe(sessionsAfterAddSource + 1); |
| 173 | 169 | ||
| @@ -185,44 +181,30 @@ describe("MCP connection pooling (regression)", () => { · 11 unmodified lines | |||
| 185 | }), | 181 | }), |
| 186 | ); | 182 | ); |
| 187 | 183 | ||
| 188 | it.effect( | 184 | it.effect("different tools on the same source share the cached connection", () => |
| 189 | "different tools on the same source share the cached connection", | 185 | Effect.gen(function* () { |
| 190 | () => | 186 | const server = yield* serveMcpServer; |
| 191 | Effect.gen(function* () { | 187 | const executor = yield* makeTestExecutor(server.url); |
| 192 | const server = yield* serveMcpServer; | 188 | const tools = yield* executor.tools.list(); |
| 193 | const executor = yield* makeTestExecutor(server.url); | 189 | const echo = tools.find((t) => t.name === "echo")!; |
| 194 | const tools = yield* executor.tools.list(); | 190 | const echo2 = tools.find((t) => t.name === "echo2")!; |
| 195 | const echo = tools.find((t) => t.name === "echo")!; | 191 | expect(echo).toBeDefined(); |
| 196 | const echo2 = tools.find((t) => t.name === "echo2")!; | 192 | expect(echo2).toBeDefined(); |
| 197 | expect(echo).toBeDefined(); | 193 | |
| 198 | expect(echo2).toBeDefined(); | 194 | const sessionsAfterAddSource = server.sessionCount(); |
| 199 | 195 | ||
| 200 | const sessionsAfterAddSource = server.sessionCount(); | 196 | // Cold handshake on first call. |
| 201 | 197 | yield* executor.tools.invoke(echo.id, { value: "a" }, { onElicitation: "accept-all" }); | |
| 202 | // Cold handshake on first call. | 198 | const baseline = server.sessionCount(); |
| 203 | yield* executor.tools.invoke( | 199 | expect(baseline).toBe(sessionsAfterAddSource + 1); |
| 204 | echo.id, | 200 | |
| 205 | { value: "a" }, | 201 | // Different tool on the SAME source — same cache key, same conn. |
| 206 | { onElicitation: "accept-all" }, | 202 | yield* executor.tools.invoke(echo2.id, { value: "b" }, { onElicitation: "accept-all" }); |
| 207 | ); | 203 | expect(server.sessionCount()).toBe(baseline); |
| 208 | const baseline = server.sessionCount(); | 204 | |
| 209 | expect(baseline).toBe(sessionsAfterAddSource + 1); | 205 | // Back to the first tool — still pooled. |
| 210 | 206 | yield* executor.tools.invoke(echo.id, { value: "c" }, { onElicitation: "accept-all" }); | |
| 211 | // Different tool on the SAME source — same cache key, same conn. | 207 | expect(server.sessionCount()).toBe(baseline); |
| 212 | yield* executor.tools.invoke( | 208 | }), |
| 213 | echo2.id, | ||
| 214 | { value: "b" }, | ||
| 215 | { onElicitation: "accept-all" }, | ||
| 216 | ); | ||
| 217 | expect(server.sessionCount()).toBe(baseline); | ||
| 218 | |||
| 219 | // Back to the first tool — still pooled. | ||
| 220 | yield* executor.tools.invoke( | ||
| 221 | echo.id, | ||
| 222 | { value: "c" }, | ||
| 223 | { onElicitation: "accept-all" }, | ||
| 224 | ); | ||
| 225 | expect(server.sessionCount()).toBe(baseline); | ||
| 226 | }), | ||
| 227 | ); | 209 | ); |
| 228 | }); | 210 | }); |
| @@ -53,10 +53,7 @@ const makeSharedOrgExecutors = () => · 52 unmodified lines | |||
| 53 | 53 | ||
| 54 | const makeFor = (innerId: ScopeId) => | 54 | const makeFor = (innerId: ScopeId) => |
| 55 | createExecutor({ | 55 | createExecutor({ |
| 56 | scopes: [ | 56 | scopes: [new Scope({ id: innerId, name: "Personal", createdAt: new Date() }), orgScope], |
| 57 | new Scope({ id: innerId, name: "Personal", createdAt: new Date() }), | ||
| 58 | orgScope, | ||
| 59 | ], | ||
| 60 | adapter, | 57 | adapter, |
| 61 | blobs, | 58 | blobs, |
| 62 | plugins, | 59 | plugins, |
| @@ -142,8 +139,7 @@ describe("MCP cross-user isolation within the same org", () => { · 79 unmodified lines | |||
| 142 | 139 | ||
| 143 | it.effect("A sees A's source, B sees org's, neither sees the other's inner", () => | 140 | it.effect("A sees A's source, B sees org's, neither sees the other's inner", () => |
| 144 | Effect.gen(function* () { | 141 | Effect.gen(function* () { |
| 145 | const { execA, execB, aInnerId, bInnerId, orgScopeId } = | 142 | const { execA, execB, aInnerId, bInnerId, orgScopeId } = yield* makeSharedOrgExecutors(); |
| 146 | yield* makeSharedOrgExecutors(); | ||
| 147 | 143 | ||
| 148 | yield* seedSource(execA.mcp.addSource, { | 144 | yield* seedSource(execA.mcp.addSource, { |
| 149 | scope: aInnerId, | 145 | scope: aInnerId, |
| @@ -145,8 +145,7 @@ describe("MCP elicitation (end-to-end)", () => { · 144 unmodified lines | |||
| 145 | gatedEcho.id, | 145 | gatedEcho.id, |
| 146 | { value: "nope" }, | 146 | { value: "nope" }, |
| 147 | { | 147 | { |
| 148 | onElicitation: () => | 148 | onElicitation: () => Effect.succeed(new ElicitationResponse({ action: "decline" })), |
| 149 | Effect.succeed(new ElicitationResponse({ action: "decline" })), | ||
| 150 | }, | 149 | }, |
| 151 | ); | 150 | ); |
| 152 | 151 | ||
| @@ -103,72 +103,69 @@ const failureError = <E>(exit: Exit.Exit<unknown, E>): E | undefined => · 102 unmodified lines | |||
| 103 | const isToolInvocationError = (error: unknown): error is ToolInvocationError => | 103 | const isToolInvocationError = (error: unknown): error is ToolInvocationError => |
| 104 | Predicate.isTagged(error, "ToolInvocationError"); | 104 | Predicate.isTagged(error, "ToolInvocationError"); |
| 105 | 105 | ||
| 106 | const createAuthRecordingServer: Effect.Effect<TestServer, Error, never> = | 106 | const createAuthRecordingServer: Effect.Effect<TestServer, Error, never> = Effect.callback< |
| 107 | Effect.callback<TestServer, Error>((resume) => { | 107 | TestServer, |
| 108 | const transports = new Map<string, StreamableHTTPServerTransport>(); | 108 | Error |
| 109 | const recorded: RecordedRequest[] = []; | 109 | >((resume) => { |
| 110 | 110 | const transports = new Map<string, StreamableHTTPServerTransport>(); | |
| 111 | const httpServer = http.createServer(async (req, res) => { | 111 | const recorded: RecordedRequest[] = []; |
| 112 | recorded.push({ | 112 | |
| 113 | authorization: req.headers["authorization"] as string | undefined, | 113 | const httpServer = http.createServer(async (req, res) => { |
| 114 | }); | 114 | recorded.push({ |
| 115 | authorization: req.headers["authorization"] as string | undefined, | ||
| 116 | }); | ||
| 115 | 117 | ||
| 116 | const sessionId = req.headers["mcp-session-id"] as string | undefined; | 118 | const sessionId = req.headers["mcp-session-id"] as string | undefined; |
| 117 | if (sessionId) { | 119 | if (sessionId) { |
| 118 | const transport = transports.get(sessionId); | 120 | const transport = transports.get(sessionId); |
| 119 | if (!transport) { | 121 | if (!transport) { |
| 120 | res.writeHead(404); | 122 | res.writeHead(404); |
| 121 | res.end("Session not found"); | 123 | res.end("Session not found"); |
| 122 | return; | ||
| 123 | } | ||
| 124 | await transport.handleRequest(req, res); | ||
| 125 | return; | 124 | return; |
| 126 | } | 125 | } |
| 127 | |||
| 128 | const mcpServer = new McpServer( | ||
| 129 | { name: "iso-test", version: "1.0.0" }, | ||
| 130 | { capabilities: {} }, | ||
| 131 | ); | ||
| 132 | mcpServer.registerTool( | ||
| 133 | "whoami", | ||
| 134 | { | ||
| 135 | description: "Echoes a marker — used to prove the invoke reached the server", | ||
| 136 | inputSchema: { marker: z.string() }, | ||
| 137 | }, | ||
| 138 | async ({ marker }: { marker: string }) => ({ | ||
| 139 | content: [{ type: "text" as const, text: `ok:${marker}` }], | ||
| 140 | }), | ||
| 141 | ); | ||
| 142 | |||
| 143 | const transport = new StreamableHTTPServerTransport({ | ||
| 144 | sessionIdGenerator: () => crypto.randomUUID(), | ||
| 145 | onsessioninitialized: (sid) => { | ||
| 146 | transports.set(sid, transport); | ||
| 147 | }, | ||
| 148 | }); | ||
| 149 | await mcpServer.connect(transport); | ||
| 150 | await transport.handleRequest(req, res); | 126 | await transport.handleRequest(req, res); |
| 151 | }); | 127 | return; |
| 128 | } | ||
| 129 | |||
| 130 | const mcpServer = new McpServer({ name: "iso-test", version: "1.0.0" }, { capabilities: {} }); | ||
| 131 | mcpServer.registerTool( | ||
| 132 | "whoami", | ||
| 133 | { | ||
| 134 | description: "Echoes a marker — used to prove the invoke reached the server", | ||
| 135 | inputSchema: { marker: z.string() }, | ||
| 136 | }, | ||
| 137 | async ({ marker }: { marker: string }) => ({ | ||
| 138 | content: [{ type: "text" as const, text: `ok:${marker}` }], | ||
| 139 | }), | ||
| 140 | ); | ||
| 152 | 141 | ||
| 153 | httpServer.listen(0, () => { | 142 | const transport = new StreamableHTTPServerTransport({ |
| 154 | const addr = httpServer.address(); | 143 | sessionIdGenerator: () => crypto.randomUUID(), |
| 155 | const port = typeof addr === "object" && addr ? addr.port : 0; | 144 | onsessioninitialized: (sid) => { |
| 156 | resume( | 145 | transports.set(sid, transport); |
| 157 | Effect.succeed({ | 146 | }, |
| 158 | url: `http://127.0.0.1:${port}`, | ||
| 159 | httpServer, | ||
| 160 | recorded: () => recorded, | ||
| 161 | }), | ||
| 162 | ); | ||
| 163 | }); | 147 | }); |
| 148 | await mcpServer.connect(transport); | ||
| 149 | await transport.handleRequest(req, res); | ||
| 164 | }); | 150 | }); |
| 165 | 151 | ||
| 166 | const serveMcpServer = Effect.acquireRelease( | 152 | httpServer.listen(0, () => { |
| 167 | createAuthRecordingServer, | 153 | const addr = httpServer.address(); |
| 168 | ({ httpServer }) => | 154 | const port = typeof addr === "object" && addr ? addr.port : 0; |
| 169 | Effect.sync(() => { | 155 | resume( |
| 170 | httpServer.close(); | 156 | Effect.succeed({ |
| 171 | }), | 157 | url: `http://127.0.0.1:${port}`, |
| 158 | httpServer, | ||
| 159 | recorded: () => recorded, | ||
| 160 | }), | ||
| 161 | ); | ||
| 162 | }); | ||
| 163 | }); | ||
| 164 | |||
| 165 | const serveMcpServer = Effect.acquireRelease(createAuthRecordingServer, ({ httpServer }) => | ||
| 166 | Effect.sync(() => { | ||
| 167 | httpServer.close(); | ||
| 168 | }), | ||
| 172 | ); | 169 | ); |
| 173 | 170 | ||
| 174 | // --------------------------------------------------------------------------- | 171 | // --------------------------------------------------------------------------- |
| @@ -181,8 +178,7 @@ const USER_A = ScopeId.make("user-a"); · 6 unmodified lines | |||
| 181 | const USER_B = ScopeId.make("user-b"); | 178 | const USER_B = ScopeId.make("user-b"); |
| 182 | const ORG = ScopeId.make("org"); | 179 | const ORG = ScopeId.make("org"); |
| 183 | 180 | ||
| 184 | const scope = (id: ScopeId, name: string): Scope => | 181 | const scope = (id: ScopeId, name: string): Scope => new Scope({ id, name, createdAt: new Date() }); |
| 185 | new Scope({ id, name, createdAt: new Date() }); | ||
| 186 | 182 | ||
| 187 | const makeLayeredMcpExecutors = () => | 183 | const makeLayeredMcpExecutors = () => |
| 188 | Effect.gen(function* () { | 184 | Effect.gen(function* () { |
| @@ -290,9 +286,7 @@ describe("per-user MCP auth isolation", () => { · 101 unmodified lines | |||
| 290 | content: [{ type: "text", text: "ok:from-user-a" }], | 286 | content: [{ type: "text", text: "ok:from-user-a" }], |
| 291 | }); | 287 | }); |
| 292 | const userARequests = server.recorded().slice(recordedBeforeUserA); | 288 | const userARequests = server.recorded().slice(recordedBeforeUserA); |
| 293 | expect( | 289 | expect(userARequests.some((r) => r.authorization === "Bearer token-user-a")).toBe(true); |
| 294 | userARequests.some((r) => r.authorization === "Bearer token-user-a"), | ||
| 295 | ).toBe(true); | ||
| 296 | 290 | ||
| 297 | // Snapshot the recorded-auth slice we'll check for the user-B | 291 | // Snapshot the recorded-auth slice we'll check for the user-B |
| 298 | // run — "token-user-a" must not appear in any request AFTER | 292 | // run — "token-user-a" must not appear in any request AFTER |
| @@ -344,95 +338,91 @@ describe("per-user MCP auth isolation", () => { · 45 unmodified lines | |||
| 344 | // must fail rather than silently fall through to any other scope's | 338 | // must fail rather than silently fall through to any other scope's |
| 345 | // value. This also locks the contract that the MCP plugin doesn't | 339 | // value. This also locks the contract that the MCP plugin doesn't |
| 346 | // quietly downgrade a missing secret to "no auth" on the transport. | 340 | // quietly downgrade a missing secret to "no auth" on the transport. |
| 347 | it.effect( | 341 | it.effect("header source: unauthenticated user B cannot invoke via a per-user secret", () => |
| 348 | "header source: unauthenticated user B cannot invoke via a per-user secret", | 342 | Effect.gen(function* () { |
| 349 | () => | 343 | const server = yield* serveMcpServer; |
| 350 | Effect.gen(function* () { | 344 | const { execUserA, execUserB } = yield* makeLayeredMcpExecutors(); |
| 351 | const server = yield* serveMcpServer; | 345 | |
| 352 | const { execUserA, execUserB } = yield* makeLayeredMcpExecutors(); | 346 | const SECRET = SecretId.make("shared-mcp-token"); |
| 347 | |||
| 348 | // User A plants their personal token at their per-user scope | ||
| 349 | // FIRST — so the subsequent addSource's discovery call can | ||
| 350 | // resolve it through user A's stack. | ||
| 351 | yield* execUserA.secrets.set( | ||
| 352 | new SetSecretInput({ | ||
| 353 | id: SECRET, | ||
| 354 | scope: USER_A, | ||
| 355 | name: "User A MCP token", | ||
| 356 | value: "token-user-a-header", | ||
| 357 | }), | ||
| 358 | ); | ||
| 353 | 359 | ||
| 354 | const SECRET = SecretId.make("shared-mcp-token"); | 360 | yield* execUserA.mcp.addSource({ |
| 361 | transport: "remote", | ||
| 362 | scope: ORG, | ||
| 363 | name: "Shared MCP (header)", | ||
| 364 | endpoint: server.url, | ||
| 365 | namespace: "iso_header", | ||
| 366 | auth: { | ||
| 367 | kind: "header", | ||
| 368 | headerName: "Authorization", | ||
| 369 | secretId: SECRET, | ||
| 370 | prefix: "Bearer ", | ||
| 371 | }, | ||
| 372 | }); | ||
| 355 | 373 | ||
| 356 | // User A plants their personal token at their per-user scope | 374 | // User A sanity invoke — server sees A's token. Asserting on |
| 357 | // FIRST — so the subsequent addSource's discovery call can | 375 | // the recorded header here guards against false-positive for |
| 358 | // resolve it through user A's stack. | 376 | // the negative check below (e.g. if the plugin silently dropped |
| 359 | yield* execUserA.secrets.set( | 377 | // auth, both invocations would fail for unrelated reasons). |
| 360 | new SetSecretInput({ | 378 | const userATools = yield* execUserA.tools.list(); |
| 361 | id: SECRET, | 379 | const whoamiForA = userATools.find((t) => t.name === "whoami")!; |
| 362 | scope: USER_A, | 380 | |
| 363 | name: "User A MCP token", | 381 | const recordedBeforeUserA = server.recorded().length; |
| 364 | value: "token-user-a-header", | 382 | const userAResult = yield* execUserA.tools.invoke( |
| 365 | }), | 383 | whoamiForA.id, |
| 366 | ); | 384 | { marker: "user-a-header" }, |
| 385 | { onElicitation: "accept-all" }, | ||
| 386 | ); | ||
| 387 | expect(userAResult).toMatchObject({ | ||
| 388 | content: [{ type: "text", text: "ok:user-a-header" }], | ||
| 389 | }); | ||
| 390 | const userARequests = server.recorded().slice(recordedBeforeUserA); | ||
| 391 | expect(userARequests.some((r) => r.authorization === "Bearer token-user-a-header")).toBe( | ||
| 392 | true, | ||
| 393 | ); | ||
| 367 | 394 | ||
| 368 | yield* execUserA.mcp.addSource({ | 395 | const recordedBeforeUserB = server.recorded().length; |
| 369 | transport: "remote", | ||
| 370 | scope: ORG, | ||
| 371 | name: "Shared MCP (header)", | ||
| 372 | endpoint: server.url, | ||
| 373 | namespace: "iso_header", | ||
| 374 | auth: { | ||
| 375 | kind: "header", | ||
| 376 | headerName: "Authorization", | ||
| 377 | secretId: SECRET, | ||
| 378 | prefix: "Bearer ", | ||
| 379 | }, | ||
| 380 | }); | ||
| 381 | 396 | ||
| 382 | // User A sanity invoke — server sees A's token. Asserting on | 397 | // User B has no personal token. The fall-through lookup walks |
| 383 | // the recorded header here guards against false-positive for | 398 | // [userB, org] — neither scope has the secret row, so the |
| 384 | // the negative check below (e.g. if the plugin silently dropped | 399 | // resolver must return null and the invoke must fail. |
| 385 | // auth, both invocations would fail for unrelated reasons). | 400 | const userBTools = yield* execUserB.tools.list(); |
| 386 | const userATools = yield* execUserA.tools.list(); | 401 | const whoamiForB = userBTools.find((t) => t.name === "whoami")!; |
| 387 | const whoamiForA = userATools.find((t) => t.name === "whoami")!; | ||
| 388 | 402 | ||
| 389 | const recordedBeforeUserA = server.recorded().length; | 403 | const userBResult = yield* Effect.exit( |
| 390 | const userAResult = yield* execUserA.tools.invoke( | 404 | execUserB.tools.invoke( |
| 391 | whoamiForA.id, | 405 | whoamiForB.id, |
| 392 | { marker: "user-a-header" }, | 406 | { marker: "user-b-header" }, |
| 393 | { onElicitation: "accept-all" }, | 407 | { onElicitation: "accept-all" }, |
| 394 | ); | 408 | ), |
| 395 | expect(userAResult).toMatchObject({ | 409 | ); |
| 396 | content: [{ type: "text", text: "ok:user-a-header" }], | ||
| 397 | }); | ||
| 398 | const userARequests = server.recorded().slice(recordedBeforeUserA); | ||
| 399 | expect( | ||
| 400 | userARequests.some( | ||
| 401 | (r) => r.authorization === "Bearer token-user-a-header", | ||
| 402 | ), | ||
| 403 | ).toBe(true); | ||
| 404 | |||
| 405 | const recordedBeforeUserB = server.recorded().length; | ||
| 406 | |||
| 407 | // User B has no personal token. The fall-through lookup walks | ||
| 408 | // [userB, org] — neither scope has the secret row, so the | ||
| 409 | // resolver must return null and the invoke must fail. | ||
| 410 | const userBTools = yield* execUserB.tools.list(); | ||
| 411 | const whoamiForB = userBTools.find((t) => t.name === "whoami")!; | ||
| 412 | |||
| 413 | const userBResult = yield* Effect.exit( | ||
| 414 | execUserB.tools.invoke( | ||
| 415 | whoamiForB.id, | ||
| 416 | { marker: "user-b-header" }, | ||
| 417 | { onElicitation: "accept-all" }, | ||
| 418 | ), | ||
| 419 | ); | ||
| 420 | |||
| 421 | expect(Exit.isFailure(userBResult)).toBe(true); | ||
| 422 | // tools.invoke wraps plugin failures in ToolInvocationError | ||
| 423 | // with the original error carried on `cause`. Pin the exact | ||
| 424 | // inner tag — a regression that swapped the "no connection | ||
| 425 | // found" check for a silent no-auth fallback would either | ||
| 426 | // succeed outright (leaking) or surface a different tag here. | ||
| 427 | const outer = failureError(userBResult); | ||
| 428 | expect(isToolInvocationError(outer)).toBe(true); | ||
| 429 | const inner = isToolInvocationError(outer) ? outer.cause : undefined; | ||
| 430 | expect(Predicate.isTagged(inner, "McpConnectionError")).toBe(true); | ||
| 431 | 410 | ||
| 432 | const afterUserB = server.recorded().slice(recordedBeforeUserB); | 411 | expect(Exit.isFailure(userBResult)).toBe(true); |
| 433 | for (const req of afterUserB) { | 412 | // tools.invoke wraps plugin failures in ToolInvocationError |
| 434 | expect(req.authorization).not.toBe("Bearer token-user-a-header"); | 413 | // with the original error carried on `cause`. Pin the exact |
| 435 | } | 414 | // inner tag — a regression that swapped the "no connection |
| 436 | }), | 415 | // found" check for a silent no-auth fallback would either |
| 416 | // succeed outright (leaking) or surface a different tag here. | ||
| 417 | const outer = failureError(userBResult); | ||
| 418 | expect(isToolInvocationError(outer)).toBe(true); | ||
| 419 | const inner = isToolInvocationError(outer) ? outer.cause : undefined; | ||
| 420 | expect(Predicate.isTagged(inner, "McpConnectionError")).toBe(true); | ||
| 421 | |||
| 422 | const afterUserB = server.recorded().slice(recordedBeforeUserB); | ||
| 423 | for (const req of afterUserB) { | ||
| 424 | expect(req.authorization).not.toBe("Bearer token-user-a-header"); | ||
| 425 | } | ||
| 426 | }), | ||
| 437 | ); | 427 | ); |
| 438 | }); | 428 | }); |
| @@ -129,10 +129,9 @@ describe("probeMcpEndpointShape", () => { · 128 unmodified lines | |||
| 129 | }), | 129 | }), |
| 130 | { status: 400, headers: { "content-type": "application/json" } }, | 130 | { status: 400, headers: { "content-type": "application/json" } }, |
| 131 | ); | 131 | ); |
| 132 | const result = yield* probeMcpEndpointShape( | 132 | const result = yield* probeMcpEndpointShape("https://backboard.railway.com/graphql/v2", { |
| 133 | "https://backboard.railway.com/graphql/v2", | 133 | fetch: stubFetch(response), |
| 134 | { fetch: stubFetch(response) }, | 134 | }); |
| 135 | ); | ||
| 136 | expect(result.kind).toBe("not-mcp"); | 135 | expect(result.kind).toBe("not-mcp"); |
| 137 | }), | 136 | }), |
| 138 | ); | 137 | ); |
| @@ -133,8 +133,7 @@ export const probeMcpEndpointShape = ( · 132 unmodified lines | |||
| 133 | } | 133 | } |
| 134 | return { | 134 | return { |
| 135 | kind: "not-mcp", | 135 | kind: "not-mcp", |
| 136 | reason: | 136 | reason: "401 without Bearer WWW-Authenticate — not an MCP auth challenge", |
| 137 | "401 without Bearer WWW-Authenticate — not an MCP auth challenge", | ||
| 138 | } as const; | 137 | } as const; |
| 139 | } | 138 | } |
| 140 | 139 | ||
| @@ -26,8 +26,6 @@ export const createStdioTransport = (config: StdioTransportConfig) => · 25 unmodified lines | |||
| 26 | new StdioClientTransport({ | 26 | new StdioClientTransport({ |
| 27 | command: config.command, | 27 | command: config.command, |
| 28 | args: config.args ? [...config.args] : undefined, | 28 | args: config.args ? [...config.args] : undefined, |
| 29 | env: config.env | 29 | env: config.env ? ({ ...process.env, ...config.env } as Record<string, string>) : undefined, |
| 30 | ? ({ ...process.env, ...config.env } as Record<string, string>) | ||
| 31 | : undefined, | ||
| 32 | cwd: config.cwd, | 30 | cwd: config.cwd, |
| 33 | }); | 31 | }); |
| @@ -16,7 +16,9 @@ import { OnePasswordGroup } from "./group"; · 15 unmodified lines | |||
| 16 | // `.addError(InternalError)` on the group — no per-handler translation. | 16 | // `.addError(InternalError)` on the group — no per-handler translation. |
| 17 | // --------------------------------------------------------------------------- | 17 | // --------------------------------------------------------------------------- |
| 18 | 18 | ||
| 19 | export class OnePasswordExtensionService extends Context.Service<OnePasswordExtensionService, OnePasswordExtension | 19 | export class OnePasswordExtensionService extends Context.Service< |
| 20 | OnePasswordExtensionService, | ||
| 21 | OnePasswordExtension | ||
| 20 | >()("OnePasswordExtensionService") {} | 22 | >()("OnePasswordExtensionService") {} |
| 21 | 23 | ||
| 22 | // --------------------------------------------------------------------------- | 24 | // --------------------------------------------------------------------------- |
| @@ -41,38 +43,48 @@ export const OnePasswordHandlers = HttpApiBuilder.group( · 18 unmodified lines | |||
| 41 | (handlers) => | 43 | (handlers) => |
| 42 | handlers | 44 | handlers |
| 43 | .handle("getConfig", () => | 45 | .handle("getConfig", () => |
| 44 | capture(Effect.gen(function* () { | 46 | capture( |
| 45 | const ext = yield* OnePasswordExtensionService; | 47 | Effect.gen(function* () { |
| 46 | return yield* ext.getConfig(); | 48 | const ext = yield* OnePasswordExtensionService; |
| 47 | })), | 49 | return yield* ext.getConfig(); |
| 50 | }), | ||
| 51 | ), | ||
| 48 | ) | 52 | ) |
| 49 | .handle("configure", ({ payload }) => | 53 | .handle("configure", ({ payload }) => |
| 50 | capture(Effect.gen(function* () { | 54 | capture( |
| 51 | const ext = yield* OnePasswordExtensionService; | 55 | Effect.gen(function* () { |
| 52 | yield* ext.configure(payload); | 56 | const ext = yield* OnePasswordExtensionService; |
| 53 | })), | 57 | yield* ext.configure(payload); |
| 58 | }), | ||
| 59 | ), | ||
| 54 | ) | 60 | ) |
| 55 | .handle("removeConfig", () => | 61 | .handle("removeConfig", () => |
| 56 | capture(Effect.gen(function* () { | 62 | capture( |
| 57 | const ext = yield* OnePasswordExtensionService; | 63 | Effect.gen(function* () { |
| 58 | yield* ext.removeConfig(); | 64 | const ext = yield* OnePasswordExtensionService; |
| 59 | })), | 65 | yield* ext.removeConfig(); |
| 66 | }), | ||
| 67 | ), | ||
| 60 | ) | 68 | ) |
| 61 | .handle("status", () => | 69 | .handle("status", () => |
| 62 | capture(Effect.gen(function* () { | 70 | capture( |
| 63 | const ext = yield* OnePasswordExtensionService; | 71 | Effect.gen(function* () { |
| 64 | return yield* ext.status(); | 72 | const ext = yield* OnePasswordExtensionService; |
| 65 | })), | 73 | return yield* ext.status(); |
| 74 | }), | ||
| 75 | ), | ||
| 66 | ) | 76 | ) |
| 67 | .handle("listVaults", ({ query: urlParams }) => | 77 | .handle("listVaults", ({ query: urlParams }) => |
| 68 | capture(Effect.gen(function* () { | 78 | capture( |
| 69 | const ext = yield* OnePasswordExtensionService; | 79 | Effect.gen(function* () { |
| 70 | const auth = | 80 | const ext = yield* OnePasswordExtensionService; |
| 71 | urlParams.authKind === "desktop-app" | 81 | const auth = |
| 72 | ? { kind: "desktop-app" as const, accountName: urlParams.account } | 82 | urlParams.authKind === "desktop-app" |
| 73 | : { kind: "service-account" as const, tokenSecretId: urlParams.account }; | 83 | ? { kind: "desktop-app" as const, accountName: urlParams.account } |
| 74 | const vaults = yield* ext.listVaults(auth); | 84 | : { kind: "service-account" as const, tokenSecretId: urlParams.account }; |
| 75 | return { vaults: [...vaults] }; | 85 | const vaults = yield* ext.listVaults(auth); |
| 76 | })), | 86 | return { vaults: [...vaults] }; |
| 87 | }), | ||
| 88 | ), | ||
| 77 | ), | 89 | ), |
| 78 | ); | 90 | ); |
| @@ -166,15 +166,10 @@ export const OpenApiGroup = HttpApiGroup.make("openapi") · 165 unmodified lines | |||
| 166 | }), | 166 | }), |
| 167 | ) | 167 | ) |
| 168 | .add( | 168 | .add( |
| 169 | HttpApiEndpoint.post( | 169 | HttpApiEndpoint.post("removeSourceBinding", "/scopes/:scopeId/openapi/source-bindings/remove", { |
| 170 | "removeSourceBinding", | 170 | params: ScopeIdParam, |
| 171 | "/scopes/:scopeId/openapi/source-bindings/remove", | 171 | payload: RemoveBindingPayload, |
| 172 | { | 172 | success: Schema.Struct({ removed: Schema.Boolean }), |
| 173 | params: ScopeIdParam, | 173 | error: DomainErrors, |
| 174 | payload: RemoveBindingPayload, | 174 | }), |
| 175 | success: Schema.Struct({ removed: Schema.Boolean }), | 175 | ); |
| 176 | error: DomainErrors, | ||
| 177 | }, | ||
| 178 | ), | ||
| 179 | ) | ||
| 180 | ; | ||
| @@ -24,7 +24,9 @@ import { OpenApiGroup } from "./group"; · 23 unmodified lines | |||
| 24 | // `.addError(InternalError)` on the group — no per-handler translation. | 24 | // `.addError(InternalError)` on the group — no per-handler translation. |
| 25 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
| 26 | 26 | ||
| 27 | export class OpenApiExtensionService extends Context.Service<OpenApiExtensionService, OpenApiPluginExtension | 27 | export class OpenApiExtensionService extends Context.Service< |
| 28 | OpenApiExtensionService, | ||
| 29 | OpenApiPluginExtension | ||
| 28 | >()("OpenApiExtensionService") {} | 30 | >()("OpenApiExtensionService") {} |
| 29 | 31 | ||
| 30 | // --------------------------------------------------------------------------- | 32 | // --------------------------------------------------------------------------- |
| @@ -401,29 +401,29 @@ export default function AddOpenApiSource(props: { · 400 unmodified lines | |||
| 401 | const result = exit.value; | 401 | const result = exit.value; |
| 402 | setPreview(result); | 402 | setPreview(result); |
| 403 | 403 | ||
| 404 | const firstServer = result.servers[0]; | 404 | const firstServer = result.servers[0]; |
| 405 | if (firstServer) { | 405 | if (firstServer) { |
| 406 | setSelectedServerIndex(0); | 406 | setSelectedServerIndex(0); |
| 407 | setVariableSelections(defaultSelectionsFor(firstServer)); | 407 | setVariableSelections(defaultSelectionsFor(firstServer)); |
| 408 | setCustomBaseUrl(""); | 408 | setCustomBaseUrl(""); |
| 409 | } else { | 409 | } else { |
| 410 | setSelectedServerIndex(-1); | 410 | setSelectedServerIndex(-1); |
| 411 | setVariableSelections({}); | 411 | setVariableSelections({}); |
| 412 | setCustomBaseUrl(""); | 412 | setCustomBaseUrl(""); |
| 413 | } | 413 | } |
| 414 | 414 | ||
| 415 | const firstPreset = result.headerPresets[0]; | 415 | const firstPreset = result.headerPresets[0]; |
| 416 | if (firstPreset) { | 416 | if (firstPreset) { |
| 417 | setStrategy({ kind: "header", presetIndex: 0 }); | 417 | setStrategy({ kind: "header", presetIndex: 0 }); |
| 418 | setCustomHeaders(entriesFromSpecPreset(firstPreset)); | 418 | setCustomHeaders(entriesFromSpecPreset(firstPreset)); |
| 419 | } else { | 419 | } else { |
| 420 | // No header presets — default to "custom" so the headers editor is | 420 | // No header presets — default to "custom" so the headers editor is |
| 421 | // visible immediately. Specs with no `security` block (e.g. Microsoft | 421 | // visible immediately. Specs with no `security` block (e.g. Microsoft |
| 422 | // Graph) would otherwise leave the user staring at just the | 422 | // Graph) would otherwise leave the user staring at just the |
| 423 | // Authentication heading with no way to add headers. | 423 | // Authentication heading with no way to add headers. |
| 424 | setStrategy({ kind: "custom" }); | 424 | setStrategy({ kind: "custom" }); |
| 425 | setCustomHeaders([]); | 425 | setCustomHeaders([]); |
| 426 | } | 426 | } |
| 427 | setAnalyzing(false); | 427 | setAnalyzing(false); |
| 428 | }; | 428 | }; |
| 429 | 429 | ||
| @@ -490,126 +490,126 @@ export default function AddOpenApiSource(props: { · 60 unmodified lines | |||
| 490 | const tokenUrl = resolveOAuthUrl(selectedOAuth2Preset.tokenUrl, resolvedBaseUrl); | 490 | const tokenUrl = resolveOAuthUrl(selectedOAuth2Preset.tokenUrl, resolvedBaseUrl); |
| 491 | 491 | ||
| 492 | if (selectedOAuth2Preset.flow === "clientCredentials") { | 492 | if (selectedOAuth2Preset.flow === "clientCredentials") { |
| 493 | // RFC 6749 §4.4: no user-interactive consent step. The client_secret | 493 | // RFC 6749 §4.4: no user-interactive consent step. The client_secret |
| 494 | // is mandatory; the backend exchanges tokens inline and returns a | 494 | // is mandatory; the backend exchanges tokens inline and returns a |
| 495 | // completed OAuth2Auth we can attach to the source directly. | 495 | // completed OAuth2Auth we can attach to the source directly. |
| 496 | if (!oauth2ClientSecretSecretId) { | 496 | if (!oauth2ClientSecretSecretId) { |
| 497 | setOauth2Error("client_credentials requires a client secret"); | 497 | setOauth2Error("client_credentials requires a client secret"); |
| 498 | return; | 498 | return; |
| 499 | } | 499 | } |
| 500 | setStartingOAuth(true); | 500 | setStartingOAuth(true); |
| 501 | const connectionId = openApiOAuthConnectionId(resolvedSourceId, selectedOAuth2Preset.flow); | 501 | const connectionId = openApiOAuthConnectionId(resolvedSourceId, selectedOAuth2Preset.flow); |
| 502 | const exit = await doStartOAuth({ | ||
| 503 | params: { scopeId }, | ||
| 504 | payload: { | ||
| 505 | endpoint: tokenUrl, | ||
| 506 | redirectUrl: tokenUrl, | ||
| 507 | connectionId, | ||
| 508 | tokenScope: scopeId, | ||
| 509 | strategy: { | ||
| 510 | kind: "client-credentials", | ||
| 511 | tokenEndpoint: tokenUrl, | ||
| 512 | clientIdSecretId: oauth2ClientIdSecretId, | ||
| 513 | clientSecretSecretId: oauth2ClientSecretSecretId, | ||
| 514 | scopes: [...oauth2SelectedScopes], | ||
| 515 | }, | ||
| 516 | pluginId: "openapi", | ||
| 517 | identityLabel: `${displayName} OAuth`, | ||
| 518 | }, | ||
| 519 | }); | ||
| 520 | setStartingOAuth(false); | ||
| 521 | if (Exit.isFailure(exit)) { | ||
| 522 | setOauth2Error(errorMessageFromExit(exit, "Failed to start OAuth")); | ||
| 523 | return; | ||
| 524 | } | ||
| 525 | const response = exit.value; | ||
| 526 | if (!response.completedConnection) { | ||
| 527 | setOauth2Error("client_credentials flow did not mint a connection"); | ||
| 528 | return; | ||
| 529 | } | ||
| 530 | setOauth2AuthState({ | ||
| 531 | fingerprint: selectedOAuth2Fingerprint, | ||
| 532 | auth: new OAuth2Auth({ | ||
| 533 | kind: "oauth2", | ||
| 534 | connectionId: response.completedConnection.connectionId, | ||
| 535 | securitySchemeName: selectedOAuth2Preset.securitySchemeName, | ||
| 536 | flow: "clientCredentials", | ||
| 537 | tokenUrl, | ||
| 538 | authorizationUrl: null, | ||
| 539 | clientIdSecretId: oauth2ClientIdSecretId, | ||
| 540 | clientSecretSecretId: oauth2ClientSecretSecretId, | ||
| 541 | scopes: [...oauth2SelectedScopes], | ||
| 542 | }), | ||
| 543 | }); | ||
| 544 | setOauth2Error(null); | ||
| 545 | return; | ||
| 546 | } | ||
| 547 | |||
| 548 | const authorizationUrl = resolveOAuthUrl( | ||
| 549 | Option.getOrElse(selectedOAuth2Preset.authorizationUrl, () => ""), | ||
| 550 | resolvedBaseUrl, | ||
| 551 | ); | ||
| 552 | const issuerUrl = inferOAuthIssuerUrl(authorizationUrl); | ||
| 553 | |||
| 554 | await oauth.openAuthorization({ | ||
| 555 | run: async () => { | ||
| 502 | const exit = await doStartOAuth({ | 556 | const exit = await doStartOAuth({ |
| 503 | params: { scopeId }, | 557 | params: { scopeId }, |
| 504 | payload: { | 558 | payload: { |
| 505 | endpoint: tokenUrl, | 559 | endpoint: authorizationUrl, |
| 506 | redirectUrl: tokenUrl, | 560 | connectionId: openApiOAuthConnectionId(resolvedSourceId, selectedOAuth2Preset.flow), |
| 507 | connectionId, | ||
| 508 | tokenScope: scopeId, | 561 | tokenScope: scopeId, |
| 562 | redirectUrl: oauth2RedirectUrl, | ||
| 509 | strategy: { | 563 | strategy: { |
| 510 | kind: "client-credentials", | 564 | kind: "authorization-code", |
| 565 | authorizationEndpoint: authorizationUrl, | ||
| 511 | tokenEndpoint: tokenUrl, | 566 | tokenEndpoint: tokenUrl, |
| 567 | issuerUrl, | ||
| 512 | clientIdSecretId: oauth2ClientIdSecretId, | 568 | clientIdSecretId: oauth2ClientIdSecretId, |
| 513 | clientSecretSecretId: oauth2ClientSecretSecretId, | 569 | clientSecretSecretId: oauth2ClientSecretSecretId ?? null, |
| 514 | scopes: [...oauth2SelectedScopes], | 570 | scopes: [...oauth2SelectedScopes], |
| 515 | }, | 571 | }, |
| 516 | pluginId: "openapi", | 572 | pluginId: "openapi", |
| 517 | identityLabel: `${displayName} OAuth`, | 573 | identityLabel: `${displayName} OAuth`, |
| 518 | }, | 574 | }, |
| 519 | }); | 575 | }); |
| 520 | setStartingOAuth(false); | ||
| 521 | if (Exit.isFailure(exit)) { | 576 | if (Exit.isFailure(exit)) { |
| 522 | setOauth2Error(errorMessageFromExit(exit, "Failed to start OAuth")); | 577 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: OAuth popup API represents start failure by rejecting run() |
| 523 | return; | 578 | throw new Error(errorMessageFromExit(exit, "Failed to start OAuth")); |
| 524 | } | 579 | } |
| 525 | const response = exit.value; | 580 | const response = exit.value; |
| 526 | if (!response.completedConnection) { | 581 | if (response.authorizationUrl === null) { |
| 527 | setOauth2Error("client_credentials flow did not mint a connection"); | 582 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: OAuth popup API represents start failure by rejecting run() |
| 528 | return; | 583 | throw new Error("Unexpected response flow from server"); |
| 529 | } | 584 | } |
| 585 | return { | ||
| 586 | sessionId: response.sessionId, | ||
| 587 | authorizationUrl: response.authorizationUrl, | ||
| 588 | }; | ||
| 589 | }, | ||
| 590 | onSuccess: (result) => { | ||
| 530 | setOauth2AuthState({ | 591 | setOauth2AuthState({ |
| 531 | fingerprint: selectedOAuth2Fingerprint, | 592 | fingerprint: selectedOAuth2Fingerprint, |
| 532 | auth: new OAuth2Auth({ | 593 | auth: new OAuth2Auth({ |
| 533 | kind: "oauth2", | 594 | kind: "oauth2", |
| 534 | connectionId: response.completedConnection.connectionId, | 595 | connectionId: result.connectionId, |
| 535 | securitySchemeName: selectedOAuth2Preset.securitySchemeName, | 596 | securitySchemeName: selectedOAuth2Preset.securitySchemeName, |
| 536 | flow: "clientCredentials", | 597 | flow: "authorizationCode", |
| 537 | tokenUrl, | 598 | tokenUrl, |
| 538 | authorizationUrl: null, | 599 | authorizationUrl, |
| 600 | issuerUrl, | ||
| 539 | clientIdSecretId: oauth2ClientIdSecretId, | 601 | clientIdSecretId: oauth2ClientIdSecretId, |
| 540 | clientSecretSecretId: oauth2ClientSecretSecretId, | 602 | clientSecretSecretId: oauth2ClientSecretSecretId, |
| 541 | scopes: [...oauth2SelectedScopes], | 603 | scopes: [...oauth2SelectedScopes], |
| 542 | }), | 604 | }), |
| 543 | }); | 605 | }); |
| 544 | setOauth2Error(null); | 606 | setOauth2Error(null); |
| 545 | return; | 607 | }, |
| 546 | } | 608 | onError: (message) => { |
| 547 | 609 | setStartingOAuth(false); | |
| 548 | const authorizationUrl = resolveOAuthUrl( | 610 | setOauth2Error(message); |
| 549 | Option.getOrElse(selectedOAuth2Preset.authorizationUrl, () => ""), | 611 | }, |
| 550 | resolvedBaseUrl, | 612 | }); |
| 551 | ); | ||
| 552 | const issuerUrl = inferOAuthIssuerUrl(authorizationUrl); | ||
| 553 | |||
| 554 | await oauth.openAuthorization({ | ||
| 555 | run: async () => { | ||
| 556 | const exit = await doStartOAuth({ | ||
| 557 | params: { scopeId }, | ||
| 558 | payload: { | ||
| 559 | endpoint: authorizationUrl, | ||
| 560 | connectionId: openApiOAuthConnectionId(resolvedSourceId, selectedOAuth2Preset.flow), | ||
| 561 | tokenScope: scopeId, | ||
| 562 | redirectUrl: oauth2RedirectUrl, | ||
| 563 | strategy: { | ||
| 564 | kind: "authorization-code", | ||
| 565 | authorizationEndpoint: authorizationUrl, | ||
| 566 | tokenEndpoint: tokenUrl, | ||
| 567 | issuerUrl, | ||
| 568 | clientIdSecretId: oauth2ClientIdSecretId, | ||
| 569 | clientSecretSecretId: oauth2ClientSecretSecretId ?? null, | ||
| 570 | scopes: [...oauth2SelectedScopes], | ||
| 571 | }, | ||
| 572 | pluginId: "openapi", | ||
| 573 | identityLabel: `${displayName} OAuth`, | ||
| 574 | }, | ||
| 575 | }); | ||
| 576 | if (Exit.isFailure(exit)) { | ||
| 577 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: OAuth popup API represents start failure by rejecting run() | ||
| 578 | throw new Error(errorMessageFromExit(exit, "Failed to start OAuth")); | ||
| 579 | } | ||
| 580 | const response = exit.value; | ||
| 581 | if (response.authorizationUrl === null) { | ||
| 582 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: OAuth popup API represents start failure by rejecting run() | ||
| 583 | throw new Error("Unexpected response flow from server"); | ||
| 584 | } | ||
| 585 | return { | ||
| 586 | sessionId: response.sessionId, | ||
| 587 | authorizationUrl: response.authorizationUrl, | ||
| 588 | }; | ||
| 589 | }, | ||
| 590 | onSuccess: (result) => { | ||
| 591 | setOauth2AuthState({ | ||
| 592 | fingerprint: selectedOAuth2Fingerprint, | ||
| 593 | auth: new OAuth2Auth({ | ||
| 594 | kind: "oauth2", | ||
| 595 | connectionId: result.connectionId, | ||
| 596 | securitySchemeName: selectedOAuth2Preset.securitySchemeName, | ||
| 597 | flow: "authorizationCode", | ||
| 598 | tokenUrl, | ||
| 599 | authorizationUrl, | ||
| 600 | issuerUrl, | ||
| 601 | clientIdSecretId: oauth2ClientIdSecretId, | ||
| 602 | clientSecretSecretId: oauth2ClientSecretSecretId, | ||
| 603 | scopes: [...oauth2SelectedScopes], | ||
| 604 | }), | ||
| 605 | }); | ||
| 606 | setOauth2Error(null); | ||
| 607 | }, | ||
| 608 | onError: (message) => { | ||
| 609 | setStartingOAuth(false); | ||
| 610 | setOauth2Error(message); | ||
| 611 | }, | ||
| 612 | }); | ||
| 613 | }, [ | 613 | }, [ |
| 614 | selectedOAuth2Preset, | 614 | selectedOAuth2Preset, |
| 615 | oauth2ClientIdSecretId, | 615 | oauth2ClientIdSecretId, |
| @@ -87,9 +87,7 @@ export default function OpenApiSourceSummary(props: { · 86 unmodified lines | |||
| 87 | } | 87 | } |
| 88 | const connections = AsyncResult.isSuccess(connectionsResult) ? connectionsResult.value : []; | 88 | const connections = AsyncResult.isSuccess(connectionsResult) ? connectionsResult.value : []; |
| 89 | const liveConnectionIds = new Set(connections.map((connection) => connection.id)); | 89 | const liveConnectionIds = new Set(connections.map((connection) => connection.id)); |
| 90 | const scopeRanks = new Map( | 90 | const scopeRanks = new Map(scopeStack.map((scope, index) => [scope.id, index] as const)); |
| 91 | scopeStack.map((scope, index) => [scope.id, index] as const), | ||
| 92 | ); | ||
| 93 | const credentialTargetScope = userScope; | 91 | const credentialTargetScope = userScope; |
| 94 | const missing = missingCredentialLabels(source, bindings, credentialTargetScope, scopeRanks, { | 92 | const missing = missingCredentialLabels(source, bindings, credentialTargetScope, scopeRanks, { |
| 95 | liveConnectionIds, | 93 | liveConnectionIds, |
| @@ -8,7 +8,13 @@ · 7 unmodified lines | |||
| 8 | 8 | ||
| 9 | import { afterEach, expect, layer } from "@effect/vitest"; | 9 | import { afterEach, expect, layer } from "@effect/vitest"; |
| 10 | import { Effect, Layer, Schema } from "effect"; | 10 | import { Effect, Layer, Schema } from "effect"; |
| 11 | import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"; | 11 | import { |
| 12 | HttpApi, | ||
| 13 | HttpApiBuilder, | ||
| 14 | HttpApiEndpoint, | ||
| 15 | HttpApiGroup, | ||
| 16 | OpenApi, | ||
| 17 | } from "effect/unstable/httpapi"; | ||
| 12 | import { FetchHttpClient, HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http"; | 18 | import { FetchHttpClient, HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http"; |
| 13 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; | 19 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; |
| 14 | 20 | ||
| @@ -91,36 +97,40 @@ const mockClientCredentialsFetch = (args: { · 76 unmodified lines | |||
| 91 | readonly expiresIn?: number; | 97 | readonly expiresIn?: number; |
| 92 | }) => { | 98 | }) => { |
| 93 | let callIndex = 0; | 99 | let callIndex = 0; |
| 94 | globalThis.fetch = Object.assign(async (_input: RequestInfo | URL, init?: RequestInit) => { | 100 | globalThis.fetch = Object.assign( |
| 95 | const url = typeof _input === "string" ? _input : _input.toString(); | 101 | async (_input: RequestInfo | URL, init?: RequestInit) => { |
| 96 | if (!url.includes("token.example.com")) { | 102 | const url = typeof _input === "string" ? _input : _input.toString(); |
| 97 | return originalFetch(_input, init); | 103 | if (!url.includes("token.example.com")) { |
| 98 | } | 104 | return originalFetch(_input, init); |
| 99 | const bodyText = | 105 | } |
| 100 | init?.body instanceof URLSearchParams | 106 | const bodyText = |
| 101 | ? init.body.toString() | 107 | init?.body instanceof URLSearchParams |
| 102 | : typeof init?.body === "string" | 108 | ? init.body.toString() |
| 103 | ? init.body | 109 | : typeof init?.body === "string" |
| 104 | : ""; | 110 | ? init.body |
| 105 | const params = new URLSearchParams(bodyText); | 111 | : ""; |
| 106 | args.calls.push({ | 112 | const params = new URLSearchParams(bodyText); |
| 107 | grantType: params.get("grant_type"), | 113 | args.calls.push({ |
| 108 | clientId: params.get("client_id"), | 114 | grantType: params.get("grant_type"), |
| 109 | clientSecret: params.get("client_secret"), | 115 | clientId: params.get("client_id"), |
| 110 | scope: params.get("scope"), | 116 | clientSecret: params.get("client_secret"), |
| 111 | }); | 117 | scope: params.get("scope"), |
| 112 | const token = args.accessTokens[Math.min(callIndex, args.accessTokens.length - 1)] ?? "unknown"; | 118 | }); |
| 113 | callIndex += 1; | 119 | const token = |
| 114 | const body: Record<string, unknown> = { | 120 | args.accessTokens[Math.min(callIndex, args.accessTokens.length - 1)] ?? "unknown"; |
| 115 | access_token: token, | 121 | callIndex += 1; |
| 116 | token_type: "Bearer", | 122 | const body: Record<string, unknown> = { |
| 117 | }; | 123 | access_token: token, |
| 118 | if (typeof args.expiresIn === "number") body.expires_in = args.expiresIn; | 124 | token_type: "Bearer", |
| 119 | return new Response(JSON.stringify(body), { | 125 | }; |
| 120 | status: 200, | 126 | if (typeof args.expiresIn === "number") body.expires_in = args.expiresIn; |
| 121 | headers: { "content-type": "application/json" }, | 127 | return new Response(JSON.stringify(body), { |
| 122 | }); | 128 | status: 200, |
| 123 | }, { preconnect: originalFetch.preconnect }); | 129 | headers: { "content-type": "application/json" }, |
| 130 | }); | ||
| 131 | }, | ||
| 132 | { preconnect: originalFetch.preconnect }, | ||
| 133 | ); | ||
| 124 | }; | 134 | }; |
| 125 | 135 | ||
| 126 | afterEach(() => { | 136 | afterEach(() => { |
| @@ -306,17 +316,13 @@ layer(TestLayer)("OpenAPI client_credentials OAuth", (it) => { · 179 unmodified lines | |||
| 306 | 316 | ||
| 307 | // Access-token secret is owned by the connection and filtered | 317 | // Access-token secret is owned by the connection and filtered |
| 308 | // out of the user-facing secret list. | 318 | // out of the user-facing secret list. |
| 309 | const userSecretIds = new Set( | 319 | const userSecretIds = new Set((yield* userExec.secrets.list()).map((s) => String(s.id))); |
| 310 | (yield* userExec.secrets.list()).map((s) => String(s.id)), | ||
| 311 | ); | ||
| 312 | expect(userSecretIds).toContain("petstore_client_id"); | 320 | expect(userSecretIds).toContain("petstore_client_id"); |
| 313 | expect(userSecretIds).toContain("petstore_client_secret"); | 321 | expect(userSecretIds).toContain("petstore_client_secret"); |
| 314 | expect(userSecretIds).not.toContain(`${auth.connectionId}.access_token`); | 322 | expect(userSecretIds).not.toContain(`${auth.connectionId}.access_token`); |
| 315 | 323 | ||
| 316 | // Admin scope sees neither alice's connection nor her token. | 324 | // Admin scope sees neither alice's connection nor her token. |
| 317 | const adminSecretIds = new Set( | 325 | const adminSecretIds = new Set((yield* adminExec.secrets.list()).map((s) => String(s.id))); |
| 318 | (yield* adminExec.secrets.list()).map((s) => String(s.id)), | ||
| 319 | ); | ||
| 320 | expect(adminSecretIds).not.toContain(`${auth.connectionId}.access_token`); | 326 | expect(adminSecretIds).not.toContain(`${auth.connectionId}.access_token`); |
| 321 | }), | 327 | }), |
| 322 | ); | 328 | ); |
| @@ -84,12 +84,7 @@ describe("OpenAPI credential status", () => { · 83 unmodified lines | |||
| 84 | it("does not treat personal bindings as satisfying org-level credential status", () => { | 84 | it("does not treat personal bindings as satisfying org-level credential status", () => { |
| 85 | expect( | 85 | expect( |
| 86 | missingCredentialLabels(source, bindings(userScope, allSlots), orgScope, scopeRanks), | 86 | missingCredentialLabels(source, bindings(userScope, allSlots), orgScope, scopeRanks), |
| 87 | ).toEqual([ | 87 | ).toEqual(["Authorization", "Client ID", "Client Secret", "OAuth client connection"]); |
| 88 | "Authorization", | ||
| 89 | "Client ID", | ||
| 90 | "Client Secret", | ||
| 91 | "OAuth client connection", | ||
| 92 | ]); | ||
| 93 | }); | 88 | }); |
| 94 | 89 | ||
| 95 | it("prefers the personal binding over a shared org binding", () => { | 90 | it("prefers the personal binding over a shared org binding", () => { |
| @@ -33,15 +33,9 @@ export const effectiveBindingForScope = ( · 32 unmodified lines | |||
| 33 | ): BindingRowForCredentialStatus | null => | 33 | ): BindingRowForCredentialStatus | null => |
| 34 | rows | 34 | rows |
| 35 | .filter( | 35 | .filter( |
| 36 | (row) => | 36 | (row) => row.slot === slot && scopeRank(ranks, row.scopeId) >= scopeRank(ranks, targetScope), |
| 37 | row.slot === slot && | ||
| 38 | scopeRank(ranks, row.scopeId) >= scopeRank(ranks, targetScope), | ||
| 39 | ) | 37 | ) |
| 40 | .sort( | 38 | .sort((a, b) => scopeRank(ranks, a.scopeId) - scopeRank(ranks, b.scopeId))[0] ?? null; |
| 41 | (a, b) => | ||
| 42 | scopeRank(ranks, a.scopeId) - | ||
| 43 | scopeRank(ranks, b.scopeId), | ||
| 44 | )[0] ?? null; | ||
| 45 | 39 | ||
| 46 | const hasSecretBinding = ( | 40 | const hasSecretBinding = ( |
| 47 | rows: readonly BindingRowForCredentialStatus[], | 41 | rows: readonly BindingRowForCredentialStatus[], |
| @@ -59,9 +53,7 @@ const hasConnectionBinding = ( · 11 unmodified lines | |||
| 59 | ) => { | 53 | ) => { |
| 60 | const binding = effectiveBindingForScope(rows, slot, targetScope, ranks); | 54 | const binding = effectiveBindingForScope(rows, slot, targetScope, ranks); |
| 61 | if (binding?.value.kind !== "connection") return false; | 55 | if (binding?.value.kind !== "connection") return false; |
| 62 | return liveConnectionIds | 56 | return liveConnectionIds ? liveConnectionIds.has(binding.value.connectionId) : true; |
| 63 | ? liveConnectionIds.has(binding.value.connectionId) | ||
| 64 | : true; | ||
| 65 | }; | 57 | }; |
| 66 | 58 | ||
| 67 | const effectiveClientSecretSlot = (oauth2: { | 59 | const effectiveClientSecretSlot = (oauth2: { |
| @@ -106,17 +98,9 @@ export function missingCredentialLabels( · 38 unmodified lines | |||
| 106 | } | 98 | } |
| 107 | 99 | ||
| 108 | if ( | 100 | if ( |
| 109 | !hasConnectionBinding( | 101 | !hasConnectionBinding(bindings, oauth2.connectionSlot, targetScope, ranks, liveConnectionIds) |
| 110 | bindings, | ||
| 111 | oauth2.connectionSlot, | ||
| 112 | targetScope, | ||
| 113 | ranks, | ||
| 114 | liveConnectionIds, | ||
| 115 | ) | ||
| 116 | ) { | 102 | ) { |
| 117 | missing.push( | 103 | missing.push(oauth2.flow === "clientCredentials" ? "OAuth client connection" : "OAuth sign-in"); |
| 118 | oauth2.flow === "clientCredentials" ? "OAuth client connection" : "OAuth sign-in", | ||
| 119 | ); | ||
| 120 | } | 104 | } |
| 121 | 105 | ||
| 122 | return missing; | 106 | return missing; |
| @@ -124,9 +124,7 @@ const extractRequestBody = ( · 123 unmodified lines | |||
| 124 | contentType: mediaType, | 124 | contentType: mediaType, |
| 125 | schema: Option.fromNullishOr(media.schema), | 125 | schema: Option.fromNullishOr(media.schema), |
| 126 | encoding: Option.fromNullishOr( | 126 | encoding: Option.fromNullishOr( |
| 127 | buildEncodingRecord( | 127 | buildEncodingRecord((media as { encoding?: Record<string, unknown> }).encoding), |
| 128 | (media as { encoding?: Record<string, unknown> }).encoding, | ||
| 129 | ), | ||
| 130 | ), | 128 | ), |
| 131 | }), | 129 | }), |
| 132 | ); | 130 | ); |
| @@ -246,9 +244,7 @@ const extractServers = (doc: ParsedDocument): ServerInfo[] => · 113 unmodified lines | |||
| 246 | new ServerVariable({ | 244 | new ServerVariable({ |
| 247 | default: String(v.default), | 245 | default: String(v.default), |
| 248 | enum: | 246 | enum: |
| 249 | enumValues && enumValues.length > 0 | 247 | enumValues && enumValues.length > 0 ? Option.some(enumValues) : Option.none(), |
| 250 | ? Option.some(enumValues) | ||
| 251 | : Option.none(), | ||
| 252 | description: Option.fromNullishOr(v.description), | 248 | description: Option.fromNullishOr(v.description), |
| 253 | }), | 249 | }), |
| 254 | ], | 250 | ], |
| @@ -36,14 +36,12 @@ const memoryProvider: SecretProvider = (() => { · 35 unmodified lines | |||
| 36 | return { | 36 | return { |
| 37 | key: "memory", | 37 | key: "memory", |
| 38 | writable: true, | 38 | writable: true, |
| 39 | get: (id, scope) => | 39 | get: (id, scope) => Effect.sync(() => store.get(`${scope}\u0000${id}`) ?? null), |
| 40 | Effect.sync(() => store.get(`${scope}\u0000${id}`) ?? null), | ||
| 41 | set: (id, value, scope) => | 40 | set: (id, value, scope) => |
| 42 | Effect.sync(() => { | 41 | Effect.sync(() => { |
| 43 | store.set(`${scope}\u0000${id}`, value); | 42 | store.set(`${scope}\u0000${id}`, value); |
| 44 | }), | 43 | }), |
| 45 | delete: (id, scope) => | 44 | delete: (id, scope) => Effect.sync(() => store.delete(`${scope}\u0000${id}`)), |
| 46 | Effect.sync(() => store.delete(`${scope}\u0000${id}`)), | ||
| 47 | list: () => Effect.sync(() => []), | 45 | list: () => Effect.sync(() => []), |
| 48 | }; | 46 | }; |
| 49 | })(); | 47 | })(); |
| @@ -61,31 +59,29 @@ type Captured = { · 11 unmodified lines | |||
| 61 | 59 | ||
| 62 | const startEchoServer = () => | 60 | const startEchoServer = () => |
| 63 | Effect.acquireRelease( | 61 | Effect.acquireRelease( |
| 64 | Effect.callback<{ baseUrl: string; captured: Captured; close: () => void }>( | 62 | Effect.callback<{ baseUrl: string; captured: Captured; close: () => void }>((resume) => { |
| 65 | (resume) => { | 63 | const captured: Captured = { contentType: "", body: "" }; |
| 66 | const captured: Captured = { contentType: "", body: "" }; | 64 | const server = createServer((req, res) => { |
| 67 | const server = createServer((req, res) => { | 65 | const chunks: Buffer[] = []; |
| 68 | const chunks: Buffer[] = []; | 66 | req.on("data", (c: Buffer) => chunks.push(c)); |
| 69 | req.on("data", (c: Buffer) => chunks.push(c)); | 67 | req.on("end", () => { |
| 70 | req.on("end", () => { | 68 | captured.contentType = req.headers["content-type"] ?? ""; |
| 71 | captured.contentType = req.headers["content-type"] ?? ""; | 69 | captured.body = Buffer.concat(chunks).toString("utf8"); |
| 72 | captured.body = Buffer.concat(chunks).toString("utf8"); | 70 | res.writeHead(200, { "content-type": "application/json" }); |
| 73 | res.writeHead(200, { "content-type": "application/json" }); | 71 | res.end(JSON.stringify({ ok: true })); |
| 74 | res.end(JSON.stringify({ ok: true })); | ||
| 75 | }); | ||
| 76 | }); | 72 | }); |
| 77 | server.listen(0, "127.0.0.1", () => { | 73 | }); |
| 78 | const port = (server.address() as AddressInfo).port; | 74 | server.listen(0, "127.0.0.1", () => { |
| 79 | resume( | 75 | const port = (server.address() as AddressInfo).port; |
| 80 | Effect.succeed({ | 76 | resume( |
| 81 | baseUrl: `http://127.0.0.1:${port}`, | 77 | Effect.succeed({ |
| 82 | captured, | 78 | baseUrl: `http://127.0.0.1:${port}`, |
| 83 | close: () => server.close(), | 79 | captured, |
| 84 | }), | 80 | close: () => server.close(), |
| 85 | ); | 81 | }), |
| 86 | }); | 82 | ); |
| 87 | }, | 83 | }); |
| 88 | ), | 84 | }), |
| 89 | (s) => Effect.sync(() => s.close()), | 85 | (s) => Effect.sync(() => s.close()), |
| 90 | ); | 86 | ); |
| 91 | 87 | ||
| @@ -130,40 +126,38 @@ const formSpec = JSON.stringify({ · 38 unmodified lines | |||
| 130 | }); | 126 | }); |
| 131 | 127 | ||
| 132 | describe("OpenAPI non-JSON request body serialization", () => { | 128 | describe("OpenAPI non-JSON request body serialization", () => { |
| 133 | it.effect( | 129 | it.effect("form-urlencoded object body is properly encoded (no '[object Object]')", () => |
| 134 | "form-urlencoded object body is properly encoded (no '[object Object]')", | 130 | Effect.gen(function* () { |
| 135 | () => | 131 | const { baseUrl, captured } = yield* startEchoServer(); |
| 136 | Effect.gen(function* () { | ||
| 137 | const { baseUrl, captured } = yield* startEchoServer(); | ||
| 138 | 132 | ||
| 139 | const executor = yield* createExecutor( | 133 | const executor = yield* createExecutor( |
| 140 | makeTestConfig({ | 134 | makeTestConfig({ |
| 141 | plugins: [ | 135 | plugins: [ |
| 142 | openApiPlugin({ httpClientLayer: FetchHttpClient.layer }), | 136 | openApiPlugin({ httpClientLayer: FetchHttpClient.layer }), |
| 143 | memorySecretsPlugin(), | 137 | memorySecretsPlugin(), |
| 144 | ] as const, | 138 | ] as const, |
| 145 | }), | 139 | }), |
| 146 | ); | 140 | ); |
| 147 | 141 | ||
| 148 | yield* executor.openapi.addSpec({ | 142 | yield* executor.openapi.addSpec({ |
| 149 | spec: formSpec, | 143 | spec: formSpec, |
| 150 | scope: TEST_SCOPE, | 144 | scope: TEST_SCOPE, |
| 151 | namespace: "form", | 145 | namespace: "form", |
| 152 | baseUrl, | 146 | baseUrl, |
| 153 | }); | 147 | }); |
| 154 | 148 | ||
| 155 | yield* executor.tools.invoke( | 149 | yield* executor.tools.invoke( |
| 156 | "form.forms.submit", | 150 | "form.forms.submit", |
| 157 | { body: { name: "Acme", email: "a@b.com" } }, | 151 | { body: { name: "Acme", email: "a@b.com" } }, |
| 158 | autoApprove, | 152 | autoApprove, |
| 159 | ); | 153 | ); |
| 160 | 154 | ||
| 161 | expect(captured.contentType).toBe("application/x-www-form-urlencoded"); | 155 | expect(captured.contentType).toBe("application/x-www-form-urlencoded"); |
| 162 | expect(captured.body).not.toBe("[object Object]"); | 156 | expect(captured.body).not.toBe("[object Object]"); |
| 163 | 157 | ||
| 164 | const parsed = new URLSearchParams(captured.body); | 158 | const parsed = new URLSearchParams(captured.body); |
| 165 | expect(parsed.get("name")).toBe("Acme"); | 159 | expect(parsed.get("name")).toBe("Acme"); |
| 166 | expect(parsed.get("email")).toBe("a@b.com"); | 160 | expect(parsed.get("email")).toBe("a@b.com"); |
| 167 | }), | 161 | }), |
| 168 | ); | 162 | ); |
| 169 | }); | 163 | }); |
| @@ -195,9 +195,10 @@ describe("OpenAPI plugin", () => { · 194 unmodified lines | |||
| 195 | 195 | ||
| 196 | const vars = Option.getOrThrow(server.variables); | 196 | const vars = Option.getOrThrow(server.variables); |
| 197 | expect(vars.region!.default).toBe("us"); | 197 | expect(vars.region!.default).toBe("us"); |
| 198 | expect( | 198 | expect(Option.getOrElse(vars.region!.enum, () => [] as readonly string[])).toEqual([ |
| 199 | Option.getOrElse(vars.region!.enum, () => [] as readonly string[]), | 199 | "us", |
| 200 | ).toEqual(["us", "de"]); | 200 | "de", |
| 201 | ]); | ||
| 201 | expect(Option.getOrElse(vars.region!.description, () => "")).toBe( | 202 | expect(Option.getOrElse(vars.region!.description, () => "")).toBe( |
| 202 | "The data-storage-location for an organization", | 203 | "The data-storage-location for an organization", |
| 203 | ); | 204 | ); |
| @@ -191,9 +191,7 @@ const isXmlContentType = (ct: string | null | undefined): boolean => { · 190 unmodified lines | |||
| 191 | const normalized = normalizeContentType(ct); | 191 | const normalized = normalizeContentType(ct); |
| 192 | if (!normalized) return false; | 192 | if (!normalized) return false; |
| 193 | return ( | 193 | return ( |
| 194 | normalized === "application/xml" || | 194 | normalized === "application/xml" || normalized === "text/xml" || normalized.endsWith("+xml") |
| 195 | normalized === "text/xml" || | ||
| 196 | normalized.endsWith("+xml") | ||
| 197 | ); | 195 | ); |
| 198 | }; | 196 | }; |
| 199 | 197 | ||
| @@ -291,8 +289,7 @@ const serializeFormUrlEncoded = ( · 91 unmodified lines | |||
| 291 | parts.push(`${encKey}=${encodeFormValue(v, allowReserved)}`); | 289 | parts.push(`${encKey}=${encodeFormValue(v, allowReserved)}`); |
| 292 | } | 290 | } |
| 293 | } else { | 291 | } else { |
| 294 | const sep = | 292 | const sep = style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ","; |
| 295 | style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ","; | ||
| 296 | parts.push( | 293 | parts.push( |
| 297 | `${encKey}=${encodeFormValue( | 294 | `${encKey}=${encodeFormValue( |
| 298 | raw.map((v) => (typeof v === "object" ? JSON.stringify(v) : String(v))).join(sep), | 295 | raw.map((v) => (typeof v === "object" ? JSON.stringify(v) : String(v))).join(sep), |
| @@ -313,18 +310,13 @@ const serializeFormUrlEncoded = ( · 14 unmodified lines | |||
| 313 | // `%5B` / `%5D`. Matches swagger-client's behaviour and remains | 310 | // `%5B` / `%5D`. Matches swagger-client's behaviour and remains |
| 314 | // accepted by common server-side parsers (qs, Rails, etc.). | 311 | // accepted by common server-side parsers (qs, Rails, etc.). |
| 315 | parts.push( | 312 | parts.push( |
| 316 | `${encodeURIComponent(`${key}[${subkey}]`)}=${encodeFormValue( | 313 | `${encodeURIComponent(`${key}[${subkey}]`)}=${encodeFormValue(subval, allowReserved)}`, |
| 317 | subval, | ||
| 318 | allowReserved, | ||
| 319 | )}`, | ||
| 320 | ); | 314 | ); |
| 321 | } | 315 | } |
| 322 | } else if (explode) { | 316 | } else if (explode) { |
| 323 | // form + explode=true on object: sub-keys become top-level fields. | 317 | // form + explode=true on object: sub-keys become top-level fields. |
40 more lines aren’t shown here.
This diff is large: its 901 lines aren’t shown here.
This diff is large: its 165 lines aren’t shown here.
This diff is large: its 136 lines aren’t shown here.
This diff is large: its 23 lines aren’t shown here.
This diff is large: its 306 lines aren’t shown here.
This diff is large: its 11 lines aren’t shown here.
This diff is large: its 35 lines aren’t shown here.
This diff is large: its 10 lines aren’t shown here.
This diff is large: its 11 lines aren’t shown here.
This diff is large: its 66 lines aren’t shown here.
This diff is large: its 303 lines aren’t shown here.
This diff is large: its 140 lines aren’t shown here.
This diff is large: its 97 lines aren’t shown here.
This diff is large: its 24 lines aren’t shown here.
This diff is large: its 12 lines aren’t shown here.
This diff is large: its 12 lines aren’t shown here.
This diff is large: its 11 lines aren’t shown here.
This diff is large: its 11 lines aren’t shown here.
This diff is large: its 13 lines aren’t shown here.
This diff is large: its 31 lines aren’t shown here.
This diff is large: its 20 lines aren’t shown here.
This diff is large: its 37 lines aren’t shown here.
This diff is large: its 11 lines aren’t shown here.
This diff is large: its 9 lines aren’t shown here.
This diff is large: its 23 lines aren’t shown here.
This diff is large: its 12 lines aren’t shown here.
This diff is large: its 13 lines aren’t shown here.
This diff is large: its 23 lines aren’t shown here.
This diff is large: its 25 lines aren’t shown here.
This diff is large: its 10 lines aren’t shown here.
This diff is large: its 20 lines aren’t shown here.
This diff is large: its 22 lines aren’t shown here.
This diff is large: its 61 lines aren’t shown here.
This diff is large: its 10 lines aren’t shown here.
This diff is large: its 10 lines aren’t shown here.
This diff is large: its 283 lines aren’t shown here.
This diff is large: its 11 lines aren’t shown here.
This diff is large: its 40 lines aren’t shown here.