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 | 47 | ## Severity ladder |
| 48 | 48 | |
| 49 | −| Level | Criteria | | |
| 50 | −|-------|----------| | |
| 49 | +| Level | Criteria | | |
| 50 | +| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | |
| 51 | 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 | 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 | 103 | Atom.optimisticFn({ |
| 104 | 104 | reducer: (current, arg: { path: { secretId: SecretId }; payload: { value: string } }) => |
| 105 | 105 | Result.map(current, (rows) => |
| 106 | − rows.map((r) => | |
| 107 | − r.id === arg.path.secretId ? { ...r, value: arg.payload.value } : r, | |
| 108 | − ), | |
| 106 | + rows.map((r) => (r.id === arg.path.secretId ? { ...r, value: arg.payload.value } : r)), | |
| 109 | 107 | ), |
| 110 | 108 | fn: updateSecret, |
| 111 | 109 | }), |
| @@ -136,8 +134,12 @@ export interface PendingSecret { · 24 unmodified lines | ||
| 136 | 134 | readonly value: string; |
| 137 | 135 | } |
| 138 | 136 | |
| 139 | −export const useSecretsWithPending = (scopeId: ScopeId) => { /* ... */ }; | |
| 140 | −export const usePendingSecrets = () => { /* ... */ }; | |
| 137 | +export const useSecretsWithPending = (scopeId: ScopeId) => { | |
| 138 | + /* ... */ | |
| 139 | +}; | |
| 140 | +export const usePendingSecrets = () => { | |
| 141 | + /* ... */ | |
| 142 | +}; | |
| 141 | 143 | ``` |
| 142 | 144 | |
| 143 | 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 | 96 | Good in `Effect.gen`: |
| 97 | 97 | |
| 98 | 98 | ```ts |
| 99 | −return yield* new SourceNotFoundError({ sourceId }); | |
| 99 | +return yield * new SourceNotFoundError({ sourceId }); | |
| 100 | 100 | ``` |
| 101 | 101 | |
| 102 | 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 | 112 | Prefer yielding the error directly in generator code: |
| 113 | 113 | |
| 114 | 114 | ```ts |
| 115 | −return yield* new SourceNotFoundError({ sourceId }); | |
| 115 | +return yield * new SourceNotFoundError({ sourceId }); | |
| 116 | 116 | ``` |
| 117 | 117 | |
| 118 | 118 | Do not write: |
| 119 | 119 | |
| 120 | 120 | ```ts |
| 121 | −return yield* Effect.fail(new SourceNotFoundError({ sourceId })); | |
| 121 | +return yield * Effect.fail(new SourceNotFoundError({ sourceId })); | |
| 122 | 122 | ``` |
| 123 | 123 | |
| 124 | 124 | Use `Effect.fail(...)` in non-generator combinator code: |
| @@ -167,16 +167,14 @@ Effect.ignore( · 42 unmodified lines | ||
| 167 | 167 | Bad in domain code: |
| 168 | 168 | |
| 169 | 169 | ```ts |
| 170 | −program.pipe(Effect.orDie) | |
| 171 | −Effect.die(error) | |
| 170 | +program.pipe(Effect.orDie); | |
| 171 | +Effect.die(error); | |
| 172 | 172 | ``` |
| 173 | 173 | |
| 174 | 174 | Good: |
| 175 | 175 | |
| 176 | 176 | ```ts |
| 177 | −program.pipe( | |
| 178 | − Effect.mapError((cause) => new DomainError({ message: "Operation failed", cause })), | |
| 179 | −) | |
| 177 | +program.pipe(Effect.mapError((cause) => new DomainError({ message: "Operation failed", cause }))); | |
| 180 | 178 | ``` |
| 181 | 179 | |
| 182 | 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 | 303 | const connectionError = (message: string) => |
| 306 | 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 | 309 | Good: |
| 312 | 310 | |
| 313 | 311 | ```ts |
| 314 | −return yield* new McpConnectionError({ | |
| 315 | − transport: "remote", | |
| 316 | − message: "Endpoint URL is required", | |
| 317 | −}); | |
| 312 | +return ( | |
| 313 | + yield * | |
| 314 | + new McpConnectionError({ | |
| 315 | + transport: "remote", | |
| 316 | + message: "Endpoint URL is required", | |
| 317 | + }) | |
| 318 | +); | |
| 318 | 319 | ``` |
| 319 | 320 | |
| 320 | 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 | 6 | # Executor CLI release runbook |
| 7 | 7 | |
| 8 | 8 | ## Authoritative doc |
| 9 | + | |
| 9 | 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 | 12 | ## What the `executor` CLI actually ships |
| 12 | 13 | |
| 13 | 14 | The CLI binary bundles: |
| 15 | + | |
| 14 | 16 | - `apps/cli/**` — CLI source + daemon |
| 15 | 17 | - `apps/local/**` — the web UI (embedded as a virtual module via `apps/cli/src/build.ts:178`) + drizzle migrations (`build.ts:205`) |
| 16 | 18 | - `packages/**` — `core`, `kernel`, `hosts/mcp`, `runtime-quickjs`, and every plugin under `packages/plugins/**` |
| 17 | 19 | |
| 18 | 20 | Does **not** ship in the CLI: |
| 21 | + | |
| 19 | 22 | - `apps/cloud/**` (Cloudflare Workers deployment) |
| 20 | 23 | - `apps/marketing/**`, `apps/desktop/**` |
| 21 | 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 | 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 | 42 | ### How it's wired |
| 43 | + | |
| 40 | 44 | `apps/cli/src/release.ts` reads `apps/cli/release-notes/next.md` and uses |
| 41 | 45 | its contents as the GitHub Release body. If the file is missing or empty, |
| 42 | 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 | 48 | GitHub Releases (durable, indexed, linkable). |
| 45 | 49 | |
| 46 | 50 | ### Writing conventions |
| 51 | + | |
| 47 | 52 | Structure release-notes files as: |
| 53 | + | |
| 48 | 54 | ``` |
| 49 | 55 | ## Highlights |
| 50 | 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 | 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 | 82 | ### When drafting from `git log` |
| 83 | + | |
| 77 | 84 | - Look at `git diff v<last>..HEAD -- README.md` first — it's the best single view of user-facing changes. |
| 78 | 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 | 86 | - Don't list every commit. Merge PRs and refactor-chain commits into one line. |
| 80 | 87 | |
| 81 | 88 | ### Pairing with changesets |
| 89 | + | |
| 82 | 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 | 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 | 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 | 93 | - Frontmatter is `"executor": patch` (or `minor`/`major` if owner says so). |
| 86 | 94 | |
| 87 | 95 | ### Starting a new release cycle |
| 96 | + | |
| 88 | 97 | There's no post-release rename step. When you start work on the next |
| 89 | 98 | release, replace the existing `next.md` content with new entries — the |
| 90 | 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 | 17 | - A list query atom (`Atom.optimistic` wraps the query) |
| 18 | 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 | 21 | If you only need the UI to be "eventually consistent" (i.e. you're fine waiting |
| 22 | 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 | 109 | ```typescript |
| 110 | 110 | import { useAtomValue, useAtomSet } from "@effect-atom/atom-react"; |
| 111 | 111 | |
| 112 | −import { | |
| 113 | − policiesOptimisticAtom, | |
| 114 | − updatePolicyOptimistic, | |
| 115 | −} from "../api/atoms"; | |
| 112 | +import { policiesOptimisticAtom, updatePolicyOptimistic } from "../api/atoms"; | |
| 116 | 113 | import { policyWriteKeys } from "../api/reactivity-keys"; |
| 117 | 114 | |
| 118 | 115 | export function PoliciesPage() { |
| @@ -203,10 +200,7 @@ export const removePolicyOptimistic = Atom.family((scopeId: ScopeId) => · 84 unmodified lines | ||
| 203 | 200 | path: { scopeId: ScopeId; policyId: PolicyId }; |
| 204 | 201 | reactivityKeys?: ReadonlyArray<unknown>; |
| 205 | 202 | }, |
| 206 | − ) => | |
| 207 | − Result.map(current, (rows) => | |
| 208 | − rows.filter((r) => r.id !== arg.path.policyId), | |
| 209 | − ), | |
| 203 | + ) => Result.map(current, (rows) => rows.filter((r) => r.id !== arg.path.policyId)), | |
| 210 | 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 | 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 | 242 | (e.g. a single mutation invalidates `tools` AND `policies`). Reactivity keys |
| 249 | 243 | still handle that — optimistic only paints the list-local change. |
| 250 | 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 | 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 | | |
| 13 | −|------------|-----| | |
| 14 | −| `git checkout -b <name>` | `gt create <name>` | | |
| 15 | −| `git commit -m "msg"` | `gt modify -c -m "msg"` | | |
| 16 | −| `git commit --amend` | `gt modify --amend` | | |
| 17 | −| `git commit -am "msg"` | `gt modify -cam "msg"` | | |
| 12 | +| Instead of | Use | | |
| 13 | +| ------------------------ | ----------------------- | | |
| 14 | +| `git checkout -b <name>` | `gt create <name>` | | |
| 15 | +| `git commit -m "msg"` | `gt modify -c -m "msg"` | | |
| 16 | +| `git commit --amend` | `gt modify --amend` | | |
| 17 | +| `git commit -am "msg"` | `gt modify -cam "msg"` | | |
| 18 | 18 | |
| 19 | 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 | 54 | Create a new branch stacked on current branch with staged changes. |
| 55 | 55 | |
| 56 | 56 | **Flags:** |
| 57 | + | |
| 57 | 58 | - `-a, --all` - Stage all unstaged changes including untracked files |
| 58 | 59 | - `-m, --message` - Commit message |
| 59 | 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 | 68 | Amend current branch's commit or create new commit. Auto-restacks descendants. |
| 68 | 69 | |
| 69 | 70 | **Flags:** |
| 71 | + | |
| 70 | 72 | - `-a, --all` - Stage all changes |
| 71 | 73 | - `-c, --commit` - Create new commit instead of amending |
| 72 | 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 | 84 | Amend staged changes to relevant commits in current stack. |
| 83 | 85 | |
| 84 | 86 | **Flags:** |
| 87 | + | |
| 85 | 88 | - `-a, --all` - Stage all unstaged changes (not untracked) |
| 86 | 89 | - `-d, --dry-run` - Print what would happen |
| 87 | 90 | - `-f, --force` - Skip confirmation |
| @@ -92,6 +95,7 @@ Amend staged changes to relevant commits in current stack. · 4 unmodified lines | ||
| 92 | 95 | Squash all commits in current branch into single commit. |
| 93 | 96 | |
| 94 | 97 | **Flags:** |
| 98 | + | |
| 95 | 99 | - `-m, --message` - Commit message |
| 96 | 100 | - `--edit` - Modify existing message |
| 97 | 101 | - `-n, --no-edit` - Keep existing message |
| @@ -101,6 +105,7 @@ Squash all commits in current branch into single commit. · 3 unmodified lines | ||
| 101 | 105 | Fold branch's changes into parent, restack descendants. |
| 102 | 106 | |
| 103 | 107 | **Flags:** |
| 108 | + | |
| 104 | 109 | - `-k, --keep` - Keep current branch name instead of parent's |
| 105 | 110 | |
| 106 | 111 | ### gt split |
| @@ -108,6 +113,7 @@ Fold branch's changes into parent, restack descendants. · 1 unmodified line | ||
| 108 | 113 | Split current branch into multiple branches. |
| 109 | 114 | |
| 110 | 115 | **Flags:** |
| 116 | + | |
| 111 | 117 | - `-c, --commit, --by-commit` - Split by commit history |
| 112 | 118 | - `-h, --hunk, --by-hunk` - Split by hunk interactively |
| 113 | 119 | - `-f, --file, --by-file` - Split files matching pathspecs |
| @@ -119,6 +125,7 @@ Split current branch into multiple branches. · 5 unmodified lines | ||
| 119 | 125 | Push branches and create/update PRs on GitHub. |
| 120 | 126 | |
| 121 | 127 | **Flags:** |
| 128 | + | |
| 122 | 129 | - `-s, --stack` - Include descendant branches |
| 123 | 130 | - `-d, --draft` - Create PRs as drafts |
| 124 | 131 | - `-p, --publish` - Publish draft PRs |
| @@ -148,6 +155,7 @@ Push branches and create/update PRs on GitHub. · 23 unmodified lines | ||
| 148 | 155 | Merge PRs from trunk to current branch via Graphite. |
| 149 | 156 | |
| 150 | 157 | **Flags:** |
| 158 | + | |
| 151 | 159 | - `-c, --confirm` - Confirm before merging |
| 152 | 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 | 166 | Switch to branch. Interactive selector if no branch provided. |
| 159 | 167 | |
| 160 | 168 | **Flags:** |
| 169 | + | |
| 161 | 170 | - `-a, --all` - Show all trunks in selection |
| 162 | 171 | - `-u, --show-untracked` - Include untracked branches |
| 163 | 172 | - `-s, --stack` - Only show current stack |
| @@ -168,6 +177,7 @@ Switch to branch. Interactive selector if no branch provided. · 4 unmodified lines | ||
| 168 | 177 | Switch to child of current branch. |
| 169 | 178 | |
| 170 | 179 | **Flags:** |
| 180 | + | |
| 171 | 181 | - `-n, --steps` - Levels to traverse |
| 172 | 182 | - `--to` - Target branch to navigate toward |
| 173 | 183 | |
| @@ -176,6 +186,7 @@ Switch to child of current branch. · 2 unmodified lines | ||
| 176 | 186 | Switch to parent of current branch. |
| 177 | 187 | |
| 178 | 188 | **Flags:** |
| 189 | + | |
| 179 | 190 | - `-n, --steps` - Levels to traverse |
| 180 | 191 | |
| 181 | 192 | ### gt top |
| @@ -193,6 +204,7 @@ Switch to branch closest to trunk in current stack. · 11 unmodified lines | ||
| 193 | 204 | Ensure each branch has parent in Git history, rebasing if needed. |
| 194 | 205 | |
| 195 | 206 | **Flags:** |
| 207 | + | |
| 196 | 208 | - `--branch` - Run from specified branch |
| 197 | 209 | - `--downstack` - Only restack branch and ancestors |
| 198 | 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 | 215 | Rebase current branch onto target, restack descendants. |
| 204 | 216 | |
| 205 | 217 | **Flags:** |
| 218 | + | |
| 206 | 219 | - `-o, --onto` - Target branch |
| 207 | 220 | - `--source` - Branch to move (default: current) |
| 208 | 221 | - `-a, --all` - Show all trunks in selection |
| @@ -218,6 +231,7 @@ Reorder branches between trunk and current via editor. · 9 unmodified lines | ||
| 218 | 231 | Sync all branches with remote, clean merged/closed PRs, restack. |
| 219 | 232 | |
| 220 | 233 | **Flags:** |
| 234 | + | |
| 221 | 235 | - `-a, --all` - Sync across all trunks |
| 222 | 236 | - `-f, --force` - Skip confirmations |
| 223 | 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 | 241 | Sync branches from trunk to specified branch/PR from remote. |
| 228 | 242 | |
| 229 | 243 | **Flags:** |
| 244 | + | |
| 230 | 245 | - `-d, --downstack` - Don't sync upstack branches |
| 231 | 246 | - `-f, --force` - Overwrite with remote |
| 232 | 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 | 254 | Delete branch and Graphite metadata. Children restack to parent. |
| 240 | 255 | |
| 241 | 256 | **Flags:** |
| 257 | + | |
| 242 | 258 | - `-f, --force` - Delete even if not merged/closed |
| 243 | 259 | - `-c, --close` - Close associated PR on GitHub |
| 244 | 260 | - `--downstack` - Also delete ancestors |
| @@ -249,6 +265,7 @@ Delete branch and Graphite metadata. Children restack to parent. · 4 unmodified lines | ||
| 249 | 265 | Rename branch. Removes PR association. |
| 250 | 266 | |
| 251 | 267 | **Flags:** |
| 268 | + | |
| 252 | 269 | - `-f, --force` - Allow rename with open PR |
| 253 | 270 | |
| 254 | 271 | ### gt track [branch] |
| @@ -256,6 +273,7 @@ Rename branch. Removes PR association. · 1 unmodified line | ||
| 256 | 273 | Start tracking branch with Graphite by selecting parent. |
| 257 | 274 | |
| 258 | 275 | **Flags:** |
| 276 | + | |
| 259 | 277 | - `-f, --force` - Use most recent tracked ancestor as parent |
| 260 | 278 | - `-p, --parent` - Specify parent branch |
| 261 | 279 | |
| @@ -264,6 +282,7 @@ Start tracking branch with Graphite by selecting parent. · 2 unmodified lines | ||
| 264 | 282 | Stop tracking branch. Children also untracked. |
| 265 | 283 | |
| 266 | 284 | **Flags:** |
| 285 | + | |
| 267 | 286 | - `-f, --force` - Skip confirmation |
| 268 | 287 | |
| 269 | 288 | ### gt pop |
| @@ -285,6 +304,7 @@ Unfreeze branch and upstack. · 15 unmodified lines | ||
| 285 | 304 | Show stacks. Forms: `gt log`, `gt log short`, `gt log long`. |
| 286 | 305 | |
| 287 | 306 | **Flags:** |
| 307 | + | |
| 288 | 308 | - `-a, --all` - Show all trunks |
| 289 | 309 | - `-r, --reverse` - Print upside down |
| 290 | 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 | 317 | Display branch information. |
| 298 | 318 | |
| 299 | 319 | **Flags:** |
| 320 | + | |
| 300 | 321 | - `-b, --body` - Show PR body |
| 301 | 322 | - `-d, --diff` - Show diff vs parent |
| 302 | 323 | - `-p, --patch` - Show commit changes |
| @@ -307,6 +328,7 @@ Display branch information. · 4 unmodified lines | ||
| 307 | 328 | Show parent, children, or trunk of current branch. |
| 308 | 329 | |
| 309 | 330 | `gt trunk` flags: |
| 331 | + | |
| 310 | 332 | - `--add` - Add trunk |
| 311 | 333 | - `-a, --all` - Show all trunks |
| 312 | 334 | |
| @@ -315,6 +337,7 @@ Show parent, children, or trunk of current branch. · 2 unmodified lines | ||
| 315 | 337 | Open PR page for branch or PR number. |
| 316 | 338 | |
| 317 | 339 | **Flags:** |
| 340 | + | |
| 318 | 341 | - `--stack` - Open stack page |
| 319 | 342 | |
| 320 | 343 | ## Conflict Resolution |
| @@ -324,6 +347,7 @@ Open PR page for branch or PR number. · 3 unmodified lines | ||
| 324 | 347 | Continue command halted by rebase conflict. |
| 325 | 348 | |
| 326 | 349 | **Flags:** |
| 350 | + | |
| 327 | 351 | - `-a, --all` - Stage all changes first |
| 328 | 352 | |
| 329 | 353 | ### gt abort |
| @@ -331,6 +355,7 @@ Continue command halted by rebase conflict. · 1 unmodified line | ||
| 331 | 355 | Abort current command halted by conflict. |
| 332 | 356 | |
| 333 | 357 | **Flags:** |
| 358 | + | |
| 334 | 359 | - `-f, --force` - Skip confirmation |
| 335 | 360 | |
| 336 | 361 | ### gt undo |
| @@ -338,6 +363,7 @@ Abort current command halted by conflict. · 1 unmodified line | ||
| 338 | 363 | Undo most recent Graphite mutations. |
| 339 | 364 | |
| 340 | 365 | **Flags:** |
| 366 | + | |
| 341 | 367 | - `-f, --force` - Skip confirmation |
| 342 | 368 | |
| 343 | 369 | ## Setup |
| @@ -347,6 +373,7 @@ Undo most recent Graphite mutations. · 3 unmodified lines | ||
| 347 | 373 | Initialize Graphite by selecting trunk branch. |
| 348 | 374 | |
| 349 | 375 | **Flags:** |
| 376 | + | |
| 350 | 377 | - `--trunk` - Trunk branch name |
| 351 | 378 | - `--reset` - Untrack all branches |
| 352 | 379 | |
| @@ -355,6 +382,7 @@ Initialize Graphite by selecting trunk branch. · 2 unmodified lines | ||
| 355 | 382 | Add auth token for GitHub. |
| 356 | 383 | |
| 357 | 384 | **Flags:** |
| 385 | + | |
| 358 | 386 | - `-t, --token` - Auth token from https://app.graphite.com/activate |
| 359 | 387 | |
| 360 | 388 | ### gt config |
| @@ -368,6 +396,7 @@ Update CLI to latest stable version. · 7 unmodified lines | ||
| 368 | 396 | ## Git Passthroughs |
| 369 | 397 | |
| 370 | 398 | These pass arguments directly to git: |
| 399 | + | |
| 371 | 400 | - `gt add [args..]` |
| 372 | 401 | - `gt cherry-pick [args..]` |
| 373 | 402 | - `gt rebase [args..]` |
| @@ -379,4 +408,5 @@ These pass arguments directly to git: · 5 unmodified lines | ||
| 379 | 408 | Create branch reverting a trunk commit. |
| 380 | 409 | |
| 381 | 410 | **Flags:** |
| 411 | + | |
| 382 | 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 | 75 | ```markdown |
| 76 | 76 | ## Highlights |
| 77 | + | |
| 77 | 78 | ### <user-facing story> |
| 78 | − bullets of concrete user value | |
| 79 | + | |
| 80 | +bullets of concrete user value | |
| 79 | 81 | |
| 80 | 82 | ## Fixes |
| 81 | 83 | |
| 82 | 84 | ## Breaking changes |
| 85 | + | |
| 83 | 86 | ### <specific surface> |
| 84 | − before / after code blocks for migrations | |
| 87 | + | |
| 88 | +before / after code blocks for migrations | |
| 85 | 89 | ``` |
| 86 | 90 | |
| 87 | 91 | Lead with **user-visible stories**, not commit subjects. Group related |
| @@ -1,17 +1,21 @@ | ||
| 1 | 1 | ## Highlights |
| 2 | 2 | |
| 3 | 3 | ### Install paths fixed |
| 4 | + | |
| 4 | 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 | 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 | 9 | ### MCP sources honor upstream `destructiveHint` |
| 10 | + | |
| 9 | 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 | 13 | ### Set tool policies from the Tools page |
| 14 | + | |
| 12 | 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 | 17 | ### Per-user OAuth for OpenAPI and MCP sources |
| 18 | + | |
| 15 | 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 | 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 | 24 | - Existing OpenAPI + MCP + google-discovery OAuth rows migrate into Connections automatically on first launch — no user action required. |
| 21 | 25 | |
| 22 | 26 | ### OpenAPI: client-credentials, non-JSON bodies, source refresh |
| 27 | + | |
| 23 | 28 | - Full **OAuth2 client-credentials** flow end-to-end. |
| 24 | 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 | 30 | - Relative OAuth2 URLs resolve against the source's `baseUrl`. |
| 26 | 31 | - Refresh a source by re-fetching its origin URL from the edit page. |
| 27 | 32 | |
| 28 | 33 | ### Layered scope isolation |
| 34 | + | |
| 29 | 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 | 37 | ### Natural CLI for tool discovery and invocation |
| 38 | + | |
| 32 | 39 | Call tools by path instead of writing TypeScript: |
| 33 | 40 | |
| 34 | 41 | ```bash |
| @@ -41,6 +48,7 @@ executor tools describe github.issues.create · 6 unmodified lines | ||
| 41 | 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 | 50 | ### Daemon lifecycle |
| 51 | + | |
| 44 | 52 | ```bash |
| 45 | 53 | executor daemon run |
| 46 | 54 | executor daemon status |
| @@ -53,16 +61,20 @@ executor daemon restart · 6 unmodified lines | ||
| 53 | 61 | `executor daemon run` now backgrounds by default. Pass `--foreground` to keep it attached for log inspection. |
| 54 | 62 | |
| 55 | 63 | ### OpenTelemetry everywhere |
| 64 | + | |
| 56 | 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 | 67 | ## New presets |
| 68 | + | |
| 59 | 69 | - **Notion** is now a featured MCP preset. |
| 60 | 70 | |
| 61 | 71 | ## Performance |
| 72 | + | |
| 62 | 73 | - `buildExecuteDescription` no longer calls `executor.tools.list`, making tool-description generation measurably faster on large workspaces. |
| 63 | 74 | - Per-scope blob and secret lookups now use a single `IN` query instead of N per-scope round-trips. |
| 64 | 75 | |
| 65 | 76 | ## Fixes |
| 77 | + | |
| 66 | 78 | - Upgrade: preserve legacy OAuth connection backfills after the `connection.kind` column is removed. |
| 67 | 79 | - OpenAPI: refreshing or editing sources with legacy inline secret/OAuth config now materializes the new source binding rows instead of dropping credentials. |
| 68 | 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 | 84 | ## Breaking changes |
| 73 | 85 | |
| 74 | 86 | ### `executor call` no longer accepts inline code |
| 87 | + | |
| 75 | 88 | The old TypeScript-as-argument forms are gone: |
| 76 | 89 | |
| 77 | 90 | ```bash |
| @@ -92,6 +105,7 @@ executor call github issues list '{"owner":"octocat","repo":"Hello-World"}' · 14 unmodified lines | ||
| 92 | 105 | `tools.discover(...)` becomes `executor tools search "<query>"`. |
| 93 | 106 | |
| 94 | 107 | ### `sources.add` CLI form simplified |
| 108 | + | |
| 95 | 109 | Use the dedicated tool: |
| 96 | 110 | |
| 97 | 111 | ```bash |
| @@ -105,4 +119,5 @@ executor call openapi addSource '{ · 7 unmodified lines | ||
| 105 | 119 | Pass `baseUrl` when the OpenAPI document has relative `servers` entries. |
| 106 | 120 | |
| 107 | 121 | ### SDK: layered scope |
| 122 | + | |
| 108 | 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 | 68 | /** Per-platform suffix used in dist directory names, npm dist-tags, and the |
| 69 | 69 | * semver prerelease segment of variant package versions. e.g. "linux-x64", |
| 70 | 70 | * "linux-x64-musl", "darwin-arm64". */ |
| 71 | −const platformTag = (t: Target) => | |
| 72 | − [platformName(t), t.arch, t.abi].filter(Boolean).join("-"); | |
| 71 | +const platformTag = (t: Target) => [platformName(t), t.arch, t.abi].filter(Boolean).join("-"); | |
| 73 | 72 | |
| 74 | 73 | /** Dist directory name (e.g. dist/executor-linux-x64). Only used as a build |
| 75 | 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 | 125 | const platformMap: Record<string, { pkg: string; node: string }> = { |
| 127 | 126 | "darwin-arm64": { pkg: "@napi-rs/keyring-darwin-arm64", node: "keyring.darwin-arm64.node" }, |
| 128 | 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 | 132 | "linux-x64": { pkg: "@napi-rs/keyring-linux-x64-gnu", node: "keyring.linux-x64-gnu.node" }, |
| 131 | 133 | "linux-arm64-musl": { |
| 132 | 134 | pkg: "@napi-rs/keyring-linux-arm64-musl", |
| @@ -146,9 +148,7 @@ const resolveKeyringNative = (t: Target): string | null => { · 13 unmodified lines | ||
| 146 | 148 | const entry = platformMap[key]; |
| 147 | 149 | if (!entry) return null; |
| 148 | 150 | try { |
| 149 | − const req = createRequire( | |
| 150 | − join(repoRoot, "node_modules", "@napi-rs/keyring", "package.json"), | |
| 151 | − ); | |
| 151 | + const req = createRequire(join(repoRoot, "node_modules", "@napi-rs/keyring", "package.json")); | |
| 152 | 152 | const pkgJson = req.resolve(`${entry.pkg}/package.json`); |
| 153 | 153 | return join(dirname(pkgJson), entry.node); |
| 154 | 154 | } catch { |
| @@ -455,7 +455,10 @@ const buildPreviewTarballs = async (binaries: Record<string, string>) => { · 300 unmodified lines | ||
| 455 | 455 | |
| 456 | 456 | const resolveTargetsFromEnv = (env: string | undefined): Target[] => { |
| 457 | 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 | 462 | const resolved = names.map((name) => { |
| 460 | 463 | const match = ALL_TARGETS.find((t) => targetPackageName(t) === name); |
| 461 | 464 | if (!match) throw new Error(`Unknown preview target: ${name}`); |
| @@ -482,10 +485,7 @@ const buildPreviewWrapperPackage = async (targets: Target[]) => { · 20 unmodified lines | ||
| 482 | 485 | await writeFile(join(binDir, "executor"), NODE_SHIM); |
| 483 | 486 | await chmod(join(binDir, "executor"), 0o755); |
| 484 | 487 | |
| 485 | − const postinstall = PREVIEW_POSTINSTALL_SCRIPT.replaceAll( | |
| 486 | − "__CDN_BASE_URL__", | |
| 487 | − `${cdnUrl}/${sha}`, | |
| 488 | − ); | |
| 488 | + const postinstall = PREVIEW_POSTINSTALL_SCRIPT.replaceAll("__CDN_BASE_URL__", `${cdnUrl}/${sha}`); | |
| 489 | 489 | await writeFile(join(wrapperDir, "postinstall.cjs"), postinstall); |
| 490 | 490 | |
| 491 | 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 | 565 | await $`bun pm pack`.cwd(pkgDir); |
| 566 | 566 | |
| 567 | 567 | const provenance = process.env.GITHUB_ACTIONS === "true" ? ["--provenance"] : []; |
| 568 | − const result = | |
| 569 | − await $`npm publish *.tgz --access public --tag ${channel} ${provenance}` | |
| 570 | − .cwd(pkgDir) | |
| 571 | − .nothrow() | |
| 572 | − .quiet(); | |
| 568 | + const result = await $`npm publish *.tgz --access public --tag ${channel} ${provenance}` | |
| 569 | + .cwd(pkgDir) | |
| 570 | + .nothrow() | |
| 571 | + .quiet(); | |
| 573 | 572 | |
| 574 | 573 | const stdout = result.stdout.toString(); |
| 575 | 574 | const stderr = result.stderr.toString(); |
| @@ -59,7 +59,8 @@ export const currentDaemonScopeId = (): string => { · 58 unmodified lines | ||
| 59 | 59 | const resolveDaemonDataDir = (path: Path.Path): string => |
| 60 | 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 | 64 | const sanitizeScopeForPath = (scopeId: string): string => scopeId.replaceAll(/[^a-z0-9.-]+/gi, "_"); |
| 64 | 65 | |
| 65 | 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 | 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 | 75 | const host = sanitizeHostForPath(canonicalDaemonHost(input.hostname)); |
| 72 | 76 | const scope = sanitizeScopeForPath(input.scopeId); |
| 73 | 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 => | |
| 77 | − `${daemonPointerPath(path, input)}.lock`; | |
| 80 | +const daemonStartLockPath = ( | |
| 81 | + path: Path.Path, | |
| 82 | + input: { hostname: string; scopeId: string }, | |
| 83 | +): string => `${daemonPointerPath(path, input)}.lock`; | |
| 78 | 84 | |
| 79 | 85 | // --------------------------------------------------------------------------- |
| 80 | 86 | // Persistence |
| @@ -195,9 +201,9 @@ export const readDaemonRecord = (input: { · 114 unmodified lines | ||
| 195 | 201 | Effect.gen(function* () { |
| 196 | 202 | const fs = yield* FileSystem.FileSystem; |
| 197 | 203 | const path = yield* Path.Path; |
| 198 | − const raw = yield* fs.readFileString(daemonRecordPath(path, input)).pipe( | |
| 199 | − Effect.catchCause(() => Effect.succeed(null)), | |
| 200 | − ); | |
| 204 | + const raw = yield* fs | |
| 205 | + .readFileString(daemonRecordPath(path, input)) | |
| 206 | + .pipe(Effect.catchCause(() => Effect.succeed(null))); | |
| 201 | 207 | if (raw === null) return null; |
| 202 | 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 | 334 | if (existingRaw !== null) { |
| 327 | 335 | const existingPid = parseLockPid(existingRaw); |
| 328 | 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< | |
| 348 | − void, | |
| 349 | − PlatformError, | |
| 350 | − FileSystem.FileSystem | Path.Path | |
| 351 | −> => | |
| 355 | +export const releaseDaemonStartLock = ( | |
| 356 | + input: DaemonStartLock, | |
| 357 | +): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> => | |
| 352 | 358 | Effect.gen(function* () { |
| 353 | 359 | const fs = yield* FileSystem.FileSystem; |
| 354 | 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 | 380 | process.kill(pid, "SIGTERM"); |
| 375 | 381 | }, |
| 376 | 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 | 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 | 174 | Effect.tryPromise({ |
| 172 | 175 | try: () => |
| 173 | 176 | new Promise<boolean>((resolve) => { |
| @@ -215,7 +218,9 @@ const pickEphemeralPort = (hostname: string): Effect.Effect<number, Error> => · 41 unmodified lines | ||
| 215 | 218 | server.listen({ port: 0, host: toProbeHost(hostname) }); |
| 216 | 219 | }), |
| 217 | 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 | 226 | export const chooseDaemonPort = (input: { |
| @@ -33,13 +33,10 @@ if (typeof Bun !== "undefined" && (await Bun.file(wasmOnDisk).exists())) { · 32 unmodified lines | ||
| 33 | 33 | type QuickJSSyncVariant = import("quickjs-emscripten").QuickJSSyncVariant; |
| 34 | 34 | const wasmBinary = await Bun.file(wasmOnDisk).arrayBuffer(); |
| 35 | 35 | const importFFI: QuickJSSyncVariant["importFFI"] = () => |
| 36 | − import("@jitl/quickjs-wasmfile-release-sync/ffi").then( | |
| 37 | − (m) => m.QuickJSFFI, | |
| 38 | − ); | |
| 36 | + import("@jitl/quickjs-wasmfile-release-sync/ffi").then((m) => m.QuickJSFFI); | |
| 39 | 37 | const importModuleLoader: QuickJSSyncVariant["importModuleLoader"] = async () => { |
| 40 | − const { default: original } = await import( | |
| 41 | − "@jitl/quickjs-wasmfile-release-sync/emscripten-module" | |
| 42 | − ); | |
| 38 | + const { default: original } = | |
| 39 | + await import("@jitl/quickjs-wasmfile-release-sync/emscripten-module"); | |
| 43 | 40 | return (moduleArg = {}) => original({ ...moduleArg, wasmBinary }); |
| 44 | 41 | }; |
| 45 | 42 | const variant: QuickJSSyncVariant = { |
| @@ -42,7 +42,10 @@ export const inspectToolPath = (input: { · 41 unmodified lines | ||
| 42 | 42 | }): ToolPathInspection => { |
| 43 | 43 | const prefixSegments = |
| 44 | 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 | 49 | let exactPath: string | undefined = undefined; |
| 47 | 50 | let matchingToolCount = 0; |
| 48 | 51 | |
| @@ -97,9 +100,7 @@ export const buildToolPath = (parts: ReadonlyArray<string>): string => { · 48 unmodified lines | ||
| 97 | 100 | throw new Error("Tool path must include at least one segment"); |
| 98 | 101 | } |
| 99 | 102 | if (segments.some((segment) => !TOOL_PATH_TOKEN.test(segment))) { |
| 100 | − throw new Error( | |
| 101 | − "Tool path segments must contain only letters, numbers, '.', '_' or '-'", | |
| 102 | − ); | |
| 103 | + throw new Error("Tool path segments must contain only letters, numbers, '.', '_' or '-'"); | |
| 103 | 104 | } |
| 104 | 105 | return segments.join("."); |
| 105 | 106 | }; |
| @@ -186,9 +187,7 @@ export interface PausedInteraction { · 80 unmodified lines | ||
| 186 | 187 | readonly requestedSchema?: Record<string, unknown>; |
| 187 | 188 | } |
| 188 | 189 | |
| 189 | −export const extractPausedInteraction = ( | |
| 190 | − structured: unknown, | |
| 191 | −): PausedInteraction | undefined => { | |
| 190 | +export const extractPausedInteraction = (structured: unknown): PausedInteraction | undefined => { | |
| 192 | 191 | if (!isRecord(structured) || !isRecord(structured.interaction)) { |
| 193 | 192 | return undefined; |
| 194 | 193 | } |
| @@ -60,12 +60,8 @@ · 59 unmodified lines | ||
| 60 | 60 | "name": "memberships_account_id_accounts_id_fk", |
| 61 | 61 | "tableFrom": "memberships", |
| 62 | 62 | "tableTo": "accounts", |
| 63 | − "columnsFrom": [ | |
| 64 | − "account_id" | |
| 65 | − ], | |
| 66 | − "columnsTo": [ | |
| 67 | − "id" | |
| 68 | − ], | |
| 63 | + "columnsFrom": ["account_id"], | |
| 64 | + "columnsTo": ["id"], | |
| 69 | 65 | "onDelete": "cascade", |
| 70 | 66 | "onUpdate": "no action" |
| 71 | 67 | }, |
| @@ -73,12 +69,8 @@ · 1 unmodified line | ||
| 73 | 69 | "name": "memberships_organization_id_organizations_id_fk", |
| 74 | 70 | "tableFrom": "memberships", |
| 75 | 71 | "tableTo": "organizations", |
| 76 | − "columnsFrom": [ | |
| 77 | − "organization_id" | |
| 78 | − ], | |
| 79 | − "columnsTo": [ | |
| 80 | − "id" | |
| 81 | − ], | |
| 72 | + "columnsFrom": ["organization_id"], | |
| 73 | + "columnsTo": ["id"], | |
| 82 | 74 | "onDelete": "cascade", |
| 83 | 75 | "onUpdate": "no action" |
| 84 | 76 | } |
| @@ -86,10 +78,7 @@ · 1 unmodified line | ||
| 86 | 78 | "compositePrimaryKeys": { |
| 87 | 79 | "memberships_account_id_organization_id_pk": { |
| 88 | 80 | "name": "memberships_account_id_organization_id_pk", |
| 89 | − "columns": [ | |
| 90 | − "account_id", | |
| 91 | − "organization_id" | |
| 92 | − ] | |
| 81 | + "columns": ["account_id", "organization_id"] | |
| 93 | 82 | } |
| 94 | 83 | }, |
| 95 | 84 | "uniqueConstraints": {}, |
| @@ -157,10 +146,7 @@ · 61 unmodified lines | ||
| 157 | 146 | "compositePrimaryKeys": { |
| 158 | 147 | "blob_namespace_key_pk": { |
| 159 | 148 | "name": "blob_namespace_key_pk", |
| 160 | − "columns": [ | |
| 161 | − "namespace", | |
| 162 | − "key" | |
| 163 | − ] | |
| 149 | + "columns": ["namespace", "key"] | |
| 164 | 150 | } |
| 165 | 151 | }, |
| 166 | 152 | "uniqueConstraints": {}, |
| @@ -266,10 +252,7 @@ · 99 unmodified lines | ||
| 266 | 252 | "compositePrimaryKeys": { |
| 267 | 253 | "definition_scope_id_id_pk": { |
| 268 | 254 | "name": "definition_scope_id_id_pk", |
| 269 | − "columns": [ | |
| 270 | − "scope_id", | |
| 271 | − "id" | |
| 272 | − ] | |
| 255 | + "columns": ["scope_id", "id"] | |
| 273 | 256 | } |
| 274 | 257 | }, |
| 275 | 258 | "uniqueConstraints": {}, |
| @@ -342,10 +325,7 @@ · 66 unmodified lines | ||
| 342 | 325 | "compositePrimaryKeys": { |
| 343 | 326 | "graphql_operation_scope_id_id_pk": { |
| 344 | 327 | "name": "graphql_operation_scope_id_id_pk", |
| 345 | − "columns": [ | |
| 346 | − "scope_id", | |
| 347 | − "id" | |
| 348 | − ] | |
| 328 | + "columns": ["scope_id", "id"] | |
| 349 | 329 | } |
| 350 | 330 | }, |
| 351 | 331 | "uniqueConstraints": {}, |
| @@ -409,10 +389,7 @@ · 57 unmodified lines | ||
| 409 | 389 | "compositePrimaryKeys": { |
| 410 | 390 | "graphql_source_scope_id_id_pk": { |
| 411 | 391 | "name": "graphql_source_scope_id_id_pk", |
| 412 | − "columns": [ | |
| 413 | − "scope_id", | |
| 414 | − "id" | |
| 415 | − ] | |
| 392 | + "columns": ["scope_id", "id"] | |
| 416 | 393 | } |
| 417 | 394 | }, |
| 418 | 395 | "uniqueConstraints": {}, |
| @@ -491,10 +468,7 @@ · 72 unmodified lines | ||
| 491 | 468 | "compositePrimaryKeys": { |
| 492 | 469 | "mcp_binding_scope_id_id_pk": { |
| 493 | 470 | "name": "mcp_binding_scope_id_id_pk", |
| 494 | − "columns": [ | |
| 495 | − "scope_id", | |
| 496 | − "id" | |
| 497 | − ] | |
| 471 | + "columns": ["scope_id", "id"] | |
| 498 | 472 | } |
| 499 | 473 | }, |
| 500 | 474 | "uniqueConstraints": {}, |
| @@ -558,10 +532,7 @@ · 57 unmodified lines | ||
| 558 | 532 | "compositePrimaryKeys": { |
| 559 | 533 | "mcp_oauth_session_scope_id_id_pk": { |
| 560 | 534 | "name": "mcp_oauth_session_scope_id_id_pk", |
| 561 | − "columns": [ | |
| 562 | − "scope_id", | |
| 563 | − "id" | |
| 564 | − ] | |
| 535 | + "columns": ["scope_id", "id"] | |
| 565 | 536 | } |
| 566 | 537 | }, |
| 567 | 538 | "uniqueConstraints": {}, |
| @@ -625,10 +596,7 @@ · 57 unmodified lines | ||
| 625 | 596 | "compositePrimaryKeys": { |
| 626 | 597 | "mcp_source_scope_id_id_pk": { |
| 627 | 598 | "name": "mcp_source_scope_id_id_pk", |
| 628 | − "columns": [ | |
| 629 | − "scope_id", | |
| 630 | − "id" | |
| 631 | − ] | |
| 599 | + "columns": ["scope_id", "id"] | |
| 632 | 600 | } |
| 633 | 601 | }, |
| 634 | 602 | "uniqueConstraints": {}, |
| @@ -686,10 +654,7 @@ · 51 unmodified lines | ||
| 686 | 654 | "compositePrimaryKeys": { |
| 687 | 655 | "openapi_oauth_session_scope_id_id_pk": { |
| 688 | 656 | "name": "openapi_oauth_session_scope_id_id_pk", |
| 689 | − "columns": [ | |
| 690 | − "scope_id", | |
| 691 | − "id" | |
| 692 | − ] | |
| 657 | + "columns": ["scope_id", "id"] | |
| 693 | 658 | } |
| 694 | 659 | }, |
| 695 | 660 | "uniqueConstraints": {}, |
| @@ -762,10 +727,7 @@ · 66 unmodified lines | ||
| 762 | 727 | "compositePrimaryKeys": { |
| 763 | 728 | "openapi_operation_scope_id_id_pk": { |
| 764 | 729 | "name": "openapi_operation_scope_id_id_pk", |
| 765 | − "columns": [ | |
| 766 | − "scope_id", | |
| 767 | − "id" | |
| 768 | − ] | |
| 730 | + "columns": ["scope_id", "id"] | |
| 769 | 731 | } |
| 770 | 732 | }, |
| 771 | 733 | "uniqueConstraints": {}, |
| @@ -847,10 +809,7 @@ · 75 unmodified lines | ||
| 847 | 809 | "compositePrimaryKeys": { |
| 848 | 810 | "openapi_source_scope_id_id_pk": { |
| 849 | 811 | "name": "openapi_source_scope_id_id_pk", |
| 850 | − "columns": [ | |
| 851 | − "scope_id", | |
| 852 | − "id" | |
| 853 | − ] | |
| 812 | + "columns": ["scope_id", "id"] | |
| 854 | 813 | } |
| 855 | 814 | }, |
| 856 | 815 | "uniqueConstraints": {}, |
| @@ -929,10 +888,7 @@ · 72 unmodified lines | ||
| 929 | 888 | "compositePrimaryKeys": { |
| 930 | 889 | "secret_scope_id_id_pk": { |
| 931 | 890 | "name": "secret_scope_id_id_pk", |
| 932 | − "columns": [ | |
| 933 | − "scope_id", | |
| 934 | − "id" | |
| 935 | − ] | |
| 891 | + "columns": ["scope_id", "id"] | |
| 936 | 892 | } |
| 937 | 893 | }, |
| 938 | 894 | "uniqueConstraints": {}, |
| @@ -1050,10 +1006,7 @@ · 111 unmodified lines | ||
| 1050 | 1006 | "compositePrimaryKeys": { |
| 1051 | 1007 | "source_scope_id_id_pk": { |
| 1052 | 1008 | "name": "source_scope_id_id_pk", |
| 1053 | − "columns": [ | |
| 1054 | − "scope_id", | |
| 1055 | − "id" | |
| 1056 | − ] | |
| 1009 | + "columns": ["scope_id", "id"] | |
| 1057 | 1010 | } |
| 1058 | 1011 | }, |
| 1059 | 1012 | "uniqueConstraints": {}, |
| @@ -1177,10 +1130,7 @@ · 117 unmodified lines | ||
| 1177 | 1130 | "compositePrimaryKeys": { |
| 1178 | 1131 | "tool_scope_id_id_pk": { |
| 1179 | 1132 | "name": "tool_scope_id_id_pk", |
| 1180 | − "columns": [ | |
| 1181 | − "scope_id", | |
| 1182 | − "id" | |
| 1183 | − ] | |
| 1133 | + "columns": ["scope_id", "id"] | |
| 1184 | 1134 | } |
| 1185 | 1135 | }, |
| 1186 | 1136 | "uniqueConstraints": {}, |
| @@ -1244,10 +1194,7 @@ · 57 unmodified lines | ||
| 1244 | 1194 | "compositePrimaryKeys": { |
| 1245 | 1195 | "workos_vault_metadata_scope_id_id_pk": { |
| 1246 | 1196 | "name": "workos_vault_metadata_scope_id_id_pk", |
| 1247 | − "columns": [ | |
| 1248 | − "scope_id", | |
| 1249 | − "id" | |
| 1250 | − ] | |
| 1197 | + "columns": ["scope_id", "id"] | |
| 1251 | 1198 | } |
| 1252 | 1199 | }, |
| 1253 | 1200 | "uniqueConstraints": {}, |
| @@ -1267,4 +1214,4 @@ · 13 unmodified lines | ||
| 1267 | 1214 | "schemas": {}, |
| 1268 | 1215 | "tables": {} |
| 1269 | 1216 | } |
| 1270 | −} | |
| 1217 | +} | |
| @@ -60,12 +60,8 @@ · 59 unmodified lines | ||
| 60 | 60 | "name": "memberships_account_id_accounts_id_fk", |
| 61 | 61 | "tableFrom": "memberships", |
| 62 | 62 | "tableTo": "accounts", |
| 63 | − "columnsFrom": [ | |
| 64 | − "account_id" | |
| 65 | − ], | |
| 66 | − "columnsTo": [ | |
| 67 | − "id" | |
| 68 | − ], | |
| 63 | + "columnsFrom": ["account_id"], | |
| 64 | + "columnsTo": ["id"], | |
| 69 | 65 | "onDelete": "cascade", |
| 70 | 66 | "onUpdate": "no action" |
| 71 | 67 | }, |
| @@ -73,12 +69,8 @@ · 1 unmodified line | ||
| 73 | 69 | "name": "memberships_organization_id_organizations_id_fk", |
| 74 | 70 | "tableFrom": "memberships", |
| 75 | 71 | "tableTo": "organizations", |
| 76 | − "columnsFrom": [ | |
| 77 | − "organization_id" | |
| 78 | − ], | |
| 79 | − "columnsTo": [ | |
| 80 | − "id" | |
| 81 | − ], | |
| 72 | + "columnsFrom": ["organization_id"], | |
| 73 | + "columnsTo": ["id"], | |
| 82 | 74 | "onDelete": "cascade", |
| 83 | 75 | "onUpdate": "no action" |
| 84 | 76 | } |
| @@ -86,10 +78,7 @@ · 1 unmodified line | ||
| 86 | 78 | "compositePrimaryKeys": { |
| 87 | 79 | "memberships_account_id_organization_id_pk": { |
| 88 | 80 | "name": "memberships_account_id_organization_id_pk", |
| 89 | − "columns": [ | |
| 90 | − "account_id", | |
| 91 | − "organization_id" | |
| 92 | − ] | |
| 81 | + "columns": ["account_id", "organization_id"] | |
| 93 | 82 | } |
| 94 | 83 | }, |
| 95 | 84 | "uniqueConstraints": {}, |
| @@ -157,10 +146,7 @@ · 61 unmodified lines | ||
| 157 | 146 | "compositePrimaryKeys": { |
| 158 | 147 | "blob_namespace_key_pk": { |
| 159 | 148 | "name": "blob_namespace_key_pk", |
| 160 | − "columns": [ | |
| 161 | − "namespace", | |
| 162 | − "key" | |
| 163 | − ] | |
| 149 | + "columns": ["namespace", "key"] | |
| 164 | 150 | } |
| 165 | 151 | }, |
| 166 | 152 | "uniqueConstraints": {}, |
| @@ -266,10 +252,7 @@ · 99 unmodified lines | ||
| 266 | 252 | "compositePrimaryKeys": { |
| 267 | 253 | "definition_scope_id_id_pk": { |
| 268 | 254 | "name": "definition_scope_id_id_pk", |
| 269 | − "columns": [ | |
| 270 | − "scope_id", | |
| 271 | − "id" | |
| 272 | − ] | |
| 255 | + "columns": ["scope_id", "id"] | |
| 273 | 256 | } |
| 274 | 257 | }, |
| 275 | 258 | "uniqueConstraints": {}, |
| @@ -342,10 +325,7 @@ · 66 unmodified lines | ||
| 342 | 325 | "compositePrimaryKeys": { |
| 343 | 326 | "graphql_operation_scope_id_id_pk": { |
| 344 | 327 | "name": "graphql_operation_scope_id_id_pk", |
| 345 | − "columns": [ | |
| 346 | − "scope_id", | |
| 347 | − "id" | |
| 348 | − ] | |
| 328 | + "columns": ["scope_id", "id"] | |
| 349 | 329 | } |
| 350 | 330 | }, |
| 351 | 331 | "uniqueConstraints": {}, |
| @@ -409,10 +389,7 @@ · 57 unmodified lines | ||
| 409 | 389 | "compositePrimaryKeys": { |
| 410 | 390 | "graphql_source_scope_id_id_pk": { |
| 411 | 391 | "name": "graphql_source_scope_id_id_pk", |
| 412 | − "columns": [ | |
| 413 | − "scope_id", | |
| 414 | − "id" | |
| 415 | − ] | |
| 392 | + "columns": ["scope_id", "id"] | |
| 416 | 393 | } |
| 417 | 394 | }, |
| 418 | 395 | "uniqueConstraints": {}, |
| @@ -491,10 +468,7 @@ · 72 unmodified lines | ||
| 491 | 468 | "compositePrimaryKeys": { |
| 492 | 469 | "mcp_binding_scope_id_id_pk": { |
| 493 | 470 | "name": "mcp_binding_scope_id_id_pk", |
| 494 | − "columns": [ | |
| 495 | − "scope_id", | |
| 496 | − "id" | |
| 497 | − ] | |
| 471 | + "columns": ["scope_id", "id"] | |
| 498 | 472 | } |
| 499 | 473 | }, |
| 500 | 474 | "uniqueConstraints": {}, |
| @@ -558,10 +532,7 @@ · 57 unmodified lines | ||
| 558 | 532 | "compositePrimaryKeys": { |
| 559 | 533 | "mcp_oauth_session_scope_id_id_pk": { |
| 560 | 534 | "name": "mcp_oauth_session_scope_id_id_pk", |
| 561 | − "columns": [ | |
| 562 | − "scope_id", | |
| 563 | − "id" | |
| 564 | − ] | |
| 535 | + "columns": ["scope_id", "id"] | |
| 565 | 536 | } |
| 566 | 537 | }, |
| 567 | 538 | "uniqueConstraints": {}, |
| @@ -625,10 +596,7 @@ · 57 unmodified lines | ||
| 625 | 596 | "compositePrimaryKeys": { |
| 626 | 597 | "mcp_source_scope_id_id_pk": { |
| 627 | 598 | "name": "mcp_source_scope_id_id_pk", |
| 628 | − "columns": [ | |
| 629 | − "scope_id", | |
| 630 | − "id" | |
| 631 | − ] | |
| 599 | + "columns": ["scope_id", "id"] | |
| 632 | 600 | } |
| 633 | 601 | }, |
| 634 | 602 | "uniqueConstraints": {}, |
| @@ -686,10 +654,7 @@ · 51 unmodified lines | ||
| 686 | 654 | "compositePrimaryKeys": { |
| 687 | 655 | "openapi_oauth_session_scope_id_id_pk": { |
| 688 | 656 | "name": "openapi_oauth_session_scope_id_id_pk", |
| 689 | − "columns": [ | |
| 690 | − "scope_id", | |
| 691 | − "id" | |
| 692 | − ] | |
| 657 | + "columns": ["scope_id", "id"] | |
| 693 | 658 | } |
| 694 | 659 | }, |
| 695 | 660 | "uniqueConstraints": {}, |
| @@ -762,10 +727,7 @@ · 66 unmodified lines | ||
| 762 | 727 | "compositePrimaryKeys": { |
| 763 | 728 | "openapi_operation_scope_id_id_pk": { |
| 764 | 729 | "name": "openapi_operation_scope_id_id_pk", |
| 765 | − "columns": [ | |
| 766 | − "scope_id", | |
| 767 | − "id" | |
| 768 | − ] | |
| 730 | + "columns": ["scope_id", "id"] | |
| 769 | 731 | } |
| 770 | 732 | }, |
| 771 | 733 | "uniqueConstraints": {}, |
| @@ -853,10 +815,7 @@ · 81 unmodified lines | ||
| 853 | 815 | "compositePrimaryKeys": { |
| 854 | 816 | "openapi_source_scope_id_id_pk": { |
| 855 | 817 | "name": "openapi_source_scope_id_id_pk", |
| 856 | − "columns": [ | |
| 857 | − "scope_id", | |
| 858 | − "id" | |
| 859 | − ] | |
| 818 | + "columns": ["scope_id", "id"] | |
| 860 | 819 | } |
| 861 | 820 | }, |
| 862 | 821 | "uniqueConstraints": {}, |
| @@ -935,10 +894,7 @@ · 72 unmodified lines | ||
| 935 | 894 | "compositePrimaryKeys": { |
| 936 | 895 | "secret_scope_id_id_pk": { |
| 937 | 896 | "name": "secret_scope_id_id_pk", |
| 938 | − "columns": [ | |
| 939 | − "scope_id", | |
| 940 | − "id" | |
| 941 | − ] | |
| 897 | + "columns": ["scope_id", "id"] | |
| 942 | 898 | } |
| 943 | 899 | }, |
| 944 | 900 | "uniqueConstraints": {}, |
| @@ -1056,10 +1012,7 @@ · 111 unmodified lines | ||
| 1056 | 1012 | "compositePrimaryKeys": { |
| 1057 | 1013 | "source_scope_id_id_pk": { |
| 1058 | 1014 | "name": "source_scope_id_id_pk", |
| 1059 | − "columns": [ | |
| 1060 | − "scope_id", | |
| 1061 | − "id" | |
| 1062 | − ] | |
| 1015 | + "columns": ["scope_id", "id"] | |
| 1063 | 1016 | } |
| 1064 | 1017 | }, |
| 1065 | 1018 | "uniqueConstraints": {}, |
| @@ -1183,10 +1136,7 @@ · 117 unmodified lines | ||
| 1183 | 1136 | "compositePrimaryKeys": { |
| 1184 | 1137 | "tool_scope_id_id_pk": { |
| 1185 | 1138 | "name": "tool_scope_id_id_pk", |
| 1186 | − "columns": [ | |
| 1187 | − "scope_id", | |
| 1188 | − "id" | |
| 1189 | − ] | |
| 1139 | + "columns": ["scope_id", "id"] | |
| 1190 | 1140 | } |
| 1191 | 1141 | }, |
| 1192 | 1142 | "uniqueConstraints": {}, |
| @@ -1250,10 +1200,7 @@ · 57 unmodified lines | ||
| 1250 | 1200 | "compositePrimaryKeys": { |
| 1251 | 1201 | "workos_vault_metadata_scope_id_id_pk": { |
| 1252 | 1202 | "name": "workos_vault_metadata_scope_id_id_pk", |
| 1253 | − "columns": [ | |
| 1254 | − "scope_id", | |
| 1255 | − "id" | |
| 1256 | − ] | |
| 1203 | + "columns": ["scope_id", "id"] | |
| 1257 | 1204 | } |
| 1258 | 1205 | }, |
| 1259 | 1206 | "uniqueConstraints": {}, |
| @@ -1273,4 +1220,4 @@ · 13 unmodified lines | ||
| 1273 | 1220 | "schemas": {}, |
| 1274 | 1221 | "tables": {} |
| 1275 | 1222 | } |
| 1276 | −} | |
| 1223 | +} | |
| @@ -60,12 +60,8 @@ · 59 unmodified lines | ||
| 60 | 60 | "name": "memberships_account_id_accounts_id_fk", |
| 61 | 61 | "tableFrom": "memberships", |
| 62 | 62 | "tableTo": "accounts", |
| 63 | − "columnsFrom": [ | |
| 64 | − "account_id" | |
| 65 | − ], | |
| 66 | − "columnsTo": [ | |
| 67 | − "id" | |
| 68 | − ], | |
| 63 | + "columnsFrom": ["account_id"], | |
| 64 | + "columnsTo": ["id"], | |
| 69 | 65 | "onDelete": "cascade", |
| 70 | 66 | "onUpdate": "no action" |
| 71 | 67 | }, |
| @@ -73,12 +69,8 @@ · 1 unmodified line | ||
| 73 | 69 | "name": "memberships_organization_id_organizations_id_fk", |
| 74 | 70 | "tableFrom": "memberships", |
| 75 | 71 | "tableTo": "organizations", |
| 76 | − "columnsFrom": [ | |
| 77 | − "organization_id" | |
| 78 | − ], | |
| 79 | − "columnsTo": [ | |
| 80 | − "id" | |
| 81 | − ], | |
| 72 | + "columnsFrom": ["organization_id"], | |
| 73 | + "columnsTo": ["id"], | |
| 82 | 74 | "onDelete": "cascade", |
| 83 | 75 | "onUpdate": "no action" |
| 84 | 76 | } |
| @@ -86,10 +78,7 @@ · 1 unmodified line | ||
| 86 | 78 | "compositePrimaryKeys": { |
| 87 | 79 | "memberships_account_id_organization_id_pk": { |
| 88 | 80 | "name": "memberships_account_id_organization_id_pk", |
| 89 | − "columns": [ | |
| 90 | − "account_id", | |
| 91 | − "organization_id" | |
| 92 | − ] | |
| 81 | + "columns": ["account_id", "organization_id"] | |
| 93 | 82 | } |
| 94 | 83 | }, |
| 95 | 84 | "uniqueConstraints": {}, |
| @@ -157,10 +146,7 @@ · 61 unmodified lines | ||
| 157 | 146 | "compositePrimaryKeys": { |
| 158 | 147 | "blob_namespace_key_pk": { |
| 159 | 148 | "name": "blob_namespace_key_pk", |
| 160 | − "columns": [ | |
| 161 | − "namespace", | |
| 162 | − "key" | |
| 163 | − ] | |
| 149 | + "columns": ["namespace", "key"] | |
| 164 | 150 | } |
| 165 | 151 | }, |
| 166 | 152 | "uniqueConstraints": {}, |
| @@ -266,10 +252,7 @@ · 99 unmodified lines | ||
| 266 | 252 | "compositePrimaryKeys": { |
| 267 | 253 | "definition_scope_id_id_pk": { |
| 268 | 254 | "name": "definition_scope_id_id_pk", |
| 269 | − "columns": [ | |
| 270 | − "scope_id", | |
| 271 | − "id" | |
| 272 | − ] | |
| 255 | + "columns": ["scope_id", "id"] | |
| 273 | 256 | } |
| 274 | 257 | }, |
| 275 | 258 | "uniqueConstraints": {}, |
| @@ -342,10 +325,7 @@ · 66 unmodified lines | ||
| 342 | 325 | "compositePrimaryKeys": { |
| 343 | 326 | "graphql_operation_scope_id_id_pk": { |
| 344 | 327 | "name": "graphql_operation_scope_id_id_pk", |
| 345 | − "columns": [ | |
| 346 | − "scope_id", | |
| 347 | − "id" | |
| 348 | − ] | |
| 328 | + "columns": ["scope_id", "id"] | |
| 349 | 329 | } |
| 350 | 330 | }, |
| 351 | 331 | "uniqueConstraints": {}, |
| @@ -409,10 +389,7 @@ · 57 unmodified lines | ||
| 409 | 389 | "compositePrimaryKeys": { |
| 410 | 390 | "graphql_source_scope_id_id_pk": { |
| 411 | 391 | "name": "graphql_source_scope_id_id_pk", |
| 412 | − "columns": [ | |
| 413 | − "scope_id", | |
| 414 | − "id" | |
| 415 | − ] | |
| 392 | + "columns": ["scope_id", "id"] | |
| 416 | 393 | } |
| 417 | 394 | }, |
| 418 | 395 | "uniqueConstraints": {}, |
| @@ -491,10 +468,7 @@ · 72 unmodified lines | ||
| 491 | 468 | "compositePrimaryKeys": { |
| 492 | 469 | "mcp_binding_scope_id_id_pk": { |
| 493 | 470 | "name": "mcp_binding_scope_id_id_pk", |
| 494 | − "columns": [ | |
| 495 | − "scope_id", | |
| 496 | − "id" | |
| 497 | − ] | |
| 471 | + "columns": ["scope_id", "id"] | |
| 498 | 472 | } |
| 499 | 473 | }, |
| 500 | 474 | "uniqueConstraints": {}, |
| @@ -558,10 +532,7 @@ · 57 unmodified lines | ||
| 558 | 532 | "compositePrimaryKeys": { |
| 559 | 533 | "mcp_oauth_session_scope_id_id_pk": { |
| 560 | 534 | "name": "mcp_oauth_session_scope_id_id_pk", |
| 561 | − "columns": [ | |
| 562 | − "scope_id", | |
| 563 | − "id" | |
| 564 | − ] | |
| 535 | + "columns": ["scope_id", "id"] | |
| 565 | 536 | } |
| 566 | 537 | }, |
| 567 | 538 | "uniqueConstraints": {}, |
| @@ -625,10 +596,7 @@ · 57 unmodified lines | ||
| 625 | 596 | "compositePrimaryKeys": { |
| 626 | 597 | "mcp_source_scope_id_id_pk": { |
| 627 | 598 | "name": "mcp_source_scope_id_id_pk", |
| 628 | − "columns": [ | |
| 629 | − "scope_id", | |
| 630 | − "id" | |
| 631 | − ] | |
| 599 | + "columns": ["scope_id", "id"] | |
| 632 | 600 | } |
| 633 | 601 | }, |
| 634 | 602 | "uniqueConstraints": {}, |
| @@ -686,10 +654,7 @@ · 51 unmodified lines | ||
| 686 | 654 | "compositePrimaryKeys": { |
| 687 | 655 | "openapi_oauth_session_scope_id_id_pk": { |
| 688 | 656 | "name": "openapi_oauth_session_scope_id_id_pk", |
| 689 | − "columns": [ | |
| 690 | − "scope_id", | |
| 691 | − "id" | |
| 692 | − ] | |
| 657 | + "columns": ["scope_id", "id"] | |
| 693 | 658 | } |
| 694 | 659 | }, |
| 695 | 660 | "uniqueConstraints": {}, |
| @@ -762,10 +727,7 @@ · 66 unmodified lines | ||
| 762 | 727 | "compositePrimaryKeys": { |
| 763 | 728 | "openapi_operation_scope_id_id_pk": { |
| 764 | 729 | "name": "openapi_operation_scope_id_id_pk", |
| 765 | − "columns": [ | |
| 766 | − "scope_id", | |
| 767 | − "id" | |
| 768 | − ] | |
| 730 | + "columns": ["scope_id", "id"] | |
| 769 | 731 | } |
| 770 | 732 | }, |
| 771 | 733 | "uniqueConstraints": {}, |
| @@ -853,10 +815,7 @@ · 81 unmodified lines | ||
| 853 | 815 | "compositePrimaryKeys": { |
| 854 | 816 | "openapi_source_scope_id_id_pk": { |
| 855 | 817 | "name": "openapi_source_scope_id_id_pk", |
| 856 | − "columns": [ | |
| 857 | − "scope_id", | |
| 858 | − "id" | |
| 859 | − ] | |
| 818 | + "columns": ["scope_id", "id"] | |
| 860 | 819 | } |
| 861 | 820 | }, |
| 862 | 821 | "uniqueConstraints": {}, |
| @@ -935,10 +894,7 @@ · 72 unmodified lines | ||
| 935 | 894 | "compositePrimaryKeys": { |
| 936 | 895 | "secret_scope_id_id_pk": { |
| 937 | 896 | "name": "secret_scope_id_id_pk", |
| 938 | − "columns": [ | |
| 939 | − "scope_id", | |
| 940 | − "id" | |
| 941 | − ] | |
| 897 | + "columns": ["scope_id", "id"] | |
| 942 | 898 | } |
| 943 | 899 | }, |
| 944 | 900 | "uniqueConstraints": {}, |
| @@ -1056,10 +1012,7 @@ · 111 unmodified lines | ||
| 1056 | 1012 | "compositePrimaryKeys": { |
| 1057 | 1013 | "source_scope_id_id_pk": { |
| 1058 | 1014 | "name": "source_scope_id_id_pk", |
| 1059 | − "columns": [ | |
| 1060 | − "scope_id", | |
| 1061 | − "id" | |
| 1062 | − ] | |
| 1015 | + "columns": ["scope_id", "id"] | |
| 1063 | 1016 | } |
| 1064 | 1017 | }, |
| 1065 | 1018 | "uniqueConstraints": {}, |
| @@ -1183,10 +1136,7 @@ · 117 unmodified lines | ||
| 1183 | 1136 | "compositePrimaryKeys": { |
| 1184 | 1137 | "tool_scope_id_id_pk": { |
| 1185 | 1138 | "name": "tool_scope_id_id_pk", |
| 1186 | − "columns": [ | |
| 1187 | − "scope_id", | |
| 1188 | − "id" | |
| 1189 | − ] | |
| 1139 | + "columns": ["scope_id", "id"] | |
| 1190 | 1140 | } |
| 1191 | 1141 | }, |
| 1192 | 1142 | "uniqueConstraints": {}, |
| @@ -1250,10 +1200,7 @@ · 57 unmodified lines | ||
| 1250 | 1200 | "compositePrimaryKeys": { |
| 1251 | 1201 | "workos_vault_metadata_scope_id_id_pk": { |
| 1252 | 1202 | "name": "workos_vault_metadata_scope_id_id_pk", |
| 1253 | − "columns": [ | |
| 1254 | − "scope_id", | |
| 1255 | − "id" | |
| 1256 | − ] | |
| 1203 | + "columns": ["scope_id", "id"] | |
| 1257 | 1204 | } |
| 1258 | 1205 | }, |
| 1259 | 1206 | "uniqueConstraints": {}, |
| @@ -1273,4 +1220,4 @@ · 13 unmodified lines | ||
| 1273 | 1220 | "schemas": {}, |
| 1274 | 1221 | "tables": {} |
| 1275 | 1222 | } |
| 1276 | −} | |
| 1223 | +} | |
| @@ -60,12 +60,8 @@ · 59 unmodified lines | ||
| 60 | 60 | "name": "memberships_account_id_accounts_id_fk", |
| 61 | 61 | "tableFrom": "memberships", |
| 62 | 62 | "tableTo": "accounts", |
| 63 | − "columnsFrom": [ | |
| 64 | − "account_id" | |
| 65 | − ], | |
| 66 | − "columnsTo": [ | |
| 67 | − "id" | |
| 68 | − ], | |
| 63 | + "columnsFrom": ["account_id"], | |
| 64 | + "columnsTo": ["id"], | |
| 69 | 65 | "onDelete": "cascade", |
| 70 | 66 | "onUpdate": "no action" |
| 71 | 67 | }, |
| @@ -73,12 +69,8 @@ · 1 unmodified line | ||
| 73 | 69 | "name": "memberships_organization_id_organizations_id_fk", |
| 74 | 70 | "tableFrom": "memberships", |
| 75 | 71 | "tableTo": "organizations", |
| 76 | − "columnsFrom": [ | |
| 77 | − "organization_id" | |
| 78 | − ], | |
| 79 | − "columnsTo": [ | |
| 80 | − "id" | |
| 81 | − ], | |
| 72 | + "columnsFrom": ["organization_id"], | |
| 73 | + "columnsTo": ["id"], | |
| 82 | 74 | "onDelete": "cascade", |
| 83 | 75 | "onUpdate": "no action" |
| 84 | 76 | } |
| @@ -86,10 +78,7 @@ · 1 unmodified line | ||
| 86 | 78 | "compositePrimaryKeys": { |
| 87 | 79 | "memberships_account_id_organization_id_pk": { |
| 88 | 80 | "name": "memberships_account_id_organization_id_pk", |
| 89 | − "columns": [ | |
| 90 | − "account_id", | |
| 91 | − "organization_id" | |
| 92 | − ] | |
| 81 | + "columns": ["account_id", "organization_id"] | |
| 93 | 82 | } |
| 94 | 83 | }, |
| 95 | 84 | "uniqueConstraints": {}, |
| @@ -157,10 +146,7 @@ · 61 unmodified lines | ||
| 157 | 146 | "compositePrimaryKeys": { |
| 158 | 147 | "blob_namespace_key_pk": { |
| 159 | 148 | "name": "blob_namespace_key_pk", |
| 160 | − "columns": [ | |
| 161 | − "namespace", | |
| 162 | − "key" | |
| 163 | − ] | |
| 149 | + "columns": ["namespace", "key"] | |
| 164 | 150 | } |
| 165 | 151 | }, |
| 166 | 152 | "uniqueConstraints": {}, |
| @@ -281,10 +267,7 @@ · 114 unmodified lines | ||
| 281 | 267 | "compositePrimaryKeys": { |
| 282 | 268 | "connection_scope_id_id_pk": { |
| 283 | 269 | "name": "connection_scope_id_id_pk", |
| 284 | − "columns": [ | |
| 285 | − "scope_id", | |
| 286 | − "id" | |
| 287 | − ] | |
| 270 | + "columns": ["scope_id", "id"] | |
| 288 | 271 | } |
| 289 | 272 | }, |
| 290 | 273 | "uniqueConstraints": {}, |
| @@ -390,10 +373,7 @@ · 99 unmodified lines | ||
| 390 | 373 | "compositePrimaryKeys": { |
| 391 | 374 | "definition_scope_id_id_pk": { |
| 392 | 375 | "name": "definition_scope_id_id_pk", |
| 393 | − "columns": [ | |
| 394 | − "scope_id", | |
| 395 | − "id" | |
| 396 | − ] | |
| 376 | + "columns": ["scope_id", "id"] | |
| 397 | 377 | } |
| 398 | 378 | }, |
| 399 | 379 | "uniqueConstraints": {}, |
| @@ -466,10 +446,7 @@ · 66 unmodified lines | ||
| 466 | 446 | "compositePrimaryKeys": { |
| 467 | 447 | "graphql_operation_scope_id_id_pk": { |
| 468 | 448 | "name": "graphql_operation_scope_id_id_pk", |
| 469 | − "columns": [ | |
| 470 | − "scope_id", | |
| 471 | − "id" | |
| 472 | − ] | |
| 449 | + "columns": ["scope_id", "id"] | |
| 473 | 450 | } |
| 474 | 451 | }, |
| 475 | 452 | "uniqueConstraints": {}, |
| @@ -533,10 +510,7 @@ · 57 unmodified lines | ||
| 533 | 510 | "compositePrimaryKeys": { |
| 534 | 511 | "graphql_source_scope_id_id_pk": { |
| 535 | 512 | "name": "graphql_source_scope_id_id_pk", |
| 536 | − "columns": [ | |
| 537 | − "scope_id", | |
| 538 | − "id" | |
| 539 | − ] | |
| 513 | + "columns": ["scope_id", "id"] | |
| 540 | 514 | } |
| 541 | 515 | }, |
| 542 | 516 | "uniqueConstraints": {}, |
| @@ -615,10 +589,7 @@ · 72 unmodified lines | ||
| 615 | 589 | "compositePrimaryKeys": { |
| 616 | 590 | "mcp_binding_scope_id_id_pk": { |
| 617 | 591 | "name": "mcp_binding_scope_id_id_pk", |
| 618 | − "columns": [ | |
| 619 | − "scope_id", | |
| 620 | − "id" | |
| 621 | − ] | |
| 592 | + "columns": ["scope_id", "id"] | |
| 622 | 593 | } |
| 623 | 594 | }, |
| 624 | 595 | "uniqueConstraints": {}, |
| @@ -682,10 +653,7 @@ · 57 unmodified lines | ||
| 682 | 653 | "compositePrimaryKeys": { |
| 683 | 654 | "mcp_oauth_session_scope_id_id_pk": { |
| 684 | 655 | "name": "mcp_oauth_session_scope_id_id_pk", |
| 685 | − "columns": [ | |
| 686 | − "scope_id", | |
| 687 | − "id" | |
| 688 | − ] | |
| 656 | + "columns": ["scope_id", "id"] | |
| 689 | 657 | } |
| 690 | 658 | }, |
| 691 | 659 | "uniqueConstraints": {}, |
| @@ -749,10 +717,7 @@ · 57 unmodified lines | ||
| 749 | 717 | "compositePrimaryKeys": { |
| 750 | 718 | "mcp_source_scope_id_id_pk": { |
| 751 | 719 | "name": "mcp_source_scope_id_id_pk", |
| 752 | − "columns": [ | |
| 753 | − "scope_id", | |
| 754 | − "id" | |
| 755 | − ] | |
| 720 | + "columns": ["scope_id", "id"] | |
| 756 | 721 | } |
| 757 | 722 | }, |
| 758 | 723 | "uniqueConstraints": {}, |
| @@ -810,10 +775,7 @@ · 51 unmodified lines | ||
| 810 | 775 | "compositePrimaryKeys": { |
| 811 | 776 | "openapi_oauth_session_scope_id_id_pk": { |
| 812 | 777 | "name": "openapi_oauth_session_scope_id_id_pk", |
| 813 | − "columns": [ | |
| 814 | − "scope_id", | |
| 815 | − "id" | |
| 816 | − ] | |
| 778 | + "columns": ["scope_id", "id"] | |
| 817 | 779 | } |
| 818 | 780 | }, |
| 819 | 781 | "uniqueConstraints": {}, |
| @@ -886,10 +848,7 @@ · 66 unmodified lines | ||
| 886 | 848 | "compositePrimaryKeys": { |
| 887 | 849 | "openapi_operation_scope_id_id_pk": { |
| 888 | 850 | "name": "openapi_operation_scope_id_id_pk", |
| 889 | − "columns": [ | |
| 890 | − "scope_id", | |
| 891 | − "id" | |
| 892 | − ] | |
| 851 | + "columns": ["scope_id", "id"] | |
| 893 | 852 | } |
| 894 | 853 | }, |
| 895 | 854 | "uniqueConstraints": {}, |
| @@ -977,10 +936,7 @@ · 81 unmodified lines | ||
| 977 | 936 | "compositePrimaryKeys": { |
| 978 | 937 | "openapi_source_scope_id_id_pk": { |
| 979 | 938 | "name": "openapi_source_scope_id_id_pk", |
| 980 | − "columns": [ | |
| 981 | − "scope_id", | |
| 982 | − "id" | |
| 983 | − ] | |
| 939 | + "columns": ["scope_id", "id"] | |
| 984 | 940 | } |
| 985 | 941 | }, |
| 986 | 942 | "uniqueConstraints": {}, |
| @@ -1080,10 +1036,7 @@ · 93 unmodified lines | ||
| 1080 | 1036 | "compositePrimaryKeys": { |
| 1081 | 1037 | "secret_scope_id_id_pk": { |
| 1082 | 1038 | "name": "secret_scope_id_id_pk", |
| 1083 | − "columns": [ | |
| 1084 | − "scope_id", | |
| 1085 | − "id" | |
| 1086 | − ] | |
| 1039 | + "columns": ["scope_id", "id"] | |
| 1087 | 1040 | } |
| 1088 | 1041 | }, |
| 1089 | 1042 | "uniqueConstraints": {}, |
| @@ -1201,10 +1154,7 @@ · 111 unmodified lines | ||
| 1201 | 1154 | "compositePrimaryKeys": { |
| 1202 | 1155 | "source_scope_id_id_pk": { |
| 1203 | 1156 | "name": "source_scope_id_id_pk", |
| 1204 | − "columns": [ | |
| 1205 | − "scope_id", | |
| 1206 | − "id" | |
| 1207 | − ] | |
| 1157 | + "columns": ["scope_id", "id"] | |
| 1208 | 1158 | } |
| 1209 | 1159 | }, |
| 1210 | 1160 | "uniqueConstraints": {}, |
| @@ -1328,10 +1278,7 @@ · 117 unmodified lines | ||
| 1328 | 1278 | "compositePrimaryKeys": { |
| 1329 | 1279 | "tool_scope_id_id_pk": { |
| 1330 | 1280 | "name": "tool_scope_id_id_pk", |
| 1331 | − "columns": [ | |
| 1332 | − "scope_id", | |
| 1333 | − "id" | |
| 1334 | − ] | |
| 1281 | + "columns": ["scope_id", "id"] | |
| 1335 | 1282 | } |
| 1336 | 1283 | }, |
| 1337 | 1284 | "uniqueConstraints": {}, |
| @@ -1395,10 +1342,7 @@ · 57 unmodified lines | ||
| 1395 | 1342 | "compositePrimaryKeys": { |
| 1396 | 1343 | "workos_vault_metadata_scope_id_id_pk": { |
| 1397 | 1344 | "name": "workos_vault_metadata_scope_id_id_pk", |
| 1398 | − "columns": [ | |
| 1399 | − "scope_id", | |
| 1400 | − "id" | |
| 1401 | − ] | |
| 1345 | + "columns": ["scope_id", "id"] | |
| 1402 | 1346 | } |
| 1403 | 1347 | }, |
| 1404 | 1348 | "uniqueConstraints": {}, |
| @@ -1418,4 +1362,4 @@ · 13 unmodified lines | ||
| 1418 | 1362 | "schemas": {}, |
| 1419 | 1363 | "tables": {} |
| 1420 | 1364 | } |
| 1421 | −} | |
| 1365 | +} | |
| @@ -60,12 +60,8 @@ · 59 unmodified lines | ||
| 60 | 60 | "name": "memberships_account_id_accounts_id_fk", |
| 61 | 61 | "tableFrom": "memberships", |
| 62 | 62 | "tableTo": "accounts", |
| 63 | − "columnsFrom": [ | |
| 64 | − "account_id" | |
| 65 | − ], | |
| 66 | − "columnsTo": [ | |
| 67 | − "id" | |
| 68 | − ], | |
| 63 | + "columnsFrom": ["account_id"], | |
| 64 | + "columnsTo": ["id"], | |
| 69 | 65 | "onDelete": "cascade", |
| 70 | 66 | "onUpdate": "no action" |
| 71 | 67 | }, |
| @@ -73,12 +69,8 @@ · 1 unmodified line | ||
| 73 | 69 | "name": "memberships_organization_id_organizations_id_fk", |
| 74 | 70 | "tableFrom": "memberships", |
| 75 | 71 | "tableTo": "organizations", |
| 76 | − "columnsFrom": [ | |
| 77 | − "organization_id" | |
| 78 | − ], | |
| 79 | − "columnsTo": [ | |
| 80 | − "id" | |
| 81 | − ], | |
| 72 | + "columnsFrom": ["organization_id"], | |
| 73 | + "columnsTo": ["id"], | |
| 82 | 74 | "onDelete": "cascade", |
| 83 | 75 | "onUpdate": "no action" |
| 84 | 76 | } |
| @@ -86,10 +78,7 @@ · 1 unmodified line | ||
| 86 | 78 | "compositePrimaryKeys": { |
| 87 | 79 | "memberships_account_id_organization_id_pk": { |
| 88 | 80 | "name": "memberships_account_id_organization_id_pk", |
| 89 | − "columns": [ | |
| 90 | − "account_id", | |
| 91 | − "organization_id" | |
| 92 | − ] | |
| 81 | + "columns": ["account_id", "organization_id"] | |
| 93 | 82 | } |
| 94 | 83 | }, |
| 95 | 84 | "uniqueConstraints": {}, |
| @@ -157,10 +146,7 @@ · 61 unmodified lines | ||
| 157 | 146 | "compositePrimaryKeys": { |
| 158 | 147 | "blob_namespace_key_pk": { |
| 159 | 148 | "name": "blob_namespace_key_pk", |
| 160 | − "columns": [ | |
| 161 | − "namespace", | |
| 162 | − "key" | |
| 163 | − ] | |
| 149 | + "columns": ["namespace", "key"] | |
| 164 | 150 | } |
| 165 | 151 | }, |
| 166 | 152 | "uniqueConstraints": {}, |
| @@ -275,10 +261,7 @@ · 108 unmodified lines | ||
| 275 | 261 | "compositePrimaryKeys": { |
| 276 | 262 | "connection_scope_id_id_pk": { |
| 277 | 263 | "name": "connection_scope_id_id_pk", |
| 278 | − "columns": [ | |
| 279 | − "scope_id", | |
| 280 | − "id" | |
| 281 | − ] | |
| 264 | + "columns": ["scope_id", "id"] | |
| 282 | 265 | } |
| 283 | 266 | }, |
| 284 | 267 | "uniqueConstraints": {}, |
| @@ -384,10 +367,7 @@ · 99 unmodified lines | ||
| 384 | 367 | "compositePrimaryKeys": { |
| 385 | 368 | "definition_scope_id_id_pk": { |
| 386 | 369 | "name": "definition_scope_id_id_pk", |
| 387 | − "columns": [ | |
| 388 | − "scope_id", | |
| 389 | − "id" | |
| 390 | − ] | |
| 370 | + "columns": ["scope_id", "id"] | |
| 391 | 371 | } |
| 392 | 372 | }, |
| 393 | 373 | "uniqueConstraints": {}, |
| @@ -460,10 +440,7 @@ · 66 unmodified lines | ||
| 460 | 440 | "compositePrimaryKeys": { |
| 461 | 441 | "graphql_operation_scope_id_id_pk": { |
| 462 | 442 | "name": "graphql_operation_scope_id_id_pk", |
| 463 | − "columns": [ | |
| 464 | − "scope_id", | |
| 465 | − "id" | |
| 466 | − ] | |
| 443 | + "columns": ["scope_id", "id"] | |
| 467 | 444 | } |
| 468 | 445 | }, |
| 469 | 446 | "uniqueConstraints": {}, |
| @@ -539,10 +516,7 @@ · 69 unmodified lines | ||
| 539 | 516 | "compositePrimaryKeys": { |
| 540 | 517 | "graphql_source_scope_id_id_pk": { |
| 541 | 518 | "name": "graphql_source_scope_id_id_pk", |
| 542 | − "columns": [ | |
| 543 | − "scope_id", | |
| 544 | − "id" | |
| 545 | − ] | |
| 519 | + "columns": ["scope_id", "id"] | |
| 546 | 520 | } |
| 547 | 521 | }, |
| 548 | 522 | "uniqueConstraints": {}, |
| @@ -621,10 +595,7 @@ · 72 unmodified lines | ||
| 621 | 595 | "compositePrimaryKeys": { |
| 622 | 596 | "mcp_binding_scope_id_id_pk": { |
| 623 | 597 | "name": "mcp_binding_scope_id_id_pk", |
| 624 | − "columns": [ | |
| 625 | − "scope_id", | |
| 626 | − "id" | |
| 627 | − ] | |
| 598 | + "columns": ["scope_id", "id"] | |
| 628 | 599 | } |
| 629 | 600 | }, |
| 630 | 601 | "uniqueConstraints": {}, |
| @@ -688,10 +659,7 @@ · 57 unmodified lines | ||
| 688 | 659 | "compositePrimaryKeys": { |
| 689 | 660 | "mcp_source_scope_id_id_pk": { |
| 690 | 661 | "name": "mcp_source_scope_id_id_pk", |
| 691 | − "columns": [ | |
| 692 | − "scope_id", | |
| 693 | − "id" | |
| 694 | − ] | |
| 662 | + "columns": ["scope_id", "id"] | |
| 695 | 663 | } |
| 696 | 664 | }, |
| 697 | 665 | "uniqueConstraints": {}, |
| @@ -815,10 +783,7 @@ · 117 unmodified lines | ||
| 815 | 783 | "compositePrimaryKeys": { |
| 816 | 784 | "oauth2_session_scope_id_id_pk": { |
| 817 | 785 | "name": "oauth2_session_scope_id_id_pk", |
| 818 | − "columns": [ | |
| 819 | − "scope_id", | |
| 820 | − "id" | |
| 821 | − ] | |
| 786 | + "columns": ["scope_id", "id"] | |
| 822 | 787 | } |
| 823 | 788 | }, |
| 824 | 789 | "uniqueConstraints": {}, |
| @@ -891,10 +856,7 @@ · 66 unmodified lines | ||
| 891 | 856 | "compositePrimaryKeys": { |
| 892 | 857 | "openapi_operation_scope_id_id_pk": { |
| 893 | 858 | "name": "openapi_operation_scope_id_id_pk", |
| 894 | − "columns": [ | |
| 895 | − "scope_id", | |
| 896 | − "id" | |
| 897 | − ] | |
| 859 | + "columns": ["scope_id", "id"] | |
| 898 | 860 | } |
| 899 | 861 | }, |
| 900 | 862 | "uniqueConstraints": {}, |
| @@ -988,10 +950,7 @@ · 87 unmodified lines | ||
| 988 | 950 | "compositePrimaryKeys": { |
| 989 | 951 | "openapi_source_scope_id_id_pk": { |
| 990 | 952 | "name": "openapi_source_scope_id_id_pk", |
| 991 | − "columns": [ | |
| 992 | − "scope_id", | |
| 993 | − "id" | |
| 994 | − ] | |
| 953 | + "columns": ["scope_id", "id"] | |
| 995 | 954 | } |
| 996 | 955 | }, |
| 997 | 956 | "uniqueConstraints": {}, |
| @@ -1213,10 +1172,7 @@ · 215 unmodified lines | ||
| 1213 | 1172 | "compositePrimaryKeys": { |
| 1214 | 1173 | "secret_scope_id_id_pk": { |
| 1215 | 1174 | "name": "secret_scope_id_id_pk", |
| 1216 | − "columns": [ | |
| 1217 | − "scope_id", | |
| 1218 | − "id" | |
| 1219 | − ] | |
| 1175 | + "columns": ["scope_id", "id"] | |
| 1220 | 1176 | } |
| 1221 | 1177 | }, |
| 1222 | 1178 | "uniqueConstraints": {}, |
| @@ -1334,10 +1290,7 @@ · 111 unmodified lines | ||
| 1334 | 1290 | "compositePrimaryKeys": { |
| 1335 | 1291 | "source_scope_id_id_pk": { |
| 1336 | 1292 | "name": "source_scope_id_id_pk", |
| 1337 | − "columns": [ | |
| 1338 | − "scope_id", | |
| 1339 | − "id" | |
| 1340 | − ] | |
| 1293 | + "columns": ["scope_id", "id"] | |
| 1341 | 1294 | } |
| 1342 | 1295 | }, |
| 1343 | 1296 | "uniqueConstraints": {}, |
| @@ -1461,10 +1414,7 @@ · 117 unmodified lines | ||
| 1461 | 1414 | "compositePrimaryKeys": { |
| 1462 | 1415 | "tool_scope_id_id_pk": { |
| 1463 | 1416 | "name": "tool_scope_id_id_pk", |
| 1464 | − "columns": [ | |
| 1465 | − "scope_id", | |
| 1466 | − "id" | |
| 1467 | − ] | |
| 1417 | + "columns": ["scope_id", "id"] | |
| 1468 | 1418 | } |
| 1469 | 1419 | }, |
| 1470 | 1420 | "uniqueConstraints": {}, |
| @@ -1546,10 +1496,7 @@ · 75 unmodified lines | ||
| 1546 | 1496 | "compositePrimaryKeys": { |
| 1547 | 1497 | "tool_policy_scope_id_id_pk": { |
| 1548 | 1498 | "name": "tool_policy_scope_id_id_pk", |
| 1549 | − "columns": [ | |
| 1550 | − "scope_id", | |
| 1551 | − "id" | |
| 1552 | − ] | |
| 1499 | + "columns": ["scope_id", "id"] | |
| 1553 | 1500 | } |
| 1554 | 1501 | }, |
| 1555 | 1502 | "uniqueConstraints": {}, |
| @@ -1613,10 +1560,7 @@ · 57 unmodified lines | ||
| 1613 | 1560 | "compositePrimaryKeys": { |
| 1614 | 1561 | "workos_vault_metadata_scope_id_id_pk": { |
| 1615 | 1562 | "name": "workos_vault_metadata_scope_id_id_pk", |
| 1616 | − "columns": [ | |
| 1617 | − "scope_id", | |
| 1618 | − "id" | |
| 1619 | − ] | |
| 1563 | + "columns": ["scope_id", "id"] | |
| 1620 | 1564 | } |
| 1621 | 1565 | }, |
| 1622 | 1566 | "uniqueConstraints": {}, |
| @@ -1636,4 +1580,4 @@ · 13 unmodified lines | ||
| 1636 | 1580 | "schemas": {}, |
| 1637 | 1581 | "tables": {} |
| 1638 | 1582 | } |
| 1639 | −} | |
| 1583 | +} | |
| @@ -60,12 +60,8 @@ · 59 unmodified lines | ||
| 60 | 60 | "name": "memberships_account_id_accounts_id_fk", |
| 61 | 61 | "tableFrom": "memberships", |
| 62 | 62 | "tableTo": "accounts", |
| 63 | − "columnsFrom": [ | |
| 64 | − "account_id" | |
| 65 | − ], | |
| 66 | − "columnsTo": [ | |
| 67 | − "id" | |
| 68 | − ], | |
| 63 | + "columnsFrom": ["account_id"], | |
| 64 | + "columnsTo": ["id"], | |
| 69 | 65 | "onDelete": "cascade", |
| 70 | 66 | "onUpdate": "no action" |
| 71 | 67 | }, |
| @@ -73,12 +69,8 @@ · 1 unmodified line | ||
| 73 | 69 | "name": "memberships_organization_id_organizations_id_fk", |
| 74 | 70 | "tableFrom": "memberships", |
| 75 | 71 | "tableTo": "organizations", |
| 76 | − "columnsFrom": [ | |
| 77 | − "organization_id" | |
| 78 | − ], | |
| 79 | − "columnsTo": [ | |
| 80 | − "id" | |
| 81 | − ], | |
| 72 | + "columnsFrom": ["organization_id"], | |
| 73 | + "columnsTo": ["id"], | |
| 82 | 74 | "onDelete": "cascade", |
| 83 | 75 | "onUpdate": "no action" |
| 84 | 76 | } |
| @@ -86,10 +78,7 @@ · 1 unmodified line | ||
| 86 | 78 | "compositePrimaryKeys": { |
| 87 | 79 | "memberships_account_id_organization_id_pk": { |
| 88 | 80 | "name": "memberships_account_id_organization_id_pk", |
| 89 | − "columns": [ | |
| 90 | − "account_id", | |
| 91 | − "organization_id" | |
| 92 | − ] | |
| 81 | + "columns": ["account_id", "organization_id"] | |
| 93 | 82 | } |
| 94 | 83 | }, |
| 95 | 84 | "uniqueConstraints": {}, |
| @@ -157,10 +146,7 @@ · 61 unmodified lines | ||
| 157 | 146 | "compositePrimaryKeys": { |
| 158 | 147 | "blob_namespace_key_pk": { |
| 159 | 148 | "name": "blob_namespace_key_pk", |
| 160 | − "columns": [ | |
| 161 | − "namespace", | |
| 162 | − "key" | |
| 163 | − ] | |
| 149 | + "columns": ["namespace", "key"] | |
| 164 | 150 | } |
| 165 | 151 | }, |
| 166 | 152 | "uniqueConstraints": {}, |
| @@ -275,10 +261,7 @@ · 108 unmodified lines | ||
| 275 | 261 | "compositePrimaryKeys": { |
| 276 | 262 | "connection_scope_id_id_pk": { |
| 277 | 263 | "name": "connection_scope_id_id_pk", |
| 278 | − "columns": [ | |
| 279 | − "scope_id", | |
| 280 | − "id" | |
| 281 | − ] | |
| 264 | + "columns": ["scope_id", "id"] | |
| 282 | 265 | } |
| 283 | 266 | }, |
| 284 | 267 | "uniqueConstraints": {}, |
| @@ -384,10 +367,7 @@ · 99 unmodified lines | ||
| 384 | 367 | "compositePrimaryKeys": { |
| 385 | 368 | "definition_scope_id_id_pk": { |
| 386 | 369 | "name": "definition_scope_id_id_pk", |
| 387 | − "columns": [ | |
| 388 | − "scope_id", | |
| 389 | − "id" | |
| 390 | − ] | |
| 370 | + "columns": ["scope_id", "id"] | |
| 391 | 371 | } |
| 392 | 372 | }, |
| 393 | 373 | "uniqueConstraints": {}, |
| @@ -460,10 +440,7 @@ · 66 unmodified lines | ||
| 460 | 440 | "compositePrimaryKeys": { |
| 461 | 441 | "graphql_operation_scope_id_id_pk": { |
| 462 | 442 | "name": "graphql_operation_scope_id_id_pk", |
| 463 | − "columns": [ | |
| 464 | − "scope_id", | |
| 465 | − "id" | |
| 466 | − ] | |
| 443 | + "columns": ["scope_id", "id"] | |
| 467 | 444 | } |
| 468 | 445 | }, |
| 469 | 446 | "uniqueConstraints": {}, |
| @@ -549,10 +526,7 @@ · 79 unmodified lines | ||
| 549 | 526 | "compositePrimaryKeys": { |
| 550 | 527 | "graphql_source_scope_id_id_pk": { |
| 551 | 528 | "name": "graphql_source_scope_id_id_pk", |
| 552 | − "columns": [ | |
| 553 | − "scope_id", | |
| 554 | − "id" | |
| 555 | − ] | |
| 529 | + "columns": ["scope_id", "id"] | |
| 556 | 530 | } |
| 557 | 531 | }, |
| 558 | 532 | "uniqueConstraints": {}, |
| @@ -631,10 +605,7 @@ · 72 unmodified lines | ||
| 631 | 605 | "compositePrimaryKeys": { |
| 632 | 606 | "mcp_binding_scope_id_id_pk": { |
| 633 | 607 | "name": "mcp_binding_scope_id_id_pk", |
| 634 | − "columns": [ | |
| 635 | − "scope_id", | |
| 636 | − "id" | |
| 637 | − ] | |
| 608 | + "columns": ["scope_id", "id"] | |
| 638 | 609 | } |
| 639 | 610 | }, |
| 640 | 611 | "uniqueConstraints": {}, |
| @@ -801,10 +772,7 @@ · 160 unmodified lines | ||
| 801 | 772 | "compositePrimaryKeys": { |
| 802 | 773 | "mcp_source_scope_id_id_pk": { |
| 803 | 774 | "name": "mcp_source_scope_id_id_pk", |
| 804 | − "columns": [ | |
| 805 | − "scope_id", | |
| 806 | − "id" | |
| 807 | − ] | |
| 775 | + "columns": ["scope_id", "id"] | |
| 808 | 776 | } |
| 809 | 777 | }, |
| 810 | 778 | "uniqueConstraints": {}, |
| @@ -928,10 +896,7 @@ · 117 unmodified lines | ||
| 928 | 896 | "compositePrimaryKeys": { |
| 929 | 897 | "oauth2_session_scope_id_id_pk": { |
| 930 | 898 | "name": "oauth2_session_scope_id_id_pk", |
| 931 | − "columns": [ | |
| 932 | − "scope_id", | |
| 933 | − "id" | |
| 934 | − ] | |
| 899 | + "columns": ["scope_id", "id"] | |
| 935 | 900 | } |
| 936 | 901 | }, |
| 937 | 902 | "uniqueConstraints": {}, |
| @@ -1004,10 +969,7 @@ · 66 unmodified lines | ||
| 1004 | 969 | "compositePrimaryKeys": { |
| 1005 | 970 | "openapi_operation_scope_id_id_pk": { |
| 1006 | 971 | "name": "openapi_operation_scope_id_id_pk", |
| 1007 | − "columns": [ | |
| 1008 | − "scope_id", | |
| 1009 | − "id" | |
| 1010 | − ] | |
| 972 | + "columns": ["scope_id", "id"] | |
| 1011 | 973 | } |
| 1012 | 974 | }, |
| 1013 | 975 | "uniqueConstraints": {}, |
| @@ -1089,10 +1051,7 @@ · 75 unmodified lines | ||
| 1089 | 1051 | "compositePrimaryKeys": { |
| 1090 | 1052 | "openapi_source_scope_id_id_pk": { |
| 1091 | 1053 | "name": "openapi_source_scope_id_id_pk", |
| 1092 | − "columns": [ | |
| 1093 | − "scope_id", | |
| 1094 | − "id" | |
| 1095 | − ] | |
| 1054 | + "columns": ["scope_id", "id"] | |
| 1096 | 1055 | } |
| 1097 | 1056 | }, |
| 1098 | 1057 | "uniqueConstraints": {}, |
| @@ -1363,10 +1322,7 @@ · 264 unmodified lines | ||
| 1363 | 1322 | "compositePrimaryKeys": { |
| 1364 | 1323 | "secret_scope_id_id_pk": { |
| 1365 | 1324 | "name": "secret_scope_id_id_pk", |
| 1366 | − "columns": [ | |
| 1367 | − "scope_id", | |
| 1368 | − "id" | |
| 1369 | − ] | |
| 1325 | + "columns": ["scope_id", "id"] | |
| 1370 | 1326 | } |
| 1371 | 1327 | }, |
| 1372 | 1328 | "uniqueConstraints": {}, |
| @@ -1484,10 +1440,7 @@ · 111 unmodified lines | ||
| 1484 | 1440 | "compositePrimaryKeys": { |
| 1485 | 1441 | "source_scope_id_id_pk": { |
| 1486 | 1442 | "name": "source_scope_id_id_pk", |
| 1487 | − "columns": [ | |
| 1488 | − "scope_id", | |
| 1489 | − "id" | |
| 1490 | − ] | |
| 1443 | + "columns": ["scope_id", "id"] | |
| 1491 | 1444 | } |
| 1492 | 1445 | }, |
| 1493 | 1446 | "uniqueConstraints": {}, |
| @@ -1611,10 +1564,7 @@ · 117 unmodified lines | ||
| 1611 | 1564 | "compositePrimaryKeys": { |
| 1612 | 1565 | "tool_scope_id_id_pk": { |
| 1613 | 1566 | "name": "tool_scope_id_id_pk", |
| 1614 | − "columns": [ | |
| 1615 | − "scope_id", | |
| 1616 | − "id" | |
| 1617 | − ] | |
| 1567 | + "columns": ["scope_id", "id"] | |
| 1618 | 1568 | } |
| 1619 | 1569 | }, |
| 1620 | 1570 | "uniqueConstraints": {}, |
| @@ -1696,10 +1646,7 @@ · 75 unmodified lines | ||
| 1696 | 1646 | "compositePrimaryKeys": { |
| 1697 | 1647 | "tool_policy_scope_id_id_pk": { |
| 1698 | 1648 | "name": "tool_policy_scope_id_id_pk", |
| 1699 | − "columns": [ | |
| 1700 | − "scope_id", | |
| 1701 | − "id" | |
| 1702 | − ] | |
| 1649 | + "columns": ["scope_id", "id"] | |
| 1703 | 1650 | } |
| 1704 | 1651 | }, |
| 1705 | 1652 | "uniqueConstraints": {}, |
| @@ -1763,10 +1710,7 @@ · 57 unmodified lines | ||
| 1763 | 1710 | "compositePrimaryKeys": { |
| 1764 | 1711 | "workos_vault_metadata_scope_id_id_pk": { |
| 1765 | 1712 | "name": "workos_vault_metadata_scope_id_id_pk", |
| 1766 | − "columns": [ | |
| 1767 | − "scope_id", | |
| 1768 | − "id" | |
| 1769 | − ] | |
| 1713 | + "columns": ["scope_id", "id"] | |
| 1770 | 1714 | } |
| 1771 | 1715 | }, |
| 1772 | 1716 | "uniqueConstraints": {}, |
| @@ -1878,10 +1822,7 @@ · 105 unmodified lines | ||
| 1878 | 1822 | "compositePrimaryKeys": { |
| 1879 | 1823 | "graphql_source_header_scope_id_id_pk": { |
| 1880 | 1824 | "name": "graphql_source_header_scope_id_id_pk", |
| 1881 | − "columns": [ | |
| 1882 | − "scope_id", | |
| 1883 | − "id" | |
| 1884 | − ] | |
| 1825 | + "columns": ["scope_id", "id"] | |
| 1885 | 1826 | } |
| 1886 | 1827 | }, |
| 1887 | 1828 | "uniqueConstraints": {}, |
| @@ -1993,10 +1934,7 @@ · 105 unmodified lines | ||
| 1993 | 1934 | "compositePrimaryKeys": { |
| 1994 | 1935 | "graphql_source_query_param_scope_id_id_pk": { |
| 1995 | 1936 | "name": "graphql_source_query_param_scope_id_id_pk", |
| 1996 | − "columns": [ | |
| 1997 | − "scope_id", | |
| 1998 | − "id" | |
| 1999 | − ] | |
| 1937 | + "columns": ["scope_id", "id"] | |
| 2000 | 1938 | } |
| 2001 | 1939 | }, |
| 2002 | 1940 | "uniqueConstraints": {}, |
| @@ -2108,10 +2046,7 @@ · 105 unmodified lines | ||
| 2108 | 2046 | "compositePrimaryKeys": { |
| 2109 | 2047 | "openapi_source_query_param_scope_id_id_pk": { |
| 2110 | 2048 | "name": "openapi_source_query_param_scope_id_id_pk", |
| 2111 | − "columns": [ | |
| 2112 | − "scope_id", | |
| 2113 | − "id" | |
| 2114 | − ] | |
| 2049 | + "columns": ["scope_id", "id"] | |
| 2115 | 2050 | } |
| 2116 | 2051 | }, |
| 2117 | 2052 | "uniqueConstraints": {}, |
| @@ -2223,10 +2158,7 @@ · 105 unmodified lines | ||
| 2223 | 2158 | "compositePrimaryKeys": { |
| 2224 | 2159 | "openapi_source_spec_fetch_header_scope_id_id_pk": { |
| 2225 | 2160 | "name": "openapi_source_spec_fetch_header_scope_id_id_pk", |
| 2226 | − "columns": [ | |
| 2227 | − "scope_id", | |
| 2228 | − "id" | |
| 2229 | − ] | |
| 2161 | + "columns": ["scope_id", "id"] | |
| 2230 | 2162 | } |
| 2231 | 2163 | }, |
| 2232 | 2164 | "uniqueConstraints": {}, |
| @@ -2338,10 +2270,7 @@ · 105 unmodified lines | ||
| 2338 | 2270 | "compositePrimaryKeys": { |
| 2339 | 2271 | "openapi_source_spec_fetch_query_param_scope_id_id_pk": { |
| 2340 | 2272 | "name": "openapi_source_spec_fetch_query_param_scope_id_id_pk", |
| 2341 | − "columns": [ | |
| 2342 | − "scope_id", | |
| 2343 | − "id" | |
| 2344 | − ] | |
| 2273 | + "columns": ["scope_id", "id"] | |
| 2345 | 2274 | } |
| 2346 | 2275 | }, |
| 2347 | 2276 | "uniqueConstraints": {}, |
| @@ -2453,10 +2382,7 @@ · 105 unmodified lines | ||
| 2453 | 2382 | "compositePrimaryKeys": { |
| 2454 | 2383 | "mcp_source_header_scope_id_id_pk": { |
| 2455 | 2384 | "name": "mcp_source_header_scope_id_id_pk", |
| 2456 | − "columns": [ | |
| 2457 | − "scope_id", | |
| 2458 | − "id" | |
| 2459 | − ] | |
| 2385 | + "columns": ["scope_id", "id"] | |
| 2460 | 2386 | } |
| 2461 | 2387 | }, |
| 2462 | 2388 | "uniqueConstraints": {}, |
| @@ -2568,10 +2494,7 @@ · 105 unmodified lines | ||
| 2568 | 2494 | "compositePrimaryKeys": { |
| 2569 | 2495 | "mcp_source_query_param_scope_id_id_pk": { |
| 2570 | 2496 | "name": "mcp_source_query_param_scope_id_id_pk", |
| 2571 | − "columns": [ | |
| 2572 | − "scope_id", | |
| 2573 | − "id" | |
| 2574 | − ] | |
| 2497 | + "columns": ["scope_id", "id"] | |
| 2575 | 2498 | } |
| 2576 | 2499 | }, |
| 2577 | 2500 | "uniqueConstraints": {}, |
| @@ -2,10 +2,7 @@ import { defineExecutorConfig } from "@executor-js/sdk"; · 1 unmodified line | ||
| 2 | 2 | import { openApiPlugin } from "@executor-js/plugin-openapi"; |
| 3 | 3 | import { mcpPlugin } from "@executor-js/plugin-mcp"; |
| 4 | 4 | import { graphqlPlugin } from "@executor-js/plugin-graphql"; |
| 5 | −import { | |
| 6 | − workosVaultPlugin, | |
| 7 | − type WorkOSVaultClient, | |
| 8 | −} from "@executor-js/plugin-workos-vault"; | |
| 5 | +import { workosVaultPlugin, type WorkOSVaultClient } from "@executor-js/plugin-workos-vault"; | |
| 9 | 6 | |
| 10 | 7 | // --------------------------------------------------------------------------- |
| 11 | 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 | 29 | import { requestScopedMiddleware } from "./api/request-scoped"; |
| 30 | 30 | import { makeApiLive } from "./api/router"; |
| 31 | 31 | |
| 32 | −class Counter extends Context.Service< | |
| 33 | − Counter, | |
| 34 | − { readonly id: number } | |
| 35 | −>()("test/Counter") {} | |
| 36 | − | |
| 37 | −const makeCounterLive = ( | |
| 38 | − counts: { acquires: number; releases: number }, | |
| 39 | − acquireDelayMs = 0, | |
| 40 | −) => | |
| 32 | +class Counter extends Context.Service<Counter, { readonly id: number }>()("test/Counter") {} | |
| 33 | + | |
| 34 | +const makeCounterLive = (counts: { acquires: number; releases: number }, acquireDelayMs = 0) => | |
| 41 | 35 | Layer.effect(Counter)( |
| 42 | 36 | Effect.acquireRelease( |
| 43 | 37 | Effect.gen(function* () { |
| @@ -72,8 +66,7 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 28 unmodified lines | ||
| 72 | 66 | Layer.provideMerge(makeCounterLive(counts)), |
| 73 | 67 | Layer.provideMerge(HttpServer.layerServices), |
| 74 | 68 | ); |
| 75 | − const handler = HttpRouter.toWebHandler(App, { disableLogger: true }) | |
| 76 | − .handler; | |
| 69 | + const handler = HttpRouter.toWebHandler(App, { disableLogger: true }).handler; | |
| 77 | 70 | |
| 78 | 71 | const a = await handler(new Request("http://test.local/")); |
| 79 | 72 | const b = await handler(new Request("http://test.local/")); |
| @@ -91,8 +84,7 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 11 unmodified lines | ||
| 91 | 84 | HttpRouter.provideRequest(makeCounterLive(counts)), |
| 92 | 85 | Layer.provideMerge(HttpServer.layerServices), |
| 93 | 86 | ); |
| 94 | − const handler = HttpRouter.toWebHandler(App, { disableLogger: true }) | |
| 95 | − .handler; | |
| 87 | + const handler = HttpRouter.toWebHandler(App, { disableLogger: true }).handler; | |
| 96 | 88 | |
| 97 | 89 | const a = await handler(new Request("http://test.local/")); |
| 98 | 90 | const b = await handler(new Request("http://test.local/")); |
| @@ -111,8 +103,7 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 12 unmodified lines | ||
| 111 | 103 | Layer.provide(requestScopedMiddleware(makeCounterLive(counts)).layer), |
| 112 | 104 | Layer.provideMerge(HttpServer.layerServices), |
| 113 | 105 | ); |
| 114 | − const handler = HttpRouter.toWebHandler(App, { disableLogger: true }) | |
| 115 | − .handler; | |
| 106 | + const handler = HttpRouter.toWebHandler(App, { disableLogger: true }).handler; | |
| 116 | 107 | |
| 117 | 108 | const a = await handler(new Request("http://test.local/")); |
| 118 | 109 | const b = await handler(new Request("http://test.local/")); |
| @@ -144,8 +135,7 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 25 unmodified lines | ||
| 144 | 135 | Layer.provide(requestScopedMiddleware(makeCounterLive(counts, 5)).layer), |
| 145 | 136 | Layer.provideMerge(HttpServer.layerServices), |
| 146 | 137 | ); |
| 147 | − const handler = HttpRouter.toWebHandler(App, { disableLogger: true }) | |
| 148 | − .handler; | |
| 138 | + const handler = HttpRouter.toWebHandler(App, { disableLogger: true }).handler; | |
| 149 | 139 | |
| 150 | 140 | const [a, b] = await Promise.all([ |
| 151 | 141 | handler(new Request("http://test.local/")), |
| @@ -93,9 +93,7 @@ const TestProtectedGate = HttpRouter.middleware()((httpEffect) => · 92 unmodified lines | ||
| 93 | 93 | ); |
| 94 | 94 | } |
| 95 | 95 | if (testState.mode === "error") { |
| 96 | − return Effect.succeed( | |
| 97 | − HttpServerResponse.jsonUnsafe({ error: "boom" }, { status: 500 }), | |
| 98 | − ); | |
| 96 | + return Effect.succeed(HttpServerResponse.jsonUnsafe({ error: "boom" }, { status: 500 })); | |
| 99 | 97 | } |
| 100 | 98 | if (testState.mode === "bad-status") { |
| 101 | 99 | return Effect.succeed( |
| @@ -130,10 +128,7 @@ const TestApiLive = Layer.mergeAll( · 28 unmodified lines | ||
| 130 | 128 | AuthTestLive, |
| 131 | 129 | ProtectedTestLive, |
| 132 | 130 | AutumnTestRoutesLive, |
| 133 | −).pipe( | |
| 134 | − Layer.provideMerge(RouterConfig), | |
| 135 | − Layer.provideMerge(HttpServer.layerServices), | |
| 136 | −); | |
| 131 | +).pipe(Layer.provideMerge(RouterConfig), Layer.provideMerge(HttpServer.layerServices)); | |
| 137 | 132 | |
| 138 | 133 | const requestHandler = HttpRouter.toWebHandler(TestApiLive, { disableLogger: true }).handler; |
| 139 | 134 | |
| @@ -1,18 +1,10 @@ | ||
| 1 | 1 | import { env } from "cloudflare:workers"; |
| 2 | 2 | import { Cause, Effect } from "effect"; |
| 3 | −import { | |
| 4 | − HttpRouter, | |
| 5 | − HttpServerRequest, | |
| 6 | − HttpServerResponse, | |
| 7 | −} from "effect/unstable/http"; | |
| 3 | +import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; | |
| 8 | 4 | import { autumnHandler } from "autumn-js/backend"; |
| 9 | 5 | |
| 10 | 6 | import { WorkOSAuth } from "../auth/workos"; |
| 11 | −import { | |
| 12 | − HttpResponseError, | |
| 13 | − isServerError, | |
| 14 | − toErrorServerResponse, | |
| 15 | −} from "./error-response"; | |
| 7 | +import { HttpResponseError, isServerError, toErrorServerResponse } from "./error-response"; | |
| 16 | 8 | |
| 17 | 9 | const handler = Effect.gen(function* () { |
| 18 | 10 | const request = yield* HttpServerRequest.HttpServerRequest; |
| @@ -1,8 +1,5 @@ | ||
| 1 | 1 | import { Cause, Data, Effect, Predicate, Result } from "effect"; |
| 2 | −import { | |
| 3 | − HttpServerRespondable, | |
| 4 | − HttpServerResponse, | |
| 5 | −} from "effect/unstable/http"; | |
| 2 | +import { HttpServerRespondable, HttpServerResponse } from "effect/unstable/http"; | |
| 6 | 3 | |
| 7 | 4 | import { captureCause } from "../observability"; |
| 8 | 5 | |
| @@ -18,11 +18,7 @@ import { CoreSharedServices } from "./core-shared-services"; · 17 unmodified lines | ||
| 18 | 18 | import { ProtectedCloudApi, RouterConfig } from "./protected-layers"; |
| 19 | 19 | import { requestScopedMiddleware } from "./request-scoped"; |
| 20 | 20 | |
| 21 | −export { | |
| 22 | − CoreSharedServices, | |
| 23 | − ProtectedCloudApi, | |
| 24 | − RouterConfig, | |
| 25 | −}; | |
| 21 | +export { CoreSharedServices, ProtectedCloudApi, RouterConfig }; | |
| 26 | 22 | |
| 27 | 23 | const DbLive = DbService.Live; |
| 28 | 24 | const UserStoreLive = UserStoreService.Live.pipe(Layer.provide(DbLive)); |
| @@ -51,9 +47,7 @@ export const BootSharedServices = Layer.mergeAll( · 22 unmodified lines | ||
| 51 | 47 | // without per-request scoping the postgres.js socket pins to the worker's |
| 52 | 48 | // boot scope and Cloudflare Workers' I/O isolation kills the second |
| 53 | 49 | // request. |
| 54 | −export const makeNonProtectedApiLive = ( | |
| 55 | − rsLive: Layer.Layer<DbService | UserStoreService>, | |
| 56 | −) => | |
| 50 | +export const makeNonProtectedApiLive = (rsLive: Layer.Layer<DbService | UserStoreService>) => | |
| 57 | 51 | HttpApiBuilder.layer(NonProtectedApi).pipe( |
| 58 | 52 | Layer.provide(Layer.mergeAll(CloudAuthPublicHandlers, CloudSessionAuthHandlers)), |
| 59 | 53 | Layer.provide(requestScopedMiddleware(rsLive).layer), |
| @@ -62,9 +56,7 @@ export const makeNonProtectedApiLive = ( · 2 unmodified lines | ||
| 62 | 56 | |
| 63 | 57 | // Routes scoped to a specific org (membership management, switching, etc.). |
| 64 | 58 | // Auth is enforced by `OrgAuth` middleware declared on `OrgHttpApi`. |
| 65 | −export const makeOrgApiLive = ( | |
| 66 | − rsLive: Layer.Layer<DbService | UserStoreService>, | |
| 67 | −) => | |
| 59 | +export const makeOrgApiLive = (rsLive: Layer.Layer<DbService | UserStoreService>) => | |
| 68 | 60 | HttpApiBuilder.layer(OrgHttpApi).pipe( |
| 69 | 61 | Layer.provide(OrgHandlers), |
| 70 | 62 | Layer.provide(requestScopedMiddleware(rsLive).layer), |
| @@ -74,7 +66,5 @@ export const makeOrgApiLive = ( · 3 unmodified lines | ||
| 74 | 66 | // Default exports use the production per-request layer. Existing callers |
| 75 | 67 | // that import `NonProtectedApiLive`/`OrgApiLive` continue to work; the |
| 76 | 68 | // `make*` factories exist for tests that need to swap in a fake. |
| 77 | −export const NonProtectedApiLive = makeNonProtectedApiLive( | |
| 78 | − RequestScopedServicesLive, | |
| 79 | −); | |
| 69 | +export const NonProtectedApiLive = makeNonProtectedApiLive(RequestScopedServicesLive); | |
| 80 | 70 | export const OrgApiLive = makeOrgApiLive(RequestScopedServicesLive); |
| @@ -8,11 +8,7 @@ import { HttpRouter, HttpServer } from "effect/unstable/http"; · 7 unmodified lines | ||
| 8 | 8 | import { Layer } from "effect"; |
| 9 | 9 | |
| 10 | 10 | import { observabilityMiddleware } from "@executor-js/api"; |
| 11 | −import { | |
| 12 | − CoreHandlers, | |
| 13 | − composePluginApi, | |
| 14 | − composePluginHandlerLayer, | |
| 15 | −} from "@executor-js/api/server"; | |
| 11 | +import { CoreHandlers, composePluginApi, composePluginHandlerLayer } from "@executor-js/api/server"; | |
| 16 | 12 | |
| 17 | 13 | import { cloudPlugins } from "./cloud-plugins"; |
| 18 | 14 | import { UserStoreService } from "../auth/context"; |
| @@ -3,10 +3,7 @@ · 2 unmodified lines | ||
| 3 | 3 | // harness can't load in the workerd test runtime. |
| 4 | 4 | |
| 5 | 5 | import { HttpApiSwagger } from "effect/unstable/httpapi"; |
| 6 | −import { | |
| 7 | − HttpRouter, | |
| 8 | − HttpServerRequest, | |
| 9 | −} from "effect/unstable/http"; | |
| 6 | +import { HttpRouter, HttpServerRequest } from "effect/unstable/http"; | |
| 10 | 7 | import { Effect, Layer } from "effect"; |
| 11 | 8 | |
| 12 | 9 | import { |
| @@ -26,11 +23,7 @@ import { DbService } from "../services/db"; · 13 unmodified lines | ||
| 26 | 23 | import { makeExecutionStack } from "../services/execution-stack"; |
| 27 | 24 | import { HttpResponseError } from "./error-response"; |
| 28 | 25 | import { RequestScopedServicesLive } from "./layers"; |
| 29 | −import { | |
| 30 | − ProtectedCloudApi, | |
| 31 | − ProtectedCloudApiLive, | |
| 32 | − RouterConfig, | |
| 33 | −} from "./protected-layers"; | |
| 26 | +import { ProtectedCloudApi, ProtectedCloudApiLive, RouterConfig } from "./protected-layers"; | |
| 34 | 27 | import { requestScopedMiddleware } from "./request-scoped"; |
| 35 | 28 | |
| 36 | 29 | // Pre-compute the per-plugin `Effect.provideService(extensionService, |
| @@ -119,9 +112,7 @@ const ExecutionStackMiddleware = HttpRouter.middleware<{ · 82 unmodified lines | ||
| 119 | 112 | // the layer rebuilds per HTTP request, satisfying Cloudflare Workers' |
| 120 | 113 | // I/O isolation. Exposed as a factory so tests can swap in a counting |
| 121 | 114 | // fake — see `apps/cloud/src/api.request-scope.node.test.ts`. |
| 122 | −export const makeProtectedApiLive = ( | |
| 123 | − rsLive: Layer.Layer<DbService | UserStoreService>, | |
| 124 | −) => { | |
| 115 | +export const makeProtectedApiLive = (rsLive: Layer.Layer<DbService | UserStoreService>) => { | |
| 125 | 116 | const protectedMiddleware = ExecutionStackMiddleware.combine( |
| 126 | 117 | requestScopedMiddleware(rsLive), |
| 127 | 118 | ).layer; |
| @@ -25,18 +25,13 @@ import { SlackContactRoutesLive } from "./slack"; · 24 unmodified lines | ||
| 25 | 25 | // so tests can substitute a counting fake for `DbService.Live` and |
| 26 | 26 | // assert per-request semantics — see |
| 27 | 27 | // `apps/cloud/src/api.request-scope.node.test.ts`. |
| 28 | −export const makeApiLive = ( | |
| 29 | − requestScopedLive: Layer.Layer<DbService | UserStoreService>, | |
| 30 | −) => | |
| 28 | +export const makeApiLive = (requestScopedLive: Layer.Layer<DbService | UserStoreService>) => | |
| 31 | 29 | Layer.mergeAll( |
| 32 | 30 | makeNonProtectedApiLive(requestScopedLive), |
| 33 | 31 | makeOrgApiLive(requestScopedLive), |
| 34 | 32 | makeProtectedApiLive(requestScopedLive), |
| 35 | 33 | AutumnRoutesLive, |
| 36 | 34 | SlackContactRoutesLive, |
| 37 | − ).pipe( | |
| 38 | − Layer.provideMerge(RouterConfig), | |
| 39 | − Layer.provideMerge(BootSharedServices), | |
| 40 | − ); | |
| 35 | + ).pipe(Layer.provideMerge(RouterConfig), Layer.provideMerge(BootSharedServices)); | |
| 41 | 36 | |
| 42 | 37 | export const ApiLive = makeApiLive(RequestScopedServicesLive); |
| @@ -3,11 +3,7 @@ import { Cause, Effect } from "effect"; · 2 unmodified lines | ||
| 3 | 3 | import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; |
| 4 | 4 | |
| 5 | 5 | import { SlackService } from "../services/slack"; |
| 6 | −import { | |
| 7 | − HttpResponseError, | |
| 8 | − isServerError, | |
| 9 | − toErrorServerResponse, | |
| 10 | −} from "./error-response"; | |
| 6 | +import { HttpResponseError, isServerError, toErrorServerResponse } from "./error-response"; | |
| 11 | 7 | |
| 12 | 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 | 24 | type UserStoreServiceType = ReturnType<typeof makeService>; |
| 25 | 25 | |
| 26 | −export class UserStoreService extends Context.Service< | |
| 27 | − UserStoreService, | |
| 28 | − UserStoreServiceType | |
| 29 | −>()("@executor-js/cloud/UserStoreService") { | |
| 26 | +export class UserStoreService extends Context.Service<UserStoreService, UserStoreServiceType>()( | |
| 27 | + "@executor-js/cloud/UserStoreService", | |
| 28 | +) { | |
| 30 | 29 | static Live = Layer.effect(this)( |
| 31 | 30 | Effect.map(DbService.asEffect(), ({ db }) => makeService(makeUserStore(db))), |
| 32 | 31 | ); |
| @@ -304,8 +304,7 @@ export const CloudSessionAuthHandlers = HttpApiBuilder.group( · 303 unmodified lines | ||
| 304 | 304 | ? yield* workos.getUser(inv.inviterUserId).pipe( |
| 305 | 305 | Effect.map((u) => ({ |
| 306 | 306 | email: u.email, |
| 307 | − name: | |
| 308 | − [u.firstName, u.lastName].filter(Boolean).join(" ") || null, | |
| 307 | + name: [u.firstName, u.lastName].filter(Boolean).join(" ") || null, | |
| 309 | 308 | })), |
| 310 | 309 | Effect.orElseSucceed(() => null), |
| 311 | 310 | ) |
| @@ -354,20 +353,15 @@ export const CloudSessionAuthHandlers = HttpApiBuilder.group( · 42 unmodified lines | ||
| 354 | 353 | // through login again. The acceptance has already succeeded |
| 355 | 354 | // server-side, so the next login will pick up the membership. |
| 356 | 355 | const refreshed = yield* workos.refreshSession(session.sealedSession, org.id); |
| 357 | − const verified = refreshed | |
| 358 | − ? yield* workos.authenticateSealedSession(refreshed) | |
| 359 | − : null; | |
| 356 | + const verified = refreshed ? yield* workos.authenticateSealedSession(refreshed) : null; | |
| 360 | 357 | |
| 361 | 358 | if (!refreshed || !verified || verified.organizationId !== org.id) { |
| 362 | − yield* Effect.logWarning( | |
| 363 | − "acceptInvitation: unable to attach org to current session", | |
| 364 | − { | |
| 365 | − userId: session.accountId, | |
| 366 | − orgId: org.id, | |
| 367 | − refreshReturnedSession: refreshed != null, | |
| 368 | − verifiedOrgId: verified?.organizationId ?? null, | |
| 369 | − }, | |
| 370 | − ); | |
| 359 | + yield* Effect.logWarning("acceptInvitation: unable to attach org to current session", { | |
| 360 | + userId: session.accountId, | |
| 361 | + orgId: org.id, | |
| 362 | + refreshReturnedSession: refreshed != null, | |
| 363 | + verifiedOrgId: verified?.organizationId ?? null, | |
| 364 | + }); | |
| 371 | 365 | deleteCookie("wos-session", { path: "/" }); |
| 372 | 366 | return yield* new WorkOSError(); |
| 373 | 367 | } |
| @@ -23,8 +23,9 @@ export type Session = { · 22 unmodified lines | ||
| 23 | 23 | readonly refreshedSession: string | null; |
| 24 | 24 | }; |
| 25 | 25 | |
| 26 | −export class SessionContext extends Context.Service<SessionContext, Session | |
| 27 | −>()("@executor-js/cloud/Session") {} | |
| 26 | +export class SessionContext extends Context.Service<SessionContext, Session>()( | |
| 27 | + "@executor-js/cloud/Session", | |
| 28 | +) {} | |
| 28 | 29 | |
| 29 | 30 | // --------------------------------------------------------------------------- |
| 30 | 31 | // Errors |
| @@ -71,12 +72,12 @@ export class AuthContext extends Context.Service< · 40 unmodified lines | ||
| 71 | 72 | } |
| 72 | 73 | >()("@executor-js/cloud/AuthContext") {} |
| 73 | 74 | |
| 74 | −export class OrgAuth extends HttpApiMiddleware.Service< | |
| 75 | − OrgAuth, | |
| 76 | − { provides: AuthContext } | |
| 77 | −>()("OrgAuth", { | |
| 78 | − error: [Unauthorized, NoOrganization], | |
| 79 | − security: { | |
| 80 | − cookie: HttpApiSecurity.apiKey({ in: "cookie", key: "wos-session" }), | |
| 75 | +export class OrgAuth extends HttpApiMiddleware.Service<OrgAuth, { provides: AuthContext }>()( | |
| 76 | + "OrgAuth", | |
| 77 | + { | |
| 78 | + error: [Unauthorized, NoOrganization], | |
| 79 | + security: { | |
| 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 | 25 | const workos = yield* WorkOSAuth; |
| 26 | 26 | const fresh = yield* workos.getOrganization(organizationId); |
| 27 | − return yield* users.use((s) => | |
| 28 | − s.upsertOrganization({ id: fresh.id, name: fresh.name }), | |
| 29 | − ); | |
| 27 | + return yield* users.use((s) => s.upsertOrganization({ id: fresh.id, name: fresh.name })); | |
| 30 | 28 | }); |
| @@ -8,8 +8,7 @@ import { GeneratePortalLinkIntent, WorkOS } from "@workos-inc/node/worker"; · 7 unmodified lines | ||
| 8 | 8 | import { WorkOSError, tryPromiseService, withServiceLogging } from "./errors"; |
| 9 | 9 | |
| 10 | 10 | const COOKIE_NAME = "wos-session"; |
| 11 | −const INVALID_COOKIE_PASSWORD_MESSAGE = | |
| 12 | − "WORKOS_COOKIE_PASSWORD must be at least 32 characters"; | |
| 11 | +const INVALID_COOKIE_PASSWORD_MESSAGE = "WORKOS_COOKIE_PASSWORD must be at least 32 characters"; | |
| 13 | 12 | |
| 14 | 13 | class WorkOSAuthConfigurationError extends Data.TaggedError("WorkOSAuthConfigurationError")<{ |
| 15 | 14 | readonly message: string; |
| @@ -247,10 +246,9 @@ const make = Effect.gen(function* () { · 231 unmodified lines | ||
| 247 | 246 | |
| 248 | 247 | export type WorkOSAuthService = Effect.Success<typeof make>; |
| 249 | 248 | |
| 250 | −export class WorkOSAuth extends Context.Service< | |
| 251 | − WorkOSAuth, | |
| 252 | − WorkOSAuthService | |
| 253 | −>()("@executor-js/cloud/WorkOSAuth") { | |
| 249 | +export class WorkOSAuth extends Context.Service<WorkOSAuth, WorkOSAuthService>()( | |
| 250 | + "@executor-js/cloud/WorkOSAuth", | |
| 251 | +) { | |
| 254 | 252 | static Default = Layer.effect(this)(make).pipe( |
| 255 | 253 | Layer.withSpan("WorkOSAuth", { attributes: { module: "WorkOSAuth" } }), |
| 256 | 254 | ); |
| @@ -102,9 +102,7 @@ describe("createCachedRemoteJWKSet", () => { · 101 unmodified lines | ||
| 102 | 102 | const jwks = createCachedRemoteJWKSet(jwksUrl, { fetch }); |
| 103 | 103 | const token = await sign(kp); |
| 104 | 104 | |
| 105 | − const verifies = Array.from({ length: 10 }, () => | |
| 106 | − jwtVerify(token, jwks, { issuer, audience }), | |
| 107 | − ); | |
| 105 | + const verifies = Array.from({ length: 10 }, () => jwtVerify(token, jwks, { issuer, audience })); | |
| 108 | 106 | // Let microtasks settle so all 10 calls hit the cache miss path. |
| 109 | 107 | await new Promise((r) => setTimeout(r, 10)); |
| 110 | 108 | resolveFetch(); |
| @@ -36,10 +36,12 @@ describe("MCP AuthKit token verification", () => { · 35 unmodified lines | ||
| 36 | 36 | const { jwks, sign } = yield* Effect.promise(() => makeVerifier()); |
| 37 | 37 | const token = yield* Effect.promise(() => sign({ aud: otherResource })); |
| 38 | 38 | |
| 39 | − const error = yield* Effect.flip(verifyMcpAccessToken(token, jwks, { | |
| 40 | − issuer, | |
| 41 | − audience: resource, | |
| 42 | − })); | |
| 39 | + const error = yield* Effect.flip( | |
| 40 | + verifyMcpAccessToken(token, jwks, { | |
| 41 | + issuer, | |
| 42 | + audience: resource, | |
| 43 | + }), | |
| 44 | + ); | |
| 43 | 45 | |
| 44 | 46 | expect(error).toBeInstanceOf(McpJwtVerificationError); |
| 45 | 47 | }), |
| @@ -45,10 +45,11 @@ const SESSION_TIMEOUT_MS = 5 * 60 * 1000; · 44 unmodified lines | ||
| 45 | 45 | const SESSION_META_KEY = "session-meta"; |
| 46 | 46 | const LAST_ACTIVITY_KEY = "last-activity-ms"; |
| 47 | 47 | |
| 48 | −const doRuntimeControls = (instance: unknown): { | |
| 48 | +const doRuntimeControls = ( | |
| 49 | + instance: unknown, | |
| 50 | +): { | |
| 49 | 51 | closeRuntime: () => Effect.Effect<void>; |
| 50 | −} => | |
| 51 | − instance as { closeRuntime: () => Effect.Effect<void> }; | |
| 52 | +} => instance as { closeRuntime: () => Effect.Effect<void> }; | |
| 52 | 53 | |
| 53 | 54 | const doActivityState = (instance: unknown): { lastActivityMs: number } => |
| 54 | 55 | instance as { lastActivityMs: number }; |
| @@ -340,9 +341,7 @@ describe("/mcp session restore", () => { · 285 unmodified lines | ||
| 340 | 341 | const ns = env.MCP_SESSION; |
| 341 | 342 | const stub = ns.get(ns.idFromString(sessionId!)); |
| 342 | 343 | await runInDurableObject(stub, async (instance) => { |
| 343 | − await Effect.runPromise( | |
| 344 | − doRuntimeControls(instance).closeRuntime(), | |
| 345 | − ); | |
| 344 | + await Effect.runPromise(doRuntimeControls(instance).closeRuntime()); | |
| 346 | 345 | }); |
| 347 | 346 | |
| 348 | 347 | const response = await mcpPost({ |
| @@ -376,9 +375,7 @@ describe("/mcp session restore", () => { · 27 unmodified lines | ||
| 376 | 375 | const ns = env.MCP_SESSION; |
| 377 | 376 | const stub = ns.get(ns.idFromString(sessionId!)); |
| 378 | 377 | await runInDurableObject(stub, async (instance) => { |
| 379 | − await Effect.runPromise( | |
| 380 | − doRuntimeControls(instance).closeRuntime(), | |
| 381 | − ); | |
| 378 | + await Effect.runPromise(doRuntimeControls(instance).closeRuntime()); | |
| 382 | 379 | }); |
| 383 | 380 | |
| 384 | 381 | const getResponse = await mcpGet({ bearer, sessionId: sessionId! }); |
| @@ -438,9 +435,7 @@ describe("/mcp session restore", () => { · 53 unmodified lines | ||
| 438 | 435 | const ns = env.MCP_SESSION; |
| 439 | 436 | const stub = ns.get(ns.idFromString(sessionId!)); |
| 440 | 437 | await runInDurableObject(stub, async (instance) => { |
| 441 | − await Effect.runPromise( | |
| 442 | − doRuntimeControls(instance).closeRuntime(), | |
| 443 | − ); | |
| 438 | + await Effect.runPromise(doRuntimeControls(instance).closeRuntime()); | |
| 444 | 439 | }); |
| 445 | 440 | |
| 446 | 441 | const response = await mcpPost({ |
| @@ -33,10 +33,7 @@ import { · 32 unmodified lines | ||
| 33 | 33 | createExecutor, |
| 34 | 34 | definePlugin, |
| 35 | 35 | } from "@executor-js/sdk"; |
| 36 | −import { | |
| 37 | − makePostgresAdapter, | |
| 38 | − makePostgresBlobStore, | |
| 39 | −} from "@executor-js/storage-postgres"; | |
| 36 | +import { makePostgresAdapter, makePostgresBlobStore } from "@executor-js/storage-postgres"; | |
| 40 | 37 | import { makeTestWorkOSVaultClient } from "@executor-js/plugin-workos-vault/testing"; |
| 41 | 38 | import executorConfig from "../executor.config"; |
| 42 | 39 | import { DbService } from "./services/db"; |
| @@ -65,7 +62,11 @@ const elicitingTestPlugin = definePlugin(() => ({ · 22 unmodified lines | ||
| 65 | 62 | name: "needsApproval", |
| 66 | 63 | description: "Tool that asks the caller to approve before returning.", |
| 67 | 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 | 70 | Effect.gen(function* () { |
| 70 | 71 | const response = yield* elicit( |
| 71 | 72 | new FormElicitation({ |
| @@ -94,11 +95,7 @@ const ELICITATION_CAPS: ClientCapabilities = { · 22 unmodified lines | ||
| 94 | 95 | |
| 95 | 96 | type BuildOptions = { readonly withElicitingPlugin?: boolean }; |
| 96 | 97 | |
| 97 | −const buildScopedExecutor = ( | |
| 98 | − scopeId: string, | |
| 99 | − scopeName: string, | |
| 100 | − options: BuildOptions = {}, | |
| 101 | −) => | |
| 98 | +const buildScopedExecutor = (scopeId: string, scopeName: string, options: BuildOptions = {}) => | |
| 102 | 99 | Effect.gen(function* () { |
| 103 | 100 | const { db } = yield* DbService; |
| 104 | 101 | const basePlugins = executorConfig.plugins({ |
| @@ -381,69 +381,69 @@ function SlackContactCta() { · 380 unmodified lines | ||
| 381 | 381 | <span aria-hidden>→</span> |
| 382 | 382 | </Button> |
| 383 | 383 | </DialogTrigger> |
| 384 | − <DialogContent> | |
| 385 | − {inviteUrl ? ( | |
| 386 | − <> | |
| 387 | − <DialogHeader> | |
| 388 | − <DialogTitle>Check your inbox</DialogTitle> | |
| 389 | − <DialogDescription> | |
| 390 | − We've created a private Slack channel and emailed you an invite. You can also | |
| 391 | − open it directly: | |
| 392 | − </DialogDescription> | |
| 393 | − </DialogHeader> | |
| 394 | − <a | |
| 395 | − href={inviteUrl} | |
| 396 | − target="_blank" | |
| 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" | |
| 399 | − > | |
| 400 | − <SlackMark className="size-4" /> | |
| 401 | − Open Slack invite | |
| 402 | − </a> | |
| 403 | − <DialogFooter> | |
| 404 | − <DialogClose asChild> | |
| 405 | − <Button type="button" variant="outline"> | |
| 406 | − Done | |
| 407 | − </Button> | |
| 408 | − </DialogClose> | |
| 409 | − </DialogFooter> | |
| 410 | − </> | |
| 411 | − ) : ( | |
| 412 | − <form onSubmit={onSubmit}> | |
| 413 | − <DialogHeader> | |
| 414 | − <DialogTitle>Get in touch on Slack</DialogTitle> | |
| 415 | − <DialogDescription> | |
| 416 | − We'll create a private Slack Connect channel between you and the Executor team. | |
| 417 | − </DialogDescription> | |
| 418 | − </DialogHeader> | |
| 419 | − <div className="grid gap-4 py-4"> | |
| 420 | − <div className="grid gap-2"> | |
| 421 | − <Label htmlFor="slack-contact-email">Work email</Label> | |
| 422 | − <Input | |
| 423 | − id="slack-contact-email" | |
| 424 | − type="email" | |
| 425 | − autoComplete="email" | |
| 426 | − required | |
| 427 | − value={email} | |
| 428 | − onChange={(e) => setEmail(e.currentTarget.value)} | |
| 429 | − placeholder="you@company.com" | |
| 430 | − /> | |
| 384 | + <DialogContent> | |
| 385 | + {inviteUrl ? ( | |
| 386 | + <> | |
| 387 | + <DialogHeader> | |
| 388 | + <DialogTitle>Check your inbox</DialogTitle> | |
| 389 | + <DialogDescription> | |
| 390 | + We've created a private Slack channel and emailed you an invite. You can also | |
| 391 | + open it directly: | |
| 392 | + </DialogDescription> | |
| 393 | + </DialogHeader> | |
| 394 | + <a | |
| 395 | + href={inviteUrl} | |
| 396 | + target="_blank" | |
| 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" | |
| 399 | + > | |
| 400 | + <SlackMark className="size-4" /> | |
| 401 | + Open Slack invite | |
| 402 | + </a> | |
| 403 | + <DialogFooter> | |
| 404 | + <DialogClose asChild> | |
| 405 | + <Button type="button" variant="outline"> | |
| 406 | + Done | |
| 407 | + </Button> | |
| 408 | + </DialogClose> | |
| 409 | + </DialogFooter> | |
| 410 | + </> | |
| 411 | + ) : ( | |
| 412 | + <form onSubmit={onSubmit}> | |
| 413 | + <DialogHeader> | |
| 414 | + <DialogTitle>Get in touch on Slack</DialogTitle> | |
| 415 | + <DialogDescription> | |
| 416 | + We'll create a private Slack Connect channel between you and the Executor team. | |
| 417 | + </DialogDescription> | |
| 418 | + </DialogHeader> | |
| 419 | + <div className="grid gap-4 py-4"> | |
| 420 | + <div className="grid gap-2"> | |
| 421 | + <Label htmlFor="slack-contact-email">Work email</Label> | |
| 422 | + <Input | |
| 423 | + id="slack-contact-email" | |
| 424 | + type="email" | |
| 425 | + autoComplete="email" | |
| 426 | + required | |
| 427 | + value={email} | |
| 428 | + onChange={(e) => setEmail(e.currentTarget.value)} | |
| 429 | + placeholder="you@company.com" | |
| 430 | + /> | |
| 431 | + </div> | |
| 432 | + {error && <p className="text-sm text-destructive">{error}</p>} | |
| 431 | 433 | </div> |
| 432 | − {error && <p className="text-sm text-destructive">{error}</p>} | |
| 433 | − </div> | |
| 434 | − <DialogFooter> | |
| 435 | − <DialogClose asChild> | |
| 436 | − <Button type="button" variant="outline" disabled={submitting}> | |
| 437 | − Cancel | |
| 434 | + <DialogFooter> | |
| 435 | + <DialogClose asChild> | |
| 436 | + <Button type="button" variant="outline" disabled={submitting}> | |
| 437 | + Cancel | |
| 438 | + </Button> | |
| 439 | + </DialogClose> | |
| 440 | + <Button type="submit" disabled={submitting || !email}> | |
| 441 | + {submitting ? "Sending…" : "Send invite"} | |
| 438 | 442 | </Button> |
| 439 | − </DialogClose> | |
| 440 | − <Button type="submit" disabled={submitting || !email}> | |
| 441 | − {submitting ? "Sending…" : "Send invite"} | |
| 442 | − </Button> | |
| 443 | − </DialogFooter> | |
| 444 | − </form> | |
| 445 | − )} | |
| 446 | − </DialogContent> | |
| 443 | + </DialogFooter> | |
| 444 | + </form> | |
| 445 | + )} | |
| 446 | + </DialogContent> | |
| 447 | 447 | </Dialog> |
| 448 | 448 | <span className="text-muted-foreground/60" aria-hidden> |
| 449 | 449 | · |
| @@ -463,8 +463,19 @@ function SlackContactCta() { · 13 unmodified lines | ||
| 463 | 463 | |
| 464 | 464 | function MailIcon({ className }: { className?: string }) { |
| 465 | 465 | return ( |
| 466 | − <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className={className} aria-hidden> | |
| 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" /> | |
| 466 | + <svg | |
| 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 | 479 | </svg> |
| 469 | 480 | ); |
| 470 | 481 | } |
| @@ -15,13 +15,6 @@ export const Route = createFileRoute("/sources/add/$pluginKey")({ · 14 unmodified lines | ||
| 15 | 15 | component: () => { |
| 16 | 16 | const { pluginKey } = Route.useParams(); |
| 17 | 17 | const { url, preset, namespace } = Route.useSearch(); |
| 18 | − return ( | |
| 19 | − <SourcesAddPage | |
| 20 | − pluginKey={pluginKey} | |
| 21 | − url={url} | |
| 22 | − preset={preset} | |
| 23 | − namespace={namespace} | |
| 24 | − /> | |
| 25 | − ); | |
| 18 | + return <SourcesAddPage pluginKey={pluginKey} url={url} preset={preset} namespace={namespace} />; | |
| 26 | 19 | }, |
| 27 | 20 | }); |
| @@ -31,192 +31,180 @@ import { Effect, Result } from "effect"; · 30 unmodified lines | ||
| 31 | 31 | |
| 32 | 32 | import { ScopeId, SecretId } from "@executor-js/sdk"; |
| 33 | 33 | |
| 34 | −import { | |
| 35 | − asUser, | |
| 36 | − testUserOrgScopeId, | |
| 37 | −} from "./services/__test-harness__/api-harness"; | |
| 34 | +import { asUser, testUserOrgScopeId } from "./services/__test-harness__/api-harness"; | |
| 38 | 35 | |
| 39 | 36 | const uniq = () => crypto.randomUUID().slice(0, 8); |
| 40 | 37 | const nextOrgId = () => `org_iso_${uniq()}`; |
| 41 | 38 | const nextUserId = () => `user_iso_${uniq()}`; |
| 42 | 39 | |
| 43 | 40 | describe("cloud secret isolation (HTTP, user-org scope stack)", () => { |
| 44 | − it.effect( | |
| 45 | − "users in different orgs cannot read each other's org-scoped secrets", | |
| 46 | − () => | |
| 47 | − Effect.gen(function* () { | |
| 48 | − const orgA = nextOrgId(); | |
| 49 | − const orgB = nextOrgId(); | |
| 50 | − const alice = nextUserId(); | |
| 51 | − const charlie = nextUserId(); | |
| 52 | − const id = `sec_${uniq()}`; | |
| 53 | − | |
| 54 | − yield* asUser(alice, orgA, (client) => | |
| 55 | − client.secrets.set({ | |
| 56 | − params: { scopeId: ScopeId.make(orgA) }, | |
| 57 | − payload: { | |
| 58 | − id: SecretId.make(id), | |
| 59 | − name: "Shared", | |
| 60 | − value: "alice-org-secret", | |
| 61 | − }, | |
| 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"); | |
| 41 | + it.effect("users in different orgs cannot read each other's org-scoped secrets", () => | |
| 42 | + Effect.gen(function* () { | |
| 43 | + const orgA = nextOrgId(); | |
| 44 | + const orgB = nextOrgId(); | |
| 45 | + const alice = nextUserId(); | |
| 46 | + const charlie = nextUserId(); | |
| 47 | + const id = `sec_${uniq()}`; | |
| 48 | + | |
| 49 | + yield* asUser(alice, orgA, (client) => | |
| 50 | + client.secrets.set({ | |
| 51 | + params: { scopeId: ScopeId.make(orgA) }, | |
| 52 | + payload: { | |
| 53 | + id: SecretId.make(id), | |
| 54 | + name: "Shared", | |
| 55 | + value: "alice-org-secret", | |
| 56 | + }, | |
| 57 | + }), | |
| 58 | + ); | |
| 71 | 59 | |
| 72 | − const charlieList = yield* asUser(charlie, orgB, (client) => | |
| 73 | − client.secrets.list({ params: { scopeId: ScopeId.make(orgB) } }), | |
| 74 | − ); | |
| 75 | − expect(charlieList.map((s) => s.id)).not.toContain(id); | |
| 60 | + const charlieStatus = yield* asUser(charlie, orgB, (client) => | |
| 61 | + client.secrets.status({ | |
| 62 | + params: { scopeId: ScopeId.make(orgB), secretId: SecretId.make(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( | |
| 81 | − "users in same org cannot read each other's user-scoped secrets", | |
| 82 | − () => | |
| 83 | − Effect.gen(function* () { | |
| 84 | − const orgId = nextOrgId(); | |
| 85 | − const aliceId = nextUserId(); | |
| 86 | − const bobId = nextUserId(); | |
| 87 | − const id = `sec_${uniq()}`; | |
| 88 | − | |
| 89 | − // Alice writes at her per-user scope — where OAuth tokens land. | |
| 90 | − yield* asUser(aliceId, orgId, (client) => | |
| 91 | − client.secrets.set({ | |
| 92 | − params: { scopeId: ScopeId.make(testUserOrgScopeId(aliceId, orgId)) }, | |
| 93 | − payload: { | |
| 94 | − id: SecretId.make(id), | |
| 95 | − name: "Alice's token", | |
| 96 | − value: "alice-token-value", | |
| 97 | − }, | |
| 98 | − }), | |
| 99 | − ); | |
| 74 | + it.effect("users in same org cannot read each other's user-scoped secrets", () => | |
| 75 | + Effect.gen(function* () { | |
| 76 | + const orgId = nextOrgId(); | |
| 77 | + const aliceId = nextUserId(); | |
| 78 | + const bobId = nextUserId(); | |
| 79 | + const id = `sec_${uniq()}`; | |
| 80 | + | |
| 81 | + // Alice writes at her per-user scope — where OAuth tokens land. | |
| 82 | + yield* asUser(aliceId, orgId, (client) => | |
| 83 | + client.secrets.set({ | |
| 84 | + params: { scopeId: ScopeId.make(testUserOrgScopeId(aliceId, orgId)) }, | |
| 85 | + payload: { | |
| 86 | + id: SecretId.make(id), | |
| 87 | + name: "Alice's token", | |
| 88 | + value: "alice-token-value", | |
| 89 | + }, | |
| 90 | + }), | |
| 91 | + ); | |
| 100 | 92 | |
| 101 | − // Bob is in the same org — his user-org scope differs. He should | |
| 102 | − // not see the token in a list. | |
| 103 | − const bobList = yield* asUser(bobId, orgId, (client) => | |
| 104 | − client.secrets.list({ | |
| 105 | − params: { scopeId: ScopeId.make(testUserOrgScopeId(bobId, orgId)) }, | |
| 106 | − }), | |
| 107 | − ); | |
| 108 | − expect(bobList.map((s) => s.id)).not.toContain(id); | |
| 93 | + // Bob is in the same org — his user-org scope differs. He should | |
| 94 | + // not see the token in a list. | |
| 95 | + const bobList = yield* asUser(bobId, orgId, (client) => | |
| 96 | + client.secrets.list({ | |
| 97 | + params: { scopeId: ScopeId.make(testUserOrgScopeId(bobId, orgId)) }, | |
| 98 | + }), | |
| 99 | + ); | |
| 100 | + expect(bobList.map((s) => s.id)).not.toContain(id); | |
| 109 | 101 | |
| 110 | − const bobStatus = yield* asUser(bobId, orgId, (client) => | |
| 111 | − client.secrets.status({ | |
| 112 | − params: { | |
| 113 | − scopeId: ScopeId.make(testUserOrgScopeId(bobId, orgId)), | |
| 114 | − secretId: SecretId.make(id), | |
| 115 | − }, | |
| 116 | − }), | |
| 117 | − ); | |
| 118 | − expect(bobStatus.status).toBe("missing"); | |
| 102 | + const bobStatus = yield* asUser(bobId, orgId, (client) => | |
| 103 | + client.secrets.status({ | |
| 104 | + params: { | |
| 105 | + scopeId: ScopeId.make(testUserOrgScopeId(bobId, orgId)), | |
| 106 | + secretId: SecretId.make(id), | |
| 107 | + }, | |
| 108 | + }), | |
| 109 | + ); | |
| 110 | + expect(bobStatus.status).toBe("missing"); | |
| 119 | 111 | |
| 120 | − // And Alice still sees her own token metadata. | |
| 121 | − const aliceStatus = yield* asUser(aliceId, orgId, (client) => | |
| 122 | − client.secrets.status({ | |
| 123 | − params: { | |
| 124 | − scopeId: ScopeId.make(testUserOrgScopeId(aliceId, orgId)), | |
| 125 | − secretId: SecretId.make(id), | |
| 126 | − }, | |
| 127 | − }), | |
| 128 | − ); | |
| 129 | − expect(aliceStatus.status).toBe("resolved"); | |
| 130 | − }), | |
| 112 | + // And Alice still sees her own token metadata. | |
| 113 | + const aliceStatus = yield* asUser(aliceId, orgId, (client) => | |
| 114 | + client.secrets.status({ | |
| 115 | + params: { | |
| 116 | + scopeId: ScopeId.make(testUserOrgScopeId(aliceId, orgId)), | |
| 117 | + secretId: SecretId.make(id), | |
| 118 | + }, | |
| 119 | + }), | |
| 120 | + ); | |
| 121 | + expect(aliceStatus.status).toBe("resolved"); | |
| 122 | + }), | |
| 131 | 123 | ); |
| 132 | 124 | |
| 133 | − it.effect( | |
| 134 | − "org-scoped secrets are visible to every user in that org", | |
| 135 | − () => | |
| 136 | − Effect.gen(function* () { | |
| 137 | − const orgId = nextOrgId(); | |
| 138 | − const adminId = nextUserId(); | |
| 139 | − const memberId = nextUserId(); | |
| 140 | − const id = `sec_${uniq()}`; | |
| 141 | − | |
| 142 | − yield* asUser(adminId, orgId, (client) => | |
| 143 | − client.secrets.set({ | |
| 144 | − params: { scopeId: ScopeId.make(orgId) }, | |
| 145 | − payload: { | |
| 146 | − id: SecretId.make(id), | |
| 147 | − name: "Org API Key", | |
| 148 | − value: "shared-org-key", | |
| 149 | − }, | |
| 150 | − }), | |
| 151 | − ); | |
| 125 | + it.effect("org-scoped secrets are visible to every user in that org", () => | |
| 126 | + Effect.gen(function* () { | |
| 127 | + const orgId = nextOrgId(); | |
| 128 | + const adminId = nextUserId(); | |
| 129 | + const memberId = nextUserId(); | |
| 130 | + const id = `sec_${uniq()}`; | |
| 131 | + | |
| 132 | + yield* asUser(adminId, orgId, (client) => | |
| 133 | + client.secrets.set({ | |
| 134 | + params: { scopeId: ScopeId.make(orgId) }, | |
| 135 | + payload: { | |
| 136 | + id: SecretId.make(id), | |
| 137 | + name: "Org API Key", | |
| 138 | + value: "shared-org-key", | |
| 139 | + }, | |
| 140 | + }), | |
| 141 | + ); | |
| 152 | 142 | |
| 153 | − const adminStatus = yield* asUser(adminId, orgId, (client) => | |
| 154 | − client.secrets.status({ | |
| 155 | − params: { scopeId: ScopeId.make(orgId), secretId: SecretId.make(id) }, | |
| 156 | − }), | |
| 157 | − ); | |
| 158 | − const memberStatus = yield* asUser(memberId, orgId, (client) => | |
| 159 | − client.secrets.status({ | |
| 160 | − params: { scopeId: ScopeId.make(orgId), secretId: SecretId.make(id) }, | |
| 161 | − }), | |
| 162 | − ); | |
| 163 | − expect(adminStatus.status).toBe("resolved"); | |
| 164 | − expect(memberStatus.status).toBe("resolved"); | |
| 165 | − }), | |
| 143 | + const adminStatus = yield* asUser(adminId, orgId, (client) => | |
| 144 | + client.secrets.status({ | |
| 145 | + params: { scopeId: ScopeId.make(orgId), secretId: SecretId.make(id) }, | |
| 146 | + }), | |
| 147 | + ); | |
| 148 | + const memberStatus = yield* asUser(memberId, orgId, (client) => | |
| 149 | + client.secrets.status({ | |
| 150 | + params: { scopeId: ScopeId.make(orgId), secretId: SecretId.make(id) }, | |
| 151 | + }), | |
| 152 | + ); | |
| 153 | + expect(adminStatus.status).toBe("resolved"); | |
| 154 | + expect(memberStatus.status).toBe("resolved"); | |
| 155 | + }), | |
| 166 | 156 | ); |
| 167 | 157 | |
| 168 | − it.effect( | |
| 169 | − "same userId in different orgs gets distinct per-user scopes", | |
| 170 | − () => | |
| 171 | − Effect.gen(function* () { | |
| 172 | − const userId = nextUserId(); | |
| 173 | − const orgA = nextOrgId(); | |
| 174 | − const orgB = nextOrgId(); | |
| 175 | − const id = `sec_${uniq()}`; | |
| 176 | − | |
| 177 | − yield* asUser(userId, orgA, (client) => | |
| 178 | − client.secrets.set({ | |
| 179 | − params: { scopeId: ScopeId.make(testUserOrgScopeId(userId, orgA)) }, | |
| 180 | − payload: { | |
| 181 | − id: SecretId.make(id), | |
| 182 | − name: "A token", | |
| 183 | − value: "value-in-a", | |
| 184 | − }, | |
| 185 | − }), | |
| 186 | − ); | |
| 158 | + it.effect("same userId in different orgs gets distinct per-user scopes", () => | |
| 159 | + Effect.gen(function* () { | |
| 160 | + const userId = nextUserId(); | |
| 161 | + const orgA = nextOrgId(); | |
| 162 | + const orgB = nextOrgId(); | |
| 163 | + const id = `sec_${uniq()}`; | |
| 164 | + | |
| 165 | + yield* asUser(userId, orgA, (client) => | |
| 166 | + client.secrets.set({ | |
| 167 | + params: { scopeId: ScopeId.make(testUserOrgScopeId(userId, orgA)) }, | |
| 168 | + payload: { | |
| 169 | + id: SecretId.make(id), | |
| 170 | + name: "A token", | |
| 171 | + value: "value-in-a", | |
| 172 | + }, | |
| 173 | + }), | |
| 174 | + ); | |
| 187 | 175 | |
| 188 | − // Same user id, different org → distinct user-org scope. The | |
| 189 | − // secret written in org A must not be visible when the same user | |
| 190 | − // logs into org B. | |
| 191 | − const listInB = yield* asUser(userId, orgB, (client) => | |
| 192 | − client.secrets.list({ | |
| 193 | − params: { scopeId: ScopeId.make(testUserOrgScopeId(userId, orgB)) }, | |
| 194 | − }), | |
| 195 | − ); | |
| 196 | − expect(listInB.map((s) => s.id)).not.toContain(id); | |
| 176 | + // Same user id, different org → distinct user-org scope. The | |
| 177 | + // secret written in org A must not be visible when the same user | |
| 178 | + // logs into org B. | |
| 179 | + const listInB = yield* asUser(userId, orgB, (client) => | |
| 180 | + client.secrets.list({ | |
| 181 | + params: { scopeId: ScopeId.make(testUserOrgScopeId(userId, orgB)) }, | |
| 182 | + }), | |
| 183 | + ); | |
| 184 | + expect(listInB.map((s) => s.id)).not.toContain(id); | |
| 197 | 185 | |
| 198 | − const statusInB = yield* asUser(userId, orgB, (client) => | |
| 199 | − client.secrets.status({ | |
| 200 | − params: { | |
| 201 | − scopeId: ScopeId.make(testUserOrgScopeId(userId, orgB)), | |
| 202 | − secretId: SecretId.make(id), | |
| 203 | − }, | |
| 204 | − }), | |
| 205 | − ); | |
| 206 | − expect(statusInB.status).toBe("missing"); | |
| 186 | + const statusInB = yield* asUser(userId, orgB, (client) => | |
| 187 | + client.secrets.status({ | |
| 188 | + params: { | |
| 189 | + scopeId: ScopeId.make(testUserOrgScopeId(userId, orgB)), | |
| 190 | + secretId: SecretId.make(id), | |
| 191 | + }, | |
| 192 | + }), | |
| 193 | + ); | |
| 194 | + expect(statusInB.status).toBe("missing"); | |
| 207 | 195 | |
| 208 | − // Sanity: the original write is still visible under the org-A | |
| 209 | − // user-org scope. | |
| 210 | − const statusInA = yield* asUser(userId, orgA, (client) => | |
| 211 | − client.secrets.status({ | |
| 212 | − params: { | |
| 213 | − scopeId: ScopeId.make(testUserOrgScopeId(userId, orgA)), | |
| 214 | − secretId: SecretId.make(id), | |
| 215 | − }, | |
| 216 | − }), | |
| 217 | − ); | |
| 218 | − expect(statusInA.status).toBe("resolved"); | |
| 219 | − }), | |
| 196 | + // Sanity: the original write is still visible under the org-A | |
| 197 | + // user-org scope. | |
| 198 | + const statusInA = yield* asUser(userId, orgA, (client) => | |
| 199 | + client.secrets.status({ | |
| 200 | + params: { | |
| 201 | + scopeId: ScopeId.make(testUserOrgScopeId(userId, orgA)), | |
| 202 | + secretId: SecretId.make(id), | |
| 203 | + }, | |
| 204 | + }), | |
| 205 | + ); | |
| 206 | + expect(statusInA.status).toBe("resolved"); | |
| 207 | + }), | |
| 220 | 208 | ); |
| 221 | 209 | |
| 222 | 210 | it.effect("secrets.set rejects a scope outside the executor's stack", () => |
| @@ -16,12 +16,7 @@ · 15 unmodified lines | ||
| 16 | 16 | |
| 17 | 17 | import { Effect, Layer } from "effect"; |
| 18 | 18 | import { HttpApiBuilder, HttpApiClient, HttpApiSwagger } from "effect/unstable/httpapi"; |
| 19 | −import { | |
| 20 | − FetchHttpClient, | |
| 21 | − HttpRouter, | |
| 22 | − HttpServer, | |
| 23 | − HttpServerRequest, | |
| 24 | −} from "effect/unstable/http"; | |
| 19 | +import { FetchHttpClient, HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http"; | |
| 25 | 20 | |
| 26 | 21 | import { |
| 27 | 22 | ExecutionEngineService, |
| @@ -31,16 +26,8 @@ import { · 3 unmodified lines | ||
| 31 | 26 | } from "@executor-js/api/server"; |
| 32 | 27 | import { createExecutionEngine } from "@executor-js/execution"; |
| 33 | 28 | import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs"; |
| 34 | −import { | |
| 35 | − Scope, | |
| 36 | − ScopeId, | |
| 37 | − collectSchemas, | |
| 38 | − createExecutor, | |
| 39 | −} from "@executor-js/sdk"; | |
| 40 | −import { | |
| 41 | − makePostgresAdapter, | |
| 42 | − makePostgresBlobStore, | |
| 43 | −} from "@executor-js/storage-postgres"; | |
| 29 | +import { Scope, ScopeId, collectSchemas, createExecutor } from "@executor-js/sdk"; | |
| 30 | +import { makePostgresAdapter, makePostgresBlobStore } from "@executor-js/storage-postgres"; | |
| 44 | 31 | import { makeTestWorkOSVaultClient } from "@executor-js/plugin-workos-vault/testing"; |
| 45 | 32 | |
| 46 | 33 | import executorConfig from "../../../executor.config"; |
| @@ -59,8 +46,7 @@ export const TEST_USER_HEADER = "x-test-user-id"; · 12 unmodified lines | ||
| 59 | 46 | // Mirrors apps/cloud/src/services/executor.ts#createScopedExecutor — the |
| 60 | 47 | // per-user scope id bakes in the org so the same user id in a different |
| 61 | 48 | // org gets a distinct scope row. |
| 62 | −const userOrgScopeId = (userId: string, orgId: string) => | |
| 63 | − `user-org:${userId}:${orgId}`; | |
| 49 | +const userOrgScopeId = (userId: string, orgId: string) => `user-org:${userId}:${orgId}`; | |
| 64 | 50 | |
| 65 | 51 | // `asOrg(orgId, …)` callers don't care which specific user they are, only |
| 66 | 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 | 63 | const fakeVault = makeTestWorkOSVaultClient(); |
| 78 | 64 | const testPlugins = executorConfig.plugins({ workosVaultClient: fakeVault }); |
| 79 | 65 | |
| 80 | −const createTestScopedExecutor = ( | |
| 81 | − userId: string, | |
| 82 | − orgId: string, | |
| 83 | − orgName: string, | |
| 84 | −) => | |
| 66 | +const createTestScopedExecutor = (userId: string, orgId: string, orgName: string) => | |
| 85 | 67 | Effect.gen(function* () { |
| 86 | 68 | const { db } = yield* DbService; |
| 87 | 69 | const plugins = testPlugins; |
| @@ -188,10 +170,7 @@ export const fetchForOrg = (orgId: string): typeof globalThis.fetch => · 100 unmodified lines | ||
| 188 | 170 | return handler(req); |
| 189 | 171 | }) as typeof globalThis.fetch; |
| 190 | 172 | |
| 191 | −export const fetchForUser = ( | |
| 192 | − userId: string, | |
| 193 | − orgId: string, | |
| 194 | −): typeof globalThis.fetch => | |
| 173 | +export const fetchForUser = (userId: string, orgId: string): typeof globalThis.fetch => | |
| 195 | 174 | ((input: RequestInfo | URL, init?: RequestInit) => { |
| 196 | 175 | const base = input instanceof Request ? input : new Request(input, init); |
| 197 | 176 | const req = new Request(base, { |
| @@ -211,9 +190,7 @@ export const clientLayerForOrg = (orgId: string) => · 13 unmodified lines | ||
| 211 | 190 | |
| 212 | 191 | export const clientLayerForUser = (userId: string, orgId: string) => |
| 213 | 192 | FetchHttpClient.layer.pipe( |
| 214 | − Layer.provide( | |
| 215 | − Layer.succeed(FetchHttpClient.Fetch)(fetchForUser(userId, orgId)), | |
| 216 | − ), | |
| 193 | + Layer.provide(Layer.succeed(FetchHttpClient.Fetch)(fetchForUser(userId, orgId))), | |
| 217 | 194 | ); |
| 218 | 195 | |
| 219 | 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 | 219 | Effect.gen(function* () { |
| 243 | 220 | const client = yield* HttpApiClient.make(ProtectedCloudApi, { baseUrl: TEST_BASE_URL }); |
| 244 | 221 | return yield* body(client); |
| 245 | − }).pipe( | |
| 246 | − Effect.provide(clientLayerForUser(userId, orgId)), | |
| 247 | − ) as Effect.Effect<A, E>; | |
| 222 | + }).pipe(Effect.provide(clientLayerForUser(userId, orgId))) as Effect.Effect<A, E>; | |
| 248 | 223 | |
| 249 | 224 | // Exposed so tests can build the same user-org scope id the harness uses |
| 250 | 225 | // when writing at a specific user's scope. |
| 251 | −export const testUserOrgScopeId = (userId: string, orgId: string) => | |
| 252 | − userOrgScopeId(userId, orgId); | |
| 226 | +export const testUserOrgScopeId = (userId: string, orgId: string) => userOrgScopeId(userId, orgId); | |
| 253 | 227 | |
| 254 | 228 | // Re-exports so call sites don't need a second import. |
| 255 | 229 | export { ProtectedCloudApi }; |
| @@ -24,10 +24,7 @@ import { combinedSchema } from "./db"; · 23 unmodified lines | ||
| 24 | 24 | |
| 25 | 25 | describe("combinedSchema", () => { |
| 26 | 26 | it("spreads every cloud + executor schema export", () => { |
| 27 | − const expected = new Set([ | |
| 28 | − ...Object.keys(cloudSchema), | |
| 29 | − ...Object.keys(executorSchema), | |
| 30 | − ]); | |
| 27 | + const expected = new Set([...Object.keys(cloudSchema), ...Object.keys(executorSchema)]); | |
| 31 | 28 | for (const key of expected) { |
| 32 | 29 | expect(combinedSchema, `combinedSchema missing "${key}"`).toHaveProperty(key); |
| 33 | 30 | } |
| @@ -60,11 +60,11 @@ const makeSql = (): Sql => · 59 unmodified lines | ||
| 60 | 60 | onnotice: () => undefined, |
| 61 | 61 | }); |
| 62 | 62 | |
| 63 | −export class DbService extends Context.Service< | |
| 64 | − DbService, | |
| 65 | − DbServiceShape | |
| 66 | −>()("@executor-js/cloud/DbService") { | |
| 67 | − static Live = Layer.effect(this)(Effect.acquireRelease( | |
| 63 | +export class DbService extends Context.Service<DbService, DbServiceShape>()( | |
| 64 | + "@executor-js/cloud/DbService", | |
| 65 | +) { | |
| 66 | + static Live = Layer.effect(this)( | |
| 67 | + Effect.acquireRelease( | |
| 68 | 68 | Effect.sync((): DbServiceShape => { |
| 69 | 69 | const sql = makeSql(); |
| 70 | 70 | return { sql, db: drizzle(sql, { schema: combinedSchema }) as DrizzleDb }; |
| @@ -20,11 +20,9 @@ export const makeExecutionStack = ( · 19 unmodified lines | ||
| 20 | 20 | organizationName: string, |
| 21 | 21 | ) => |
| 22 | 22 | Effect.gen(function* () { |
| 23 | − const executor = yield* createScopedExecutor( | |
| 24 | − userId, | |
| 25 | − organizationId, | |
| 26 | − organizationName, | |
| 27 | − ).pipe(Effect.withSpan("McpSessionDO.createScopedExecutor")); | |
| 23 | + const executor = yield* createScopedExecutor(userId, organizationId, organizationName).pipe( | |
| 24 | + Effect.withSpan("McpSessionDO.createScopedExecutor"), | |
| 25 | + ); | |
| 28 | 26 | const codeExecutor = makeDynamicWorkerExecutor({ loader: env.LOADER }); |
| 29 | 27 | const autumn = yield* AutumnService; |
| 30 | 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", { | |
| 4 | − id: text('id').notNull(), | |
| 5 | − scope_id: text('scope_id').notNull(), | |
| 6 | − plugin_id: text('plugin_id').notNull(), | |
| 7 | − kind: text('kind').notNull(), | |
| 8 | − name: text('name').notNull(), | |
| 9 | − url: text('url'), | |
| 10 | − can_remove: boolean('can_remove').default(true).notNull(), | |
| 11 | − can_refresh: boolean('can_refresh').default(false).notNull(), | |
| 12 | − can_edit: boolean('can_edit').default(false).notNull(), | |
| 13 | − created_at: timestamp('created_at').notNull(), | |
| 14 | − updated_at: timestamp('updated_at').notNull() | |
| 15 | −}, (table) => [ | |
| 16 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 17 | − index("source_scope_id_idx").on(table.scope_id), | |
| 18 | − index("source_plugin_id_idx").on(table.plugin_id), | |
| 19 | −]); | |
| 12 | +export const source = pgTable( | |
| 13 | + "source", | |
| 14 | + { | |
| 15 | + id: text("id").notNull(), | |
| 16 | + scope_id: text("scope_id").notNull(), | |
| 17 | + plugin_id: text("plugin_id").notNull(), | |
| 18 | + kind: text("kind").notNull(), | |
| 19 | + name: text("name").notNull(), | |
| 20 | + url: text("url"), | |
| 21 | + can_remove: boolean("can_remove").default(true).notNull(), | |
| 22 | + can_refresh: boolean("can_refresh").default(false).notNull(), | |
| 23 | + can_edit: boolean("can_edit").default(false).notNull(), | |
| 24 | + created_at: timestamp("created_at").notNull(), | |
| 25 | + updated_at: timestamp("updated_at").notNull(), | |
| 26 | + }, | |
| 27 | + (table) => [ | |
| 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", { | |
| 22 | − id: text('id').notNull(), | |
| 23 | − scope_id: text('scope_id').notNull(), | |
| 24 | − source_id: text('source_id').notNull(), | |
| 25 | − plugin_id: text('plugin_id').notNull(), | |
| 26 | − name: text('name').notNull(), | |
| 27 | − description: text('description').notNull(), | |
| 28 | − input_schema: jsonb('input_schema'), | |
| 29 | − output_schema: jsonb('output_schema'), | |
| 30 | − created_at: timestamp('created_at').notNull(), | |
| 31 | − updated_at: timestamp('updated_at').notNull() | |
| 32 | −}, (table) => [ | |
| 33 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 34 | − index("tool_scope_id_idx").on(table.scope_id), | |
| 35 | − index("tool_source_id_idx").on(table.source_id), | |
| 36 | − index("tool_plugin_id_idx").on(table.plugin_id), | |
| 37 | −]); | |
| 34 | +export const tool = pgTable( | |
| 35 | + "tool", | |
| 36 | + { | |
| 37 | + id: text("id").notNull(), | |
| 38 | + scope_id: text("scope_id").notNull(), | |
| 39 | + source_id: text("source_id").notNull(), | |
| 40 | + plugin_id: text("plugin_id").notNull(), | |
| 41 | + name: text("name").notNull(), | |
| 42 | + description: text("description").notNull(), | |
| 43 | + input_schema: jsonb("input_schema"), | |
| 44 | + output_schema: jsonb("output_schema"), | |
| 45 | + created_at: timestamp("created_at").notNull(), | |
| 46 | + updated_at: timestamp("updated_at").notNull(), | |
| 47 | + }, | |
| 48 | + (table) => [ | |
| 49 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 40 | − id: text('id').notNull(), | |
| 41 | − scope_id: text('scope_id').notNull(), | |
| 42 | − source_id: text('source_id').notNull(), | |
| 43 | − plugin_id: text('plugin_id').notNull(), | |
| 44 | − name: text('name').notNull(), | |
| 45 | − schema: jsonb('schema').notNull(), | |
| 46 | − created_at: timestamp('created_at').notNull() | |
| 47 | −}, (table) => [ | |
| 48 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 49 | − index("definition_scope_id_idx").on(table.scope_id), | |
| 50 | − index("definition_source_id_idx").on(table.source_id), | |
| 51 | − index("definition_plugin_id_idx").on(table.plugin_id), | |
| 52 | −]); | |
| 56 | +export const definition = pgTable( | |
| 57 | + "definition", | |
| 58 | + { | |
| 59 | + id: text("id").notNull(), | |
| 60 | + scope_id: text("scope_id").notNull(), | |
| 61 | + source_id: text("source_id").notNull(), | |
| 62 | + plugin_id: text("plugin_id").notNull(), | |
| 63 | + name: text("name").notNull(), | |
| 64 | + schema: jsonb("schema").notNull(), | |
| 65 | + created_at: timestamp("created_at").notNull(), | |
| 66 | + }, | |
| 67 | + (table) => [ | |
| 68 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 55 | − id: text('id').notNull(), | |
| 56 | − scope_id: text('scope_id').notNull(), | |
| 57 | − name: text('name').notNull(), | |
| 58 | − provider: text('provider').notNull(), | |
| 59 | − owned_by_connection_id: text('owned_by_connection_id'), | |
| 60 | − created_at: timestamp('created_at').notNull() | |
| 61 | −}, (table) => [ | |
| 62 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 63 | − index("secret_scope_id_idx").on(table.scope_id), | |
| 64 | − index("secret_provider_idx").on(table.provider), | |
| 65 | − index("secret_owned_by_connection_id_idx").on(table.owned_by_connection_id), | |
| 66 | −]); | |
| 75 | +export const secret = pgTable( | |
| 76 | + "secret", | |
| 77 | + { | |
| 78 | + id: text("id").notNull(), | |
| 79 | + scope_id: text("scope_id").notNull(), | |
| 80 | + name: text("name").notNull(), | |
| 81 | + provider: text("provider").notNull(), | |
| 82 | + owned_by_connection_id: text("owned_by_connection_id"), | |
| 83 | + created_at: timestamp("created_at").notNull(), | |
| 84 | + }, | |
| 85 | + (table) => [ | |
| 86 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 69 | − id: text('id').notNull(), | |
| 70 | − scope_id: text('scope_id').notNull(), | |
| 71 | − provider: text('provider').notNull(), | |
| 72 | − identity_label: text('identity_label'), | |
| 73 | − access_token_secret_id: text('access_token_secret_id').notNull(), | |
| 74 | − refresh_token_secret_id: text('refresh_token_secret_id'), | |
| 75 | − expires_at: bigint('expires_at', { mode: 'number' }), | |
| 76 | − scope: text('scope'), | |
| 77 | − provider_state: jsonb('provider_state'), | |
| 78 | − created_at: timestamp('created_at').notNull(), | |
| 79 | − updated_at: timestamp('updated_at').notNull() | |
| 80 | −}, (table) => [ | |
| 81 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 82 | − index("connection_scope_id_idx").on(table.scope_id), | |
| 83 | − index("connection_provider_idx").on(table.provider), | |
| 84 | −]); | |
| 93 | +export const connection = pgTable( | |
| 94 | + "connection", | |
| 95 | + { | |
| 96 | + id: text("id").notNull(), | |
| 97 | + scope_id: text("scope_id").notNull(), | |
| 98 | + provider: text("provider").notNull(), | |
| 99 | + identity_label: text("identity_label"), | |
| 100 | + access_token_secret_id: text("access_token_secret_id").notNull(), | |
| 101 | + refresh_token_secret_id: text("refresh_token_secret_id"), | |
| 102 | + expires_at: bigint("expires_at", { mode: "number" }), | |
| 103 | + scope: text("scope"), | |
| 104 | + provider_state: jsonb("provider_state"), | |
| 105 | + created_at: timestamp("created_at").notNull(), | |
| 106 | + updated_at: timestamp("updated_at").notNull(), | |
| 107 | + }, | |
| 108 | + (table) => [ | |
| 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", { | |
| 87 | − id: text('id').notNull(), | |
| 88 | − scope_id: text('scope_id').notNull(), | |
| 89 | − plugin_id: text('plugin_id').notNull(), | |
| 90 | − strategy: text('strategy').notNull(), | |
| 91 | − connection_id: text('connection_id').notNull(), | |
| 92 | − token_scope: text('token_scope').notNull(), | |
| 93 | − redirect_url: text('redirect_url').notNull(), | |
| 94 | − payload: jsonb('payload').notNull(), | |
| 95 | − expires_at: bigint('expires_at', { mode: 'number' }).notNull(), | |
| 96 | − created_at: timestamp('created_at').notNull() | |
| 97 | −}, (table) => [ | |
| 98 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 99 | − index("oauth2_session_scope_id_idx").on(table.scope_id), | |
| 100 | − index("oauth2_session_plugin_id_idx").on(table.plugin_id), | |
| 101 | − index("oauth2_session_connection_id_idx").on(table.connection_id), | |
| 102 | −]); | |
| 115 | +export const oauth2_session = pgTable( | |
| 116 | + "oauth2_session", | |
| 117 | + { | |
| 118 | + id: text("id").notNull(), | |
| 119 | + scope_id: text("scope_id").notNull(), | |
| 120 | + plugin_id: text("plugin_id").notNull(), | |
| 121 | + strategy: text("strategy").notNull(), | |
| 122 | + connection_id: text("connection_id").notNull(), | |
| 123 | + token_scope: text("token_scope").notNull(), | |
| 124 | + redirect_url: text("redirect_url").notNull(), | |
| 125 | + payload: jsonb("payload").notNull(), | |
| 126 | + expires_at: bigint("expires_at", { mode: "number" }).notNull(), | |
| 127 | + created_at: timestamp("created_at").notNull(), | |
| 128 | + }, | |
| 129 | + (table) => [ | |
| 130 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 105 | − id: text('id').notNull(), | |
| 106 | − scope_id: text('scope_id').notNull(), | |
| 107 | − pattern: text('pattern').notNull(), | |
| 108 | − action: text('action').notNull(), | |
| 109 | − position: text('position').notNull(), | |
| 110 | − created_at: timestamp('created_at').notNull(), | |
| 111 | − updated_at: timestamp('updated_at').notNull() | |
| 112 | −}, (table) => [ | |
| 113 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 114 | − index("tool_policy_scope_id_position_idx").on(table.scope_id, table.position), | |
| 115 | −]); | |
| 137 | +export const tool_policy = pgTable( | |
| 138 | + "tool_policy", | |
| 139 | + { | |
| 140 | + id: text("id").notNull(), | |
| 141 | + scope_id: text("scope_id").notNull(), | |
| 142 | + pattern: text("pattern").notNull(), | |
| 143 | + action: text("action").notNull(), | |
| 144 | + position: text("position").notNull(), | |
| 145 | + created_at: timestamp("created_at").notNull(), | |
| 146 | + updated_at: timestamp("updated_at").notNull(), | |
| 147 | + }, | |
| 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", { | |
| 118 | − id: text('id').notNull(), | |
| 119 | − scope_id: text('scope_id').notNull(), | |
| 120 | − name: text('name').notNull(), | |
| 121 | − spec: text('spec').notNull(), | |
| 122 | − source_url: text('source_url'), | |
| 123 | − base_url: text('base_url'), | |
| 124 | − headers: jsonb('headers'), | |
| 125 | − oauth2: jsonb('oauth2') | |
| 126 | −}, (table) => [ | |
| 127 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 128 | − index("openapi_source_scope_id_idx").on(table.scope_id), | |
| 129 | −]); | |
| 154 | +export const openapi_source = pgTable( | |
| 155 | + "openapi_source", | |
| 156 | + { | |
| 157 | + id: text("id").notNull(), | |
| 158 | + scope_id: text("scope_id").notNull(), | |
| 159 | + name: text("name").notNull(), | |
| 160 | + spec: text("spec").notNull(), | |
| 161 | + source_url: text("source_url"), | |
| 162 | + base_url: text("base_url"), | |
| 163 | + headers: jsonb("headers"), | |
| 164 | + oauth2: jsonb("oauth2"), | |
| 165 | + }, | |
| 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", { | |
| 132 | − id: text('id').notNull(), | |
| 133 | − scope_id: text('scope_id').notNull(), | |
| 134 | − source_id: text('source_id').notNull(), | |
| 135 | − binding: jsonb('binding').notNull() | |
| 136 | −}, (table) => [ | |
| 137 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 138 | − index("openapi_operation_scope_id_idx").on(table.scope_id), | |
| 139 | − index("openapi_operation_source_id_idx").on(table.source_id), | |
| 140 | −]); | |
| 172 | +export const openapi_operation = pgTable( | |
| 173 | + "openapi_operation", | |
| 174 | + { | |
| 175 | + id: text("id").notNull(), | |
| 176 | + scope_id: text("scope_id").notNull(), | |
| 177 | + source_id: text("source_id").notNull(), | |
| 178 | + binding: jsonb("binding").notNull(), | |
| 179 | + }, | |
| 180 | + (table) => [ | |
| 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", { | |
| 143 | − id: text('id').primaryKey(), | |
| 144 | − source_id: text('source_id').notNull(), | |
| 145 | − source_scope_id: text('source_scope_id').notNull(), | |
| 146 | − target_scope_id: text('target_scope_id').notNull(), | |
| 147 | − slot: text('slot').notNull(), | |
| 148 | − kind: text('kind', { enum: ['secret', 'connection', 'text'] }).notNull(), | |
| 149 | − secret_id: text('secret_id'), | |
| 150 | − connection_id: text('connection_id'), | |
| 151 | − text_value: text('text_value'), | |
| 152 | − created_at: timestamp('created_at').notNull(), | |
| 153 | − updated_at: timestamp('updated_at').notNull() | |
| 154 | −}, (table) => [ | |
| 155 | − index("openapi_source_binding_source_id_idx").on(table.source_id), | |
| 156 | − index("openapi_source_binding_source_scope_id_idx").on(table.source_scope_id), | |
| 157 | − index("openapi_source_binding_target_scope_id_idx").on(table.target_scope_id), | |
| 158 | − index("openapi_source_binding_slot_idx").on(table.slot), | |
| 159 | − index("openapi_source_binding_secret_id_idx").on(table.secret_id), | |
| 160 | − index("openapi_source_binding_connection_id_idx").on(table.connection_id), | |
| 161 | −]); | |
| 187 | +export const openapi_source_binding = pgTable( | |
| 188 | + "openapi_source_binding", | |
| 189 | + { | |
| 190 | + id: text("id").primaryKey(), | |
| 191 | + source_id: text("source_id").notNull(), | |
| 192 | + source_scope_id: text("source_scope_id").notNull(), | |
| 193 | + target_scope_id: text("target_scope_id").notNull(), | |
| 194 | + slot: text("slot").notNull(), | |
| 195 | + kind: text("kind", { enum: ["secret", "connection", "text"] }).notNull(), | |
| 196 | + secret_id: text("secret_id"), | |
| 197 | + connection_id: text("connection_id"), | |
| 198 | + text_value: text("text_value"), | |
| 199 | + created_at: timestamp("created_at").notNull(), | |
| 200 | + updated_at: timestamp("updated_at").notNull(), | |
| 201 | + }, | |
| 202 | + (table) => [ | |
| 203 | + index("openapi_source_binding_source_id_idx").on(table.source_id), | |
| 204 | + index("openapi_source_binding_source_scope_id_idx").on(table.source_scope_id), | |
| 205 | + index("openapi_source_binding_target_scope_id_idx").on(table.target_scope_id), | |
| 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", { | |
| 164 | − id: text('id').notNull(), | |
| 165 | − scope_id: text('scope_id').notNull(), | |
| 166 | − source_id: text('source_id').notNull(), | |
| 167 | − name: text('name').notNull(), | |
| 168 | − kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | |
| 169 | − text_value: text('text_value'), | |
| 170 | − secret_id: text('secret_id'), | |
| 171 | − secret_prefix: text('secret_prefix') | |
| 172 | −}, (table) => [ | |
| 173 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 174 | − index("openapi_source_query_param_scope_id_idx").on(table.scope_id), | |
| 175 | − index("openapi_source_query_param_source_id_idx").on(table.source_id), | |
| 176 | − index("openapi_source_query_param_secret_id_idx").on(table.secret_id), | |
| 177 | −]); | |
| 212 | +export const openapi_source_query_param = pgTable( | |
| 213 | + "openapi_source_query_param", | |
| 214 | + { | |
| 215 | + id: text("id").notNull(), | |
| 216 | + scope_id: text("scope_id").notNull(), | |
| 217 | + source_id: text("source_id").notNull(), | |
| 218 | + name: text("name").notNull(), | |
| 219 | + kind: text("kind", { enum: ["text", "secret"] }).notNull(), | |
| 220 | + text_value: text("text_value"), | |
| 221 | + secret_id: text("secret_id"), | |
| 222 | + secret_prefix: text("secret_prefix"), | |
| 223 | + }, | |
| 224 | + (table) => [ | |
| 225 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 180 | − id: text('id').notNull(), | |
| 181 | − scope_id: text('scope_id').notNull(), | |
| 182 | − source_id: text('source_id').notNull(), | |
| 183 | − name: text('name').notNull(), | |
| 184 | − kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | |
| 185 | − text_value: text('text_value'), | |
| 186 | − secret_id: text('secret_id'), | |
| 187 | − secret_prefix: text('secret_prefix') | |
| 188 | −}, (table) => [ | |
| 189 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 190 | − index("openapi_source_spec_fetch_header_scope_id_idx").on(table.scope_id), | |
| 191 | − index("openapi_source_spec_fetch_header_source_id_idx").on(table.source_id), | |
| 192 | − index("openapi_source_spec_fetch_header_secret_id_idx").on(table.secret_id), | |
| 193 | −]); | |
| 232 | +export const openapi_source_spec_fetch_header = pgTable( | |
| 233 | + "openapi_source_spec_fetch_header", | |
| 234 | + { | |
| 235 | + id: text("id").notNull(), | |
| 236 | + scope_id: text("scope_id").notNull(), | |
| 237 | + source_id: text("source_id").notNull(), | |
| 238 | + name: text("name").notNull(), | |
| 239 | + kind: text("kind", { enum: ["text", "secret"] }).notNull(), | |
| 240 | + text_value: text("text_value"), | |
| 241 | + secret_id: text("secret_id"), | |
| 242 | + secret_prefix: text("secret_prefix"), | |
| 243 | + }, | |
| 244 | + (table) => [ | |
| 245 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 196 | − id: text('id').notNull(), | |
| 197 | − scope_id: text('scope_id').notNull(), | |
| 198 | − source_id: text('source_id').notNull(), | |
| 199 | − name: text('name').notNull(), | |
| 200 | − kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | |
| 201 | − text_value: text('text_value'), | |
| 202 | − secret_id: text('secret_id'), | |
| 203 | − secret_prefix: text('secret_prefix') | |
| 204 | −}, (table) => [ | |
| 205 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 206 | − index("openapi_source_spec_fetch_query_param_scope_id_idx").on(table.scope_id), | |
| 207 | − index("openapi_source_spec_fetch_query_param_source_id_idx").on(table.source_id), | |
| 208 | − index("openapi_source_spec_fetch_query_param_secret_id_idx").on(table.secret_id), | |
| 209 | −]); | |
| 252 | +export const openapi_source_spec_fetch_query_param = pgTable( | |
| 253 | + "openapi_source_spec_fetch_query_param", | |
| 254 | + { | |
| 255 | + id: text("id").notNull(), | |
| 256 | + scope_id: text("scope_id").notNull(), | |
| 257 | + source_id: text("source_id").notNull(), | |
| 258 | + name: text("name").notNull(), | |
| 259 | + kind: text("kind", { enum: ["text", "secret"] }).notNull(), | |
| 260 | + text_value: text("text_value"), | |
| 261 | + secret_id: text("secret_id"), | |
| 262 | + secret_prefix: text("secret_prefix"), | |
| 263 | + }, | |
| 264 | + (table) => [ | |
| 265 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 212 | − id: text('id').notNull(), | |
| 213 | − scope_id: text('scope_id').notNull(), | |
| 214 | − name: text('name').notNull(), | |
| 215 | − config: jsonb('config').notNull(), | |
| 216 | − auth_kind: text('auth_kind', { enum: ['none', 'header', 'oauth2'] }).default("none").notNull(), | |
| 217 | − auth_header_name: text('auth_header_name'), | |
| 218 | − auth_secret_id: text('auth_secret_id'), | |
| 219 | − auth_secret_prefix: text('auth_secret_prefix'), | |
| 220 | − auth_connection_id: text('auth_connection_id'), | |
| 221 | − auth_client_id_secret_id: text('auth_client_id_secret_id'), | |
| 222 | − auth_client_secret_secret_id: text('auth_client_secret_secret_id'), | |
| 223 | − created_at: timestamp('created_at').notNull() | |
| 224 | −}, (table) => [ | |
| 225 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 226 | − index("mcp_source_scope_id_idx").on(table.scope_id), | |
| 227 | − index("mcp_source_auth_secret_id_idx").on(table.auth_secret_id), | |
| 228 | − index("mcp_source_auth_connection_id_idx").on(table.auth_connection_id), | |
| 229 | − index("mcp_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id), | |
| 230 | − index("mcp_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id), | |
| 231 | −]); | |
| 272 | +export const mcp_source = pgTable( | |
| 273 | + "mcp_source", | |
| 274 | + { | |
| 275 | + id: text("id").notNull(), | |
| 276 | + scope_id: text("scope_id").notNull(), | |
| 277 | + name: text("name").notNull(), | |
| 278 | + config: jsonb("config").notNull(), | |
| 279 | + auth_kind: text("auth_kind", { enum: ["none", "header", "oauth2"] }) | |
| 280 | + .default("none") | |
| 281 | + .notNull(), | |
| 282 | + auth_header_name: text("auth_header_name"), | |
| 283 | + auth_secret_id: text("auth_secret_id"), | |
| 284 | + auth_secret_prefix: text("auth_secret_prefix"), | |
| 285 | + auth_connection_id: text("auth_connection_id"), | |
| 286 | + auth_client_id_secret_id: text("auth_client_id_secret_id"), | |
| 287 | + auth_client_secret_secret_id: text("auth_client_secret_secret_id"), | |
| 288 | + created_at: timestamp("created_at").notNull(), | |
| 289 | + }, | |
| 290 | + (table) => [ | |
| 291 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 234 | − id: text('id').notNull(), | |
| 235 | − scope_id: text('scope_id').notNull(), | |
| 236 | − source_id: text('source_id').notNull(), | |
| 237 | − name: text('name').notNull(), | |
| 238 | − kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | |
| 239 | − text_value: text('text_value'), | |
| 240 | − secret_id: text('secret_id'), | |
| 241 | − secret_prefix: text('secret_prefix') | |
| 242 | −}, (table) => [ | |
| 243 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 244 | − index("mcp_source_header_scope_id_idx").on(table.scope_id), | |
| 245 | − index("mcp_source_header_source_id_idx").on(table.source_id), | |
| 246 | − index("mcp_source_header_secret_id_idx").on(table.secret_id), | |
| 247 | −]); | |
| 300 | +export const mcp_source_header = pgTable( | |
| 301 | + "mcp_source_header", | |
| 302 | + { | |
| 303 | + id: text("id").notNull(), | |
| 304 | + scope_id: text("scope_id").notNull(), | |
| 305 | + source_id: text("source_id").notNull(), | |
| 306 | + name: text("name").notNull(), | |
| 307 | + kind: text("kind", { enum: ["text", "secret"] }).notNull(), | |
| 308 | + text_value: text("text_value"), | |
| 309 | + secret_id: text("secret_id"), | |
| 310 | + secret_prefix: text("secret_prefix"), | |
| 311 | + }, | |
| 312 | + (table) => [ | |
| 313 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 250 | − id: text('id').notNull(), | |
| 251 | − scope_id: text('scope_id').notNull(), | |
| 252 | − source_id: text('source_id').notNull(), | |
| 253 | − name: text('name').notNull(), | |
| 254 | − kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | |
| 255 | − text_value: text('text_value'), | |
| 256 | − secret_id: text('secret_id'), | |
| 257 | − secret_prefix: text('secret_prefix') | |
| 258 | −}, (table) => [ | |
| 259 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 260 | − index("mcp_source_query_param_scope_id_idx").on(table.scope_id), | |
| 261 | − index("mcp_source_query_param_source_id_idx").on(table.source_id), | |
| 262 | − index("mcp_source_query_param_secret_id_idx").on(table.secret_id), | |
| 263 | −]); | |
| 320 | +export const mcp_source_query_param = pgTable( | |
| 321 | + "mcp_source_query_param", | |
| 322 | + { | |
| 323 | + id: text("id").notNull(), | |
| 324 | + scope_id: text("scope_id").notNull(), | |
| 325 | + source_id: text("source_id").notNull(), | |
| 326 | + name: text("name").notNull(), | |
| 327 | + kind: text("kind", { enum: ["text", "secret"] }).notNull(), | |
| 328 | + text_value: text("text_value"), | |
| 329 | + secret_id: text("secret_id"), | |
| 330 | + secret_prefix: text("secret_prefix"), | |
| 331 | + }, | |
| 332 | + (table) => [ | |
| 333 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 266 | − id: text('id').notNull(), | |
| 267 | − scope_id: text('scope_id').notNull(), | |
| 268 | − source_id: text('source_id').notNull(), | |
| 269 | − binding: jsonb('binding').notNull(), | |
| 270 | − created_at: timestamp('created_at').notNull() | |
| 271 | −}, (table) => [ | |
| 272 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 273 | − index("mcp_binding_scope_id_idx").on(table.scope_id), | |
| 274 | − index("mcp_binding_source_id_idx").on(table.source_id), | |
| 275 | −]); | |
| 340 | +export const mcp_binding = pgTable( | |
| 341 | + "mcp_binding", | |
| 342 | + { | |
| 343 | + id: text("id").notNull(), | |
| 344 | + scope_id: text("scope_id").notNull(), | |
| 345 | + source_id: text("source_id").notNull(), | |
| 346 | + binding: jsonb("binding").notNull(), | |
| 347 | + created_at: timestamp("created_at").notNull(), | |
| 348 | + }, | |
| 349 | + (table) => [ | |
| 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", { | |
| 278 | − id: text('id').notNull(), | |
| 279 | − scope_id: text('scope_id').notNull(), | |
| 280 | − name: text('name').notNull(), | |
| 281 | − endpoint: text('endpoint').notNull(), | |
| 282 | − auth_kind: text('auth_kind', { enum: ['none', 'oauth2'] }).default("none").notNull(), | |
| 283 | − auth_connection_id: text('auth_connection_id') | |
| 284 | −}, (table) => [ | |
| 285 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 286 | − index("graphql_source_scope_id_idx").on(table.scope_id), | |
| 287 | − index("graphql_source_auth_connection_id_idx").on(table.auth_connection_id), | |
| 288 | −]); | |
| 356 | +export const graphql_source = pgTable( | |
| 357 | + "graphql_source", | |
| 358 | + { | |
| 359 | + id: text("id").notNull(), | |
| 360 | + scope_id: text("scope_id").notNull(), | |
| 361 | + name: text("name").notNull(), | |
| 362 | + endpoint: text("endpoint").notNull(), | |
| 363 | + auth_kind: text("auth_kind", { enum: ["none", "oauth2"] }) | |
| 364 | + .default("none") | |
| 365 | + .notNull(), | |
| 366 | + auth_connection_id: text("auth_connection_id"), | |
| 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", { | |
| 291 | − id: text('id').notNull(), | |
| 292 | − scope_id: text('scope_id').notNull(), | |
| 293 | − source_id: text('source_id').notNull(), | |
| 294 | − name: text('name').notNull(), | |
| 295 | − kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | |
| 296 | − text_value: text('text_value'), | |
| 297 | − secret_id: text('secret_id'), | |
| 298 | − secret_prefix: text('secret_prefix') | |
| 299 | −}, (table) => [ | |
| 300 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 301 | − index("graphql_source_header_scope_id_idx").on(table.scope_id), | |
| 302 | − index("graphql_source_header_source_id_idx").on(table.source_id), | |
| 303 | − index("graphql_source_header_secret_id_idx").on(table.secret_id), | |
| 304 | −]); | |
| 375 | +export const graphql_source_header = pgTable( | |
| 376 | + "graphql_source_header", | |
| 377 | + { | |
| 378 | + id: text("id").notNull(), | |
| 379 | + scope_id: text("scope_id").notNull(), | |
| 380 | + source_id: text("source_id").notNull(), | |
| 381 | + name: text("name").notNull(), | |
| 382 | + kind: text("kind", { enum: ["text", "secret"] }).notNull(), | |
| 383 | + text_value: text("text_value"), | |
| 384 | + secret_id: text("secret_id"), | |
| 385 | + secret_prefix: text("secret_prefix"), | |
| 386 | + }, | |
| 387 | + (table) => [ | |
| 388 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 307 | − id: text('id').notNull(), | |
| 308 | − scope_id: text('scope_id').notNull(), | |
| 309 | − source_id: text('source_id').notNull(), | |
| 310 | − name: text('name').notNull(), | |
| 311 | − kind: text('kind', { enum: ['text', 'secret'] }).notNull(), | |
| 312 | − text_value: text('text_value'), | |
| 313 | − secret_id: text('secret_id'), | |
| 314 | − secret_prefix: text('secret_prefix') | |
| 315 | −}, (table) => [ | |
| 316 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 317 | − index("graphql_source_query_param_scope_id_idx").on(table.scope_id), | |
| 318 | − index("graphql_source_query_param_source_id_idx").on(table.source_id), | |
| 319 | − index("graphql_source_query_param_secret_id_idx").on(table.secret_id), | |
| 320 | −]); | |
| 395 | +export const graphql_source_query_param = pgTable( | |
| 396 | + "graphql_source_query_param", | |
| 397 | + { | |
| 398 | + id: text("id").notNull(), | |
| 399 | + scope_id: text("scope_id").notNull(), | |
| 400 | + source_id: text("source_id").notNull(), | |
| 401 | + name: text("name").notNull(), | |
| 402 | + kind: text("kind", { enum: ["text", "secret"] }).notNull(), | |
| 403 | + text_value: text("text_value"), | |
| 404 | + secret_id: text("secret_id"), | |
| 405 | + secret_prefix: text("secret_prefix"), | |
| 406 | + }, | |
| 407 | + (table) => [ | |
| 408 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 323 | − id: text('id').notNull(), | |
| 324 | − scope_id: text('scope_id').notNull(), | |
| 325 | − source_id: text('source_id').notNull(), | |
| 326 | − binding: jsonb('binding').notNull() | |
| 327 | −}, (table) => [ | |
| 328 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 329 | − index("graphql_operation_scope_id_idx").on(table.scope_id), | |
| 330 | − index("graphql_operation_source_id_idx").on(table.source_id), | |
| 331 | −]); | |
| 415 | +export const graphql_operation = pgTable( | |
| 416 | + "graphql_operation", | |
| 417 | + { | |
| 418 | + id: text("id").notNull(), | |
| 419 | + scope_id: text("scope_id").notNull(), | |
| 420 | + source_id: text("source_id").notNull(), | |
| 421 | + binding: jsonb("binding").notNull(), | |
| 422 | + }, | |
| 423 | + (table) => [ | |
| 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", { | |
| 334 | − id: text('id').notNull(), | |
| 335 | − scope_id: text('scope_id').notNull(), | |
| 336 | − name: text('name').notNull(), | |
| 337 | − purpose: text('purpose'), | |
| 338 | − created_at: timestamp('created_at').notNull() | |
| 339 | −}, (table) => [ | |
| 340 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 341 | − index("workos_vault_metadata_scope_id_idx").on(table.scope_id), | |
| 342 | −]); | |
| 430 | +export const workos_vault_metadata = pgTable( | |
| 431 | + "workos_vault_metadata", | |
| 432 | + { | |
| 433 | + id: text("id").notNull(), | |
| 434 | + scope_id: text("scope_id").notNull(), | |
| 435 | + name: text("name").notNull(), | |
| 436 | + purpose: text("purpose"), | |
| 437 | + created_at: timestamp("created_at").notNull(), | |
| 438 | + }, | |
| 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", { | |
| 345 | − namespace: text('namespace').notNull(), | |
| 346 | − key: text('key').notNull(), | |
| 347 | − value: text('value').notNull() | |
| 348 | −}, (table) => [ | |
| 349 | − primaryKey({ columns: [table.namespace, table.key] }), | |
| 350 | −]); | |
| 445 | +export const blob = pgTable( | |
| 446 | + "blob", | |
| 447 | + { | |
| 448 | + namespace: text("namespace").notNull(), | |
| 449 | + key: text("key").notNull(), | |
| 450 | + value: text("value").notNull(), | |
| 451 | + }, | |
| 452 | + (table) => [primaryKey({ columns: [table.namespace, table.key] })], | |
| 453 | +); | |
| @@ -10,16 +10,8 @@ · 9 unmodified lines | ||
| 10 | 10 | |
| 11 | 11 | import { Effect } from "effect"; |
| 12 | 12 | |
| 13 | −import { | |
| 14 | − Scope, | |
| 15 | − ScopeId, | |
| 16 | − collectSchemas, | |
| 17 | − createExecutor, | |
| 18 | −} from "@executor-js/sdk"; | |
| 19 | −import { | |
| 20 | − makePostgresAdapter, | |
| 21 | − makePostgresBlobStore, | |
| 22 | −} from "@executor-js/storage-postgres"; | |
| 13 | +import { Scope, ScopeId, collectSchemas, createExecutor } from "@executor-js/sdk"; | |
| 14 | +import { makePostgresAdapter, makePostgresBlobStore } from "@executor-js/storage-postgres"; | |
| 23 | 15 | |
| 24 | 16 | import { env } from "cloudflare:workers"; |
| 25 | 17 | import executorConfig from "../../executor.config"; |
| @@ -48,21 +48,15 @@ const RegistrationBody = Schema.Struct({ · 47 unmodified lines | ||
| 48 | 48 | grant_types: Schema.optional(Schema.Array(Schema.String)), |
| 49 | 49 | response_types: Schema.optional(Schema.Array(Schema.String)), |
| 50 | 50 | }); |
| 51 | −const decodeRegistrationBody = Schema.decodeUnknownOption( | |
| 52 | − Schema.fromJsonString(RegistrationBody), | |
| 53 | −); | |
| 51 | +const decodeRegistrationBody = Schema.decodeUnknownOption(Schema.fromJsonString(RegistrationBody)); | |
| 54 | 52 | |
| 55 | 53 | const startFakeServer = async (): Promise<FakeServer> => { |
| 56 | 54 | const clients = new Map<string, { redirect_uris: readonly string[] }>(); |
| 57 | − const codes = new Map< | |
| 58 | − string, | |
| 59 | − { readonly clientId: string; readonly codeChallenge: string } | |
| 60 | − >(); | |
| 55 | + const codes = new Map<string, { readonly clientId: string; readonly codeChallenge: string }>(); | |
| 61 | 56 | const accessTokens = new Map<string, { readonly refresh: string }>(); |
| 62 | 57 | const refreshTokens = new Map<string, string>(); |
| 63 | 58 | let seq = 0; |
| 64 | − const next = (p: string) => | |
| 65 | − `${p}_${++seq}_${randomBytes(6).toString("hex")}`; | |
| 59 | + const next = (p: string) => `${p}_${++seq}_${randomBytes(6).toString("hex")}`; | |
| 66 | 60 | let registrations = 0; |
| 67 | 61 | let tokenCalls = 0; |
| 68 | 62 | |
| @@ -76,16 +70,10 @@ const startFakeServer = async (): Promise<FakeServer> => { · 7 unmodified lines | ||
| 76 | 70 | |
| 77 | 71 | const server: Server = createServer(async (req, res) => { |
| 78 | 72 | const url = new URL(req.url!, `http://${req.headers.host}`); |
| 79 | − const send = ( | |
| 80 | − status: number, | |
| 81 | − body: unknown, | |
| 82 | − headers: Record<string, string> = {}, | |
| 83 | − ) => { | |
| 84 | − const payload = | |
| 85 | − typeof body === "string" ? body : JSON.stringify(body); | |
| 73 | + const send = (status: number, body: unknown, headers: Record<string, string> = {}) => { | |
| 74 | + const payload = typeof body === "string" ? body : JSON.stringify(body); | |
| 86 | 75 | res.writeHead(status, { |
| 87 | − "content-type": | |
| 88 | − typeof body === "string" ? "text/plain" : "application/json", | |
| 76 | + "content-type": typeof body === "string" ? "text/plain" : "application/json", | |
| 89 | 77 | ...headers, |
| 90 | 78 | }); |
| 91 | 79 | res.end(payload); |
| @@ -130,10 +118,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 38 unmodified lines | ||
| 130 | 118 | client_id: clientId, |
| 131 | 119 | client_id_issued_at: Math.floor(Date.now() / 1000), |
| 132 | 120 | redirect_uris: parsed.redirect_uris ?? [], |
| 133 | − grant_types: parsed.grant_types ?? [ | |
| 134 | − "authorization_code", | |
| 135 | − "refresh_token", | |
| 136 | − ], | |
| 121 | + grant_types: parsed.grant_types ?? ["authorization_code", "refresh_token"], | |
| 137 | 122 | response_types: parsed.response_types ?? ["code"], |
| 138 | 123 | token_endpoint_auth_method: "none", |
| 139 | 124 | }); |
| @@ -171,9 +156,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 31 unmodified lines | ||
| 171 | 156 | const record = codes.get(code); |
| 172 | 157 | if (!record) return send(400, { error: "invalid_grant" }); |
| 173 | 158 | codes.delete(code); |
| 174 | − const computed = createHash("sha256") | |
| 175 | − .update(verifier) | |
| 176 | − .digest("base64url"); | |
| 159 | + const computed = createHash("sha256").update(verifier).digest("base64url"); | |
| 177 | 160 | if (computed !== record.codeChallenge) { |
| 178 | 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) => | |
| 233 | − server.listen(0, "127.0.0.1", () => resolve()), | |
| 234 | − ); | |
| 215 | + await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", () => resolve())); | |
| 235 | 216 | const address = server.address() as AddressInfo; |
| 236 | 217 | |
| 237 | 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(() => | |
| 356 | − followAuthorize(startedA.authorizationUrl!), | |
| 357 | − ); | |
| 336 | + const redirA = yield* Effect.promise(() => followAuthorize(startedA.authorizationUrl!)); | |
| 358 | 337 | const completedA = yield* asUser(userA, orgId, (client) => |
| 359 | 338 | client.oauth.complete({ |
| 360 | 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(() => | |
| 381 | − followAuthorize(startedB.authorizationUrl!), | |
| 382 | − ); | |
| 359 | + const redirB = yield* Effect.promise(() => followAuthorize(startedB.authorizationUrl!)); | |
| 383 | 360 | const completedB = yield* asUser(userB, orgId, (client) => |
| 384 | 361 | client.oauth.complete({ |
| 385 | 362 | params: { scopeId: scopeB }, |
| @@ -58,9 +58,7 @@ describe("JsonRpcRequestIdQueue", () => { · 57 unmodified lines | ||
| 58 | 58 | queue |
| 59 | 59 | .run(jsonRpcRequest({ jsonrpc: "2.0", id: 2, method: "tools/call" }), async () => "done") |
| 60 | 60 | .then((v) => ({ kind: "settled" as const, v })), |
| 61 | − new Promise<{ kind: "blocked" }>((r) => | |
| 62 | − setTimeout(() => r({ kind: "blocked" }), 200), | |
| 63 | − ), | |
| 61 | + new Promise<{ kind: "blocked" }>((r) => setTimeout(() => r({ kind: "blocked" }), 200)), | |
| 64 | 62 | ]); |
| 65 | 63 | |
| 66 | 64 | expect(otherDone.kind).toBe("settled"); |
| @@ -64,9 +64,7 @@ const extractJsonRpcRequestIdKeys = async (request: Request): Promise<ReadonlyAr · 63 unmodified lines | ||
| 64 | 64 | const contentType = request.headers.get("content-type") ?? ""; |
| 65 | 65 | if (!contentType.includes("application/json")) return []; |
| 66 | 66 | |
| 67 | − const parsed = await Effect.runPromiseExit( | |
| 68 | − Effect.tryPromise(() => request.clone().json()), | |
| 69 | − ); | |
| 67 | + const parsed = await Effect.runPromiseExit(Effect.tryPromise(() => request.clone().json())); | |
| 70 | 68 | if (Exit.isFailure(parsed)) { |
| 71 | 69 | return []; |
| 72 | 70 | } |
| @@ -192,8 +190,6 @@ export const makeMcpWorkerTransport = ( · 119 unmodified lines | ||
| 192 | 190 | try: () => transport.close(), |
| 193 | 191 | catch: (cause) => new McpWorkerTransportError({ cause }), |
| 194 | 192 | }), |
| 195 | − ).pipe( | |
| 196 | − Effect.withSpan("mcp.worker_transport.close"), | |
| 197 | − ), | |
| 193 | + ).pipe(Effect.withSpan("mcp.worker_transport.close")), | |
| 198 | 194 | } satisfies McpWorkerTransport; |
| 199 | 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 | 16 | export type ISlackService = Readonly<{ |
| 17 | − createConnectInvite: (input: { | |
| 18 | − email: string; | |
| 19 | − organization?: string; | |
| 20 | − }) => Effect.Effect< | |
| 17 | + createConnectInvite: (input: { email: string; organization?: string }) => Effect.Effect< | |
| 21 | 18 | { |
| 22 | 19 | channel: { id: string; name: string }; |
| 23 | 20 | invite: { invite_id: string; url: string }; |
| @@ -46,9 +43,7 @@ const make = Effect.sync(() => { · 22 unmodified lines | ||
| 46 | 43 | |
| 47 | 44 | if (!token) { |
| 48 | 45 | const notConfigured = (method: string) => |
| 49 | − Effect.fail( | |
| 50 | − new SlackError({ method, error: "SLACK_BOT_TOKEN is not configured" }), | |
| 51 | − ); | |
| 46 | + Effect.fail(new SlackError({ method, error: "SLACK_BOT_TOKEN is not configured" })); | |
| 52 | 47 | return { |
| 53 | 48 | createConnectInvite: () => notConfigured("createConnectInvite"), |
| 54 | 49 | } satisfies ISlackService; |
| @@ -83,20 +78,17 @@ const make = Effect.sync(() => { · 28 unmodified lines | ||
| 83 | 78 | Effect.withSpan(`slack.${method}`), |
| 84 | 79 | ); |
| 85 | 80 | |
| 86 | − const createConnectInvite: ISlackService["createConnectInvite"] = ({ | |
| 87 | − email, | |
| 88 | − organization, | |
| 89 | − }) => | |
| 81 | + const createConnectInvite: ISlackService["createConnectInvite"] = ({ email, organization }) => | |
| 90 | 82 | Effect.gen(function* () { |
| 91 | 83 | // Slack channel names: lowercase, no spaces, max 80 chars, unique per |
| 92 | 84 | // workspace. Use the org name when available; fall back to the email |
| 93 | 85 | // local-part for unauthenticated submissions. |
| 94 | 86 | const baseName = slugify(organization ?? email).slice(0, 80) || "contact"; |
| 95 | 87 | const tryCreate = (n: string) => |
| 96 | − call<SlackResponse & { channel: { id: string; name: string } }>( | |
| 97 | − "conversations.create", | |
| 98 | − { name: n, is_private: false }, | |
| 99 | − ); | |
| 88 | + call<SlackResponse & { channel: { id: string; name: string } }>("conversations.create", { | |
| 89 | + name: n, | |
| 90 | + is_private: false, | |
| 91 | + }); | |
| 100 | 92 | |
| 101 | 93 | const created = yield* tryCreate(baseName).pipe( |
| 102 | 94 | Effect.catchTag("SlackError", (err) => |
| @@ -125,9 +117,7 @@ const make = Effect.sync(() => { · 22 unmodified lines | ||
| 125 | 117 | channel: { id: channel.id, name: channel.name }, |
| 126 | 118 | invite: { invite_id: invite.invite_id, url: invite.url }, |
| 127 | 119 | }; |
| 128 | − }).pipe( | |
| 129 | − Effect.withSpan("slack.createConnectInvite", { attributes: { "slack.email": email } }), | |
| 130 | − ); | |
| 120 | + }).pipe(Effect.withSpan("slack.createConnectInvite", { attributes: { "slack.email": email } })); | |
| 131 | 121 | |
| 132 | 122 | return { createConnectInvite } satisfies ISlackService; |
| 133 | 123 | }); |
| @@ -14,11 +14,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/ · 13 unmodified lines | ||
| 14 | 14 | |
| 15 | 15 | import { ScopeId, SecretId } from "@executor-js/sdk"; |
| 16 | 16 | |
| 17 | −import { | |
| 18 | − asOrg, | |
| 19 | − asUser, | |
| 20 | − testUserOrgScopeId, | |
| 21 | −} from "./__test-harness__/api-harness"; | |
| 17 | +import { asOrg, asUser, testUserOrgScopeId } from "./__test-harness__/api-harness"; | |
| 22 | 18 | |
| 23 | 19 | const MINIMAL_OPENAPI_SPEC = JSON.stringify({ |
| 24 | 20 | openapi: "3.0.0", |
| @@ -108,7 +104,10 @@ const startEchoServer = () => { · 83 unmodified lines | ||
| 108 | 104 | |
| 109 | 105 | return new Promise<{ |
| 110 | 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 | 111 | readonly close: () => Promise<void>; |
| 113 | 112 | }>((resolveServer) => { |
| 114 | 113 | server.listen(0, "127.0.0.1", () => { |
| @@ -225,10 +224,7 @@ const startGraphqlServer = () => { · 110 unmodified lines | ||
| 225 | 224 | }; |
| 226 | 225 | |
| 227 | 226 | const createCloudMcpServer = () => { |
| 228 | − const server = new McpServer( | |
| 229 | − { name: "cloud-e2e-mcp", version: "1.0.0" }, | |
| 230 | − { capabilities: {} }, | |
| 231 | − ); | |
| 227 | + const server = new McpServer({ name: "cloud-e2e-mcp", version: "1.0.0" }, { capabilities: {} }); | |
| 232 | 228 | |
| 233 | 229 | server.registerTool( |
| 234 | 230 | "simple_echo", |
| @@ -831,9 +827,7 @@ describe("sources api (HTTP)", () => { · 596 unmodified lines | ||
| 831 | 827 | const sources = yield* asOrg(orgId, (client) => |
| 832 | 828 | client.sources.list({ params: { scopeId: ScopeId.make(orgId) } }), |
| 833 | 829 | ); |
| 834 | − expect(sources.find((source) => source.id === namespace)?.scopeId).toBe( | |
| 835 | − ScopeId.make(orgId), | |
| 836 | − ); | |
| 830 | + expect(sources.find((source) => source.id === namespace)?.scopeId).toBe(ScopeId.make(orgId)); | |
| 837 | 831 | }), |
| 838 | 832 | ); |
| 839 | 833 | |
| @@ -49,10 +49,7 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http/build · 48 unmodified lines | ||
| 49 | 49 | import { resourceFromAttributes } from "@opentelemetry/resources"; |
| 50 | 50 | import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base"; |
| 51 | 51 | import { WebTracerProvider } from "@opentelemetry/sdk-trace-web"; |
| 52 | −import { | |
| 53 | − ATTR_SERVICE_NAME, | |
| 54 | − ATTR_SERVICE_VERSION, | |
| 55 | −} from "@opentelemetry/semantic-conventions"; | |
| 52 | +import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions"; | |
| 56 | 53 | import { env } from "cloudflare:workers"; |
| 57 | 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 | 55 | const TestMcpOrganizationAuthLive = Layer.succeed(McpOrganizationAuth)({ |
| 56 | − authorize: (_accountId, organizationId) => | |
| 57 | − Effect.succeed(!organizationId.startsWith("revoked_")), | |
| 56 | + authorize: (_accountId, organizationId) => Effect.succeed(!organizationId.startsWith("revoked_")), | |
| 58 | 57 | }); |
| 59 | 58 | |
| 60 | 59 | // --------------------------------------------------------------------------- |
| @@ -112,14 +111,14 @@ const handleSeedOrg = async ( · 51 unmodified lines | ||
| 112 | 111 | // OTLPTraceExporter — not Durable-Object-specific) to stand in. |
| 113 | 112 | const testMcpFetch = HttpEffect.toWebHandler( |
| 114 | 113 | mcpApp.pipe( |
| 115 | − Effect.provide( | |
| 116 | − Layer.mergeAll(TestMcpAuthLive, TestMcpOrganizationAuthLive, DoTelemetryLive), | |
| 117 | − ), | |
| 114 | + Effect.provide(Layer.mergeAll(TestMcpAuthLive, TestMcpOrganizationAuthLive, DoTelemetryLive)), | |
| 118 | 115 | ), |
| 119 | 116 | ); |
| 120 | 117 | |
| 121 | 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 | 124 | export default { |
| @@ -69,9 +69,7 @@ export const OnboardingPage = () => { · 68 unmodified lines | ||
| 69 | 69 | }; |
| 70 | 70 | |
| 71 | 71 | const suggestedName = |
| 72 | − auth.status === "authenticated" && | |
| 73 | − auth.user.name != null && | |
| 74 | − auth.user.name.trim() !== "" | |
| 72 | + auth.status === "authenticated" && auth.user.name != null && auth.user.name.trim() !== "" | |
| 75 | 73 | ? `${auth.user.name}'s Organization` |
| 76 | 74 | : ""; |
| 77 | 75 | |
| @@ -80,7 +78,8 @@ export const OnboardingPage = () => { · 2 unmodified lines | ||
| 80 | 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 | 83 | const invitations = AsyncResult.match(invitationsResult, { |
| 85 | 84 | onInitial: () => [] as readonly PendingInvitation[], |
| 86 | 85 | onFailure: () => [] as readonly PendingInvitation[], |
| @@ -130,10 +129,7 @@ export const OnboardingPage = () => { · 43 unmodified lines | ||
| 130 | 129 | )} |
| 131 | 130 | |
| 132 | 131 | {!isLoading && (count === 0 || sole || count > 1) && ( |
| 133 | − <CreateOrgSection | |
| 134 | − isPrimary={count === 0} | |
| 135 | − form={form} | |
| 136 | − /> | |
| 132 | + <CreateOrgSection isPrimary={count === 0} form={form} /> | |
| 137 | 133 | )} |
| 138 | 134 | |
| 139 | 135 | <footer className="flex items-center justify-center"> |
| @@ -19,10 +19,7 @@ const devCrashGuard = (): Plugin => { · 18 unmodified lines | ||
| 19 | 19 | if (installed) return; |
| 20 | 20 | installed = true; |
| 21 | 21 | process.on("uncaughtException", (err, origin) => { |
| 22 | − console.error( | |
| 23 | − `[dev-crash-guard] uncaughtException (origin=${origin}):`, | |
| 24 | − err, | |
| 25 | − ); | |
| 22 | + console.error(`[dev-crash-guard] uncaughtException (origin=${origin}):`, err); | |
| 26 | 23 | }); |
| 27 | 24 | process.on("unhandledRejection", (reason, promise) => { |
| 28 | 25 | console.error("[dev-crash-guard] unhandledRejection:", reason, promise); |
| @@ -16,11 +16,11 @@ · 15 unmodified lines | ||
| 16 | 16 | "WORKOS_COOKIE_PASSWORD": "test_cookie_password_at_least_32_chars!", |
| 17 | 17 | "MCP_AUTHKIT_DOMAIN": "https://test-authkit.example.com", |
| 18 | 18 | "MCP_RESOURCE_ORIGIN": "https://test-resource.example.com", |
| 19 | − "NODE_ENV": "test" | |
| 19 | + "NODE_ENV": "test", | |
| 20 | 20 | }, |
| 21 | 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"}], | |
| 25 | − "migrations": [{"tag": "v1", "new_classes": ["McpSessionDO"]}] | |
| 24 | + "worker_loaders": [{ "binding": "LOADER" }], | |
| 25 | + "migrations": [{ "tag": "v1", "new_classes": ["McpSessionDO"] }], | |
| 26 | 26 | } |
| @@ -12,11 +12,11 @@ · 11 unmodified lines | ||
| 12 | 12 | "WORKOS_COOKIE_PASSWORD": "test_cookie_password_at_least_32_chars!", |
| 13 | 13 | "MCP_AUTHKIT_DOMAIN": "https://test-authkit.example.com", |
| 14 | 14 | "MCP_RESOURCE_ORIGIN": "https://test-resource.example.com", |
| 15 | − "NODE_ENV": "test" | |
| 15 | + "NODE_ENV": "test", | |
| 16 | 16 | }, |
| 17 | 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"}], | |
| 21 | − "migrations": [{"tag": "v1", "new_classes": ["McpSessionDO"]}] | |
| 20 | + "worker_loaders": [{ "binding": "LOADER" }], | |
| 21 | + "migrations": [{ "tag": "v1", "new_classes": ["McpSessionDO"] }], | |
| 22 | 22 | } |
| @@ -99,11 +99,7 @@ const installCli = (): void => { · 98 unmodified lines | ||
| 99 | 99 | // Patch shell profiles with PATH |
| 100 | 100 | if (process.platform === "win32") { |
| 101 | 101 | // Add bin dir to the user PATH via registry so new terminals pick it up |
| 102 | − const result = spawn( | |
| 103 | − "reg", | |
| 104 | − ["query", "HKCU\\Environment", "/v", "Path"], | |
| 105 | − { stdio: "pipe" }, | |
| 106 | − ); | |
| 102 | + const result = spawn("reg", ["query", "HKCU\\Environment", "/v", "Path"], { stdio: "pipe" }); | |
| 107 | 103 | let out = ""; |
| 108 | 104 | result.stdout?.on("data", (d: Buffer) => (out += d.toString())); |
| 109 | 105 | result.on("close", (code) => { |
| @@ -128,8 +124,15 @@ const installCli = (): void => { · 18 unmodified lines | ||
| 128 | 124 | if (!current.toLowerCase().includes(CLI_BIN_DIR.toLowerCase())) { |
| 129 | 125 | const updated = current ? `${current};${CLI_BIN_DIR}` : CLI_BIN_DIR; |
| 130 | 126 | spawn("reg", [ |
| 131 | − "add", "HKCU\\Environment", "/v", "Path", | |
| 132 | − "/t", "REG_EXPAND_SZ", "/d", updated, "/f", | |
| 127 | + "add", | |
| 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 | 33 | "foreignKeys": {}, |
| 34 | 34 | "compositePrimaryKeys": { |
| 35 | 35 | "blob_namespace_key_pk": { |
| 36 | − "columns": [ | |
| 37 | − "namespace", | |
| 38 | − "key" | |
| 39 | − ], | |
| 36 | + "columns": ["namespace", "key"], | |
| 40 | 37 | "name": "blob_namespace_key_pk" |
| 41 | 38 | } |
| 42 | 39 | }, |
| @@ -99,33 +96,24 @@ · 56 unmodified lines | ||
| 99 | 96 | "indexes": { |
| 100 | 97 | "definition_scope_id_idx": { |
| 101 | 98 | "name": "definition_scope_id_idx", |
| 102 | − "columns": [ | |
| 103 | − "scope_id" | |
| 104 | − ], | |
| 99 | + "columns": ["scope_id"], | |
| 105 | 100 | "isUnique": false |
| 106 | 101 | }, |
| 107 | 102 | "definition_source_id_idx": { |
| 108 | 103 | "name": "definition_source_id_idx", |
| 109 | − "columns": [ | |
| 110 | − "source_id" | |
| 111 | − ], | |
| 104 | + "columns": ["source_id"], | |
| 112 | 105 | "isUnique": false |
| 113 | 106 | }, |
| 114 | 107 | "definition_plugin_id_idx": { |
| 115 | 108 | "name": "definition_plugin_id_idx", |
| 116 | − "columns": [ | |
| 117 | − "plugin_id" | |
| 118 | − ], | |
| 109 | + "columns": ["plugin_id"], | |
| 119 | 110 | "isUnique": false |
| 120 | 111 | } |
| 121 | 112 | }, |
| 122 | 113 | "foreignKeys": {}, |
| 123 | 114 | "compositePrimaryKeys": { |
| 124 | 115 | "definition_scope_id_id_pk": { |
| 125 | − "columns": [ | |
| 126 | − "scope_id", | |
| 127 | − "id" | |
| 128 | − ], | |
| 116 | + "columns": ["scope_id", "id"], | |
| 129 | 117 | "name": "definition_scope_id_id_pk" |
| 130 | 118 | } |
| 131 | 119 | }, |
| @@ -174,26 +162,19 @@ · 42 unmodified lines | ||
| 174 | 162 | "indexes": { |
| 175 | 163 | "google_discovery_binding_scope_id_idx": { |
| 176 | 164 | "name": "google_discovery_binding_scope_id_idx", |
| 177 | − "columns": [ | |
| 178 | − "scope_id" | |
| 179 | − ], | |
| 165 | + "columns": ["scope_id"], | |
| 180 | 166 | "isUnique": false |
| 181 | 167 | }, |
| 182 | 168 | "google_discovery_binding_source_id_idx": { |
| 183 | 169 | "name": "google_discovery_binding_source_id_idx", |
| 184 | − "columns": [ | |
| 185 | − "source_id" | |
| 186 | − ], | |
| 170 | + "columns": ["source_id"], | |
| 187 | 171 | "isUnique": false |
| 188 | 172 | } |
| 189 | 173 | }, |
| 190 | 174 | "foreignKeys": {}, |
| 191 | 175 | "compositePrimaryKeys": { |
| 192 | 176 | "google_discovery_binding_scope_id_id_pk": { |
| 193 | − "columns": [ | |
| 194 | − "scope_id", | |
| 195 | − "id" | |
| 196 | − ], | |
| 177 | + "columns": ["scope_id", "id"], | |
| 197 | 178 | "name": "google_discovery_binding_scope_id_id_pk" |
| 198 | 179 | } |
| 199 | 180 | }, |
| @@ -235,19 +216,14 @@ · 35 unmodified lines | ||
| 235 | 216 | "indexes": { |
| 236 | 217 | "google_discovery_oauth_session_scope_id_idx": { |
| 237 | 218 | "name": "google_discovery_oauth_session_scope_id_idx", |
| 238 | − "columns": [ | |
| 239 | − "scope_id" | |
| 240 | − ], | |
| 219 | + "columns": ["scope_id"], | |
| 241 | 220 | "isUnique": false |
| 242 | 221 | } |
| 243 | 222 | }, |
| 244 | 223 | "foreignKeys": {}, |
| 245 | 224 | "compositePrimaryKeys": { |
| 246 | 225 | "google_discovery_oauth_session_scope_id_id_pk": { |
| 247 | − "columns": [ | |
| 248 | − "scope_id", | |
| 249 | − "id" | |
| 250 | − ], | |
| 226 | + "columns": ["scope_id", "id"], | |
| 251 | 227 | "name": "google_discovery_oauth_session_scope_id_id_pk" |
| 252 | 228 | } |
| 253 | 229 | }, |
| @@ -303,19 +279,14 @@ · 49 unmodified lines | ||
| 303 | 279 | "indexes": { |
| 304 | 280 | "google_discovery_source_scope_id_idx": { |
| 305 | 281 | "name": "google_discovery_source_scope_id_idx", |
| 306 | − "columns": [ | |
| 307 | − "scope_id" | |
| 308 | − ], | |
| 282 | + "columns": ["scope_id"], | |
| 309 | 283 | "isUnique": false |
| 310 | 284 | } |
| 311 | 285 | }, |
| 312 | 286 | "foreignKeys": {}, |
| 313 | 287 | "compositePrimaryKeys": { |
| 314 | 288 | "google_discovery_source_scope_id_id_pk": { |
| 315 | − "columns": [ | |
| 316 | − "scope_id", | |
| 317 | − "id" | |
| 318 | − ], | |
| 289 | + "columns": ["scope_id", "id"], | |
| 319 | 290 | "name": "google_discovery_source_scope_id_id_pk" |
| 320 | 291 | } |
| 321 | 292 | }, |
| @@ -357,26 +328,19 @@ · 35 unmodified lines | ||
| 357 | 328 | "indexes": { |
| 358 | 329 | "graphql_operation_scope_id_idx": { |
| 359 | 330 | "name": "graphql_operation_scope_id_idx", |
| 360 | − "columns": [ | |
| 361 | − "scope_id" | |
| 362 | − ], | |
| 331 | + "columns": ["scope_id"], | |
| 363 | 332 | "isUnique": false |
| 364 | 333 | }, |
| 365 | 334 | "graphql_operation_source_id_idx": { |
| 366 | 335 | "name": "graphql_operation_source_id_idx", |
| 367 | − "columns": [ | |
| 368 | − "source_id" | |
| 369 | − ], | |
| 336 | + "columns": ["source_id"], | |
| 370 | 337 | "isUnique": false |
| 371 | 338 | } |
| 372 | 339 | }, |
| 373 | 340 | "foreignKeys": {}, |
| 374 | 341 | "compositePrimaryKeys": { |
| 375 | 342 | "graphql_operation_scope_id_id_pk": { |
| 376 | − "columns": [ | |
| 377 | − "scope_id", | |
| 378 | − "id" | |
| 379 | − ], | |
| 343 | + "columns": ["scope_id", "id"], | |
| 380 | 344 | "name": "graphql_operation_scope_id_id_pk" |
| 381 | 345 | } |
| 382 | 346 | }, |
| @@ -425,19 +389,14 @@ · 42 unmodified lines | ||
| 425 | 389 | "indexes": { |
| 426 | 390 | "graphql_source_scope_id_idx": { |
| 427 | 391 | "name": "graphql_source_scope_id_idx", |
| 428 | − "columns": [ | |
| 429 | − "scope_id" | |
| 430 | − ], | |
| 392 | + "columns": ["scope_id"], | |
| 431 | 393 | "isUnique": false |
| 432 | 394 | } |
| 433 | 395 | }, |
| 434 | 396 | "foreignKeys": {}, |
| 435 | 397 | "compositePrimaryKeys": { |
| 436 | 398 | "graphql_source_scope_id_id_pk": { |
| 437 | − "columns": [ | |
| 438 | − "scope_id", | |
| 439 | − "id" | |
| 440 | − ], | |
| 399 | + "columns": ["scope_id", "id"], | |
| 441 | 400 | "name": "graphql_source_scope_id_id_pk" |
| 442 | 401 | } |
| 443 | 402 | }, |
| @@ -486,26 +445,19 @@ · 42 unmodified lines | ||
| 486 | 445 | "indexes": { |
| 487 | 446 | "mcp_binding_scope_id_idx": { |
| 488 | 447 | "name": "mcp_binding_scope_id_idx", |
| 489 | − "columns": [ | |
| 490 | − "scope_id" | |
| 491 | − ], | |
| 448 | + "columns": ["scope_id"], | |
| 492 | 449 | "isUnique": false |
| 493 | 450 | }, |
| 494 | 451 | "mcp_binding_source_id_idx": { |
| 495 | 452 | "name": "mcp_binding_source_id_idx", |
| 496 | − "columns": [ | |
| 497 | − "source_id" | |
| 498 | − ], | |
| 453 | + "columns": ["source_id"], | |
| 499 | 454 | "isUnique": false |
| 500 | 455 | } |
| 501 | 456 | }, |
| 502 | 457 | "foreignKeys": {}, |
| 503 | 458 | "compositePrimaryKeys": { |
| 504 | 459 | "mcp_binding_scope_id_id_pk": { |
| 505 | − "columns": [ | |
| 506 | − "scope_id", | |
| 507 | − "id" | |
| 508 | − ], | |
| 460 | + "columns": ["scope_id", "id"], | |
| 509 | 461 | "name": "mcp_binding_scope_id_id_pk" |
| 510 | 462 | } |
| 511 | 463 | }, |
| @@ -554,19 +506,14 @@ · 42 unmodified lines | ||
| 554 | 506 | "indexes": { |
| 555 | 507 | "mcp_oauth_session_scope_id_idx": { |
| 556 | 508 | "name": "mcp_oauth_session_scope_id_idx", |
| 557 | − "columns": [ | |
| 558 | − "scope_id" | |
| 559 | − ], | |
| 509 | + "columns": ["scope_id"], | |
| 560 | 510 | "isUnique": false |
| 561 | 511 | } |
| 562 | 512 | }, |
| 563 | 513 | "foreignKeys": {}, |
| 564 | 514 | "compositePrimaryKeys": { |
| 565 | 515 | "mcp_oauth_session_scope_id_id_pk": { |
| 566 | − "columns": [ | |
| 567 | − "scope_id", | |
| 568 | − "id" | |
| 569 | − ], | |
| 516 | + "columns": ["scope_id", "id"], | |
| 570 | 517 | "name": "mcp_oauth_session_scope_id_id_pk" |
| 571 | 518 | } |
| 572 | 519 | }, |
| @@ -615,19 +562,14 @@ · 42 unmodified lines | ||
| 615 | 562 | "indexes": { |
| 616 | 563 | "mcp_source_scope_id_idx": { |
| 617 | 564 | "name": "mcp_source_scope_id_idx", |
| 618 | − "columns": [ | |
| 619 | − "scope_id" | |
| 620 | − ], | |
| 565 | + "columns": ["scope_id"], | |
| 621 | 566 | "isUnique": false |
| 622 | 567 | } |
| 623 | 568 | }, |
| 624 | 569 | "foreignKeys": {}, |
| 625 | 570 | "compositePrimaryKeys": { |
| 626 | 571 | "mcp_source_scope_id_id_pk": { |
| 627 | − "columns": [ | |
| 628 | − "scope_id", | |
| 629 | − "id" | |
| 630 | − ], | |
| 572 | + "columns": ["scope_id", "id"], | |
| 631 | 573 | "name": "mcp_source_scope_id_id_pk" |
| 632 | 574 | } |
| 633 | 575 | }, |
| @@ -669,19 +611,14 @@ · 35 unmodified lines | ||
| 669 | 611 | "indexes": { |
| 670 | 612 | "openapi_oauth_session_scope_id_idx": { |
| 671 | 613 | "name": "openapi_oauth_session_scope_id_idx", |
| 672 | − "columns": [ | |
| 673 | − "scope_id" | |
| 674 | − ], | |
| 614 | + "columns": ["scope_id"], | |
| 675 | 615 | "isUnique": false |
| 676 | 616 | } |
| 677 | 617 | }, |
| 678 | 618 | "foreignKeys": {}, |
| 679 | 619 | "compositePrimaryKeys": { |
| 680 | 620 | "openapi_oauth_session_scope_id_id_pk": { |
| 681 | − "columns": [ | |
| 682 | − "scope_id", | |
| 683 | − "id" | |
| 684 | − ], | |
| 621 | + "columns": ["scope_id", "id"], | |
| 685 | 622 | "name": "openapi_oauth_session_scope_id_id_pk" |
| 686 | 623 | } |
| 687 | 624 | }, |
| @@ -723,26 +660,19 @@ · 35 unmodified lines | ||
| 723 | 660 | "indexes": { |
| 724 | 661 | "openapi_operation_scope_id_idx": { |
| 725 | 662 | "name": "openapi_operation_scope_id_idx", |
| 726 | − "columns": [ | |
| 727 | − "scope_id" | |
| 728 | − ], | |
| 663 | + "columns": ["scope_id"], | |
| 729 | 664 | "isUnique": false |
| 730 | 665 | }, |
| 731 | 666 | "openapi_operation_source_id_idx": { |
| 732 | 667 | "name": "openapi_operation_source_id_idx", |
| 733 | − "columns": [ | |
| 734 | − "source_id" | |
| 735 | − ], | |
| 668 | + "columns": ["source_id"], | |
| 736 | 669 | "isUnique": false |
| 737 | 670 | } |
| 738 | 671 | }, |
| 739 | 672 | "foreignKeys": {}, |
| 740 | 673 | "compositePrimaryKeys": { |
| 741 | 674 | "openapi_operation_scope_id_id_pk": { |
| 742 | − "columns": [ | |
| 743 | − "scope_id", | |
| 744 | − "id" | |
| 745 | − ], | |
| 675 | + "columns": ["scope_id", "id"], | |
| 746 | 676 | "name": "openapi_operation_scope_id_id_pk" |
| 747 | 677 | } |
| 748 | 678 | }, |
| @@ -812,19 +742,14 @@ · 63 unmodified lines | ||
| 812 | 742 | "indexes": { |
| 813 | 743 | "openapi_source_scope_id_idx": { |
| 814 | 744 | "name": "openapi_source_scope_id_idx", |
| 815 | − "columns": [ | |
| 816 | − "scope_id" | |
| 817 | − ], | |
| 745 | + "columns": ["scope_id"], | |
| 818 | 746 | "isUnique": false |
| 819 | 747 | } |
| 820 | 748 | }, |
| 821 | 749 | "foreignKeys": {}, |
| 822 | 750 | "compositePrimaryKeys": { |
| 823 | 751 | "openapi_source_scope_id_id_pk": { |
| 824 | − "columns": [ | |
| 825 | − "scope_id", | |
| 826 | − "id" | |
| 827 | − ], | |
| 752 | + "columns": ["scope_id", "id"], | |
| 828 | 753 | "name": "openapi_source_scope_id_id_pk" |
| 829 | 754 | } |
| 830 | 755 | }, |
| @@ -873,26 +798,19 @@ · 42 unmodified lines | ||
| 873 | 798 | "indexes": { |
| 874 | 799 | "secret_scope_id_idx": { |
| 875 | 800 | "name": "secret_scope_id_idx", |
| 876 | − "columns": [ | |
| 877 | − "scope_id" | |
| 878 | − ], | |
| 801 | + "columns": ["scope_id"], | |
| 879 | 802 | "isUnique": false |
| 880 | 803 | }, |
| 881 | 804 | "secret_provider_idx": { |
| 882 | 805 | "name": "secret_provider_idx", |
| 883 | − "columns": [ | |
| 884 | − "provider" | |
| 885 | − ], | |
| 806 | + "columns": ["provider"], | |
| 886 | 807 | "isUnique": false |
| 887 | 808 | } |
| 888 | 809 | }, |
| 889 | 810 | "foreignKeys": {}, |
| 890 | 811 | "compositePrimaryKeys": { |
| 891 | 812 | "secret_scope_id_id_pk": { |
| 892 | − "columns": [ | |
| 893 | − "scope_id", | |
| 894 | − "id" | |
| 895 | − ], | |
| 813 | + "columns": ["scope_id", "id"], | |
| 896 | 814 | "name": "secret_scope_id_id_pk" |
| 897 | 815 | } |
| 898 | 816 | }, |
| @@ -986,26 +904,19 @@ · 87 unmodified lines | ||
| 986 | 904 | "indexes": { |
| 987 | 905 | "source_scope_id_idx": { |
| 988 | 906 | "name": "source_scope_id_idx", |
| 989 | − "columns": [ | |
| 990 | − "scope_id" | |
| 991 | − ], | |
| 907 | + "columns": ["scope_id"], | |
| 992 | 908 | "isUnique": false |
| 993 | 909 | }, |
| 994 | 910 | "source_plugin_id_idx": { |
| 995 | 911 | "name": "source_plugin_id_idx", |
| 996 | − "columns": [ | |
| 997 | − "plugin_id" | |
| 998 | − ], | |
| 912 | + "columns": ["plugin_id"], | |
| 999 | 913 | "isUnique": false |
| 1000 | 914 | } |
| 1001 | 915 | }, |
| 1002 | 916 | "foreignKeys": {}, |
| 1003 | 917 | "compositePrimaryKeys": { |
| 1004 | 918 | "source_scope_id_id_pk": { |
| 1005 | − "columns": [ | |
| 1006 | − "scope_id", | |
| 1007 | − "id" | |
| 1008 | − ], | |
| 919 | + "columns": ["scope_id", "id"], | |
| 1009 | 920 | "name": "source_scope_id_id_pk" |
| 1010 | 921 | } |
| 1011 | 922 | }, |
| @@ -1089,33 +1000,24 @@ · 77 unmodified lines | ||
| 1089 | 1000 | "indexes": { |
| 1090 | 1001 | "tool_scope_id_idx": { |
| 1091 | 1002 | "name": "tool_scope_id_idx", |
| 1092 | − "columns": [ | |
| 1093 | − "scope_id" | |
| 1094 | − ], | |
| 1003 | + "columns": ["scope_id"], | |
| 1095 | 1004 | "isUnique": false |
| 1096 | 1005 | }, |
| 1097 | 1006 | "tool_source_id_idx": { |
| 1098 | 1007 | "name": "tool_source_id_idx", |
| 1099 | − "columns": [ | |
| 1100 | − "source_id" | |
| 1101 | − ], | |
| 1008 | + "columns": ["source_id"], | |
| 1102 | 1009 | "isUnique": false |
| 1103 | 1010 | }, |
| 1104 | 1011 | "tool_plugin_id_idx": { |
| 1105 | 1012 | "name": "tool_plugin_id_idx", |
| 1106 | − "columns": [ | |
| 1107 | − "plugin_id" | |
| 1108 | − ], | |
| 1013 | + "columns": ["plugin_id"], | |
| 1109 | 1014 | "isUnique": false |
| 1110 | 1015 | } |
| 1111 | 1016 | }, |
| 1112 | 1017 | "foreignKeys": {}, |
| 1113 | 1018 | "compositePrimaryKeys": { |
| 1114 | 1019 | "tool_scope_id_id_pk": { |
| 1115 | − "columns": [ | |
| 1116 | − "scope_id", | |
| 1117 | − "id" | |
| 1118 | − ], | |
| 1020 | + "columns": ["scope_id", "id"], | |
| 1119 | 1021 | "name": "tool_scope_id_id_pk" |
| 1120 | 1022 | } |
| 1121 | 1023 | }, |
| @@ -1133,4 +1035,4 @@ · 11 unmodified lines | ||
| 1133 | 1035 | "internal": { |
| 1134 | 1036 | "indexes": {} |
| 1135 | 1037 | } |
| 1136 | −} | |
| 1038 | +} | |
| @@ -33,10 +33,7 @@ · 32 unmodified lines | ||
| 33 | 33 | "foreignKeys": {}, |
| 34 | 34 | "compositePrimaryKeys": { |
| 35 | 35 | "blob_namespace_key_pk": { |
| 36 | − "columns": [ | |
| 37 | − "namespace", | |
| 38 | − "key" | |
| 39 | − ], | |
| 36 | + "columns": ["namespace", "key"], | |
| 40 | 37 | "name": "blob_namespace_key_pk" |
| 41 | 38 | } |
| 42 | 39 | }, |
| @@ -99,33 +96,24 @@ · 56 unmodified lines | ||
| 99 | 96 | "indexes": { |
| 100 | 97 | "definition_scope_id_idx": { |
| 101 | 98 | "name": "definition_scope_id_idx", |
| 102 | − "columns": [ | |
| 103 | − "scope_id" | |
| 104 | − ], | |
| 99 | + "columns": ["scope_id"], | |
| 105 | 100 | "isUnique": false |
| 106 | 101 | }, |
| 107 | 102 | "definition_source_id_idx": { |
| 108 | 103 | "name": "definition_source_id_idx", |
| 109 | − "columns": [ | |
| 110 | − "source_id" | |
| 111 | − ], | |
| 104 | + "columns": ["source_id"], | |
| 112 | 105 | "isUnique": false |
| 113 | 106 | }, |
| 114 | 107 | "definition_plugin_id_idx": { |
| 115 | 108 | "name": "definition_plugin_id_idx", |
| 116 | − "columns": [ | |
| 117 | − "plugin_id" | |
| 118 | − ], | |
| 109 | + "columns": ["plugin_id"], | |
| 119 | 110 | "isUnique": false |
| 120 | 111 | } |
| 121 | 112 | }, |
| 122 | 113 | "foreignKeys": {}, |
| 123 | 114 | "compositePrimaryKeys": { |
| 124 | 115 | "definition_scope_id_id_pk": { |
| 125 | − "columns": [ | |
| 126 | − "scope_id", | |
| 127 | − "id" | |
| 128 | − ], | |
| 116 | + "columns": ["scope_id", "id"], | |
| 129 | 117 | "name": "definition_scope_id_id_pk" |
| 130 | 118 | } |
| 131 | 119 | }, |
| @@ -174,26 +162,19 @@ · 42 unmodified lines | ||
| 174 | 162 | "indexes": { |
| 175 | 163 | "google_discovery_binding_scope_id_idx": { |
| 176 | 164 | "name": "google_discovery_binding_scope_id_idx", |
| 177 | − "columns": [ | |
| 178 | − "scope_id" | |
| 179 | − ], | |
| 165 | + "columns": ["scope_id"], | |
| 180 | 166 | "isUnique": false |
| 181 | 167 | }, |
| 182 | 168 | "google_discovery_binding_source_id_idx": { |
| 183 | 169 | "name": "google_discovery_binding_source_id_idx", |
| 184 | − "columns": [ | |
| 185 | − "source_id" | |
| 186 | − ], | |
| 170 | + "columns": ["source_id"], | |
| 187 | 171 | "isUnique": false |
| 188 | 172 | } |
| 189 | 173 | }, |
| 190 | 174 | "foreignKeys": {}, |
| 191 | 175 | "compositePrimaryKeys": { |
| 192 | 176 | "google_discovery_binding_scope_id_id_pk": { |
| 193 | − "columns": [ | |
| 194 | − "scope_id", | |
| 195 | − "id" | |
| 196 | − ], | |
| 177 | + "columns": ["scope_id", "id"], | |
| 197 | 178 | "name": "google_discovery_binding_scope_id_id_pk" |
| 198 | 179 | } |
| 199 | 180 | }, |
| @@ -235,19 +216,14 @@ · 35 unmodified lines | ||
| 235 | 216 | "indexes": { |
| 236 | 217 | "google_discovery_oauth_session_scope_id_idx": { |
| 237 | 218 | "name": "google_discovery_oauth_session_scope_id_idx", |
| 238 | − "columns": [ | |
| 239 | − "scope_id" | |
| 240 | − ], | |
| 219 | + "columns": ["scope_id"], | |
| 241 | 220 | "isUnique": false |
| 242 | 221 | } |
| 243 | 222 | }, |
| 244 | 223 | "foreignKeys": {}, |
| 245 | 224 | "compositePrimaryKeys": { |
| 246 | 225 | "google_discovery_oauth_session_scope_id_id_pk": { |
| 247 | − "columns": [ | |
| 248 | − "scope_id", | |
| 249 | − "id" | |
| 250 | − ], | |
| 226 | + "columns": ["scope_id", "id"], | |
| 251 | 227 | "name": "google_discovery_oauth_session_scope_id_id_pk" |
| 252 | 228 | } |
| 253 | 229 | }, |
| @@ -303,19 +279,14 @@ · 49 unmodified lines | ||
| 303 | 279 | "indexes": { |
| 304 | 280 | "google_discovery_source_scope_id_idx": { |
| 305 | 281 | "name": "google_discovery_source_scope_id_idx", |
| 306 | − "columns": [ | |
| 307 | − "scope_id" | |
| 308 | − ], | |
| 282 | + "columns": ["scope_id"], | |
| 309 | 283 | "isUnique": false |
| 310 | 284 | } |
| 311 | 285 | }, |
| 312 | 286 | "foreignKeys": {}, |
| 313 | 287 | "compositePrimaryKeys": { |
| 314 | 288 | "google_discovery_source_scope_id_id_pk": { |
| 315 | − "columns": [ | |
| 316 | − "scope_id", | |
| 317 | − "id" | |
| 318 | − ], | |
| 289 | + "columns": ["scope_id", "id"], | |
| 319 | 290 | "name": "google_discovery_source_scope_id_id_pk" |
| 320 | 291 | } |
| 321 | 292 | }, |
| @@ -357,26 +328,19 @@ · 35 unmodified lines | ||
| 357 | 328 | "indexes": { |
| 358 | 329 | "graphql_operation_scope_id_idx": { |
| 359 | 330 | "name": "graphql_operation_scope_id_idx", |
| 360 | − "columns": [ | |
| 361 | − "scope_id" | |
| 362 | − ], | |
| 331 | + "columns": ["scope_id"], | |
| 363 | 332 | "isUnique": false |
| 364 | 333 | }, |
| 365 | 334 | "graphql_operation_source_id_idx": { |
| 366 | 335 | "name": "graphql_operation_source_id_idx", |
| 367 | − "columns": [ | |
| 368 | − "source_id" | |
| 369 | − ], | |
| 336 | + "columns": ["source_id"], | |
| 370 | 337 | "isUnique": false |
| 371 | 338 | } |
| 372 | 339 | }, |
| 373 | 340 | "foreignKeys": {}, |
| 374 | 341 | "compositePrimaryKeys": { |
| 375 | 342 | "graphql_operation_scope_id_id_pk": { |
| 376 | − "columns": [ | |
| 377 | − "scope_id", | |
| 378 | − "id" | |
| 379 | − ], | |
| 343 | + "columns": ["scope_id", "id"], | |
| 380 | 344 | "name": "graphql_operation_scope_id_id_pk" |
| 381 | 345 | } |
| 382 | 346 | }, |
| @@ -425,19 +389,14 @@ · 42 unmodified lines | ||
| 425 | 389 | "indexes": { |
| 426 | 390 | "graphql_source_scope_id_idx": { |
| 427 | 391 | "name": "graphql_source_scope_id_idx", |
| 428 | − "columns": [ | |
| 429 | − "scope_id" | |
| 430 | − ], | |
| 392 | + "columns": ["scope_id"], | |
| 431 | 393 | "isUnique": false |
| 432 | 394 | } |
| 433 | 395 | }, |
| 434 | 396 | "foreignKeys": {}, |
| 435 | 397 | "compositePrimaryKeys": { |
| 436 | 398 | "graphql_source_scope_id_id_pk": { |
| 437 | − "columns": [ | |
| 438 | − "scope_id", | |
| 439 | − "id" | |
| 440 | − ], | |
| 399 | + "columns": ["scope_id", "id"], | |
| 441 | 400 | "name": "graphql_source_scope_id_id_pk" |
| 442 | 401 | } |
| 443 | 402 | }, |
| @@ -486,26 +445,19 @@ · 42 unmodified lines | ||
| 486 | 445 | "indexes": { |
| 487 | 446 | "mcp_binding_scope_id_idx": { |
| 488 | 447 | "name": "mcp_binding_scope_id_idx", |
| 489 | − "columns": [ | |
| 490 | − "scope_id" | |
| 491 | − ], | |
| 448 | + "columns": ["scope_id"], | |
| 492 | 449 | "isUnique": false |
| 493 | 450 | }, |
| 494 | 451 | "mcp_binding_source_id_idx": { |
| 495 | 452 | "name": "mcp_binding_source_id_idx", |
| 496 | − "columns": [ | |
| 497 | − "source_id" | |
| 498 | − ], | |
| 453 | + "columns": ["source_id"], | |
| 499 | 454 | "isUnique": false |
| 500 | 455 | } |
| 501 | 456 | }, |
| 502 | 457 | "foreignKeys": {}, |
| 503 | 458 | "compositePrimaryKeys": { |
| 504 | 459 | "mcp_binding_scope_id_id_pk": { |
| 505 | − "columns": [ | |
| 506 | − "scope_id", | |
| 507 | − "id" | |
| 508 | − ], | |
| 460 | + "columns": ["scope_id", "id"], | |
| 509 | 461 | "name": "mcp_binding_scope_id_id_pk" |
| 510 | 462 | } |
| 511 | 463 | }, |
| @@ -554,19 +506,14 @@ · 42 unmodified lines | ||
| 554 | 506 | "indexes": { |
| 555 | 507 | "mcp_oauth_session_scope_id_idx": { |
| 556 | 508 | "name": "mcp_oauth_session_scope_id_idx", |
| 557 | − "columns": [ | |
| 558 | − "scope_id" | |
| 559 | − ], | |
| 509 | + "columns": ["scope_id"], | |
| 560 | 510 | "isUnique": false |
| 561 | 511 | } |
| 562 | 512 | }, |
| 563 | 513 | "foreignKeys": {}, |
| 564 | 514 | "compositePrimaryKeys": { |
| 565 | 515 | "mcp_oauth_session_scope_id_id_pk": { |
| 566 | − "columns": [ | |
| 567 | − "scope_id", | |
| 568 | − "id" | |
| 569 | − ], | |
| 516 | + "columns": ["scope_id", "id"], | |
| 570 | 517 | "name": "mcp_oauth_session_scope_id_id_pk" |
| 571 | 518 | } |
| 572 | 519 | }, |
| @@ -615,19 +562,14 @@ · 42 unmodified lines | ||
| 615 | 562 | "indexes": { |
| 616 | 563 | "mcp_source_scope_id_idx": { |
| 617 | 564 | "name": "mcp_source_scope_id_idx", |
| 618 | − "columns": [ | |
| 619 | − "scope_id" | |
| 620 | − ], | |
| 565 | + "columns": ["scope_id"], | |
| 621 | 566 | "isUnique": false |
| 622 | 567 | } |
| 623 | 568 | }, |
| 624 | 569 | "foreignKeys": {}, |
| 625 | 570 | "compositePrimaryKeys": { |
| 626 | 571 | "mcp_source_scope_id_id_pk": { |
| 627 | − "columns": [ | |
| 628 | − "scope_id", | |
| 629 | − "id" | |
| 630 | − ], | |
| 572 | + "columns": ["scope_id", "id"], | |
| 631 | 573 | "name": "mcp_source_scope_id_id_pk" |
| 632 | 574 | } |
| 633 | 575 | }, |
| @@ -669,19 +611,14 @@ · 35 unmodified lines | ||
| 669 | 611 | "indexes": { |
| 670 | 612 | "openapi_oauth_session_scope_id_idx": { |
| 671 | 613 | "name": "openapi_oauth_session_scope_id_idx", |
| 672 | − "columns": [ | |
| 673 | − "scope_id" | |
| 674 | − ], | |
| 614 | + "columns": ["scope_id"], | |
| 675 | 615 | "isUnique": false |
| 676 | 616 | } |
| 677 | 617 | }, |
| 678 | 618 | "foreignKeys": {}, |
| 679 | 619 | "compositePrimaryKeys": { |
| 680 | 620 | "openapi_oauth_session_scope_id_id_pk": { |
| 681 | − "columns": [ | |
| 682 | − "scope_id", | |
| 683 | − "id" | |
| 684 | − ], | |
| 621 | + "columns": ["scope_id", "id"], | |
| 685 | 622 | "name": "openapi_oauth_session_scope_id_id_pk" |
| 686 | 623 | } |
| 687 | 624 | }, |
| @@ -723,26 +660,19 @@ · 35 unmodified lines | ||
| 723 | 660 | "indexes": { |
| 724 | 661 | "openapi_operation_scope_id_idx": { |
| 725 | 662 | "name": "openapi_operation_scope_id_idx", |
| 726 | − "columns": [ | |
| 727 | − "scope_id" | |
| 728 | − ], | |
| 663 | + "columns": ["scope_id"], | |
| 729 | 664 | "isUnique": false |
| 730 | 665 | }, |
| 731 | 666 | "openapi_operation_source_id_idx": { |
| 732 | 667 | "name": "openapi_operation_source_id_idx", |
| 733 | − "columns": [ | |
| 734 | − "source_id" | |
| 735 | − ], | |
| 668 | + "columns": ["source_id"], | |
| 736 | 669 | "isUnique": false |
| 737 | 670 | } |
| 738 | 671 | }, |
| 739 | 672 | "foreignKeys": {}, |
| 740 | 673 | "compositePrimaryKeys": { |
| 741 | 674 | "openapi_operation_scope_id_id_pk": { |
| 742 | − "columns": [ | |
| 743 | − "scope_id", | |
| 744 | − "id" | |
| 745 | − ], | |
| 675 | + "columns": ["scope_id", "id"], | |
| 746 | 676 | "name": "openapi_operation_scope_id_id_pk" |
| 747 | 677 | } |
| 748 | 678 | }, |
| @@ -819,19 +749,14 @@ · 70 unmodified lines | ||
| 819 | 749 | "indexes": { |
| 820 | 750 | "openapi_source_scope_id_idx": { |
| 821 | 751 | "name": "openapi_source_scope_id_idx", |
| 822 | − "columns": [ | |
| 823 | − "scope_id" | |
| 824 | − ], | |
| 752 | + "columns": ["scope_id"], | |
| 825 | 753 | "isUnique": false |
| 826 | 754 | } |
| 827 | 755 | }, |
| 828 | 756 | "foreignKeys": {}, |
| 829 | 757 | "compositePrimaryKeys": { |
| 830 | 758 | "openapi_source_scope_id_id_pk": { |
| 831 | − "columns": [ | |
| 832 | − "scope_id", | |
| 833 | − "id" | |
| 834 | − ], | |
| 759 | + "columns": ["scope_id", "id"], | |
| 835 | 760 | "name": "openapi_source_scope_id_id_pk" |
| 836 | 761 | } |
| 837 | 762 | }, |
| @@ -880,26 +805,19 @@ · 42 unmodified lines | ||
| 880 | 805 | "indexes": { |
| 881 | 806 | "secret_scope_id_idx": { |
| 882 | 807 | "name": "secret_scope_id_idx", |
| 883 | − "columns": [ | |
| 884 | − "scope_id" | |
| 885 | − ], | |
| 808 | + "columns": ["scope_id"], | |
| 886 | 809 | "isUnique": false |
| 887 | 810 | }, |
| 888 | 811 | "secret_provider_idx": { |
| 889 | 812 | "name": "secret_provider_idx", |
| 890 | − "columns": [ | |
| 891 | − "provider" | |
| 892 | − ], | |
| 813 | + "columns": ["provider"], | |
| 893 | 814 | "isUnique": false |
| 894 | 815 | } |
| 895 | 816 | }, |
| 896 | 817 | "foreignKeys": {}, |
| 897 | 818 | "compositePrimaryKeys": { |
| 898 | 819 | "secret_scope_id_id_pk": { |
| 899 | − "columns": [ | |
| 900 | − "scope_id", | |
| 901 | − "id" | |
| 902 | − ], | |
| 820 | + "columns": ["scope_id", "id"], | |
| 903 | 821 | "name": "secret_scope_id_id_pk" |
| 904 | 822 | } |
| 905 | 823 | }, |
| @@ -993,26 +911,19 @@ · 87 unmodified lines | ||
| 993 | 911 | "indexes": { |
| 994 | 912 | "source_scope_id_idx": { |
| 995 | 913 | "name": "source_scope_id_idx", |
| 996 | − "columns": [ | |
| 997 | − "scope_id" | |
| 998 | − ], | |
| 914 | + "columns": ["scope_id"], | |
| 999 | 915 | "isUnique": false |
| 1000 | 916 | }, |
| 1001 | 917 | "source_plugin_id_idx": { |
| 1002 | 918 | "name": "source_plugin_id_idx", |
| 1003 | − "columns": [ | |
| 1004 | − "plugin_id" | |
| 1005 | − ], | |
| 919 | + "columns": ["plugin_id"], | |
| 1006 | 920 | "isUnique": false |
| 1007 | 921 | } |
| 1008 | 922 | }, |
| 1009 | 923 | "foreignKeys": {}, |
| 1010 | 924 | "compositePrimaryKeys": { |
| 1011 | 925 | "source_scope_id_id_pk": { |
| 1012 | − "columns": [ | |
| 1013 | − "scope_id", | |
| 1014 | − "id" | |
| 1015 | − ], | |
| 926 | + "columns": ["scope_id", "id"], | |
| 1016 | 927 | "name": "source_scope_id_id_pk" |
| 1017 | 928 | } |
| 1018 | 929 | }, |
| @@ -1096,33 +1007,24 @@ · 77 unmodified lines | ||
| 1096 | 1007 | "indexes": { |
| 1097 | 1008 | "tool_scope_id_idx": { |
| 1098 | 1009 | "name": "tool_scope_id_idx", |
| 1099 | − "columns": [ | |
| 1100 | − "scope_id" | |
| 1101 | − ], | |
| 1010 | + "columns": ["scope_id"], | |
| 1102 | 1011 | "isUnique": false |
| 1103 | 1012 | }, |
| 1104 | 1013 | "tool_source_id_idx": { |
| 1105 | 1014 | "name": "tool_source_id_idx", |
| 1106 | − "columns": [ | |
| 1107 | − "source_id" | |
| 1108 | − ], | |
| 1015 | + "columns": ["source_id"], | |
| 1109 | 1016 | "isUnique": false |
| 1110 | 1017 | }, |
| 1111 | 1018 | "tool_plugin_id_idx": { |
| 1112 | 1019 | "name": "tool_plugin_id_idx", |
| 1113 | − "columns": [ | |
| 1114 | − "plugin_id" | |
| 1115 | − ], | |
| 1020 | + "columns": ["plugin_id"], | |
| 1116 | 1021 | "isUnique": false |
| 1117 | 1022 | } |
| 1118 | 1023 | }, |
| 1119 | 1024 | "foreignKeys": {}, |
| 1120 | 1025 | "compositePrimaryKeys": { |
| 1121 | 1026 | "tool_scope_id_id_pk": { |
| 1122 | − "columns": [ | |
| 1123 | − "scope_id", | |
| 1124 | − "id" | |
| 1125 | − ], | |
| 1027 | + "columns": ["scope_id", "id"], | |
| 1126 | 1028 | "name": "tool_scope_id_id_pk" |
| 1127 | 1029 | } |
| 1128 | 1030 | }, |
| @@ -1140,4 +1042,4 @@ · 11 unmodified lines | ||
| 1140 | 1042 | "internal": { |
| 1141 | 1043 | "indexes": {} |
| 1142 | 1044 | } |
| 1143 | −} | |
| 1045 | +} | |
| @@ -33,10 +33,7 @@ · 32 unmodified lines | ||
| 33 | 33 | "foreignKeys": {}, |
| 34 | 34 | "compositePrimaryKeys": { |
| 35 | 35 | "blob_namespace_key_pk": { |
| 36 | − "columns": [ | |
| 37 | − "namespace", | |
| 38 | − "key" | |
| 39 | − ], | |
| 36 | + "columns": ["namespace", "key"], | |
| 40 | 37 | "name": "blob_namespace_key_pk" |
| 41 | 38 | } |
| 42 | 39 | }, |
| @@ -134,26 +131,19 @@ · 91 unmodified lines | ||
| 134 | 131 | "indexes": { |
| 135 | 132 | "connection_scope_id_idx": { |
| 136 | 133 | "name": "connection_scope_id_idx", |
| 137 | − "columns": [ | |
| 138 | − "scope_id" | |
| 139 | − ], | |
| 134 | + "columns": ["scope_id"], | |
| 140 | 135 | "isUnique": false |
| 141 | 136 | }, |
| 142 | 137 | "connection_provider_idx": { |
| 143 | 138 | "name": "connection_provider_idx", |
| 144 | − "columns": [ | |
| 145 | − "provider" | |
| 146 | − ], | |
| 139 | + "columns": ["provider"], | |
| 147 | 140 | "isUnique": false |
| 148 | 141 | } |
| 149 | 142 | }, |
| 150 | 143 | "foreignKeys": {}, |
| 151 | 144 | "compositePrimaryKeys": { |
| 152 | 145 | "connection_scope_id_id_pk": { |
| 153 | − "columns": [ | |
| 154 | − "scope_id", | |
| 155 | − "id" | |
| 156 | − ], | |
| 146 | + "columns": ["scope_id", "id"], | |
| 157 | 147 | "name": "connection_scope_id_id_pk" |
| 158 | 148 | } |
| 159 | 149 | }, |
| @@ -216,33 +206,24 @@ · 56 unmodified lines | ||
| 216 | 206 | "indexes": { |
| 217 | 207 | "definition_scope_id_idx": { |
| 218 | 208 | "name": "definition_scope_id_idx", |
| 219 | − "columns": [ | |
| 220 | − "scope_id" | |
| 221 | − ], | |
| 209 | + "columns": ["scope_id"], | |
| 222 | 210 | "isUnique": false |
| 223 | 211 | }, |
| 224 | 212 | "definition_source_id_idx": { |
| 225 | 213 | "name": "definition_source_id_idx", |
| 226 | − "columns": [ | |
| 227 | − "source_id" | |
| 228 | − ], | |
| 214 | + "columns": ["source_id"], | |
| 229 | 215 | "isUnique": false |
| 230 | 216 | }, |
| 231 | 217 | "definition_plugin_id_idx": { |
| 232 | 218 | "name": "definition_plugin_id_idx", |
| 233 | − "columns": [ | |
| 234 | − "plugin_id" | |
| 235 | − ], | |
| 219 | + "columns": ["plugin_id"], | |
| 236 | 220 | "isUnique": false |
| 237 | 221 | } |
| 238 | 222 | }, |
| 239 | 223 | "foreignKeys": {}, |
| 240 | 224 | "compositePrimaryKeys": { |
| 241 | 225 | "definition_scope_id_id_pk": { |
| 242 | − "columns": [ | |
| 243 | − "scope_id", | |
| 244 | − "id" | |
| 245 | − ], | |
| 226 | + "columns": ["scope_id", "id"], | |
| 246 | 227 | "name": "definition_scope_id_id_pk" |
| 247 | 228 | } |
| 248 | 229 | }, |
| @@ -291,26 +272,19 @@ · 42 unmodified lines | ||
| 291 | 272 | "indexes": { |
| 292 | 273 | "google_discovery_binding_scope_id_idx": { |
| 293 | 274 | "name": "google_discovery_binding_scope_id_idx", |
| 294 | − "columns": [ | |
| 295 | − "scope_id" | |
| 296 | − ], | |
| 275 | + "columns": ["scope_id"], | |
| 297 | 276 | "isUnique": false |
| 298 | 277 | }, |
| 299 | 278 | "google_discovery_binding_source_id_idx": { |
| 300 | 279 | "name": "google_discovery_binding_source_id_idx", |
| 301 | − "columns": [ | |
| 302 | − "source_id" | |
| 303 | − ], | |
| 280 | + "columns": ["source_id"], | |
| 304 | 281 | "isUnique": false |
| 305 | 282 | } |
| 306 | 283 | }, |
| 307 | 284 | "foreignKeys": {}, |
| 308 | 285 | "compositePrimaryKeys": { |
| 309 | 286 | "google_discovery_binding_scope_id_id_pk": { |
| 310 | − "columns": [ | |
| 311 | − "scope_id", | |
| 312 | − "id" | |
| 313 | − ], | |
| 287 | + "columns": ["scope_id", "id"], | |
| 314 | 288 | "name": "google_discovery_binding_scope_id_id_pk" |
| 315 | 289 | } |
| 316 | 290 | }, |
| @@ -352,19 +326,14 @@ · 35 unmodified lines | ||
| 352 | 326 | "indexes": { |
| 353 | 327 | "google_discovery_oauth_session_scope_id_idx": { |
| 354 | 328 | "name": "google_discovery_oauth_session_scope_id_idx", |
| 355 | − "columns": [ | |
| 356 | − "scope_id" | |
| 357 | − ], | |
| 329 | + "columns": ["scope_id"], | |
| 358 | 330 | "isUnique": false |
| 359 | 331 | } |
| 360 | 332 | }, |
| 361 | 333 | "foreignKeys": {}, |
| 362 | 334 | "compositePrimaryKeys": { |
| 363 | 335 | "google_discovery_oauth_session_scope_id_id_pk": { |
| 364 | − "columns": [ | |
| 365 | − "scope_id", | |
| 366 | − "id" | |
| 367 | − ], | |
| 336 | + "columns": ["scope_id", "id"], | |
| 368 | 337 | "name": "google_discovery_oauth_session_scope_id_id_pk" |
| 369 | 338 | } |
| 370 | 339 | }, |
| @@ -420,19 +389,14 @@ · 49 unmodified lines | ||
| 420 | 389 | "indexes": { |
| 421 | 390 | "google_discovery_source_scope_id_idx": { |
| 422 | 391 | "name": "google_discovery_source_scope_id_idx", |
| 423 | − "columns": [ | |
| 424 | − "scope_id" | |
| 425 | − ], | |
| 392 | + "columns": ["scope_id"], | |
| 426 | 393 | "isUnique": false |
| 427 | 394 | } |
| 428 | 395 | }, |
| 429 | 396 | "foreignKeys": {}, |
| 430 | 397 | "compositePrimaryKeys": { |
| 431 | 398 | "google_discovery_source_scope_id_id_pk": { |
| 432 | − "columns": [ | |
| 433 | − "scope_id", | |
| 434 | − "id" | |
| 435 | − ], | |
| 399 | + "columns": ["scope_id", "id"], | |
| 436 | 400 | "name": "google_discovery_source_scope_id_id_pk" |
| 437 | 401 | } |
| 438 | 402 | }, |
| @@ -474,26 +438,19 @@ · 35 unmodified lines | ||
| 474 | 438 | "indexes": { |
| 475 | 439 | "graphql_operation_scope_id_idx": { |
| 476 | 440 | "name": "graphql_operation_scope_id_idx", |
| 477 | − "columns": [ | |
| 478 | − "scope_id" | |
| 479 | − ], | |
| 441 | + "columns": ["scope_id"], | |
| 480 | 442 | "isUnique": false |
| 481 | 443 | }, |
| 482 | 444 | "graphql_operation_source_id_idx": { |
| 483 | 445 | "name": "graphql_operation_source_id_idx", |
| 484 | − "columns": [ | |
| 485 | − "source_id" | |
| 486 | − ], | |
| 446 | + "columns": ["source_id"], | |
| 487 | 447 | "isUnique": false |
| 488 | 448 | } |
| 489 | 449 | }, |
| 490 | 450 | "foreignKeys": {}, |
| 491 | 451 | "compositePrimaryKeys": { |
| 492 | 452 | "graphql_operation_scope_id_id_pk": { |
| 493 | − "columns": [ | |
| 494 | − "scope_id", | |
| 495 | − "id" | |
| 496 | − ], | |
| 453 | + "columns": ["scope_id", "id"], | |
| 497 | 454 | "name": "graphql_operation_scope_id_id_pk" |
| 498 | 455 | } |
| 499 | 456 | }, |
| @@ -542,19 +499,14 @@ · 42 unmodified lines | ||
| 542 | 499 | "indexes": { |
| 543 | 500 | "graphql_source_scope_id_idx": { |
| 544 | 501 | "name": "graphql_source_scope_id_idx", |
| 545 | − "columns": [ | |
| 546 | − "scope_id" | |
| 547 | − ], | |
| 502 | + "columns": ["scope_id"], | |
| 548 | 503 | "isUnique": false |
| 549 | 504 | } |
| 550 | 505 | }, |
| 551 | 506 | "foreignKeys": {}, |
| 552 | 507 | "compositePrimaryKeys": { |
| 553 | 508 | "graphql_source_scope_id_id_pk": { |
| 554 | − "columns": [ | |
| 555 | − "scope_id", | |
| 556 | − "id" | |
| 557 | − ], | |
| 509 | + "columns": ["scope_id", "id"], | |
| 558 | 510 | "name": "graphql_source_scope_id_id_pk" |
| 559 | 511 | } |
| 560 | 512 | }, |
| @@ -603,26 +555,19 @@ · 42 unmodified lines | ||
| 603 | 555 | "indexes": { |
| 604 | 556 | "mcp_binding_scope_id_idx": { |
| 605 | 557 | "name": "mcp_binding_scope_id_idx", |
| 606 | − "columns": [ | |
| 607 | − "scope_id" | |
| 608 | − ], | |
| 558 | + "columns": ["scope_id"], | |
| 609 | 559 | "isUnique": false |
| 610 | 560 | }, |
| 611 | 561 | "mcp_binding_source_id_idx": { |
| 612 | 562 | "name": "mcp_binding_source_id_idx", |
| 613 | − "columns": [ | |
| 614 | − "source_id" | |
| 615 | − ], | |
| 563 | + "columns": ["source_id"], | |
| 616 | 564 | "isUnique": false |
| 617 | 565 | } |
| 618 | 566 | }, |
| 619 | 567 | "foreignKeys": {}, |
| 620 | 568 | "compositePrimaryKeys": { |
| 621 | 569 | "mcp_binding_scope_id_id_pk": { |
| 622 | − "columns": [ | |
| 623 | − "scope_id", | |
| 624 | − "id" | |
| 625 | − ], | |
| 570 | + "columns": ["scope_id", "id"], | |
| 626 | 571 | "name": "mcp_binding_scope_id_id_pk" |
| 627 | 572 | } |
| 628 | 573 | }, |
| @@ -671,19 +616,14 @@ · 42 unmodified lines | ||
| 671 | 616 | "indexes": { |
| 672 | 617 | "mcp_oauth_session_scope_id_idx": { |
| 673 | 618 | "name": "mcp_oauth_session_scope_id_idx", |
| 674 | − "columns": [ | |
| 675 | − "scope_id" | |
| 676 | − ], | |
| 619 | + "columns": ["scope_id"], | |
| 677 | 620 | "isUnique": false |
| 678 | 621 | } |
| 679 | 622 | }, |
| 680 | 623 | "foreignKeys": {}, |
| 681 | 624 | "compositePrimaryKeys": { |
| 682 | 625 | "mcp_oauth_session_scope_id_id_pk": { |
| 683 | − "columns": [ | |
| 684 | − "scope_id", | |
| 685 | − "id" | |
| 686 | − ], | |
| 626 | + "columns": ["scope_id", "id"], | |
| 687 | 627 | "name": "mcp_oauth_session_scope_id_id_pk" |
| 688 | 628 | } |
| 689 | 629 | }, |
| @@ -732,19 +672,14 @@ · 42 unmodified lines | ||
| 732 | 672 | "indexes": { |
| 733 | 673 | "mcp_source_scope_id_idx": { |
| 734 | 674 | "name": "mcp_source_scope_id_idx", |
| 735 | − "columns": [ | |
| 736 | − "scope_id" | |
| 737 | − ], | |
| 675 | + "columns": ["scope_id"], | |
| 738 | 676 | "isUnique": false |
| 739 | 677 | } |
| 740 | 678 | }, |
| 741 | 679 | "foreignKeys": {}, |
| 742 | 680 | "compositePrimaryKeys": { |
| 743 | 681 | "mcp_source_scope_id_id_pk": { |
| 744 | − "columns": [ | |
| 745 | − "scope_id", | |
| 746 | − "id" | |
| 747 | − ], | |
| 682 | + "columns": ["scope_id", "id"], | |
| 748 | 683 | "name": "mcp_source_scope_id_id_pk" |
| 749 | 684 | } |
| 750 | 685 | }, |
| @@ -786,19 +721,14 @@ · 35 unmodified lines | ||
| 786 | 721 | "indexes": { |
| 787 | 722 | "openapi_oauth_session_scope_id_idx": { |
| 788 | 723 | "name": "openapi_oauth_session_scope_id_idx", |
| 789 | − "columns": [ | |
| 790 | − "scope_id" | |
| 791 | − ], | |
| 724 | + "columns": ["scope_id"], | |
| 792 | 725 | "isUnique": false |
| 793 | 726 | } |
| 794 | 727 | }, |
| 795 | 728 | "foreignKeys": {}, |
| 796 | 729 | "compositePrimaryKeys": { |
| 797 | 730 | "openapi_oauth_session_scope_id_id_pk": { |
| 798 | − "columns": [ | |
| 799 | − "scope_id", | |
| 800 | − "id" | |
| 801 | − ], | |
| 731 | + "columns": ["scope_id", "id"], | |
| 802 | 732 | "name": "openapi_oauth_session_scope_id_id_pk" |
| 803 | 733 | } |
| 804 | 734 | }, |
| @@ -840,26 +770,19 @@ · 35 unmodified lines | ||
| 840 | 770 | "indexes": { |
| 841 | 771 | "openapi_operation_scope_id_idx": { |
| 842 | 772 | "name": "openapi_operation_scope_id_idx", |
| 843 | − "columns": [ | |
| 844 | − "scope_id" | |
| 845 | − ], | |
| 773 | + "columns": ["scope_id"], | |
| 846 | 774 | "isUnique": false |
| 847 | 775 | }, |
| 848 | 776 | "openapi_operation_source_id_idx": { |
| 849 | 777 | "name": "openapi_operation_source_id_idx", |
| 850 | − "columns": [ | |
| 851 | − "source_id" | |
| 852 | − ], | |
| 778 | + "columns": ["source_id"], | |
| 853 | 779 | "isUnique": false |
| 854 | 780 | } |
| 855 | 781 | }, |
| 856 | 782 | "foreignKeys": {}, |
| 857 | 783 | "compositePrimaryKeys": { |
| 858 | 784 | "openapi_operation_scope_id_id_pk": { |
| 859 | − "columns": [ | |
| 860 | − "scope_id", | |
| 861 | − "id" | |
| 862 | − ], | |
| 785 | + "columns": ["scope_id", "id"], | |
| 863 | 786 | "name": "openapi_operation_scope_id_id_pk" |
| 864 | 787 | } |
| 865 | 788 | }, |
| @@ -936,19 +859,14 @@ · 70 unmodified lines | ||
| 936 | 859 | "indexes": { |
| 937 | 860 | "openapi_source_scope_id_idx": { |
| 938 | 861 | "name": "openapi_source_scope_id_idx", |
| 939 | − "columns": [ | |
| 940 | − "scope_id" | |
| 941 | − ], | |
| 862 | + "columns": ["scope_id"], | |
| 942 | 863 | "isUnique": false |
| 943 | 864 | } |
| 944 | 865 | }, |
| 945 | 866 | "foreignKeys": {}, |
| 946 | 867 | "compositePrimaryKeys": { |
| 947 | 868 | "openapi_source_scope_id_id_pk": { |
| 948 | − "columns": [ | |
| 949 | − "scope_id", | |
| 950 | − "id" | |
| 951 | − ], | |
| 869 | + "columns": ["scope_id", "id"], | |
| 952 | 870 | "name": "openapi_source_scope_id_id_pk" |
| 953 | 871 | } |
| 954 | 872 | }, |
| @@ -1004,33 +922,24 @@ · 49 unmodified lines | ||
| 1004 | 922 | "indexes": { |
| 1005 | 923 | "secret_scope_id_idx": { |
| 1006 | 924 | "name": "secret_scope_id_idx", |
| 1007 | − "columns": [ | |
| 1008 | − "scope_id" | |
| 1009 | − ], | |
| 925 | + "columns": ["scope_id"], | |
| 1010 | 926 | "isUnique": false |
| 1011 | 927 | }, |
| 1012 | 928 | "secret_provider_idx": { |
| 1013 | 929 | "name": "secret_provider_idx", |
| 1014 | − "columns": [ | |
| 1015 | − "provider" | |
| 1016 | − ], | |
| 930 | + "columns": ["provider"], | |
| 1017 | 931 | "isUnique": false |
| 1018 | 932 | }, |
| 1019 | 933 | "secret_owned_by_connection_id_idx": { |
| 1020 | 934 | "name": "secret_owned_by_connection_id_idx", |
| 1021 | − "columns": [ | |
| 1022 | − "owned_by_connection_id" | |
| 1023 | − ], | |
| 935 | + "columns": ["owned_by_connection_id"], | |
| 1024 | 936 | "isUnique": false |
| 1025 | 937 | } |
| 1026 | 938 | }, |
| 1027 | 939 | "foreignKeys": {}, |
| 1028 | 940 | "compositePrimaryKeys": { |
| 1029 | 941 | "secret_scope_id_id_pk": { |
| 1030 | − "columns": [ | |
| 1031 | − "scope_id", | |
| 1032 | − "id" | |
| 1033 | − ], | |
| 942 | + "columns": ["scope_id", "id"], | |
| 1034 | 943 | "name": "secret_scope_id_id_pk" |
| 1035 | 944 | } |
| 1036 | 945 | }, |
| @@ -1124,26 +1033,19 @@ · 87 unmodified lines | ||
| 1124 | 1033 | "indexes": { |
| 1125 | 1034 | "source_scope_id_idx": { |
| 1126 | 1035 | "name": "source_scope_id_idx", |
| 1127 | − "columns": [ | |
| 1128 | − "scope_id" | |
| 1129 | − ], | |
| 1036 | + "columns": ["scope_id"], | |
| 1130 | 1037 | "isUnique": false |
| 1131 | 1038 | }, |
| 1132 | 1039 | "source_plugin_id_idx": { |
| 1133 | 1040 | "name": "source_plugin_id_idx", |
| 1134 | − "columns": [ | |
| 1135 | − "plugin_id" | |
| 1136 | − ], | |
| 1041 | + "columns": ["plugin_id"], | |
| 1137 | 1042 | "isUnique": false |
| 1138 | 1043 | } |
| 1139 | 1044 | }, |
| 1140 | 1045 | "foreignKeys": {}, |
| 1141 | 1046 | "compositePrimaryKeys": { |
| 1142 | 1047 | "source_scope_id_id_pk": { |
| 1143 | − "columns": [ | |
| 1144 | − "scope_id", | |
| 1145 | − "id" | |
| 1146 | − ], | |
| 1048 | + "columns": ["scope_id", "id"], | |
| 1147 | 1049 | "name": "source_scope_id_id_pk" |
| 1148 | 1050 | } |
| 1149 | 1051 | }, |
| @@ -1227,33 +1129,24 @@ · 77 unmodified lines | ||
| 1227 | 1129 | "indexes": { |
| 1228 | 1130 | "tool_scope_id_idx": { |
| 1229 | 1131 | "name": "tool_scope_id_idx", |
| 1230 | − "columns": [ | |
| 1231 | − "scope_id" | |
| 1232 | − ], | |
| 1132 | + "columns": ["scope_id"], | |
| 1233 | 1133 | "isUnique": false |
| 1234 | 1134 | }, |
| 1235 | 1135 | "tool_source_id_idx": { |
| 1236 | 1136 | "name": "tool_source_id_idx", |
| 1237 | − "columns": [ | |
| 1238 | − "source_id" | |
| 1239 | − ], | |
| 1137 | + "columns": ["source_id"], | |
| 1240 | 1138 | "isUnique": false |
| 1241 | 1139 | }, |
| 1242 | 1140 | "tool_plugin_id_idx": { |
| 1243 | 1141 | "name": "tool_plugin_id_idx", |
| 1244 | − "columns": [ | |
| 1245 | − "plugin_id" | |
| 1246 | − ], | |
| 1142 | + "columns": ["plugin_id"], | |
| 1247 | 1143 | "isUnique": false |
| 1248 | 1144 | } |
| 1249 | 1145 | }, |
| 1250 | 1146 | "foreignKeys": {}, |
| 1251 | 1147 | "compositePrimaryKeys": { |
| 1252 | 1148 | "tool_scope_id_id_pk": { |
| 1253 | − "columns": [ | |
| 1254 | − "scope_id", | |
| 1255 | − "id" | |
| 1256 | − ], | |
| 1149 | + "columns": ["scope_id", "id"], | |
| 1257 | 1150 | "name": "tool_scope_id_id_pk" |
| 1258 | 1151 | } |
| 1259 | 1152 | }, |
| @@ -1271,4 +1164,4 @@ · 11 unmodified lines | ||
| 1271 | 1164 | "internal": { |
| 1272 | 1165 | "indexes": {} |
| 1273 | 1166 | } |
| 1274 | −} | |
| 1167 | +} | |
| @@ -88,26 +88,19 @@ · 87 unmodified lines | ||
| 88 | 88 | "indexes": { |
| 89 | 89 | "connection_scope_id_idx": { |
| 90 | 90 | "name": "connection_scope_id_idx", |
| 91 | − "columns": [ | |
| 92 | − "scope_id" | |
| 93 | − ], | |
| 91 | + "columns": ["scope_id"], | |
| 94 | 92 | "isUnique": false |
| 95 | 93 | }, |
| 96 | 94 | "connection_provider_idx": { |
| 97 | 95 | "name": "connection_provider_idx", |
| 98 | − "columns": [ | |
| 99 | − "provider" | |
| 100 | − ], | |
| 96 | + "columns": ["provider"], | |
| 101 | 97 | "isUnique": false |
| 102 | 98 | } |
| 103 | 99 | }, |
| 104 | 100 | "foreignKeys": {}, |
| 105 | 101 | "compositePrimaryKeys": { |
| 106 | 102 | "connection_scope_id_id_pk": { |
| 107 | − "columns": [ | |
| 108 | − "scope_id", | |
| 109 | − "id" | |
| 110 | − ], | |
| 103 | + "columns": ["scope_id", "id"], | |
| 111 | 104 | "name": "connection_scope_id_id_pk" |
| 112 | 105 | } |
| 113 | 106 | }, |
| @@ -170,33 +163,24 @@ · 56 unmodified lines | ||
| 170 | 163 | "indexes": { |
| 171 | 164 | "definition_scope_id_idx": { |
| 172 | 165 | "name": "definition_scope_id_idx", |
| 173 | − "columns": [ | |
| 174 | − "scope_id" | |
| 175 | − ], | |
| 166 | + "columns": ["scope_id"], | |
| 176 | 167 | "isUnique": false |
| 177 | 168 | }, |
| 178 | 169 | "definition_source_id_idx": { |
| 179 | 170 | "name": "definition_source_id_idx", |
| 180 | − "columns": [ | |
| 181 | − "source_id" | |
| 182 | − ], | |
| 171 | + "columns": ["source_id"], | |
| 183 | 172 | "isUnique": false |
| 184 | 173 | }, |
| 185 | 174 | "definition_plugin_id_idx": { |
| 186 | 175 | "name": "definition_plugin_id_idx", |
| 187 | − "columns": [ | |
| 188 | − "plugin_id" | |
| 189 | − ], | |
| 176 | + "columns": ["plugin_id"], | |
| 190 | 177 | "isUnique": false |
| 191 | 178 | } |
| 192 | 179 | }, |
| 193 | 180 | "foreignKeys": {}, |
| 194 | 181 | "compositePrimaryKeys": { |
| 195 | 182 | "definition_scope_id_id_pk": { |
| 196 | − "columns": [ | |
| 197 | − "scope_id", | |
| 198 | − "id" | |
| 199 | − ], | |
| 183 | + "columns": ["scope_id", "id"], | |
| 200 | 184 | "name": "definition_scope_id_id_pk" |
| 201 | 185 | } |
| 202 | 186 | }, |
| @@ -245,26 +229,19 @@ · 42 unmodified lines | ||
| 245 | 229 | "indexes": { |
| 246 | 230 | "google_discovery_binding_scope_id_idx": { |
| 247 | 231 | "name": "google_discovery_binding_scope_id_idx", |
| 248 | − "columns": [ | |
| 249 | − "scope_id" | |
| 250 | − ], | |
| 232 | + "columns": ["scope_id"], | |
| 251 | 233 | "isUnique": false |
| 252 | 234 | }, |
| 253 | 235 | "google_discovery_binding_source_id_idx": { |
| 254 | 236 | "name": "google_discovery_binding_source_id_idx", |
| 255 | − "columns": [ | |
| 256 | − "source_id" | |
| 257 | − ], | |
| 237 | + "columns": ["source_id"], | |
| 258 | 238 | "isUnique": false |
| 259 | 239 | } |
| 260 | 240 | }, |
| 261 | 241 | "foreignKeys": {}, |
| 262 | 242 | "compositePrimaryKeys": { |
| 263 | 243 | "google_discovery_binding_scope_id_id_pk": { |
| 264 | − "columns": [ | |
| 265 | − "scope_id", | |
| 266 | − "id" | |
| 267 | − ], | |
| 244 | + "columns": ["scope_id", "id"], | |
| 268 | 245 | "name": "google_discovery_binding_scope_id_id_pk" |
| 269 | 246 | } |
| 270 | 247 | }, |
| @@ -306,19 +283,14 @@ · 35 unmodified lines | ||
| 306 | 283 | "indexes": { |
| 307 | 284 | "google_discovery_oauth_session_scope_id_idx": { |
| 308 | 285 | "name": "google_discovery_oauth_session_scope_id_idx", |
| 309 | − "columns": [ | |
| 310 | − "scope_id" | |
| 311 | − ], | |
| 286 | + "columns": ["scope_id"], | |
| 312 | 287 | "isUnique": false |
| 313 | 288 | } |
| 314 | 289 | }, |
| 315 | 290 | "foreignKeys": {}, |
| 316 | 291 | "compositePrimaryKeys": { |
| 317 | 292 | "google_discovery_oauth_session_scope_id_id_pk": { |
| 318 | − "columns": [ | |
| 319 | − "scope_id", | |
| 320 | − "id" | |
| 321 | − ], | |
| 293 | + "columns": ["scope_id", "id"], | |
| 322 | 294 | "name": "google_discovery_oauth_session_scope_id_id_pk" |
| 323 | 295 | } |
| 324 | 296 | }, |
| @@ -374,19 +346,14 @@ · 49 unmodified lines | ||
| 374 | 346 | "indexes": { |
| 375 | 347 | "google_discovery_source_scope_id_idx": { |
| 376 | 348 | "name": "google_discovery_source_scope_id_idx", |
| 377 | − "columns": [ | |
| 378 | − "scope_id" | |
| 379 | − ], | |
| 349 | + "columns": ["scope_id"], | |
| 380 | 350 | "isUnique": false |
| 381 | 351 | } |
| 382 | 352 | }, |
| 383 | 353 | "foreignKeys": {}, |
| 384 | 354 | "compositePrimaryKeys": { |
| 385 | 355 | "google_discovery_source_scope_id_id_pk": { |
| 386 | − "columns": [ | |
| 387 | − "scope_id", | |
| 388 | − "id" | |
| 389 | − ], | |
| 356 | + "columns": ["scope_id", "id"], | |
| 390 | 357 | "name": "google_discovery_source_scope_id_id_pk" |
| 391 | 358 | } |
| 392 | 359 | }, |
| @@ -428,26 +395,19 @@ · 35 unmodified lines | ||
| 428 | 395 | "indexes": { |
| 429 | 396 | "graphql_operation_scope_id_idx": { |
| 430 | 397 | "name": "graphql_operation_scope_id_idx", |
| 431 | − "columns": [ | |
| 432 | − "scope_id" | |
| 433 | − ], | |
| 398 | + "columns": ["scope_id"], | |
| 434 | 399 | "isUnique": false |
| 435 | 400 | }, |
| 436 | 401 | "graphql_operation_source_id_idx": { |
| 437 | 402 | "name": "graphql_operation_source_id_idx", |
| 438 | − "columns": [ | |
| 439 | − "source_id" | |
| 440 | − ], | |
| 403 | + "columns": ["source_id"], | |
| 441 | 404 | "isUnique": false |
| 442 | 405 | } |
| 443 | 406 | }, |
| 444 | 407 | "foreignKeys": {}, |
| 445 | 408 | "compositePrimaryKeys": { |
| 446 | 409 | "graphql_operation_scope_id_id_pk": { |
| 447 | − "columns": [ | |
| 448 | − "scope_id", | |
| 449 | − "id" | |
| 450 | − ], | |
| 410 | + "columns": ["scope_id", "id"], | |
| 451 | 411 | "name": "graphql_operation_scope_id_id_pk" |
| 452 | 412 | } |
| 453 | 413 | }, |
| @@ -496,19 +456,14 @@ · 42 unmodified lines | ||
| 496 | 456 | "indexes": { |
| 497 | 457 | "graphql_source_scope_id_idx": { |
| 498 | 458 | "name": "graphql_source_scope_id_idx", |
| 499 | − "columns": [ | |
| 500 | − "scope_id" | |
| 501 | − ], | |
| 459 | + "columns": ["scope_id"], | |
| 502 | 460 | "isUnique": false |
| 503 | 461 | } |
| 504 | 462 | }, |
| 505 | 463 | "foreignKeys": {}, |
| 506 | 464 | "compositePrimaryKeys": { |
| 507 | 465 | "graphql_source_scope_id_id_pk": { |
| 508 | − "columns": [ | |
| 509 | − "scope_id", | |
| 510 | − "id" | |
| 511 | − ], | |
| 466 | + "columns": ["scope_id", "id"], | |
| 512 | 467 | "name": "graphql_source_scope_id_id_pk" |
| 513 | 468 | } |
| 514 | 469 | }, |
| @@ -557,26 +512,19 @@ · 42 unmodified lines | ||
| 557 | 512 | "indexes": { |
| 558 | 513 | "mcp_binding_scope_id_idx": { |
| 559 | 514 | "name": "mcp_binding_scope_id_idx", |
| 560 | − "columns": [ | |
| 561 | − "scope_id" | |
| 562 | − ], | |
| 515 | + "columns": ["scope_id"], | |
| 563 | 516 | "isUnique": false |
| 564 | 517 | }, |
| 565 | 518 | "mcp_binding_source_id_idx": { |
| 566 | 519 | "name": "mcp_binding_source_id_idx", |
| 567 | − "columns": [ | |
| 568 | − "source_id" | |
| 569 | − ], | |
| 520 | + "columns": ["source_id"], | |
| 570 | 521 | "isUnique": false |
| 571 | 522 | } |
| 572 | 523 | }, |
| 573 | 524 | "foreignKeys": {}, |
| 574 | 525 | "compositePrimaryKeys": { |
| 575 | 526 | "mcp_binding_scope_id_id_pk": { |
| 576 | − "columns": [ | |
| 577 | − "scope_id", | |
| 578 | − "id" | |
| 579 | − ], | |
| 527 | + "columns": ["scope_id", "id"], | |
| 580 | 528 | "name": "mcp_binding_scope_id_id_pk" |
| 581 | 529 | } |
| 582 | 530 | }, |
| @@ -625,19 +573,14 @@ · 42 unmodified lines | ||
| 625 | 573 | "indexes": { |
| 626 | 574 | "mcp_oauth_session_scope_id_idx": { |
| 627 | 575 | "name": "mcp_oauth_session_scope_id_idx", |
| 628 | − "columns": [ | |
| 629 | − "scope_id" | |
| 630 | − ], | |
| 576 | + "columns": ["scope_id"], | |
| 631 | 577 | "isUnique": false |
| 632 | 578 | } |
| 633 | 579 | }, |
| 634 | 580 | "foreignKeys": {}, |
| 635 | 581 | "compositePrimaryKeys": { |
| 636 | 582 | "mcp_oauth_session_scope_id_id_pk": { |
| 637 | − "columns": [ | |
| 638 | − "scope_id", | |
| 639 | − "id" | |
| 640 | − ], | |
| 583 | + "columns": ["scope_id", "id"], | |
| 641 | 584 | "name": "mcp_oauth_session_scope_id_id_pk" |
| 642 | 585 | } |
| 643 | 586 | }, |
| @@ -686,19 +629,14 @@ · 42 unmodified lines | ||
| 686 | 629 | "indexes": { |
| 687 | 630 | "mcp_source_scope_id_idx": { |
| 688 | 631 | "name": "mcp_source_scope_id_idx", |
| 689 | − "columns": [ | |
| 690 | − "scope_id" | |
| 691 | − ], | |
| 632 | + "columns": ["scope_id"], | |
| 692 | 633 | "isUnique": false |
| 693 | 634 | } |
| 694 | 635 | }, |
| 695 | 636 | "foreignKeys": {}, |
| 696 | 637 | "compositePrimaryKeys": { |
| 697 | 638 | "mcp_source_scope_id_id_pk": { |
| 698 | − "columns": [ | |
| 699 | − "scope_id", | |
| 700 | − "id" | |
| 701 | − ], | |
| 639 | + "columns": ["scope_id", "id"], | |
| 702 | 640 | "name": "mcp_source_scope_id_id_pk" |
| 703 | 641 | } |
| 704 | 642 | }, |
| @@ -740,19 +678,14 @@ · 35 unmodified lines | ||
| 740 | 678 | "indexes": { |
| 741 | 679 | "openapi_oauth_session_scope_id_idx": { |
| 742 | 680 | "name": "openapi_oauth_session_scope_id_idx", |
| 743 | − "columns": [ | |
| 744 | − "scope_id" | |
| 745 | − ], | |
| 681 | + "columns": ["scope_id"], | |
| 746 | 682 | "isUnique": false |
| 747 | 683 | } |
| 748 | 684 | }, |
| 749 | 685 | "foreignKeys": {}, |
| 750 | 686 | "compositePrimaryKeys": { |
| 751 | 687 | "openapi_oauth_session_scope_id_id_pk": { |
| 752 | − "columns": [ | |
| 753 | − "scope_id", | |
| 754 | − "id" | |
| 755 | − ], | |
| 688 | + "columns": ["scope_id", "id"], | |
| 756 | 689 | "name": "openapi_oauth_session_scope_id_id_pk" |
| 757 | 690 | } |
| 758 | 691 | }, |
| @@ -794,26 +727,19 @@ · 35 unmodified lines | ||
| 794 | 727 | "indexes": { |
| 795 | 728 | "openapi_operation_scope_id_idx": { |
| 796 | 729 | "name": "openapi_operation_scope_id_idx", |
| 797 | − "columns": [ | |
| 798 | − "scope_id" | |
| 799 | − ], | |
| 730 | + "columns": ["scope_id"], | |
| 800 | 731 | "isUnique": false |
| 801 | 732 | }, |
| 802 | 733 | "openapi_operation_source_id_idx": { |
| 803 | 734 | "name": "openapi_operation_source_id_idx", |
| 804 | − "columns": [ | |
| 805 | − "source_id" | |
| 806 | − ], | |
| 735 | + "columns": ["source_id"], | |
| 807 | 736 | "isUnique": false |
| 808 | 737 | } |
| 809 | 738 | }, |
| 810 | 739 | "foreignKeys": {}, |
| 811 | 740 | "compositePrimaryKeys": { |
| 812 | 741 | "openapi_operation_scope_id_id_pk": { |
| 813 | − "columns": [ | |
| 814 | − "scope_id", | |
| 815 | − "id" | |
| 816 | − ], | |
| 742 | + "columns": ["scope_id", "id"], | |
| 817 | 743 | "name": "openapi_operation_scope_id_id_pk" |
| 818 | 744 | } |
| 819 | 745 | }, |
| @@ -890,19 +816,14 @@ · 70 unmodified lines | ||
| 890 | 816 | "indexes": { |
| 891 | 817 | "openapi_source_scope_id_idx": { |
| 892 | 818 | "name": "openapi_source_scope_id_idx", |
| 893 | − "columns": [ | |
| 894 | − "scope_id" | |
| 895 | − ], | |
| 819 | + "columns": ["scope_id"], | |
| 896 | 820 | "isUnique": false |
| 897 | 821 | } |
| 898 | 822 | }, |
| 899 | 823 | "foreignKeys": {}, |
| 900 | 824 | "compositePrimaryKeys": { |
| 901 | 825 | "openapi_source_scope_id_id_pk": { |
| 902 | − "columns": [ | |
| 903 | − "scope_id", | |
| 904 | − "id" | |
| 905 | − ], | |
| 826 | + "columns": ["scope_id", "id"], | |
| 906 | 827 | "name": "openapi_source_scope_id_id_pk" |
| 907 | 828 | } |
| 908 | 829 | }, |
| @@ -972,30 +893,22 @@ · 63 unmodified lines | ||
| 972 | 893 | "indexes": { |
| 973 | 894 | "openapi_source_binding_source_id_idx": { |
| 974 | 895 | "name": "openapi_source_binding_source_id_idx", |
| 975 | − "columns": [ | |
| 976 | − "source_id" | |
| 977 | − ], | |
| 896 | + "columns": ["source_id"], | |
| 978 | 897 | "isUnique": false |
| 979 | 898 | }, |
| 980 | 899 | "openapi_source_binding_source_scope_id_idx": { |
| 981 | 900 | "name": "openapi_source_binding_source_scope_id_idx", |
| 982 | − "columns": [ | |
| 983 | − "source_scope_id" | |
| 984 | − ], | |
| 901 | + "columns": ["source_scope_id"], | |
| 985 | 902 | "isUnique": false |
| 986 | 903 | }, |
| 987 | 904 | "openapi_source_binding_target_scope_id_idx": { |
| 988 | 905 | "name": "openapi_source_binding_target_scope_id_idx", |
| 989 | − "columns": [ | |
| 990 | − "target_scope_id" | |
| 991 | − ], | |
| 906 | + "columns": ["target_scope_id"], | |
| 992 | 907 | "isUnique": false |
| 993 | 908 | }, |
| 994 | 909 | "openapi_source_binding_slot_idx": { |
| 995 | 910 | "name": "openapi_source_binding_slot_idx", |
| 996 | − "columns": [ | |
| 997 | − "slot" | |
| 998 | − ], | |
| 911 | + "columns": ["slot"], | |
| 999 | 912 | "isUnique": false |
| 1000 | 913 | } |
| 1001 | 914 | }, |
| @@ -1053,33 +966,24 @@ · 51 unmodified lines | ||
| 1053 | 966 | "indexes": { |
| 1054 | 967 | "secret_scope_id_idx": { |
| 1055 | 968 | "name": "secret_scope_id_idx", |
| 1056 | − "columns": [ | |
| 1057 | − "scope_id" | |
| 1058 | − ], | |
| 969 | + "columns": ["scope_id"], | |
| 1059 | 970 | "isUnique": false |
| 1060 | 971 | }, |
| 1061 | 972 | "secret_provider_idx": { |
| 1062 | 973 | "name": "secret_provider_idx", |
| 1063 | − "columns": [ | |
| 1064 | − "provider" | |
| 1065 | − ], | |
| 974 | + "columns": ["provider"], | |
| 1066 | 975 | "isUnique": false |
| 1067 | 976 | }, |
| 1068 | 977 | "secret_owned_by_connection_id_idx": { |
| 1069 | 978 | "name": "secret_owned_by_connection_id_idx", |
| 1070 | − "columns": [ | |
| 1071 | − "owned_by_connection_id" | |
| 1072 | − ], | |
| 979 | + "columns": ["owned_by_connection_id"], | |
| 1073 | 980 | "isUnique": false |
| 1074 | 981 | } |
| 1075 | 982 | }, |
| 1076 | 983 | "foreignKeys": {}, |
| 1077 | 984 | "compositePrimaryKeys": { |
| 1078 | 985 | "secret_scope_id_id_pk": { |
| 1079 | − "columns": [ | |
| 1080 | − "scope_id", | |
| 1081 | − "id" | |
| 1082 | − ], | |
| 986 | + "columns": ["scope_id", "id"], | |
| 1083 | 987 | "name": "secret_scope_id_id_pk" |
| 1084 | 988 | } |
| 1085 | 989 | }, |
| @@ -1173,26 +1077,19 @@ · 87 unmodified lines | ||
| 1173 | 1077 | "indexes": { |
| 1174 | 1078 | "source_scope_id_idx": { |
| 1175 | 1079 | "name": "source_scope_id_idx", |
| 1176 | − "columns": [ | |
| 1177 | − "scope_id" | |
| 1178 | − ], | |
| 1080 | + "columns": ["scope_id"], | |
| 1179 | 1081 | "isUnique": false |
| 1180 | 1082 | }, |
| 1181 | 1083 | "source_plugin_id_idx": { |
| 1182 | 1084 | "name": "source_plugin_id_idx", |
| 1183 | − "columns": [ | |
| 1184 | − "plugin_id" | |
| 1185 | − ], | |
| 1085 | + "columns": ["plugin_id"], | |
| 1186 | 1086 | "isUnique": false |
| 1187 | 1087 | } |
| 1188 | 1088 | }, |
| 1189 | 1089 | "foreignKeys": {}, |
| 1190 | 1090 | "compositePrimaryKeys": { |
| 1191 | 1091 | "source_scope_id_id_pk": { |
| 1192 | − "columns": [ | |
| 1193 | − "scope_id", | |
| 1194 | − "id" | |
| 1195 | − ], | |
| 1092 | + "columns": ["scope_id", "id"], | |
| 1196 | 1093 | "name": "source_scope_id_id_pk" |
| 1197 | 1094 | } |
| 1198 | 1095 | }, |
| @@ -1276,33 +1173,24 @@ · 77 unmodified lines | ||
| 1276 | 1173 | "indexes": { |
| 1277 | 1174 | "tool_scope_id_idx": { |
| 1278 | 1175 | "name": "tool_scope_id_idx", |
| 1279 | − "columns": [ | |
| 1280 | − "scope_id" | |
| 1281 | − ], | |
| 1176 | + "columns": ["scope_id"], | |
| 1282 | 1177 | "isUnique": false |
| 1283 | 1178 | }, |
| 1284 | 1179 | "tool_source_id_idx": { |
| 1285 | 1180 | "name": "tool_source_id_idx", |
| 1286 | − "columns": [ | |
| 1287 | − "source_id" | |
| 1288 | − ], | |
| 1181 | + "columns": ["source_id"], | |
| 1289 | 1182 | "isUnique": false |
| 1290 | 1183 | }, |
| 1291 | 1184 | "tool_plugin_id_idx": { |
| 1292 | 1185 | "name": "tool_plugin_id_idx", |
| 1293 | − "columns": [ | |
| 1294 | − "plugin_id" | |
| 1295 | − ], | |
| 1186 | + "columns": ["plugin_id"], | |
| 1296 | 1187 | "isUnique": false |
| 1297 | 1188 | } |
| 1298 | 1189 | }, |
| 1299 | 1190 | "foreignKeys": {}, |
| 1300 | 1191 | "compositePrimaryKeys": { |
| 1301 | 1192 | "tool_scope_id_id_pk": { |
| 1302 | − "columns": [ | |
| 1303 | − "scope_id", | |
| 1304 | − "id" | |
| 1305 | − ], | |
| 1193 | + "columns": ["scope_id", "id"], | |
| 1306 | 1194 | "name": "tool_scope_id_id_pk" |
| 1307 | 1195 | } |
| 1308 | 1196 | }, |
| @@ -1338,10 +1226,7 @@ · 29 unmodified lines | ||
| 1338 | 1226 | "foreignKeys": {}, |
| 1339 | 1227 | "compositePrimaryKeys": { |
| 1340 | 1228 | "blob_namespace_key_pk": { |
| 1341 | − "columns": [ | |
| 1342 | − "namespace", | |
| 1343 | − "key" | |
| 1344 | − ], | |
| 1229 | + "columns": ["namespace", "key"], | |
| 1345 | 1230 | "name": "blob_namespace_key_pk" |
| 1346 | 1231 | } |
| 1347 | 1232 | }, |
| @@ -88,26 +88,19 @@ · 87 unmodified lines | ||
| 88 | 88 | "indexes": { |
| 89 | 89 | "connection_scope_id_idx": { |
| 90 | 90 | "name": "connection_scope_id_idx", |
| 91 | − "columns": [ | |
| 92 | − "scope_id" | |
| 93 | − ], | |
| 91 | + "columns": ["scope_id"], | |
| 94 | 92 | "isUnique": false |
| 95 | 93 | }, |
| 96 | 94 | "connection_provider_idx": { |
| 97 | 95 | "name": "connection_provider_idx", |
| 98 | − "columns": [ | |
| 99 | − "provider" | |
| 100 | − ], | |
| 96 | + "columns": ["provider"], | |
| 101 | 97 | "isUnique": false |
| 102 | 98 | } |
| 103 | 99 | }, |
| 104 | 100 | "foreignKeys": {}, |
| 105 | 101 | "compositePrimaryKeys": { |
| 106 | 102 | "connection_scope_id_id_pk": { |
| 107 | − "columns": [ | |
| 108 | − "scope_id", | |
| 109 | − "id" | |
| 110 | − ], | |
| 103 | + "columns": ["scope_id", "id"], | |
| 111 | 104 | "name": "connection_scope_id_id_pk" |
| 112 | 105 | } |
| 113 | 106 | }, |
| @@ -170,33 +163,24 @@ · 56 unmodified lines | ||
| 170 | 163 | "indexes": { |
| 171 | 164 | "definition_scope_id_idx": { |
| 172 | 165 | "name": "definition_scope_id_idx", |
| 173 | − "columns": [ | |
| 174 | − "scope_id" | |
| 175 | − ], | |
| 166 | + "columns": ["scope_id"], | |
| 176 | 167 | "isUnique": false |
| 177 | 168 | }, |
| 178 | 169 | "definition_source_id_idx": { |
| 179 | 170 | "name": "definition_source_id_idx", |
| 180 | − "columns": [ | |
| 181 | − "source_id" | |
| 182 | − ], | |
| 171 | + "columns": ["source_id"], | |
| 183 | 172 | "isUnique": false |
| 184 | 173 | }, |
| 185 | 174 | "definition_plugin_id_idx": { |
| 186 | 175 | "name": "definition_plugin_id_idx", |
| 187 | − "columns": [ | |
| 188 | − "plugin_id" | |
| 189 | − ], | |
| 176 | + "columns": ["plugin_id"], | |
| 190 | 177 | "isUnique": false |
| 191 | 178 | } |
| 192 | 179 | }, |
| 193 | 180 | "foreignKeys": {}, |
| 194 | 181 | "compositePrimaryKeys": { |
| 195 | 182 | "definition_scope_id_id_pk": { |
| 196 | − "columns": [ | |
| 197 | − "scope_id", | |
| 198 | − "id" | |
| 199 | − ], | |
| 183 | + "columns": ["scope_id", "id"], | |
| 200 | 184 | "name": "definition_scope_id_id_pk" |
| 201 | 185 | } |
| 202 | 186 | }, |
| @@ -245,26 +229,19 @@ · 42 unmodified lines | ||
| 245 | 229 | "indexes": { |
| 246 | 230 | "google_discovery_binding_scope_id_idx": { |
| 247 | 231 | "name": "google_discovery_binding_scope_id_idx", |
| 248 | − "columns": [ | |
| 249 | − "scope_id" | |
| 250 | − ], | |
| 232 | + "columns": ["scope_id"], | |
| 251 | 233 | "isUnique": false |
| 252 | 234 | }, |
| 253 | 235 | "google_discovery_binding_source_id_idx": { |
| 254 | 236 | "name": "google_discovery_binding_source_id_idx", |
| 255 | − "columns": [ | |
| 256 | − "source_id" | |
| 257 | − ], | |
| 237 | + "columns": ["source_id"], | |
| 258 | 238 | "isUnique": false |
| 259 | 239 | } |
| 260 | 240 | }, |
| 261 | 241 | "foreignKeys": {}, |
| 262 | 242 | "compositePrimaryKeys": { |
| 263 | 243 | "google_discovery_binding_scope_id_id_pk": { |
| 264 | − "columns": [ | |
| 265 | − "scope_id", | |
| 266 | − "id" | |
| 267 | − ], | |
| 244 | + "columns": ["scope_id", "id"], | |
| 268 | 245 | "name": "google_discovery_binding_scope_id_id_pk" |
| 269 | 246 | } |
| 270 | 247 | }, |
| @@ -306,19 +283,14 @@ · 35 unmodified lines | ||
| 306 | 283 | "indexes": { |
| 307 | 284 | "google_discovery_oauth_session_scope_id_idx": { |
| 308 | 285 | "name": "google_discovery_oauth_session_scope_id_idx", |
| 309 | − "columns": [ | |
| 310 | − "scope_id" | |
| 311 | − ], | |
| 286 | + "columns": ["scope_id"], | |
| 312 | 287 | "isUnique": false |
| 313 | 288 | } |
| 314 | 289 | }, |
| 315 | 290 | "foreignKeys": {}, |
| 316 | 291 | "compositePrimaryKeys": { |
| 317 | 292 | "google_discovery_oauth_session_scope_id_id_pk": { |
| 318 | − "columns": [ | |
| 319 | − "scope_id", | |
| 320 | − "id" | |
| 321 | − ], | |
| 293 | + "columns": ["scope_id", "id"], | |
| 322 | 294 | "name": "google_discovery_oauth_session_scope_id_id_pk" |
| 323 | 295 | } |
| 324 | 296 | }, |
| @@ -374,19 +346,14 @@ · 49 unmodified lines | ||
| 374 | 346 | "indexes": { |
| 375 | 347 | "google_discovery_source_scope_id_idx": { |
| 376 | 348 | "name": "google_discovery_source_scope_id_idx", |
| 377 | − "columns": [ | |
| 378 | − "scope_id" | |
| 379 | − ], | |
| 349 | + "columns": ["scope_id"], | |
| 380 | 350 | "isUnique": false |
| 381 | 351 | } |
| 382 | 352 | }, |
| 383 | 353 | "foreignKeys": {}, |
| 384 | 354 | "compositePrimaryKeys": { |
| 385 | 355 | "google_discovery_source_scope_id_id_pk": { |
| 386 | − "columns": [ | |
| 387 | − "scope_id", | |
| 388 | − "id" | |
| 389 | − ], | |
| 356 | + "columns": ["scope_id", "id"], | |
| 390 | 357 | "name": "google_discovery_source_scope_id_id_pk" |
| 391 | 358 | } |
| 392 | 359 | }, |
| @@ -428,26 +395,19 @@ · 35 unmodified lines | ||
| 428 | 395 | "indexes": { |
| 429 | 396 | "graphql_operation_scope_id_idx": { |
| 430 | 397 | "name": "graphql_operation_scope_id_idx", |
| 431 | − "columns": [ | |
| 432 | − "scope_id" | |
| 433 | − ], | |
| 398 | + "columns": ["scope_id"], | |
| 434 | 399 | "isUnique": false |
| 435 | 400 | }, |
| 436 | 401 | "graphql_operation_source_id_idx": { |
| 437 | 402 | "name": "graphql_operation_source_id_idx", |
| 438 | − "columns": [ | |
| 439 | − "source_id" | |
| 440 | − ], | |
| 403 | + "columns": ["source_id"], | |
| 441 | 404 | "isUnique": false |
| 442 | 405 | } |
| 443 | 406 | }, |
| 444 | 407 | "foreignKeys": {}, |
| 445 | 408 | "compositePrimaryKeys": { |
| 446 | 409 | "graphql_operation_scope_id_id_pk": { |
| 447 | − "columns": [ | |
| 448 | − "scope_id", | |
| 449 | − "id" | |
| 450 | − ], | |
| 410 | + "columns": ["scope_id", "id"], | |
| 451 | 411 | "name": "graphql_operation_scope_id_id_pk" |
| 452 | 412 | } |
| 453 | 413 | }, |
| @@ -496,19 +456,14 @@ · 42 unmodified lines | ||
| 496 | 456 | "indexes": { |
| 497 | 457 | "graphql_source_scope_id_idx": { |
| 498 | 458 | "name": "graphql_source_scope_id_idx", |
| 499 | − "columns": [ | |
| 500 | − "scope_id" | |
| 501 | − ], | |
| 459 | + "columns": ["scope_id"], | |
| 502 | 460 | "isUnique": false |
| 503 | 461 | } |
| 504 | 462 | }, |
| 505 | 463 | "foreignKeys": {}, |
| 506 | 464 | "compositePrimaryKeys": { |
| 507 | 465 | "graphql_source_scope_id_id_pk": { |
| 508 | − "columns": [ | |
| 509 | − "scope_id", | |
| 510 | − "id" | |
| 511 | − ], | |
| 466 | + "columns": ["scope_id", "id"], | |
| 512 | 467 | "name": "graphql_source_scope_id_id_pk" |
| 513 | 468 | } |
| 514 | 469 | }, |
| @@ -557,26 +512,19 @@ · 42 unmodified lines | ||
| 557 | 512 | "indexes": { |
| 558 | 513 | "mcp_binding_scope_id_idx": { |
| 559 | 514 | "name": "mcp_binding_scope_id_idx", |
| 560 | − "columns": [ | |
| 561 | − "scope_id" | |
| 562 | − ], | |
| 515 | + "columns": ["scope_id"], | |
| 563 | 516 | "isUnique": false |
| 564 | 517 | }, |
| 565 | 518 | "mcp_binding_source_id_idx": { |
| 566 | 519 | "name": "mcp_binding_source_id_idx", |
| 567 | − "columns": [ | |
| 568 | − "source_id" | |
| 569 | − ], | |
| 520 | + "columns": ["source_id"], | |
| 570 | 521 | "isUnique": false |
| 571 | 522 | } |
| 572 | 523 | }, |
| 573 | 524 | "foreignKeys": {}, |
| 574 | 525 | "compositePrimaryKeys": { |
| 575 | 526 | "mcp_binding_scope_id_id_pk": { |
| 576 | − "columns": [ | |
| 577 | − "scope_id", | |
| 578 | − "id" | |
| 579 | − ], | |
| 527 | + "columns": ["scope_id", "id"], | |
| 580 | 528 | "name": "mcp_binding_scope_id_id_pk" |
| 581 | 529 | } |
| 582 | 530 | }, |
| @@ -625,19 +573,14 @@ · 42 unmodified lines | ||
| 625 | 573 | "indexes": { |
| 626 | 574 | "mcp_oauth_session_scope_id_idx": { |
| 627 | 575 | "name": "mcp_oauth_session_scope_id_idx", |
| 628 | − "columns": [ | |
| 629 | − "scope_id" | |
| 630 | − ], | |
| 576 | + "columns": ["scope_id"], | |
| 631 | 577 | "isUnique": false |
| 632 | 578 | } |
| 633 | 579 | }, |
| 634 | 580 | "foreignKeys": {}, |
| 635 | 581 | "compositePrimaryKeys": { |
| 636 | 582 | "mcp_oauth_session_scope_id_id_pk": { |
| 637 | − "columns": [ | |
| 638 | − "scope_id", | |
| 639 | − "id" | |
| 640 | − ], | |
| 583 | + "columns": ["scope_id", "id"], | |
| 641 | 584 | "name": "mcp_oauth_session_scope_id_id_pk" |
| 642 | 585 | } |
| 643 | 586 | }, |
| @@ -686,19 +629,14 @@ · 42 unmodified lines | ||
| 686 | 629 | "indexes": { |
| 687 | 630 | "mcp_source_scope_id_idx": { |
| 688 | 631 | "name": "mcp_source_scope_id_idx", |
| 689 | − "columns": [ | |
| 690 | − "scope_id" | |
| 691 | − ], | |
| 632 | + "columns": ["scope_id"], | |
| 692 | 633 | "isUnique": false |
| 693 | 634 | } |
| 694 | 635 | }, |
| 695 | 636 | "foreignKeys": {}, |
| 696 | 637 | "compositePrimaryKeys": { |
| 697 | 638 | "mcp_source_scope_id_id_pk": { |
| 698 | − "columns": [ | |
| 699 | − "scope_id", | |
| 700 | − "id" | |
| 701 | − ], | |
| 639 | + "columns": ["scope_id", "id"], | |
| 702 | 640 | "name": "mcp_source_scope_id_id_pk" |
| 703 | 641 | } |
| 704 | 642 | }, |
| @@ -740,19 +678,14 @@ · 35 unmodified lines | ||
| 740 | 678 | "indexes": { |
| 741 | 679 | "openapi_oauth_session_scope_id_idx": { |
| 742 | 680 | "name": "openapi_oauth_session_scope_id_idx", |
| 743 | − "columns": [ | |
| 744 | − "scope_id" | |
| 745 | − ], | |
| 681 | + "columns": ["scope_id"], | |
| 746 | 682 | "isUnique": false |
| 747 | 683 | } |
| 748 | 684 | }, |
| 749 | 685 | "foreignKeys": {}, |
| 750 | 686 | "compositePrimaryKeys": { |
| 751 | 687 | "openapi_oauth_session_scope_id_id_pk": { |
| 752 | − "columns": [ | |
| 753 | − "scope_id", | |
| 754 | − "id" | |
| 755 | − ], | |
| 688 | + "columns": ["scope_id", "id"], | |
| 756 | 689 | "name": "openapi_oauth_session_scope_id_id_pk" |
| 757 | 690 | } |
| 758 | 691 | }, |
| @@ -794,26 +727,19 @@ · 35 unmodified lines | ||
| 794 | 727 | "indexes": { |
| 795 | 728 | "openapi_operation_scope_id_idx": { |
| 796 | 729 | "name": "openapi_operation_scope_id_idx", |
| 797 | − "columns": [ | |
| 798 | − "scope_id" | |
| 799 | − ], | |
| 730 | + "columns": ["scope_id"], | |
| 800 | 731 | "isUnique": false |
| 801 | 732 | }, |
| 802 | 733 | "openapi_operation_source_id_idx": { |
| 803 | 734 | "name": "openapi_operation_source_id_idx", |
| 804 | − "columns": [ | |
| 805 | − "source_id" | |
| 806 | − ], | |
| 735 | + "columns": ["source_id"], | |
| 807 | 736 | "isUnique": false |
| 808 | 737 | } |
| 809 | 738 | }, |
| 810 | 739 | "foreignKeys": {}, |
| 811 | 740 | "compositePrimaryKeys": { |
| 812 | 741 | "openapi_operation_scope_id_id_pk": { |
| 813 | − "columns": [ | |
| 814 | − "scope_id", | |
| 815 | − "id" | |
| 816 | − ], | |
| 742 | + "columns": ["scope_id", "id"], | |
| 817 | 743 | "name": "openapi_operation_scope_id_id_pk" |
| 818 | 744 | } |
| 819 | 745 | }, |
| @@ -890,19 +816,14 @@ · 70 unmodified lines | ||
| 890 | 816 | "indexes": { |
| 891 | 817 | "openapi_source_scope_id_idx": { |
| 892 | 818 | "name": "openapi_source_scope_id_idx", |
| 893 | − "columns": [ | |
| 894 | − "scope_id" | |
| 895 | − ], | |
| 819 | + "columns": ["scope_id"], | |
| 896 | 820 | "isUnique": false |
| 897 | 821 | } |
| 898 | 822 | }, |
| 899 | 823 | "foreignKeys": {}, |
| 900 | 824 | "compositePrimaryKeys": { |
| 901 | 825 | "openapi_source_scope_id_id_pk": { |
| 902 | − "columns": [ | |
| 903 | − "scope_id", | |
| 904 | − "id" | |
| 905 | − ], | |
| 826 | + "columns": ["scope_id", "id"], | |
| 906 | 827 | "name": "openapi_source_scope_id_id_pk" |
| 907 | 828 | } |
| 908 | 829 | }, |
| @@ -972,30 +893,22 @@ · 63 unmodified lines | ||
| 972 | 893 | "indexes": { |
| 973 | 894 | "openapi_source_binding_source_id_idx": { |
| 974 | 895 | "name": "openapi_source_binding_source_id_idx", |
| 975 | − "columns": [ | |
| 976 | − "source_id" | |
| 977 | − ], | |
| 896 | + "columns": ["source_id"], | |
| 978 | 897 | "isUnique": false |
| 979 | 898 | }, |
| 980 | 899 | "openapi_source_binding_source_scope_id_idx": { |
| 981 | 900 | "name": "openapi_source_binding_source_scope_id_idx", |
| 982 | − "columns": [ | |
| 983 | − "source_scope_id" | |
| 984 | − ], | |
| 901 | + "columns": ["source_scope_id"], | |
| 985 | 902 | "isUnique": false |
| 986 | 903 | }, |
| 987 | 904 | "openapi_source_binding_target_scope_id_idx": { |
| 988 | 905 | "name": "openapi_source_binding_target_scope_id_idx", |
| 989 | − "columns": [ | |
| 990 | − "target_scope_id" | |
| 991 | − ], | |
| 906 | + "columns": ["target_scope_id"], | |
| 992 | 907 | "isUnique": false |
| 993 | 908 | }, |
| 994 | 909 | "openapi_source_binding_slot_idx": { |
| 995 | 910 | "name": "openapi_source_binding_slot_idx", |
| 996 | − "columns": [ | |
| 997 | − "slot" | |
| 998 | − ], | |
| 911 | + "columns": ["slot"], | |
| 999 | 912 | "isUnique": false |
| 1000 | 913 | } |
| 1001 | 914 | }, |
| @@ -1053,33 +966,24 @@ · 51 unmodified lines | ||
| 1053 | 966 | "indexes": { |
| 1054 | 967 | "secret_scope_id_idx": { |
| 1055 | 968 | "name": "secret_scope_id_idx", |
| 1056 | − "columns": [ | |
| 1057 | − "scope_id" | |
| 1058 | − ], | |
| 969 | + "columns": ["scope_id"], | |
| 1059 | 970 | "isUnique": false |
| 1060 | 971 | }, |
| 1061 | 972 | "secret_provider_idx": { |
| 1062 | 973 | "name": "secret_provider_idx", |
| 1063 | − "columns": [ | |
| 1064 | − "provider" | |
| 1065 | − ], | |
| 974 | + "columns": ["provider"], | |
| 1066 | 975 | "isUnique": false |
| 1067 | 976 | }, |
| 1068 | 977 | "secret_owned_by_connection_id_idx": { |
| 1069 | 978 | "name": "secret_owned_by_connection_id_idx", |
| 1070 | − "columns": [ | |
| 1071 | − "owned_by_connection_id" | |
| 1072 | − ], | |
| 979 | + "columns": ["owned_by_connection_id"], | |
| 1073 | 980 | "isUnique": false |
| 1074 | 981 | } |
| 1075 | 982 | }, |
| 1076 | 983 | "foreignKeys": {}, |
| 1077 | 984 | "compositePrimaryKeys": { |
| 1078 | 985 | "secret_scope_id_id_pk": { |
| 1079 | − "columns": [ | |
| 1080 | − "scope_id", | |
| 1081 | − "id" | |
| 1082 | − ], | |
| 986 | + "columns": ["scope_id", "id"], | |
| 1083 | 987 | "name": "secret_scope_id_id_pk" |
| 1084 | 988 | } |
| 1085 | 989 | }, |
| @@ -1173,26 +1077,19 @@ · 87 unmodified lines | ||
| 1173 | 1077 | "indexes": { |
| 1174 | 1078 | "source_scope_id_idx": { |
| 1175 | 1079 | "name": "source_scope_id_idx", |
| 1176 | − "columns": [ | |
| 1177 | − "scope_id" | |
| 1178 | − ], | |
| 1080 | + "columns": ["scope_id"], | |
| 1179 | 1081 | "isUnique": false |
| 1180 | 1082 | }, |
| 1181 | 1083 | "source_plugin_id_idx": { |
| 1182 | 1084 | "name": "source_plugin_id_idx", |
| 1183 | − "columns": [ | |
| 1184 | − "plugin_id" | |
| 1185 | − ], | |
| 1085 | + "columns": ["plugin_id"], | |
| 1186 | 1086 | "isUnique": false |
| 1187 | 1087 | } |
| 1188 | 1088 | }, |
| 1189 | 1089 | "foreignKeys": {}, |
| 1190 | 1090 | "compositePrimaryKeys": { |
| 1191 | 1091 | "source_scope_id_id_pk": { |
| 1192 | − "columns": [ | |
| 1193 | − "scope_id", | |
| 1194 | − "id" | |
| 1195 | − ], | |
| 1092 | + "columns": ["scope_id", "id"], | |
| 1196 | 1093 | "name": "source_scope_id_id_pk" |
| 1197 | 1094 | } |
| 1198 | 1095 | }, |
| @@ -1276,33 +1173,24 @@ · 77 unmodified lines | ||
| 1276 | 1173 | "indexes": { |
| 1277 | 1174 | "tool_scope_id_idx": { |
| 1278 | 1175 | "name": "tool_scope_id_idx", |
| 1279 | − "columns": [ | |
| 1280 | − "scope_id" | |
| 1281 | − ], | |
| 1176 | + "columns": ["scope_id"], | |
| 1282 | 1177 | "isUnique": false |
| 1283 | 1178 | }, |
| 1284 | 1179 | "tool_source_id_idx": { |
| 1285 | 1180 | "name": "tool_source_id_idx", |
| 1286 | − "columns": [ | |
| 1287 | − "source_id" | |
| 1288 | − ], | |
| 1181 | + "columns": ["source_id"], | |
| 1289 | 1182 | "isUnique": false |
| 1290 | 1183 | }, |
| 1291 | 1184 | "tool_plugin_id_idx": { |
| 1292 | 1185 | "name": "tool_plugin_id_idx", |
| 1293 | − "columns": [ | |
| 1294 | − "plugin_id" | |
| 1295 | − ], | |
| 1186 | + "columns": ["plugin_id"], | |
| 1296 | 1187 | "isUnique": false |
| 1297 | 1188 | } |
| 1298 | 1189 | }, |
| 1299 | 1190 | "foreignKeys": {}, |
| 1300 | 1191 | "compositePrimaryKeys": { |
| 1301 | 1192 | "tool_scope_id_id_pk": { |
| 1302 | − "columns": [ | |
| 1303 | − "scope_id", | |
| 1304 | − "id" | |
| 1305 | − ], | |
| 1193 | + "columns": ["scope_id", "id"], | |
| 1306 | 1194 | "name": "tool_scope_id_id_pk" |
| 1307 | 1195 | } |
| 1308 | 1196 | }, |
| @@ -1365,20 +1253,14 @@ · 56 unmodified lines | ||
| 1365 | 1253 | "indexes": { |
| 1366 | 1254 | "tool_policy_scope_id_position_idx": { |
| 1367 | 1255 | "name": "tool_policy_scope_id_position_idx", |
| 1368 | − "columns": [ | |
| 1369 | − "scope_id", | |
| 1370 | − "position" | |
| 1371 | − ], | |
| 1256 | + "columns": ["scope_id", "position"], | |
| 1372 | 1257 | "isUnique": false |
| 1373 | 1258 | } |
| 1374 | 1259 | }, |
| 1375 | 1260 | "foreignKeys": {}, |
| 1376 | 1261 | "compositePrimaryKeys": { |
| 1377 | 1262 | "tool_policy_scope_id_id_pk": { |
| 1378 | − "columns": [ | |
| 1379 | − "scope_id", | |
| 1380 | − "id" | |
| 1381 | − ], | |
| 1263 | + "columns": ["scope_id", "id"], | |
| 1382 | 1264 | "name": "tool_policy_scope_id_id_pk" |
| 1383 | 1265 | } |
| 1384 | 1266 | }, |
| @@ -1414,10 +1296,7 @@ · 29 unmodified lines | ||
| 1414 | 1296 | "foreignKeys": {}, |
| 1415 | 1297 | "compositePrimaryKeys": { |
| 1416 | 1298 | "blob_namespace_key_pk": { |
| 1417 | − "columns": [ | |
| 1418 | − "namespace", | |
| 1419 | − "key" | |
| 1420 | − ], | |
| 1299 | + "columns": ["namespace", "key"], | |
| 1421 | 1300 | "name": "blob_namespace_key_pk" |
| 1422 | 1301 | } |
| 1423 | 1302 | }, |
| @@ -88,26 +88,19 @@ · 87 unmodified lines | ||
| 88 | 88 | "indexes": { |
| 89 | 89 | "connection_scope_id_idx": { |
| 90 | 90 | "name": "connection_scope_id_idx", |
| 91 | − "columns": [ | |
| 92 | − "scope_id" | |
| 93 | − ], | |
| 91 | + "columns": ["scope_id"], | |
| 94 | 92 | "isUnique": false |
| 95 | 93 | }, |
| 96 | 94 | "connection_provider_idx": { |
| 97 | 95 | "name": "connection_provider_idx", |
| 98 | − "columns": [ | |
| 99 | − "provider" | |
| 100 | − ], | |
| 96 | + "columns": ["provider"], | |
| 101 | 97 | "isUnique": false |
| 102 | 98 | } |
| 103 | 99 | }, |
| 104 | 100 | "foreignKeys": {}, |
| 105 | 101 | "compositePrimaryKeys": { |
| 106 | 102 | "connection_scope_id_id_pk": { |
| 107 | − "columns": [ | |
| 108 | − "scope_id", | |
| 109 | − "id" | |
| 110 | − ], | |
| 103 | + "columns": ["scope_id", "id"], | |
| 111 | 104 | "name": "connection_scope_id_id_pk" |
| 112 | 105 | } |
| 113 | 106 | }, |
| @@ -170,33 +163,24 @@ · 56 unmodified lines | ||
| 170 | 163 | "indexes": { |
| 171 | 164 | "definition_scope_id_idx": { |
| 172 | 165 | "name": "definition_scope_id_idx", |
| 173 | − "columns": [ | |
| 174 | − "scope_id" | |
| 175 | − ], | |
| 166 | + "columns": ["scope_id"], | |
| 176 | 167 | "isUnique": false |
| 177 | 168 | }, |
| 178 | 169 | "definition_source_id_idx": { |
| 179 | 170 | "name": "definition_source_id_idx", |
| 180 | − "columns": [ | |
| 181 | − "source_id" | |
| 182 | − ], | |
| 171 | + "columns": ["source_id"], | |
| 183 | 172 | "isUnique": false |
| 184 | 173 | }, |
| 185 | 174 | "definition_plugin_id_idx": { |
| 186 | 175 | "name": "definition_plugin_id_idx", |
| 187 | − "columns": [ | |
| 188 | − "plugin_id" | |
| 189 | − ], | |
| 176 | + "columns": ["plugin_id"], | |
| 190 | 177 | "isUnique": false |
| 191 | 178 | } |
| 192 | 179 | }, |
| 193 | 180 | "foreignKeys": {}, |
| 194 | 181 | "compositePrimaryKeys": { |
| 195 | 182 | "definition_scope_id_id_pk": { |
| 196 | − "columns": [ | |
| 197 | − "scope_id", | |
| 198 | − "id" | |
| 199 | − ], | |
| 183 | + "columns": ["scope_id", "id"], | |
| 200 | 184 | "name": "definition_scope_id_id_pk" |
| 201 | 185 | } |
| 202 | 186 | }, |
| @@ -245,26 +229,19 @@ · 42 unmodified lines | ||
| 245 | 229 | "indexes": { |
| 246 | 230 | "google_discovery_binding_scope_id_idx": { |
| 247 | 231 | "name": "google_discovery_binding_scope_id_idx", |
| 248 | − "columns": [ | |
| 249 | − "scope_id" | |
| 250 | − ], | |
| 232 | + "columns": ["scope_id"], | |
| 251 | 233 | "isUnique": false |
| 252 | 234 | }, |
| 253 | 235 | "google_discovery_binding_source_id_idx": { |
| 254 | 236 | "name": "google_discovery_binding_source_id_idx", |
| 255 | − "columns": [ | |
| 256 | − "source_id" | |
| 257 | − ], | |
| 237 | + "columns": ["source_id"], | |
| 258 | 238 | "isUnique": false |
| 259 | 239 | } |
| 260 | 240 | }, |
| 261 | 241 | "foreignKeys": {}, |
| 262 | 242 | "compositePrimaryKeys": { |
| 263 | 243 | "google_discovery_binding_scope_id_id_pk": { |
| 264 | − "columns": [ | |
| 265 | − "scope_id", | |
| 266 | − "id" | |
| 267 | − ], | |
| 244 | + "columns": ["scope_id", "id"], | |
| 268 | 245 | "name": "google_discovery_binding_scope_id_id_pk" |
| 269 | 246 | } |
| 270 | 247 | }, |
| @@ -306,19 +283,14 @@ · 35 unmodified lines | ||
| 306 | 283 | "indexes": { |
| 307 | 284 | "google_discovery_oauth_session_scope_id_idx": { |
| 308 | 285 | "name": "google_discovery_oauth_session_scope_id_idx", |
| 309 | − "columns": [ | |
| 310 | − "scope_id" | |
| 311 | − ], | |
| 286 | + "columns": ["scope_id"], | |
| 312 | 287 | "isUnique": false |
| 313 | 288 | } |
| 314 | 289 | }, |
| 315 | 290 | "foreignKeys": {}, |
| 316 | 291 | "compositePrimaryKeys": { |
| 317 | 292 | "google_discovery_oauth_session_scope_id_id_pk": { |
| 318 | − "columns": [ | |
| 319 | − "scope_id", | |
| 320 | − "id" | |
| 321 | − ], | |
| 293 | + "columns": ["scope_id", "id"], | |
| 322 | 294 | "name": "google_discovery_oauth_session_scope_id_id_pk" |
| 323 | 295 | } |
| 324 | 296 | }, |
| @@ -374,19 +346,14 @@ · 49 unmodified lines | ||
| 374 | 346 | "indexes": { |
| 375 | 347 | "google_discovery_source_scope_id_idx": { |
| 376 | 348 | "name": "google_discovery_source_scope_id_idx", |
| 377 | − "columns": [ | |
| 378 | − "scope_id" | |
| 379 | − ], | |
| 349 | + "columns": ["scope_id"], | |
| 380 | 350 | "isUnique": false |
| 381 | 351 | } |
| 382 | 352 | }, |
| 383 | 353 | "foreignKeys": {}, |
| 384 | 354 | "compositePrimaryKeys": { |
| 385 | 355 | "google_discovery_source_scope_id_id_pk": { |
| 386 | − "columns": [ | |
| 387 | − "scope_id", | |
| 388 | − "id" | |
| 389 | − ], | |
| 356 | + "columns": ["scope_id", "id"], | |
| 390 | 357 | "name": "google_discovery_source_scope_id_id_pk" |
| 391 | 358 | } |
| 392 | 359 | }, |
| @@ -428,26 +395,19 @@ · 35 unmodified lines | ||
| 428 | 395 | "indexes": { |
| 429 | 396 | "graphql_operation_scope_id_idx": { |
| 430 | 397 | "name": "graphql_operation_scope_id_idx", |
| 431 | − "columns": [ | |
| 432 | − "scope_id" | |
| 433 | − ], | |
| 398 | + "columns": ["scope_id"], | |
| 434 | 399 | "isUnique": false |
| 435 | 400 | }, |
| 436 | 401 | "graphql_operation_source_id_idx": { |
| 437 | 402 | "name": "graphql_operation_source_id_idx", |
| 438 | − "columns": [ | |
| 439 | − "source_id" | |
| 440 | − ], | |
| 403 | + "columns": ["source_id"], | |
| 441 | 404 | "isUnique": false |
| 442 | 405 | } |
| 443 | 406 | }, |
| 444 | 407 | "foreignKeys": {}, |
| 445 | 408 | "compositePrimaryKeys": { |
| 446 | 409 | "graphql_operation_scope_id_id_pk": { |
| 447 | − "columns": [ | |
| 448 | − "scope_id", | |
| 449 | − "id" | |
| 450 | − ], | |
| 410 | + "columns": ["scope_id", "id"], | |
| 451 | 411 | "name": "graphql_operation_scope_id_id_pk" |
| 452 | 412 | } |
| 453 | 413 | }, |
| @@ -496,19 +456,14 @@ · 42 unmodified lines | ||
| 496 | 456 | "indexes": { |
| 497 | 457 | "graphql_source_scope_id_idx": { |
| 498 | 458 | "name": "graphql_source_scope_id_idx", |
| 499 | − "columns": [ | |
| 500 | − "scope_id" | |
| 501 | − ], | |
| 459 | + "columns": ["scope_id"], | |
| 502 | 460 | "isUnique": false |
| 503 | 461 | } |
| 504 | 462 | }, |
| 505 | 463 | "foreignKeys": {}, |
| 506 | 464 | "compositePrimaryKeys": { |
| 507 | 465 | "graphql_source_scope_id_id_pk": { |
| 508 | − "columns": [ | |
| 509 | − "scope_id", | |
| 510 | − "id" | |
| 511 | − ], | |
| 466 | + "columns": ["scope_id", "id"], | |
| 512 | 467 | "name": "graphql_source_scope_id_id_pk" |
| 513 | 468 | } |
| 514 | 469 | }, |
| @@ -557,26 +512,19 @@ · 42 unmodified lines | ||
| 557 | 512 | "indexes": { |
| 558 | 513 | "mcp_binding_scope_id_idx": { |
| 559 | 514 | "name": "mcp_binding_scope_id_idx", |
| 560 | − "columns": [ | |
| 561 | − "scope_id" | |
| 562 | − ], | |
| 515 | + "columns": ["scope_id"], | |
| 563 | 516 | "isUnique": false |
| 564 | 517 | }, |
| 565 | 518 | "mcp_binding_source_id_idx": { |
| 566 | 519 | "name": "mcp_binding_source_id_idx", |
| 567 | − "columns": [ | |
| 568 | − "source_id" | |
| 569 | − ], | |
| 520 | + "columns": ["source_id"], | |
| 570 | 521 | "isUnique": false |
| 571 | 522 | } |
| 572 | 523 | }, |
| 573 | 524 | "foreignKeys": {}, |
| 574 | 525 | "compositePrimaryKeys": { |
| 575 | 526 | "mcp_binding_scope_id_id_pk": { |
| 576 | − "columns": [ | |
| 577 | − "scope_id", | |
| 578 | − "id" | |
| 579 | − ], | |
| 527 | + "columns": ["scope_id", "id"], | |
| 580 | 528 | "name": "mcp_binding_scope_id_id_pk" |
| 581 | 529 | } |
| 582 | 530 | }, |
| @@ -625,19 +573,14 @@ · 42 unmodified lines | ||
| 625 | 573 | "indexes": { |
| 626 | 574 | "mcp_oauth_session_scope_id_idx": { |
| 627 | 575 | "name": "mcp_oauth_session_scope_id_idx", |
| 628 | − "columns": [ | |
| 629 | − "scope_id" | |
| 630 | − ], | |
| 576 | + "columns": ["scope_id"], | |
| 631 | 577 | "isUnique": false |
| 632 | 578 | } |
| 633 | 579 | }, |
| 634 | 580 | "foreignKeys": {}, |
| 635 | 581 | "compositePrimaryKeys": { |
| 636 | 582 | "mcp_oauth_session_scope_id_id_pk": { |
| 637 | − "columns": [ | |
| 638 | − "scope_id", | |
| 639 | − "id" | |
| 640 | − ], | |
| 583 | + "columns": ["scope_id", "id"], | |
| 641 | 584 | "name": "mcp_oauth_session_scope_id_id_pk" |
| 642 | 585 | } |
| 643 | 586 | }, |
| @@ -686,19 +629,14 @@ · 42 unmodified lines | ||
| 686 | 629 | "indexes": { |
| 687 | 630 | "mcp_source_scope_id_idx": { |
| 688 | 631 | "name": "mcp_source_scope_id_idx", |
| 689 | − "columns": [ | |
| 690 | − "scope_id" | |
| 691 | − ], | |
| 632 | + "columns": ["scope_id"], | |
| 692 | 633 | "isUnique": false |
| 693 | 634 | } |
| 694 | 635 | }, |
| 695 | 636 | "foreignKeys": {}, |
| 696 | 637 | "compositePrimaryKeys": { |
| 697 | 638 | "mcp_source_scope_id_id_pk": { |
| 698 | − "columns": [ | |
| 699 | − "scope_id", | |
| 700 | − "id" | |
| 701 | − ], | |
| 639 | + "columns": ["scope_id", "id"], | |
| 702 | 640 | "name": "mcp_source_scope_id_id_pk" |
| 703 | 641 | } |
| 704 | 642 | }, |
| @@ -740,19 +678,14 @@ · 35 unmodified lines | ||
| 740 | 678 | "indexes": { |
| 741 | 679 | "openapi_oauth_session_scope_id_idx": { |
| 742 | 680 | "name": "openapi_oauth_session_scope_id_idx", |
| 743 | − "columns": [ | |
| 744 | − "scope_id" | |
| 745 | − ], | |
| 681 | + "columns": ["scope_id"], | |
| 746 | 682 | "isUnique": false |
| 747 | 683 | } |
| 748 | 684 | }, |
| 749 | 685 | "foreignKeys": {}, |
| 750 | 686 | "compositePrimaryKeys": { |
| 751 | 687 | "openapi_oauth_session_scope_id_id_pk": { |
| 752 | − "columns": [ | |
| 753 | − "scope_id", | |
| 754 | − "id" | |
| 755 | − ], | |
| 688 | + "columns": ["scope_id", "id"], | |
| 756 | 689 | "name": "openapi_oauth_session_scope_id_id_pk" |
| 757 | 690 | } |
| 758 | 691 | }, |
| @@ -794,26 +727,19 @@ · 35 unmodified lines | ||
| 794 | 727 | "indexes": { |
| 795 | 728 | "openapi_operation_scope_id_idx": { |
| 796 | 729 | "name": "openapi_operation_scope_id_idx", |
| 797 | − "columns": [ | |
| 798 | − "scope_id" | |
| 799 | − ], | |
| 730 | + "columns": ["scope_id"], | |
| 800 | 731 | "isUnique": false |
| 801 | 732 | }, |
| 802 | 733 | "openapi_operation_source_id_idx": { |
| 803 | 734 | "name": "openapi_operation_source_id_idx", |
| 804 | − "columns": [ | |
| 805 | − "source_id" | |
| 806 | − ], | |
| 735 | + "columns": ["source_id"], | |
| 807 | 736 | "isUnique": false |
| 808 | 737 | } |
| 809 | 738 | }, |
| 810 | 739 | "foreignKeys": {}, |
| 811 | 740 | "compositePrimaryKeys": { |
| 812 | 741 | "openapi_operation_scope_id_id_pk": { |
| 813 | − "columns": [ | |
| 814 | − "scope_id", | |
| 815 | − "id" | |
| 816 | − ], | |
| 742 | + "columns": ["scope_id", "id"], | |
| 817 | 743 | "name": "openapi_operation_scope_id_id_pk" |
| 818 | 744 | } |
| 819 | 745 | }, |
| @@ -890,19 +816,14 @@ · 70 unmodified lines | ||
| 890 | 816 | "indexes": { |
| 891 | 817 | "openapi_source_scope_id_idx": { |
| 892 | 818 | "name": "openapi_source_scope_id_idx", |
| 893 | − "columns": [ | |
| 894 | − "scope_id" | |
| 895 | − ], | |
| 819 | + "columns": ["scope_id"], | |
| 896 | 820 | "isUnique": false |
| 897 | 821 | } |
| 898 | 822 | }, |
| 899 | 823 | "foreignKeys": {}, |
| 900 | 824 | "compositePrimaryKeys": { |
| 901 | 825 | "openapi_source_scope_id_id_pk": { |
| 902 | − "columns": [ | |
| 903 | − "scope_id", | |
| 904 | − "id" | |
| 905 | − ], | |
| 826 | + "columns": ["scope_id", "id"], | |
| 906 | 827 | "name": "openapi_source_scope_id_id_pk" |
| 907 | 828 | } |
| 908 | 829 | }, |
| @@ -972,30 +893,22 @@ · 63 unmodified lines | ||
| 972 | 893 | "indexes": { |
| 973 | 894 | "openapi_source_binding_source_id_idx": { |
| 974 | 895 | "name": "openapi_source_binding_source_id_idx", |
| 975 | − "columns": [ | |
| 976 | − "source_id" | |
| 977 | − ], | |
| 896 | + "columns": ["source_id"], | |
| 978 | 897 | "isUnique": false |
| 979 | 898 | }, |
| 980 | 899 | "openapi_source_binding_source_scope_id_idx": { |
| 981 | 900 | "name": "openapi_source_binding_source_scope_id_idx", |
| 982 | − "columns": [ | |
| 983 | − "source_scope_id" | |
| 984 | − ], | |
| 901 | + "columns": ["source_scope_id"], | |
| 985 | 902 | "isUnique": false |
| 986 | 903 | }, |
| 987 | 904 | "openapi_source_binding_target_scope_id_idx": { |
| 988 | 905 | "name": "openapi_source_binding_target_scope_id_idx", |
| 989 | − "columns": [ | |
| 990 | − "target_scope_id" | |
| 991 | − ], | |
| 906 | + "columns": ["target_scope_id"], | |
| 992 | 907 | "isUnique": false |
| 993 | 908 | }, |
| 994 | 909 | "openapi_source_binding_slot_idx": { |
| 995 | 910 | "name": "openapi_source_binding_slot_idx", |
| 996 | − "columns": [ | |
| 997 | − "slot" | |
| 998 | − ], | |
| 911 | + "columns": ["slot"], | |
| 999 | 912 | "isUnique": false |
| 1000 | 913 | } |
| 1001 | 914 | }, |
| @@ -1053,33 +966,24 @@ · 51 unmodified lines | ||
| 1053 | 966 | "indexes": { |
| 1054 | 967 | "secret_scope_id_idx": { |
| 1055 | 968 | "name": "secret_scope_id_idx", |
| 1056 | − "columns": [ | |
| 1057 | − "scope_id" | |
| 1058 | − ], | |
| 969 | + "columns": ["scope_id"], | |
| 1059 | 970 | "isUnique": false |
| 1060 | 971 | }, |
| 1061 | 972 | "secret_provider_idx": { |
| 1062 | 973 | "name": "secret_provider_idx", |
| 1063 | − "columns": [ | |
| 1064 | − "provider" | |
| 1065 | − ], | |
| 974 | + "columns": ["provider"], | |
| 1066 | 975 | "isUnique": false |
| 1067 | 976 | }, |
| 1068 | 977 | "secret_owned_by_connection_id_idx": { |
| 1069 | 978 | "name": "secret_owned_by_connection_id_idx", |
| 1070 | − "columns": [ | |
| 1071 | − "owned_by_connection_id" | |
| 1072 | − ], | |
| 979 | + "columns": ["owned_by_connection_id"], | |
| 1073 | 980 | "isUnique": false |
| 1074 | 981 | } |
| 1075 | 982 | }, |
| 1076 | 983 | "foreignKeys": {}, |
| 1077 | 984 | "compositePrimaryKeys": { |
| 1078 | 985 | "secret_scope_id_id_pk": { |
| 1079 | − "columns": [ | |
| 1080 | − "scope_id", | |
| 1081 | − "id" | |
| 1082 | − ], | |
| 986 | + "columns": ["scope_id", "id"], | |
| 1083 | 987 | "name": "secret_scope_id_id_pk" |
| 1084 | 988 | } |
| 1085 | 989 | }, |
| @@ -1173,26 +1077,19 @@ · 87 unmodified lines | ||
| 1173 | 1077 | "indexes": { |
| 1174 | 1078 | "source_scope_id_idx": { |
| 1175 | 1079 | "name": "source_scope_id_idx", |
| 1176 | − "columns": [ | |
| 1177 | − "scope_id" | |
| 1178 | − ], | |
| 1080 | + "columns": ["scope_id"], | |
| 1179 | 1081 | "isUnique": false |
| 1180 | 1082 | }, |
| 1181 | 1083 | "source_plugin_id_idx": { |
| 1182 | 1084 | "name": "source_plugin_id_idx", |
| 1183 | − "columns": [ | |
| 1184 | − "plugin_id" | |
| 1185 | − ], | |
| 1085 | + "columns": ["plugin_id"], | |
| 1186 | 1086 | "isUnique": false |
| 1187 | 1087 | } |
| 1188 | 1088 | }, |
| 1189 | 1089 | "foreignKeys": {}, |
| 1190 | 1090 | "compositePrimaryKeys": { |
| 1191 | 1091 | "source_scope_id_id_pk": { |
| 1192 | − "columns": [ | |
| 1193 | − "scope_id", | |
| 1194 | − "id" | |
| 1195 | − ], | |
| 1092 | + "columns": ["scope_id", "id"], | |
| 1196 | 1093 | "name": "source_scope_id_id_pk" |
| 1197 | 1094 | } |
| 1198 | 1095 | }, |
| @@ -1276,33 +1173,24 @@ · 77 unmodified lines | ||
| 1276 | 1173 | "indexes": { |
| 1277 | 1174 | "tool_scope_id_idx": { |
| 1278 | 1175 | "name": "tool_scope_id_idx", |
| 1279 | − "columns": [ | |
| 1280 | − "scope_id" | |
| 1281 | − ], | |
| 1176 | + "columns": ["scope_id"], | |
| 1282 | 1177 | "isUnique": false |
| 1283 | 1178 | }, |
| 1284 | 1179 | "tool_source_id_idx": { |
| 1285 | 1180 | "name": "tool_source_id_idx", |
| 1286 | − "columns": [ | |
| 1287 | − "source_id" | |
| 1288 | − ], | |
| 1181 | + "columns": ["source_id"], | |
| 1289 | 1182 | "isUnique": false |
| 1290 | 1183 | }, |
| 1291 | 1184 | "tool_plugin_id_idx": { |
| 1292 | 1185 | "name": "tool_plugin_id_idx", |
| 1293 | − "columns": [ | |
| 1294 | − "plugin_id" | |
| 1295 | − ], | |
| 1186 | + "columns": ["plugin_id"], | |
| 1296 | 1187 | "isUnique": false |
| 1297 | 1188 | } |
| 1298 | 1189 | }, |
| 1299 | 1190 | "foreignKeys": {}, |
| 1300 | 1191 | "compositePrimaryKeys": { |
| 1301 | 1192 | "tool_scope_id_id_pk": { |
| 1302 | − "columns": [ | |
| 1303 | − "scope_id", | |
| 1304 | − "id" | |
| 1305 | − ], | |
| 1193 | + "columns": ["scope_id", "id"], | |
| 1306 | 1194 | "name": "tool_scope_id_id_pk" |
| 1307 | 1195 | } |
| 1308 | 1196 | }, |
| @@ -1365,20 +1253,14 @@ · 56 unmodified lines | ||
| 1365 | 1253 | "indexes": { |
| 1366 | 1254 | "tool_policy_scope_id_position_idx": { |
| 1367 | 1255 | "name": "tool_policy_scope_id_position_idx", |
| 1368 | − "columns": [ | |
| 1369 | − "scope_id", | |
| 1370 | − "position" | |
| 1371 | − ], | |
| 1256 | + "columns": ["scope_id", "position"], | |
| 1372 | 1257 | "isUnique": false |
| 1373 | 1258 | } |
| 1374 | 1259 | }, |
| 1375 | 1260 | "foreignKeys": {}, |
| 1376 | 1261 | "compositePrimaryKeys": { |
| 1377 | 1262 | "tool_policy_scope_id_id_pk": { |
| 1378 | − "columns": [ | |
| 1379 | − "scope_id", | |
| 1380 | − "id" | |
| 1381 | − ], | |
| 1263 | + "columns": ["scope_id", "id"], | |
| 1382 | 1264 | "name": "tool_policy_scope_id_id_pk" |
| 1383 | 1265 | } |
| 1384 | 1266 | }, |
| @@ -1414,10 +1296,7 @@ · 29 unmodified lines | ||
| 1414 | 1296 | "foreignKeys": {}, |
| 1415 | 1297 | "compositePrimaryKeys": { |
| 1416 | 1298 | "blob_namespace_key_pk": { |
| 1417 | − "columns": [ | |
| 1418 | − "namespace", | |
| 1419 | − "key" | |
| 1420 | − ], | |
| 1299 | + "columns": ["namespace", "key"], | |
| 1421 | 1300 | "name": "blob_namespace_key_pk" |
| 1422 | 1301 | } |
| 1423 | 1302 | }, |
| @@ -33,10 +33,7 @@ · 32 unmodified lines | ||
| 33 | 33 | "foreignKeys": {}, |
| 34 | 34 | "compositePrimaryKeys": { |
| 35 | 35 | "blob_namespace_key_pk": { |
| 36 | − "columns": [ | |
| 37 | − "namespace", | |
| 38 | − "key" | |
| 39 | − ], | |
| 36 | + "columns": ["namespace", "key"], | |
| 40 | 37 | "name": "blob_namespace_key_pk" |
| 41 | 38 | } |
| 42 | 39 | }, |
| @@ -127,26 +124,19 @@ · 84 unmodified lines | ||
| 127 | 124 | "indexes": { |
| 128 | 125 | "connection_scope_id_idx": { |
| 129 | 126 | "name": "connection_scope_id_idx", |
| 130 | − "columns": [ | |
| 131 | − "scope_id" | |
| 132 | − ], | |
| 127 | + "columns": ["scope_id"], | |
| 133 | 128 | "isUnique": false |
| 134 | 129 | }, |
| 135 | 130 | "connection_provider_idx": { |
| 136 | 131 | "name": "connection_provider_idx", |
| 137 | − "columns": [ | |
| 138 | − "provider" | |
| 139 | − ], | |
| 132 | + "columns": ["provider"], | |
| 140 | 133 | "isUnique": false |
| 141 | 134 | } |
| 142 | 135 | }, |
| 143 | 136 | "foreignKeys": {}, |
| 144 | 137 | "compositePrimaryKeys": { |
| 145 | 138 | "connection_scope_id_id_pk": { |
| 146 | − "columns": [ | |
| 147 | − "scope_id", | |
| 148 | − "id" | |
| 149 | − ], | |
| 139 | + "columns": ["scope_id", "id"], | |
| 150 | 140 | "name": "connection_scope_id_id_pk" |
| 151 | 141 | } |
| 152 | 142 | }, |
| @@ -209,33 +199,24 @@ · 56 unmodified lines | ||
| 209 | 199 | "indexes": { |
| 210 | 200 | "definition_scope_id_idx": { |
| 211 | 201 | "name": "definition_scope_id_idx", |
| 212 | − "columns": [ | |
| 213 | − "scope_id" | |
| 214 | − ], | |
| 202 | + "columns": ["scope_id"], | |
| 215 | 203 | "isUnique": false |
| 216 | 204 | }, |
| 217 | 205 | "definition_source_id_idx": { |
| 218 | 206 | "name": "definition_source_id_idx", |
| 219 | − "columns": [ | |
| 220 | − "source_id" | |
| 221 | − ], | |
| 207 | + "columns": ["source_id"], | |
| 222 | 208 | "isUnique": false |
| 223 | 209 | }, |
| 224 | 210 | "definition_plugin_id_idx": { |
| 225 | 211 | "name": "definition_plugin_id_idx", |
| 226 | − "columns": [ | |
| 227 | − "plugin_id" | |
| 228 | − ], | |
| 212 | + "columns": ["plugin_id"], | |
| 229 | 213 | "isUnique": false |
| 230 | 214 | } |
| 231 | 215 | }, |
| 232 | 216 | "foreignKeys": {}, |
| 233 | 217 | "compositePrimaryKeys": { |
| 234 | 218 | "definition_scope_id_id_pk": { |
| 235 | − "columns": [ | |
| 236 | − "scope_id", | |
| 237 | − "id" | |
| 238 | − ], | |
| 219 | + "columns": ["scope_id", "id"], | |
| 239 | 220 | "name": "definition_scope_id_id_pk" |
| 240 | 221 | } |
| 241 | 222 | }, |
| @@ -284,26 +265,19 @@ · 42 unmodified lines | ||
| 284 | 265 | "indexes": { |
| 285 | 266 | "google_discovery_binding_scope_id_idx": { |
| 286 | 267 | "name": "google_discovery_binding_scope_id_idx", |
| 287 | − "columns": [ | |
| 288 | − "scope_id" | |
| 289 | − ], | |
| 268 | + "columns": ["scope_id"], | |
| 290 | 269 | "isUnique": false |
| 291 | 270 | }, |
| 292 | 271 | "google_discovery_binding_source_id_idx": { |
| 293 | 272 | "name": "google_discovery_binding_source_id_idx", |
| 294 | − "columns": [ | |
| 295 | − "source_id" | |
| 296 | − ], | |
| 273 | + "columns": ["source_id"], | |
| 297 | 274 | "isUnique": false |
| 298 | 275 | } |
| 299 | 276 | }, |
| 300 | 277 | "foreignKeys": {}, |
| 301 | 278 | "compositePrimaryKeys": { |
| 302 | 279 | "google_discovery_binding_scope_id_id_pk": { |
| 303 | − "columns": [ | |
| 304 | − "scope_id", | |
| 305 | − "id" | |
| 306 | − ], | |
| 280 | + "columns": ["scope_id", "id"], | |
| 307 | 281 | "name": "google_discovery_binding_scope_id_id_pk" |
| 308 | 282 | } |
| 309 | 283 | }, |
| @@ -359,19 +333,14 @@ · 49 unmodified lines | ||
| 359 | 333 | "indexes": { |
| 360 | 334 | "google_discovery_source_scope_id_idx": { |
| 361 | 335 | "name": "google_discovery_source_scope_id_idx", |
| 362 | − "columns": [ | |
| 363 | − "scope_id" | |
| 364 | − ], | |
| 336 | + "columns": ["scope_id"], | |
| 365 | 337 | "isUnique": false |
| 366 | 338 | } |
| 367 | 339 | }, |
| 368 | 340 | "foreignKeys": {}, |
| 369 | 341 | "compositePrimaryKeys": { |
| 370 | 342 | "google_discovery_source_scope_id_id_pk": { |
| 371 | − "columns": [ | |
| 372 | − "scope_id", | |
| 373 | − "id" | |
| 374 | − ], | |
| 343 | + "columns": ["scope_id", "id"], | |
| 375 | 344 | "name": "google_discovery_source_scope_id_id_pk" |
| 376 | 345 | } |
| 377 | 346 | }, |
| @@ -413,26 +382,19 @@ · 35 unmodified lines | ||
| 413 | 382 | "indexes": { |
| 414 | 383 | "graphql_operation_scope_id_idx": { |
| 415 | 384 | "name": "graphql_operation_scope_id_idx", |
| 416 | − "columns": [ | |
| 417 | − "scope_id" | |
| 418 | − ], | |
| 385 | + "columns": ["scope_id"], | |
| 419 | 386 | "isUnique": false |
| 420 | 387 | }, |
| 421 | 388 | "graphql_operation_source_id_idx": { |
| 422 | 389 | "name": "graphql_operation_source_id_idx", |
| 423 | − "columns": [ | |
| 424 | − "source_id" | |
| 425 | − ], | |
| 390 | + "columns": ["source_id"], | |
| 426 | 391 | "isUnique": false |
| 427 | 392 | } |
| 428 | 393 | }, |
| 429 | 394 | "foreignKeys": {}, |
| 430 | 395 | "compositePrimaryKeys": { |
| 431 | 396 | "graphql_operation_scope_id_id_pk": { |
| 432 | − "columns": [ | |
| 433 | − "scope_id", | |
| 434 | − "id" | |
| 435 | − ], | |
| 397 | + "columns": ["scope_id", "id"], | |
| 436 | 398 | "name": "graphql_operation_scope_id_id_pk" |
| 437 | 399 | } |
| 438 | 400 | }, |
| @@ -495,19 +457,14 @@ · 56 unmodified lines | ||
| 495 | 457 | "indexes": { |
| 496 | 458 | "graphql_source_scope_id_idx": { |
| 497 | 459 | "name": "graphql_source_scope_id_idx", |
| 498 | − "columns": [ | |
| 499 | − "scope_id" | |
| 500 | − ], | |
| 460 | + "columns": ["scope_id"], | |
| 501 | 461 | "isUnique": false |
| 502 | 462 | } |
| 503 | 463 | }, |
| 504 | 464 | "foreignKeys": {}, |
| 505 | 465 | "compositePrimaryKeys": { |
| 506 | 466 | "graphql_source_scope_id_id_pk": { |
| 507 | − "columns": [ | |
| 508 | − "scope_id", | |
| 509 | − "id" | |
| 510 | − ], | |
| 467 | + "columns": ["scope_id", "id"], | |
| 511 | 468 | "name": "graphql_source_scope_id_id_pk" |
| 512 | 469 | } |
| 513 | 470 | }, |
| @@ -556,26 +513,19 @@ · 42 unmodified lines | ||
| 556 | 513 | "indexes": { |
| 557 | 514 | "mcp_binding_scope_id_idx": { |
| 558 | 515 | "name": "mcp_binding_scope_id_idx", |
| 559 | − "columns": [ | |
| 560 | − "scope_id" | |
| 561 | − ], | |
| 516 | + "columns": ["scope_id"], | |
| 562 | 517 | "isUnique": false |
| 563 | 518 | }, |
| 564 | 519 | "mcp_binding_source_id_idx": { |
| 565 | 520 | "name": "mcp_binding_source_id_idx", |
| 566 | − "columns": [ | |
| 567 | − "source_id" | |
| 568 | − ], | |
| 521 | + "columns": ["source_id"], | |
| 569 | 522 | "isUnique": false |
| 570 | 523 | } |
| 571 | 524 | }, |
| 572 | 525 | "foreignKeys": {}, |
| 573 | 526 | "compositePrimaryKeys": { |
| 574 | 527 | "mcp_binding_scope_id_id_pk": { |
| 575 | − "columns": [ | |
| 576 | − "scope_id", | |
| 577 | − "id" | |
| 578 | − ], | |
| 528 | + "columns": ["scope_id", "id"], | |
| 579 | 529 | "name": "mcp_binding_scope_id_id_pk" |
| 580 | 530 | } |
| 581 | 531 | }, |
| @@ -624,19 +574,14 @@ · 42 unmodified lines | ||
| 624 | 574 | "indexes": { |
| 625 | 575 | "mcp_source_scope_id_idx": { |
| 626 | 576 | "name": "mcp_source_scope_id_idx", |
| 627 | − "columns": [ | |
| 628 | − "scope_id" | |
| 629 | − ], | |
| 577 | + "columns": ["scope_id"], | |
| 630 | 578 | "isUnique": false |
| 631 | 579 | } |
| 632 | 580 | }, |
| 633 | 581 | "foreignKeys": {}, |
| 634 | 582 | "compositePrimaryKeys": { |
| 635 | 583 | "mcp_source_scope_id_id_pk": { |
| 636 | − "columns": [ | |
| 637 | − "scope_id", | |
| 638 | − "id" | |
| 639 | − ], | |
| 584 | + "columns": ["scope_id", "id"], | |
| 640 | 585 | "name": "mcp_source_scope_id_id_pk" |
| 641 | 586 | } |
| 642 | 587 | }, |
| @@ -720,33 +665,24 @@ · 77 unmodified lines | ||
| 720 | 665 | "indexes": { |
| 721 | 666 | "oauth2_session_scope_id_idx": { |
| 722 | 667 | "name": "oauth2_session_scope_id_idx", |
| 723 | − "columns": [ | |
| 724 | − "scope_id" | |
| 725 | − ], | |
| 668 | + "columns": ["scope_id"], | |
| 726 | 669 | "isUnique": false |
| 727 | 670 | }, |
| 728 | 671 | "oauth2_session_plugin_id_idx": { |
| 729 | 672 | "name": "oauth2_session_plugin_id_idx", |
| 730 | − "columns": [ | |
| 731 | − "plugin_id" | |
| 732 | − ], | |
| 673 | + "columns": ["plugin_id"], | |
| 733 | 674 | "isUnique": false |
| 734 | 675 | }, |
| 735 | 676 | "oauth2_session_connection_id_idx": { |
| 736 | 677 | "name": "oauth2_session_connection_id_idx", |
| 737 | − "columns": [ | |
| 738 | − "connection_id" | |
| 739 | − ], | |
| 678 | + "columns": ["connection_id"], | |
| 740 | 679 | "isUnique": false |
| 741 | 680 | } |
| 742 | 681 | }, |
| 743 | 682 | "foreignKeys": {}, |
| 744 | 683 | "compositePrimaryKeys": { |
| 745 | 684 | "oauth2_session_scope_id_id_pk": { |
| 746 | − "columns": [ | |
| 747 | − "scope_id", | |
| 748 | − "id" | |
| 749 | − ], | |
| 685 | + "columns": ["scope_id", "id"], | |
| 750 | 686 | "name": "oauth2_session_scope_id_id_pk" |
| 751 | 687 | } |
| 752 | 688 | }, |
| @@ -788,26 +724,19 @@ · 35 unmodified lines | ||
| 788 | 724 | "indexes": { |
| 789 | 725 | "openapi_operation_scope_id_idx": { |
| 790 | 726 | "name": "openapi_operation_scope_id_idx", |
| 791 | − "columns": [ | |
| 792 | − "scope_id" | |
| 793 | − ], | |
| 727 | + "columns": ["scope_id"], | |
| 794 | 728 | "isUnique": false |
| 795 | 729 | }, |
| 796 | 730 | "openapi_operation_source_id_idx": { |
| 797 | 731 | "name": "openapi_operation_source_id_idx", |
| 798 | − "columns": [ | |
| 799 | − "source_id" | |
| 800 | − ], | |
| 732 | + "columns": ["source_id"], | |
| 801 | 733 | "isUnique": false |
| 802 | 734 | } |
| 803 | 735 | }, |
| 804 | 736 | "foreignKeys": {}, |
| 805 | 737 | "compositePrimaryKeys": { |
| 806 | 738 | "openapi_operation_scope_id_id_pk": { |
| 807 | − "columns": [ | |
| 808 | − "scope_id", | |
| 809 | − "id" | |
| 810 | − ], | |
| 739 | + "columns": ["scope_id", "id"], | |
| 811 | 740 | "name": "openapi_operation_scope_id_id_pk" |
| 812 | 741 | } |
| 813 | 742 | }, |
| @@ -891,19 +820,14 @@ · 77 unmodified lines | ||
| 891 | 820 | "indexes": { |
| 892 | 821 | "openapi_source_scope_id_idx": { |
| 893 | 822 | "name": "openapi_source_scope_id_idx", |
| 894 | − "columns": [ | |
| 895 | − "scope_id" | |
| 896 | − ], | |
| 823 | + "columns": ["scope_id"], | |
| 897 | 824 | "isUnique": false |
| 898 | 825 | } |
| 899 | 826 | }, |
| 900 | 827 | "foreignKeys": {}, |
| 901 | 828 | "compositePrimaryKeys": { |
| 902 | 829 | "openapi_source_scope_id_id_pk": { |
| 903 | − "columns": [ | |
| 904 | − "scope_id", | |
| 905 | − "id" | |
| 906 | − ], | |
| 830 | + "columns": ["scope_id", "id"], | |
| 907 | 831 | "name": "openapi_source_scope_id_id_pk" |
| 908 | 832 | } |
| 909 | 833 | }, |
| @@ -973,30 +897,22 @@ · 63 unmodified lines | ||
| 973 | 897 | "indexes": { |
| 974 | 898 | "openapi_source_binding_source_id_idx": { |
| 975 | 899 | "name": "openapi_source_binding_source_id_idx", |
| 976 | − "columns": [ | |
| 977 | − "source_id" | |
| 978 | − ], | |
| 900 | + "columns": ["source_id"], | |
| 979 | 901 | "isUnique": false |
| 980 | 902 | }, |
| 981 | 903 | "openapi_source_binding_source_scope_id_idx": { |
| 982 | 904 | "name": "openapi_source_binding_source_scope_id_idx", |
| 983 | − "columns": [ | |
| 984 | − "source_scope_id" | |
| 985 | − ], | |
| 905 | + "columns": ["source_scope_id"], | |
| 986 | 906 | "isUnique": false |
| 987 | 907 | }, |
| 988 | 908 | "openapi_source_binding_target_scope_id_idx": { |
| 989 | 909 | "name": "openapi_source_binding_target_scope_id_idx", |
| 990 | − "columns": [ | |
| 991 | − "target_scope_id" | |
| 992 | − ], | |
| 910 | + "columns": ["target_scope_id"], | |
| 993 | 911 | "isUnique": false |
| 994 | 912 | }, |
| 995 | 913 | "openapi_source_binding_slot_idx": { |
| 996 | 914 | "name": "openapi_source_binding_slot_idx", |
| 997 | − "columns": [ | |
| 998 | − "slot" | |
| 999 | − ], | |
| 915 | + "columns": ["slot"], | |
| 1000 | 916 | "isUnique": false |
| 1001 | 917 | } |
| 1002 | 918 | }, |
| @@ -1054,33 +970,24 @@ · 51 unmodified lines | ||
| 1054 | 970 | "indexes": { |
| 1055 | 971 | "secret_scope_id_idx": { |
| 1056 | 972 | "name": "secret_scope_id_idx", |
| 1057 | − "columns": [ | |
| 1058 | − "scope_id" | |
| 1059 | − ], | |
| 973 | + "columns": ["scope_id"], | |
| 1060 | 974 | "isUnique": false |
| 1061 | 975 | }, |
| 1062 | 976 | "secret_provider_idx": { |
| 1063 | 977 | "name": "secret_provider_idx", |
| 1064 | − "columns": [ | |
| 1065 | − "provider" | |
| 1066 | − ], | |
| 978 | + "columns": ["provider"], | |
| 1067 | 979 | "isUnique": false |
| 1068 | 980 | }, |
| 1069 | 981 | "secret_owned_by_connection_id_idx": { |
| 1070 | 982 | "name": "secret_owned_by_connection_id_idx", |
| 1071 | − "columns": [ | |
| 1072 | − "owned_by_connection_id" | |
| 1073 | − ], | |
| 983 | + "columns": ["owned_by_connection_id"], | |
| 1074 | 984 | "isUnique": false |
| 1075 | 985 | } |
| 1076 | 986 | }, |
| 1077 | 987 | "foreignKeys": {}, |
| 1078 | 988 | "compositePrimaryKeys": { |
| 1079 | 989 | "secret_scope_id_id_pk": { |
| 1080 | − "columns": [ | |
| 1081 | − "scope_id", | |
| 1082 | − "id" | |
| 1083 | − ], | |
| 990 | + "columns": ["scope_id", "id"], | |
| 1084 | 991 | "name": "secret_scope_id_id_pk" |
| 1085 | 992 | } |
| 1086 | 993 | }, |
| @@ -1174,26 +1081,19 @@ · 87 unmodified lines | ||
| 1174 | 1081 | "indexes": { |
| 1175 | 1082 | "source_scope_id_idx": { |
| 1176 | 1083 | "name": "source_scope_id_idx", |
| 1177 | − "columns": [ | |
| 1178 | − "scope_id" | |
| 1179 | − ], | |
| 1084 | + "columns": ["scope_id"], | |
| 1180 | 1085 | "isUnique": false |
| 1181 | 1086 | }, |
| 1182 | 1087 | "source_plugin_id_idx": { |
| 1183 | 1088 | "name": "source_plugin_id_idx", |
| 1184 | − "columns": [ | |
| 1185 | − "plugin_id" | |
| 1186 | − ], | |
| 1089 | + "columns": ["plugin_id"], | |
| 1187 | 1090 | "isUnique": false |
| 1188 | 1091 | } |
| 1189 | 1092 | }, |
| 1190 | 1093 | "foreignKeys": {}, |
| 1191 | 1094 | "compositePrimaryKeys": { |
| 1192 | 1095 | "source_scope_id_id_pk": { |
| 1193 | − "columns": [ | |
| 1194 | − "scope_id", | |
| 1195 | − "id" | |
| 1196 | − ], | |
| 1096 | + "columns": ["scope_id", "id"], | |
| 1197 | 1097 | "name": "source_scope_id_id_pk" |
| 1198 | 1098 | } |
| 1199 | 1099 | }, |
| @@ -1277,33 +1177,24 @@ · 77 unmodified lines | ||
| 1277 | 1177 | "indexes": { |
| 1278 | 1178 | "tool_scope_id_idx": { |
| 1279 | 1179 | "name": "tool_scope_id_idx", |
| 1280 | − "columns": [ | |
| 1281 | − "scope_id" | |
| 1282 | − ], | |
| 1180 | + "columns": ["scope_id"], | |
| 1283 | 1181 | "isUnique": false |
| 1284 | 1182 | }, |
| 1285 | 1183 | "tool_source_id_idx": { |
| 1286 | 1184 | "name": "tool_source_id_idx", |
| 1287 | − "columns": [ | |
| 1288 | − "source_id" | |
| 1289 | − ], | |
| 1185 | + "columns": ["source_id"], | |
| 1290 | 1186 | "isUnique": false |
| 1291 | 1187 | }, |
| 1292 | 1188 | "tool_plugin_id_idx": { |
| 1293 | 1189 | "name": "tool_plugin_id_idx", |
| 1294 | − "columns": [ | |
| 1295 | − "plugin_id" | |
| 1296 | − ], | |
| 1190 | + "columns": ["plugin_id"], | |
| 1297 | 1191 | "isUnique": false |
| 1298 | 1192 | } |
| 1299 | 1193 | }, |
| 1300 | 1194 | "foreignKeys": {}, |
| 1301 | 1195 | "compositePrimaryKeys": { |
| 1302 | 1196 | "tool_scope_id_id_pk": { |
| 1303 | − "columns": [ | |
| 1304 | − "scope_id", | |
| 1305 | − "id" | |
| 1306 | − ], | |
| 1197 | + "columns": ["scope_id", "id"], | |
| 1307 | 1198 | "name": "tool_scope_id_id_pk" |
| 1308 | 1199 | } |
| 1309 | 1200 | }, |
| @@ -1366,20 +1257,14 @@ · 56 unmodified lines | ||
| 1366 | 1257 | "indexes": { |
| 1367 | 1258 | "tool_policy_scope_id_position_idx": { |
| 1368 | 1259 | "name": "tool_policy_scope_id_position_idx", |
| 1369 | − "columns": [ | |
| 1370 | − "scope_id", | |
| 1371 | − "position" | |
| 1372 | − ], | |
| 1260 | + "columns": ["scope_id", "position"], | |
| 1373 | 1261 | "isUnique": false |
| 1374 | 1262 | } |
| 1375 | 1263 | }, |
| 1376 | 1264 | "foreignKeys": {}, |
| 1377 | 1265 | "compositePrimaryKeys": { |
| 1378 | 1266 | "tool_policy_scope_id_id_pk": { |
| 1379 | − "columns": [ | |
| 1380 | − "scope_id", | |
| 1381 | − "id" | |
| 1382 | − ], | |
| 1267 | + "columns": ["scope_id", "id"], | |
| 1383 | 1268 | "name": "tool_policy_scope_id_id_pk" |
| 1384 | 1269 | } |
| 1385 | 1270 | }, |
| @@ -1397,4 +1282,4 @@ · 11 unmodified lines | ||
| 1397 | 1282 | "internal": { |
| 1398 | 1283 | "indexes": {} |
| 1399 | 1284 | } |
| 1400 | −} | |
| 1285 | +} | |
| @@ -33,10 +33,7 @@ · 32 unmodified lines | ||
| 33 | 33 | "foreignKeys": {}, |
| 34 | 34 | "compositePrimaryKeys": { |
| 35 | 35 | "blob_namespace_key_pk": { |
| 36 | − "columns": [ | |
| 37 | − "namespace", | |
| 38 | − "key" | |
| 39 | − ], | |
| 36 | + "columns": ["namespace", "key"], | |
| 40 | 37 | "name": "blob_namespace_key_pk" |
| 41 | 38 | } |
| 42 | 39 | }, |
| @@ -127,26 +124,19 @@ · 84 unmodified lines | ||
| 127 | 124 | "indexes": { |
| 128 | 125 | "connection_scope_id_idx": { |
| 129 | 126 | "name": "connection_scope_id_idx", |
| 130 | − "columns": [ | |
| 131 | − "scope_id" | |
| 132 | − ], | |
| 127 | + "columns": ["scope_id"], | |
| 133 | 128 | "isUnique": false |
| 134 | 129 | }, |
| 135 | 130 | "connection_provider_idx": { |
| 136 | 131 | "name": "connection_provider_idx", |
| 137 | − "columns": [ | |
| 138 | − "provider" | |
| 139 | − ], | |
| 132 | + "columns": ["provider"], | |
| 140 | 133 | "isUnique": false |
| 141 | 134 | } |
| 142 | 135 | }, |
| 143 | 136 | "foreignKeys": {}, |
| 144 | 137 | "compositePrimaryKeys": { |
| 145 | 138 | "connection_scope_id_id_pk": { |
| 146 | − "columns": [ | |
| 147 | − "scope_id", | |
| 148 | − "id" | |
| 149 | − ], | |
| 139 | + "columns": ["scope_id", "id"], | |
| 150 | 140 | "name": "connection_scope_id_id_pk" |
| 151 | 141 | } |
| 152 | 142 | }, |
| @@ -209,33 +199,24 @@ · 56 unmodified lines | ||
| 209 | 199 | "indexes": { |
| 210 | 200 | "definition_scope_id_idx": { |
| 211 | 201 | "name": "definition_scope_id_idx", |
| 212 | − "columns": [ | |
| 213 | − "scope_id" | |
| 214 | − ], | |
| 202 | + "columns": ["scope_id"], | |
| 215 | 203 | "isUnique": false |
| 216 | 204 | }, |
| 217 | 205 | "definition_source_id_idx": { |
| 218 | 206 | "name": "definition_source_id_idx", |
| 219 | − "columns": [ | |
| 220 | − "source_id" | |
| 221 | − ], | |
| 207 | + "columns": ["source_id"], | |
| 222 | 208 | "isUnique": false |
| 223 | 209 | }, |
| 224 | 210 | "definition_plugin_id_idx": { |
| 225 | 211 | "name": "definition_plugin_id_idx", |
| 226 | − "columns": [ | |
| 227 | − "plugin_id" | |
| 228 | − ], | |
| 212 | + "columns": ["plugin_id"], | |
| 229 | 213 | "isUnique": false |
| 230 | 214 | } |
| 231 | 215 | }, |
| 232 | 216 | "foreignKeys": {}, |
| 233 | 217 | "compositePrimaryKeys": { |
| 234 | 218 | "definition_scope_id_id_pk": { |
| 235 | − "columns": [ | |
| 236 | − "scope_id", | |
| 237 | − "id" | |
| 238 | − ], | |
| 219 | + "columns": ["scope_id", "id"], | |
| 239 | 220 | "name": "definition_scope_id_id_pk" |
| 240 | 221 | } |
| 241 | 222 | }, |
| @@ -284,26 +265,19 @@ · 42 unmodified lines | ||
| 284 | 265 | "indexes": { |
| 285 | 266 | "google_discovery_binding_scope_id_idx": { |
| 286 | 267 | "name": "google_discovery_binding_scope_id_idx", |
| 287 | − "columns": [ | |
| 288 | − "scope_id" | |
| 289 | − ], | |
| 268 | + "columns": ["scope_id"], | |
| 290 | 269 | "isUnique": false |
| 291 | 270 | }, |
| 292 | 271 | "google_discovery_binding_source_id_idx": { |
| 293 | 272 | "name": "google_discovery_binding_source_id_idx", |
| 294 | − "columns": [ | |
| 295 | − "source_id" | |
| 296 | − ], | |
| 273 | + "columns": ["source_id"], | |
| 297 | 274 | "isUnique": false |
| 298 | 275 | } |
| 299 | 276 | }, |
| 300 | 277 | "foreignKeys": {}, |
| 301 | 278 | "compositePrimaryKeys": { |
| 302 | 279 | "google_discovery_binding_scope_id_id_pk": { |
| 303 | − "columns": [ | |
| 304 | − "scope_id", | |
| 305 | − "id" | |
| 306 | − ], | |
| 280 | + "columns": ["scope_id", "id"], | |
| 307 | 281 | "name": "google_discovery_binding_scope_id_id_pk" |
| 308 | 282 | } |
| 309 | 283 | }, |
| @@ -395,40 +369,29 @@ · 85 unmodified lines | ||
| 395 | 369 | "indexes": { |
| 396 | 370 | "google_discovery_source_scope_id_idx": { |
| 397 | 371 | "name": "google_discovery_source_scope_id_idx", |
| 398 | − "columns": [ | |
| 399 | − "scope_id" | |
| 400 | − ], | |
| 372 | + "columns": ["scope_id"], | |
| 401 | 373 | "isUnique": false |
| 402 | 374 | }, |
| 403 | 375 | "google_discovery_source_auth_connection_id_idx": { |
| 404 | 376 | "name": "google_discovery_source_auth_connection_id_idx", |
| 405 | − "columns": [ | |
| 406 | − "auth_connection_id" | |
| 407 | − ], | |
| 377 | + "columns": ["auth_connection_id"], | |
| 408 | 378 | "isUnique": false |
| 409 | 379 | }, |
| 410 | 380 | "google_discovery_source_auth_client_id_secret_id_idx": { |
| 411 | 381 | "name": "google_discovery_source_auth_client_id_secret_id_idx", |
| 412 | − "columns": [ | |
| 413 | − "auth_client_id_secret_id" | |
| 414 | − ], | |
| 382 | + "columns": ["auth_client_id_secret_id"], | |
| 415 | 383 | "isUnique": false |
| 416 | 384 | }, |
| 417 | 385 | "google_discovery_source_auth_client_secret_secret_id_idx": { |
| 418 | 386 | "name": "google_discovery_source_auth_client_secret_secret_id_idx", |
| 419 | − "columns": [ | |
| 420 | − "auth_client_secret_secret_id" | |
| 421 | − ], | |
| 387 | + "columns": ["auth_client_secret_secret_id"], | |
| 422 | 388 | "isUnique": false |
| 423 | 389 | } |
| 424 | 390 | }, |
| 425 | 391 | "foreignKeys": {}, |
| 426 | 392 | "compositePrimaryKeys": { |
| 427 | 393 | "google_discovery_source_scope_id_id_pk": { |
| 428 | − "columns": [ | |
| 429 | − "scope_id", | |
| 430 | − "id" | |
| 431 | − ], | |
| 394 | + "columns": ["scope_id", "id"], | |
| 432 | 395 | "name": "google_discovery_source_scope_id_id_pk" |
| 433 | 396 | } |
| 434 | 397 | }, |
| @@ -470,26 +433,19 @@ · 35 unmodified lines | ||
| 470 | 433 | "indexes": { |
| 471 | 434 | "graphql_operation_scope_id_idx": { |
| 472 | 435 | "name": "graphql_operation_scope_id_idx", |
| 473 | − "columns": [ | |
| 474 | − "scope_id" | |
| 475 | − ], | |
| 436 | + "columns": ["scope_id"], | |
| 476 | 437 | "isUnique": false |
| 477 | 438 | }, |
| 478 | 439 | "graphql_operation_source_id_idx": { |
| 479 | 440 | "name": "graphql_operation_source_id_idx", |
| 480 | − "columns": [ | |
| 481 | − "source_id" | |
| 482 | − ], | |
| 441 | + "columns": ["source_id"], | |
| 483 | 442 | "isUnique": false |
| 484 | 443 | } |
| 485 | 444 | }, |
| 486 | 445 | "foreignKeys": {}, |
| 487 | 446 | "compositePrimaryKeys": { |
| 488 | 447 | "graphql_operation_scope_id_id_pk": { |
| 489 | − "columns": [ | |
| 490 | − "scope_id", | |
| 491 | − "id" | |
| 492 | − ], | |
| 448 | + "columns": ["scope_id", "id"], | |
| 493 | 449 | "name": "graphql_operation_scope_id_id_pk" |
| 494 | 450 | } |
| 495 | 451 | }, |
| @@ -546,26 +502,19 @@ · 50 unmodified lines | ||
| 546 | 502 | "indexes": { |
| 547 | 503 | "graphql_source_scope_id_idx": { |
| 548 | 504 | "name": "graphql_source_scope_id_idx", |
| 549 | − "columns": [ | |
| 550 | − "scope_id" | |
| 551 | − ], | |
| 505 | + "columns": ["scope_id"], | |
| 552 | 506 | "isUnique": false |
| 553 | 507 | }, |
| 554 | 508 | "graphql_source_auth_connection_id_idx": { |
| 555 | 509 | "name": "graphql_source_auth_connection_id_idx", |
| 556 | − "columns": [ | |
| 557 | − "auth_connection_id" | |
| 558 | − ], | |
| 510 | + "columns": ["auth_connection_id"], | |
| 559 | 511 | "isUnique": false |
| 560 | 512 | } |
| 561 | 513 | }, |
| 562 | 514 | "foreignKeys": {}, |
| 563 | 515 | "compositePrimaryKeys": { |
| 564 | 516 | "graphql_source_scope_id_id_pk": { |
| 565 | − "columns": [ | |
| 566 | − "scope_id", | |
| 567 | − "id" | |
| 568 | − ], | |
| 517 | + "columns": ["scope_id", "id"], | |
| 569 | 518 | "name": "graphql_source_scope_id_id_pk" |
| 570 | 519 | } |
| 571 | 520 | }, |
| @@ -614,26 +563,19 @@ · 42 unmodified lines | ||
| 614 | 563 | "indexes": { |
| 615 | 564 | "mcp_binding_scope_id_idx": { |
| 616 | 565 | "name": "mcp_binding_scope_id_idx", |
| 617 | − "columns": [ | |
| 618 | − "scope_id" | |
| 619 | − ], | |
| 566 | + "columns": ["scope_id"], | |
| 620 | 567 | "isUnique": false |
| 621 | 568 | }, |
| 622 | 569 | "mcp_binding_source_id_idx": { |
| 623 | 570 | "name": "mcp_binding_source_id_idx", |
| 624 | − "columns": [ | |
| 625 | − "source_id" | |
| 626 | − ], | |
| 571 | + "columns": ["source_id"], | |
| 627 | 572 | "isUnique": false |
| 628 | 573 | } |
| 629 | 574 | }, |
| 630 | 575 | "foreignKeys": {}, |
| 631 | 576 | "compositePrimaryKeys": { |
| 632 | 577 | "mcp_binding_scope_id_id_pk": { |
| 633 | − "columns": [ | |
| 634 | − "scope_id", | |
| 635 | − "id" | |
| 636 | − ], | |
| 578 | + "columns": ["scope_id", "id"], | |
| 637 | 579 | "name": "mcp_binding_scope_id_id_pk" |
| 638 | 580 | } |
| 639 | 581 | }, |
| @@ -732,47 +674,34 @@ · 92 unmodified lines | ||
| 732 | 674 | "indexes": { |
| 733 | 675 | "mcp_source_scope_id_idx": { |
| 734 | 676 | "name": "mcp_source_scope_id_idx", |
| 735 | − "columns": [ | |
| 736 | − "scope_id" | |
| 737 | − ], | |
| 677 | + "columns": ["scope_id"], | |
| 738 | 678 | "isUnique": false |
| 739 | 679 | }, |
| 740 | 680 | "mcp_source_auth_secret_id_idx": { |
| 741 | 681 | "name": "mcp_source_auth_secret_id_idx", |
| 742 | − "columns": [ | |
| 743 | − "auth_secret_id" | |
| 744 | − ], | |
| 682 | + "columns": ["auth_secret_id"], | |
| 745 | 683 | "isUnique": false |
| 746 | 684 | }, |
| 747 | 685 | "mcp_source_auth_connection_id_idx": { |
| 748 | 686 | "name": "mcp_source_auth_connection_id_idx", |
| 749 | − "columns": [ | |
| 750 | − "auth_connection_id" | |
| 751 | − ], | |
| 687 | + "columns": ["auth_connection_id"], | |
| 752 | 688 | "isUnique": false |
| 753 | 689 | }, |
| 754 | 690 | "mcp_source_auth_client_id_secret_id_idx": { |
| 755 | 691 | "name": "mcp_source_auth_client_id_secret_id_idx", |
| 756 | − "columns": [ | |
| 757 | − "auth_client_id_secret_id" | |
| 758 | − ], | |
| 692 | + "columns": ["auth_client_id_secret_id"], | |
| 759 | 693 | "isUnique": false |
| 760 | 694 | }, |
| 761 | 695 | "mcp_source_auth_client_secret_secret_id_idx": { |
| 762 | 696 | "name": "mcp_source_auth_client_secret_secret_id_idx", |
| 763 | − "columns": [ | |
| 764 | − "auth_client_secret_secret_id" | |
| 765 | − ], | |
| 697 | + "columns": ["auth_client_secret_secret_id"], | |
| 766 | 698 | "isUnique": false |
| 767 | 699 | } |
| 768 | 700 | }, |
| 769 | 701 | "foreignKeys": {}, |
| 770 | 702 | "compositePrimaryKeys": { |
| 771 | 703 | "mcp_source_scope_id_id_pk": { |
| 772 | − "columns": [ | |
| 773 | − "scope_id", | |
| 774 | − "id" | |
| 775 | − ], | |
| 704 | + "columns": ["scope_id", "id"], | |
| 776 | 705 | "name": "mcp_source_scope_id_id_pk" |
| 777 | 706 | } |
| 778 | 707 | }, |
| @@ -856,33 +785,24 @@ · 77 unmodified lines | ||
| 856 | 785 | "indexes": { |
| 857 | 786 | "oauth2_session_scope_id_idx": { |
| 858 | 787 | "name": "oauth2_session_scope_id_idx", |
| 859 | − "columns": [ | |
| 860 | − "scope_id" | |
| 861 | − ], | |
| 788 | + "columns": ["scope_id"], | |
| 862 | 789 | "isUnique": false |
| 863 | 790 | }, |
| 864 | 791 | "oauth2_session_plugin_id_idx": { |
| 865 | 792 | "name": "oauth2_session_plugin_id_idx", |
| 866 | − "columns": [ | |
| 867 | − "plugin_id" | |
| 868 | − ], | |
| 793 | + "columns": ["plugin_id"], | |
| 869 | 794 | "isUnique": false |
| 870 | 795 | }, |
| 871 | 796 | "oauth2_session_connection_id_idx": { |
| 872 | 797 | "name": "oauth2_session_connection_id_idx", |
| 873 | − "columns": [ | |
| 874 | − "connection_id" | |
| 875 | − ], | |
| 798 | + "columns": ["connection_id"], | |
| 876 | 799 | "isUnique": false |
| 877 | 800 | } |
| 878 | 801 | }, |
| 879 | 802 | "foreignKeys": {}, |
| 880 | 803 | "compositePrimaryKeys": { |
| 881 | 804 | "oauth2_session_scope_id_id_pk": { |
| 882 | − "columns": [ | |
| 883 | − "scope_id", | |
| 884 | − "id" | |
| 885 | − ], | |
| 805 | + "columns": ["scope_id", "id"], | |
| 886 | 806 | "name": "oauth2_session_scope_id_id_pk" |
| 887 | 807 | } |
| 888 | 808 | }, |
| @@ -924,26 +844,19 @@ · 35 unmodified lines | ||
| 924 | 844 | "indexes": { |
| 925 | 845 | "openapi_operation_scope_id_idx": { |
| 926 | 846 | "name": "openapi_operation_scope_id_idx", |
| 927 | − "columns": [ | |
| 928 | − "scope_id" | |
| 929 | − ], | |
| 847 | + "columns": ["scope_id"], | |
| 930 | 848 | "isUnique": false |
| 931 | 849 | }, |
| 932 | 850 | "openapi_operation_source_id_idx": { |
| 933 | 851 | "name": "openapi_operation_source_id_idx", |
| 934 | − "columns": [ | |
| 935 | − "source_id" | |
| 936 | − ], | |
| 852 | + "columns": ["source_id"], | |
| 937 | 853 | "isUnique": false |
| 938 | 854 | } |
| 939 | 855 | }, |
| 940 | 856 | "foreignKeys": {}, |
| 941 | 857 | "compositePrimaryKeys": { |
| 942 | 858 | "openapi_operation_scope_id_id_pk": { |
| 943 | − "columns": [ | |
| 944 | − "scope_id", | |
| 945 | − "id" | |
| 946 | − ], | |
| 859 | + "columns": ["scope_id", "id"], | |
| 947 | 860 | "name": "openapi_operation_scope_id_id_pk" |
| 948 | 861 | } |
| 949 | 862 | }, |
| @@ -1013,19 +926,14 @@ · 63 unmodified lines | ||
| 1013 | 926 | "indexes": { |
| 1014 | 927 | "openapi_source_scope_id_idx": { |
| 1015 | 928 | "name": "openapi_source_scope_id_idx", |
| 1016 | − "columns": [ | |
| 1017 | − "scope_id" | |
| 1018 | − ], | |
| 929 | + "columns": ["scope_id"], | |
| 1019 | 930 | "isUnique": false |
| 1020 | 931 | } |
| 1021 | 932 | }, |
| 1022 | 933 | "foreignKeys": {}, |
| 1023 | 934 | "compositePrimaryKeys": { |
| 1024 | 935 | "openapi_source_scope_id_id_pk": { |
| 1025 | − "columns": [ | |
| 1026 | − "scope_id", | |
| 1027 | − "id" | |
| 1028 | − ], | |
| 936 | + "columns": ["scope_id", "id"], | |
| 1029 | 937 | "name": "openapi_source_scope_id_id_pk" |
| 1030 | 938 | } |
| 1031 | 939 | }, |
| @@ -1117,44 +1025,32 @@ · 85 unmodified lines | ||
| 1117 | 1025 | "indexes": { |
| 1118 | 1026 | "openapi_source_binding_source_id_idx": { |
| 1119 | 1027 | "name": "openapi_source_binding_source_id_idx", |
| 1120 | − "columns": [ | |
| 1121 | − "source_id" | |
| 1122 | − ], | |
| 1028 | + "columns": ["source_id"], | |
| 1123 | 1029 | "isUnique": false |
| 1124 | 1030 | }, |
| 1125 | 1031 | "openapi_source_binding_source_scope_id_idx": { |
| 1126 | 1032 | "name": "openapi_source_binding_source_scope_id_idx", |
| 1127 | − "columns": [ | |
| 1128 | − "source_scope_id" | |
| 1129 | − ], | |
| 1033 | + "columns": ["source_scope_id"], | |
| 1130 | 1034 | "isUnique": false |
| 1131 | 1035 | }, |
| 1132 | 1036 | "openapi_source_binding_target_scope_id_idx": { |
| 1133 | 1037 | "name": "openapi_source_binding_target_scope_id_idx", |
| 1134 | − "columns": [ | |
| 1135 | − "target_scope_id" | |
| 1136 | − ], | |
| 1038 | + "columns": ["target_scope_id"], | |
| 1137 | 1039 | "isUnique": false |
| 1138 | 1040 | }, |
| 1139 | 1041 | "openapi_source_binding_slot_idx": { |
| 1140 | 1042 | "name": "openapi_source_binding_slot_idx", |
| 1141 | − "columns": [ | |
| 1142 | − "slot" | |
| 1143 | − ], | |
| 1043 | + "columns": ["slot"], | |
| 1144 | 1044 | "isUnique": false |
| 1145 | 1045 | }, |
| 1146 | 1046 | "openapi_source_binding_secret_id_idx": { |
| 1147 | 1047 | "name": "openapi_source_binding_secret_id_idx", |
| 1148 | − "columns": [ | |
| 1149 | − "secret_id" | |
| 1150 | − ], | |
| 1048 | + "columns": ["secret_id"], | |
| 1151 | 1049 | "isUnique": false |
| 1152 | 1050 | }, |
| 1153 | 1051 | "openapi_source_binding_connection_id_idx": { |
| 1154 | 1052 | "name": "openapi_source_binding_connection_id_idx", |
| 1155 | − "columns": [ | |
| 1156 | − "connection_id" | |
| 1157 | − ], | |
| 1053 | + "columns": ["connection_id"], | |
| 1158 | 1054 | "isUnique": false |
| 1159 | 1055 | } |
| 1160 | 1056 | }, |
| @@ -1212,33 +1108,24 @@ · 51 unmodified lines | ||
| 1212 | 1108 | "indexes": { |
| 1213 | 1109 | "secret_scope_id_idx": { |
| 1214 | 1110 | "name": "secret_scope_id_idx", |
| 1215 | − "columns": [ | |
| 1216 | − "scope_id" | |
| 1217 | − ], | |
| 1111 | + "columns": ["scope_id"], | |
| 1218 | 1112 | "isUnique": false |
| 1219 | 1113 | }, |
| 1220 | 1114 | "secret_provider_idx": { |
| 1221 | 1115 | "name": "secret_provider_idx", |
| 1222 | − "columns": [ | |
| 1223 | − "provider" | |
| 1224 | − ], | |
| 1116 | + "columns": ["provider"], | |
| 1225 | 1117 | "isUnique": false |
| 1226 | 1118 | }, |
| 1227 | 1119 | "secret_owned_by_connection_id_idx": { |
| 1228 | 1120 | "name": "secret_owned_by_connection_id_idx", |
| 1229 | − "columns": [ | |
| 1230 | − "owned_by_connection_id" | |
| 1231 | − ], | |
| 1121 | + "columns": ["owned_by_connection_id"], | |
| 1232 | 1122 | "isUnique": false |
| 1233 | 1123 | } |
| 1234 | 1124 | }, |
| 1235 | 1125 | "foreignKeys": {}, |
| 1236 | 1126 | "compositePrimaryKeys": { |
| 1237 | 1127 | "secret_scope_id_id_pk": { |
| 1238 | − "columns": [ | |
| 1239 | − "scope_id", | |
| 1240 | − "id" | |
| 1241 | − ], | |
| 1128 | + "columns": ["scope_id", "id"], | |
| 1242 | 1129 | "name": "secret_scope_id_id_pk" |
| 1243 | 1130 | } |
| 1244 | 1131 | }, |
| @@ -1332,26 +1219,19 @@ · 87 unmodified lines | ||
| 1332 | 1219 | "indexes": { |
| 1333 | 1220 | "source_scope_id_idx": { |
| 1334 | 1221 | "name": "source_scope_id_idx", |
| 1335 | − "columns": [ | |
| 1336 | − "scope_id" | |
| 1337 | − ], | |
| 1222 | + "columns": ["scope_id"], | |
| 1338 | 1223 | "isUnique": false |
| 1339 | 1224 | }, |
| 1340 | 1225 | "source_plugin_id_idx": { |
| 1341 | 1226 | "name": "source_plugin_id_idx", |
| 1342 | − "columns": [ | |
| 1343 | − "plugin_id" | |
| 1344 | − ], | |
| 1227 | + "columns": ["plugin_id"], | |
| 1345 | 1228 | "isUnique": false |
| 1346 | 1229 | } |
| 1347 | 1230 | }, |
| 1348 | 1231 | "foreignKeys": {}, |
| 1349 | 1232 | "compositePrimaryKeys": { |
| 1350 | 1233 | "source_scope_id_id_pk": { |
| 1351 | − "columns": [ | |
| 1352 | − "scope_id", | |
| 1353 | − "id" | |
| 1354 | − ], | |
| 1234 | + "columns": ["scope_id", "id"], | |
| 1355 | 1235 | "name": "source_scope_id_id_pk" |
| 1356 | 1236 | } |
| 1357 | 1237 | }, |
| @@ -1435,33 +1315,24 @@ · 77 unmodified lines | ||
| 1435 | 1315 | "indexes": { |
| 1436 | 1316 | "tool_scope_id_idx": { |
| 1437 | 1317 | "name": "tool_scope_id_idx", |
| 1438 | − "columns": [ | |
| 1439 | − "scope_id" | |
| 1440 | − ], | |
| 1318 | + "columns": ["scope_id"], | |
| 1441 | 1319 | "isUnique": false |
| 1442 | 1320 | }, |
| 1443 | 1321 | "tool_source_id_idx": { |
| 1444 | 1322 | "name": "tool_source_id_idx", |
| 1445 | − "columns": [ | |
| 1446 | − "source_id" | |
| 1447 | − ], | |
| 1323 | + "columns": ["source_id"], | |
| 1448 | 1324 | "isUnique": false |
| 1449 | 1325 | }, |
| 1450 | 1326 | "tool_plugin_id_idx": { |
| 1451 | 1327 | "name": "tool_plugin_id_idx", |
| 1452 | − "columns": [ | |
| 1453 | − "plugin_id" | |
| 1454 | − ], | |
| 1328 | + "columns": ["plugin_id"], | |
| 1455 | 1329 | "isUnique": false |
| 1456 | 1330 | } |
| 1457 | 1331 | }, |
| 1458 | 1332 | "foreignKeys": {}, |
| 1459 | 1333 | "compositePrimaryKeys": { |
| 1460 | 1334 | "tool_scope_id_id_pk": { |
| 1461 | − "columns": [ | |
| 1462 | − "scope_id", | |
| 1463 | − "id" | |
| 1464 | − ], | |
| 1335 | + "columns": ["scope_id", "id"], | |
| 1465 | 1336 | "name": "tool_scope_id_id_pk" |
| 1466 | 1337 | } |
| 1467 | 1338 | }, |
| @@ -1524,20 +1395,14 @@ · 56 unmodified lines | ||
| 1524 | 1395 | "indexes": { |
| 1525 | 1396 | "tool_policy_scope_id_position_idx": { |
| 1526 | 1397 | "name": "tool_policy_scope_id_position_idx", |
| 1527 | − "columns": [ | |
| 1528 | − "scope_id", | |
| 1529 | − "position" | |
| 1530 | − ], | |
| 1398 | + "columns": ["scope_id", "position"], | |
| 1531 | 1399 | "isUnique": false |
| 1532 | 1400 | } |
| 1533 | 1401 | }, |
| 1534 | 1402 | "foreignKeys": {}, |
| 1535 | 1403 | "compositePrimaryKeys": { |
| 1536 | 1404 | "tool_policy_scope_id_id_pk": { |
| 1537 | − "columns": [ | |
| 1538 | − "scope_id", | |
| 1539 | − "id" | |
| 1540 | − ], | |
| 1405 | + "columns": ["scope_id", "id"], | |
| 1541 | 1406 | "name": "tool_policy_scope_id_id_pk" |
| 1542 | 1407 | } |
| 1543 | 1408 | }, |
| @@ -1607,33 +1472,24 @@ · 63 unmodified lines | ||
| 1607 | 1472 | "indexes": { |
| 1608 | 1473 | "graphql_source_header_scope_id_idx": { |
| 1609 | 1474 | "name": "graphql_source_header_scope_id_idx", |
| 1610 | − "columns": [ | |
| 1611 | − "scope_id" | |
| 1612 | − ], | |
| 1475 | + "columns": ["scope_id"], | |
| 1613 | 1476 | "isUnique": false |
| 1614 | 1477 | }, |
| 1615 | 1478 | "graphql_source_header_source_id_idx": { |
| 1616 | 1479 | "name": "graphql_source_header_source_id_idx", |
| 1617 | − "columns": [ | |
| 1618 | − "source_id" | |
| 1619 | − ], | |
| 1480 | + "columns": ["source_id"], | |
| 1620 | 1481 | "isUnique": false |
| 1621 | 1482 | }, |
| 1622 | 1483 | "graphql_source_header_secret_id_idx": { |
| 1623 | 1484 | "name": "graphql_source_header_secret_id_idx", |
| 1624 | − "columns": [ | |
| 1625 | − "secret_id" | |
| 1626 | − ], | |
| 1485 | + "columns": ["secret_id"], | |
| 1627 | 1486 | "isUnique": false |
| 1628 | 1487 | } |
| 1629 | 1488 | }, |
| 1630 | 1489 | "foreignKeys": {}, |
| 1631 | 1490 | "compositePrimaryKeys": { |
| 1632 | 1491 | "graphql_source_header_scope_id_id_pk": { |
| 1633 | − "columns": [ | |
| 1634 | − "scope_id", | |
| 1635 | − "id" | |
| 1636 | − ], | |
| 1492 | + "columns": ["scope_id", "id"], | |
| 1637 | 1493 | "name": "graphql_source_header_scope_id_id_pk" |
| 1638 | 1494 | } |
| 1639 | 1495 | }, |
| @@ -1703,33 +1559,24 @@ · 63 unmodified lines | ||
| 1703 | 1559 | "indexes": { |
| 1704 | 1560 | "graphql_source_query_param_scope_id_idx": { |
| 1705 | 1561 | "name": "graphql_source_query_param_scope_id_idx", |
| 1706 | − "columns": [ | |
| 1707 | − "scope_id" | |
| 1708 | − ], | |
| 1562 | + "columns": ["scope_id"], | |
| 1709 | 1563 | "isUnique": false |
| 1710 | 1564 | }, |
| 1711 | 1565 | "graphql_source_query_param_source_id_idx": { |
| 1712 | 1566 | "name": "graphql_source_query_param_source_id_idx", |
| 1713 | − "columns": [ | |
| 1714 | − "source_id" | |
| 1715 | − ], | |
| 1567 | + "columns": ["source_id"], | |
| 1716 | 1568 | "isUnique": false |
| 1717 | 1569 | }, |
| 1718 | 1570 | "graphql_source_query_param_secret_id_idx": { |
| 1719 | 1571 | "name": "graphql_source_query_param_secret_id_idx", |
| 1720 | − "columns": [ | |
| 1721 | − "secret_id" | |
| 1722 | − ], | |
| 1572 | + "columns": ["secret_id"], | |
| 1723 | 1573 | "isUnique": false |
| 1724 | 1574 | } |
| 1725 | 1575 | }, |
| 1726 | 1576 | "foreignKeys": {}, |
| 1727 | 1577 | "compositePrimaryKeys": { |
| 1728 | 1578 | "graphql_source_query_param_scope_id_id_pk": { |
| 1729 | − "columns": [ | |
| 1730 | − "scope_id", | |
| 1731 | − "id" | |
| 1732 | − ], | |
| 1579 | + "columns": ["scope_id", "id"], | |
| 1733 | 1580 | "name": "graphql_source_query_param_scope_id_id_pk" |
| 1734 | 1581 | } |
| 1735 | 1582 | }, |
| @@ -1799,33 +1646,24 @@ · 63 unmodified lines | ||
| 1799 | 1646 | "indexes": { |
| 1800 | 1647 | "openapi_source_query_param_scope_id_idx": { |
| 1801 | 1648 | "name": "openapi_source_query_param_scope_id_idx", |
| 1802 | − "columns": [ | |
| 1803 | − "scope_id" | |
| 1804 | − ], | |
| 1649 | + "columns": ["scope_id"], | |
| 1805 | 1650 | "isUnique": false |
| 1806 | 1651 | }, |
| 1807 | 1652 | "openapi_source_query_param_source_id_idx": { |
| 1808 | 1653 | "name": "openapi_source_query_param_source_id_idx", |
| 1809 | − "columns": [ | |
| 1810 | − "source_id" | |
| 1811 | − ], | |
| 1654 | + "columns": ["source_id"], | |
| 1812 | 1655 | "isUnique": false |
| 1813 | 1656 | }, |
| 1814 | 1657 | "openapi_source_query_param_secret_id_idx": { |
| 1815 | 1658 | "name": "openapi_source_query_param_secret_id_idx", |
| 1816 | − "columns": [ | |
| 1817 | − "secret_id" | |
| 1818 | − ], | |
| 1659 | + "columns": ["secret_id"], | |
| 1819 | 1660 | "isUnique": false |
| 1820 | 1661 | } |
| 1821 | 1662 | }, |
| 1822 | 1663 | "foreignKeys": {}, |
| 1823 | 1664 | "compositePrimaryKeys": { |
| 1824 | 1665 | "openapi_source_query_param_scope_id_id_pk": { |
| 1825 | − "columns": [ | |
| 1826 | − "scope_id", | |
| 1827 | − "id" | |
| 1828 | − ], | |
| 1666 | + "columns": ["scope_id", "id"], | |
| 1829 | 1667 | "name": "openapi_source_query_param_scope_id_id_pk" |
| 1830 | 1668 | } |
| 1831 | 1669 | }, |
| @@ -1895,33 +1733,24 @@ · 63 unmodified lines | ||
| 1895 | 1733 | "indexes": { |
| 1896 | 1734 | "openapi_source_spec_fetch_header_scope_id_idx": { |
| 1897 | 1735 | "name": "openapi_source_spec_fetch_header_scope_id_idx", |
| 1898 | − "columns": [ | |
| 1899 | − "scope_id" | |
| 1900 | − ], | |
| 1736 | + "columns": ["scope_id"], | |
| 1901 | 1737 | "isUnique": false |
| 1902 | 1738 | }, |
| 1903 | 1739 | "openapi_source_spec_fetch_header_source_id_idx": { |
| 1904 | 1740 | "name": "openapi_source_spec_fetch_header_source_id_idx", |
| 1905 | − "columns": [ | |
| 1906 | − "source_id" | |
| 1907 | − ], | |
| 1741 | + "columns": ["source_id"], | |
| 1908 | 1742 | "isUnique": false |
| 1909 | 1743 | }, |
| 1910 | 1744 | "openapi_source_spec_fetch_header_secret_id_idx": { |
| 1911 | 1745 | "name": "openapi_source_spec_fetch_header_secret_id_idx", |
| 1912 | − "columns": [ | |
| 1913 | − "secret_id" | |
| 1914 | − ], | |
| 1746 | + "columns": ["secret_id"], | |
| 1915 | 1747 | "isUnique": false |
| 1916 | 1748 | } |
| 1917 | 1749 | }, |
| 1918 | 1750 | "foreignKeys": {}, |
| 1919 | 1751 | "compositePrimaryKeys": { |
| 1920 | 1752 | "openapi_source_spec_fetch_header_scope_id_id_pk": { |
| 1921 | − "columns": [ | |
| 1922 | − "scope_id", | |
| 1923 | − "id" | |
| 1924 | − ], | |
| 1753 | + "columns": ["scope_id", "id"], | |
| 1925 | 1754 | "name": "openapi_source_spec_fetch_header_scope_id_id_pk" |
| 1926 | 1755 | } |
| 1927 | 1756 | }, |
| @@ -1991,33 +1820,24 @@ · 63 unmodified lines | ||
| 1991 | 1820 | "indexes": { |
| 1992 | 1821 | "openapi_source_spec_fetch_query_param_scope_id_idx": { |
| 1993 | 1822 | "name": "openapi_source_spec_fetch_query_param_scope_id_idx", |
| 1994 | − "columns": [ | |
| 1995 | − "scope_id" | |
| 1996 | − ], | |
| 1823 | + "columns": ["scope_id"], | |
| 1997 | 1824 | "isUnique": false |
| 1998 | 1825 | }, |
| 1999 | 1826 | "openapi_source_spec_fetch_query_param_source_id_idx": { |
| 2000 | 1827 | "name": "openapi_source_spec_fetch_query_param_source_id_idx", |
| 2001 | − "columns": [ | |
| 2002 | − "source_id" | |
| 2003 | − ], | |
| 1828 | + "columns": ["source_id"], | |
| 2004 | 1829 | "isUnique": false |
| 2005 | 1830 | }, |
| 2006 | 1831 | "openapi_source_spec_fetch_query_param_secret_id_idx": { |
| 2007 | 1832 | "name": "openapi_source_spec_fetch_query_param_secret_id_idx", |
| 2008 | − "columns": [ | |
| 2009 | − "secret_id" | |
| 2010 | − ], | |
| 1833 | + "columns": ["secret_id"], | |
| 2011 | 1834 | "isUnique": false |
| 2012 | 1835 | } |
| 2013 | 1836 | }, |
| 2014 | 1837 | "foreignKeys": {}, |
| 2015 | 1838 | "compositePrimaryKeys": { |
| 2016 | 1839 | "openapi_source_spec_fetch_query_param_scope_id_id_pk": { |
| 2017 | − "columns": [ | |
| 2018 | − "scope_id", | |
| 2019 | − "id" | |
| 2020 | − ], | |
| 1840 | + "columns": ["scope_id", "id"], | |
| 2021 | 1841 | "name": "openapi_source_spec_fetch_query_param_scope_id_id_pk" |
| 2022 | 1842 | } |
| 2023 | 1843 | }, |
| @@ -2087,33 +1907,24 @@ · 63 unmodified lines | ||
| 2087 | 1907 | "indexes": { |
| 2088 | 1908 | "mcp_source_header_scope_id_idx": { |
| 2089 | 1909 | "name": "mcp_source_header_scope_id_idx", |
| 2090 | − "columns": [ | |
| 2091 | − "scope_id" | |
| 2092 | − ], | |
| 1910 | + "columns": ["scope_id"], | |
| 2093 | 1911 | "isUnique": false |
| 2094 | 1912 | }, |
| 2095 | 1913 | "mcp_source_header_source_id_idx": { |
| 2096 | 1914 | "name": "mcp_source_header_source_id_idx", |
| 2097 | − "columns": [ | |
| 2098 | − "source_id" | |
| 2099 | − ], | |
| 1915 | + "columns": ["source_id"], | |
| 2100 | 1916 | "isUnique": false |
| 2101 | 1917 | }, |
| 2102 | 1918 | "mcp_source_header_secret_id_idx": { |
| 2103 | 1919 | "name": "mcp_source_header_secret_id_idx", |
| 2104 | − "columns": [ | |
| 2105 | − "secret_id" | |
| 2106 | − ], | |
| 1920 | + "columns": ["secret_id"], | |
| 2107 | 1921 | "isUnique": false |
| 2108 | 1922 | } |
| 2109 | 1923 | }, |
| 2110 | 1924 | "foreignKeys": {}, |
| 2111 | 1925 | "compositePrimaryKeys": { |
| 2112 | 1926 | "mcp_source_header_scope_id_id_pk": { |
| 2113 | − "columns": [ | |
| 2114 | − "scope_id", | |
| 2115 | − "id" | |
| 2116 | − ], | |
| 1927 | + "columns": ["scope_id", "id"], | |
| 2117 | 1928 | "name": "mcp_source_header_scope_id_id_pk" |
| 2118 | 1929 | } |
| 2119 | 1930 | }, |
| @@ -2183,33 +1994,24 @@ · 63 unmodified lines | ||
| 2183 | 1994 | "indexes": { |
| 2184 | 1995 | "mcp_source_query_param_scope_id_idx": { |
| 2185 | 1996 | "name": "mcp_source_query_param_scope_id_idx", |
| 2186 | − "columns": [ | |
| 2187 | − "scope_id" | |
| 2188 | − ], | |
| 1997 | + "columns": ["scope_id"], | |
| 2189 | 1998 | "isUnique": false |
| 2190 | 1999 | }, |
| 2191 | 2000 | "mcp_source_query_param_source_id_idx": { |
| 2192 | 2001 | "name": "mcp_source_query_param_source_id_idx", |
| 2193 | − "columns": [ | |
| 2194 | − "source_id" | |
| 2195 | − ], | |
| 2002 | + "columns": ["source_id"], | |
| 2196 | 2003 | "isUnique": false |
| 2197 | 2004 | }, |
| 2198 | 2005 | "mcp_source_query_param_secret_id_idx": { |
| 2199 | 2006 | "name": "mcp_source_query_param_secret_id_idx", |
| 2200 | − "columns": [ | |
| 2201 | − "secret_id" | |
| 2202 | − ], | |
| 2007 | + "columns": ["secret_id"], | |
| 2203 | 2008 | "isUnique": false |
| 2204 | 2009 | } |
| 2205 | 2010 | }, |
| 2206 | 2011 | "foreignKeys": {}, |
| 2207 | 2012 | "compositePrimaryKeys": { |
| 2208 | 2013 | "mcp_source_query_param_scope_id_id_pk": { |
| 2209 | − "columns": [ | |
| 2210 | − "scope_id", | |
| 2211 | − "id" | |
| 2212 | − ], | |
| 2014 | + "columns": ["scope_id", "id"], | |
| 2213 | 2015 | "name": "mcp_source_query_param_scope_id_id_pk" |
| 2214 | 2016 | } |
| 2215 | 2017 | }, |
| @@ -2279,33 +2081,24 @@ · 63 unmodified lines | ||
| 2279 | 2081 | "indexes": { |
| 2280 | 2082 | "google_discovery_source_credential_header_scope_id_idx": { |
| 2281 | 2083 | "name": "google_discovery_source_credential_header_scope_id_idx", |
| 2282 | − "columns": [ | |
| 2283 | − "scope_id" | |
| 2284 | − ], | |
| 2084 | + "columns": ["scope_id"], | |
| 2285 | 2085 | "isUnique": false |
| 2286 | 2086 | }, |
| 2287 | 2087 | "google_discovery_source_credential_header_source_id_idx": { |
| 2288 | 2088 | "name": "google_discovery_source_credential_header_source_id_idx", |
| 2289 | − "columns": [ | |
| 2290 | − "source_id" | |
| 2291 | − ], | |
| 2089 | + "columns": ["source_id"], | |
| 2292 | 2090 | "isUnique": false |
| 2293 | 2091 | }, |
| 2294 | 2092 | "google_discovery_source_credential_header_secret_id_idx": { |
| 2295 | 2093 | "name": "google_discovery_source_credential_header_secret_id_idx", |
| 2296 | − "columns": [ | |
| 2297 | − "secret_id" | |
| 2298 | − ], | |
| 2094 | + "columns": ["secret_id"], | |
| 2299 | 2095 | "isUnique": false |
| 2300 | 2096 | } |
| 2301 | 2097 | }, |
| 2302 | 2098 | "foreignKeys": {}, |
| 2303 | 2099 | "compositePrimaryKeys": { |
| 2304 | 2100 | "google_discovery_source_credential_header_scope_id_id_pk": { |
| 2305 | − "columns": [ | |
| 2306 | − "scope_id", | |
| 2307 | − "id" | |
| 2308 | − ], | |
| 2101 | + "columns": ["scope_id", "id"], | |
| 2309 | 2102 | "name": "google_discovery_source_credential_header_scope_id_id_pk" |
| 2310 | 2103 | } |
| 2311 | 2104 | }, |
| @@ -2375,33 +2168,24 @@ · 63 unmodified lines | ||
| 2375 | 2168 | "indexes": { |
| 2376 | 2169 | "google_discovery_source_credential_query_param_scope_id_idx": { |
| 2377 | 2170 | "name": "google_discovery_source_credential_query_param_scope_id_idx", |
| 2378 | − "columns": [ | |
| 2379 | − "scope_id" | |
| 2380 | − ], | |
| 2171 | + "columns": ["scope_id"], | |
| 2381 | 2172 | "isUnique": false |
| 2382 | 2173 | }, |
| 2383 | 2174 | "google_discovery_source_credential_query_param_source_id_idx": { |
| 2384 | 2175 | "name": "google_discovery_source_credential_query_param_source_id_idx", |
| 2385 | − "columns": [ | |
| 2386 | − "source_id" | |
| 2387 | − ], | |
| 2176 | + "columns": ["source_id"], | |
| 2388 | 2177 | "isUnique": false |
| 2389 | 2178 | }, |
| 2390 | 2179 | "google_discovery_source_credential_query_param_secret_id_idx": { |
| 2391 | 2180 | "name": "google_discovery_source_credential_query_param_secret_id_idx", |
| 2392 | − "columns": [ | |
| 2393 | − "secret_id" | |
| 2394 | − ], | |
| 2181 | + "columns": ["secret_id"], | |
| 2395 | 2182 | "isUnique": false |
| 2396 | 2183 | } |
| 2397 | 2184 | }, |
| 2398 | 2185 | "foreignKeys": {}, |
| 2399 | 2186 | "compositePrimaryKeys": { |
| 2400 | 2187 | "google_discovery_source_credential_query_param_scope_id_id_pk": { |
| 2401 | − "columns": [ | |
| 2402 | − "scope_id", | |
| 2403 | − "id" | |
| 2404 | − ], | |
| 2188 | + "columns": ["scope_id", "id"], | |
| 2405 | 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 | 30 | beforeEach(() => { |
| 31 | 31 | clientDir = mkdtempSync(join(tmpdir(), "exec-local-serve-")); |
| 32 | 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 | 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 | 18 | const DEFAULT_ALLOWED_HOSTS = ["localhost", "127.0.0.1", "[::1]", "::1"]; |
| 19 | 19 | |
| 20 | −const makeIsAllowedHost = (allowed: ReadonlySet<string>) => (request: Request): boolean => { | |
| 21 | − const host = request.headers.get("host"); | |
| 22 | − if (!host) return true; | |
| 23 | − const hostname = host.replace(/:\d+$/, ""); | |
| 24 | − return allowed.has(hostname); | |
| 25 | −}; | |
| 20 | +const makeIsAllowedHost = | |
| 21 | + (allowed: ReadonlySet<string>) => | |
| 22 | + (request: Request): boolean => { | |
| 23 | + const host = request.headers.get("host"); | |
| 24 | + if (!host) return true; | |
| 25 | + const hostname = host.replace(/:\d+$/, ""); | |
| 26 | + return allowed.has(hostname); | |
| 27 | + }; | |
| 26 | 28 | |
| 27 | 29 | // --------------------------------------------------------------------------- |
| 28 | 30 | // Static files |
| @@ -10,11 +10,7 @@ import { join } from "node:path"; · 9 unmodified lines | ||
| 10 | 10 | import * as fs from "node:fs"; |
| 11 | 11 | import * as jsonc from "jsonc-parser"; |
| 12 | 12 | |
| 13 | −import type { | |
| 14 | − SourceConfig, | |
| 15 | − ExecutorFileConfig, | |
| 16 | − ConfigHeaderValue, | |
| 17 | −} from "@executor-js/config"; | |
| 13 | +import type { SourceConfig, ExecutorFileConfig, ConfigHeaderValue } from "@executor-js/config"; | |
| 18 | 14 | import { SECRET_REF_PREFIX } from "@executor-js/config"; |
| 19 | 15 | |
| 20 | 16 | import type { LocalExecutor } from "./executor"; |
| @@ -57,8 +53,7 @@ const translateHeaders = ( · 36 unmodified lines | ||
| 57 | 53 | // Config path resolution |
| 58 | 54 | // --------------------------------------------------------------------------- |
| 59 | 55 | |
| 60 | −export const resolveConfigPath = (scopeDir: string): string => | |
| 61 | − join(scopeDir, "executor.jsonc"); | |
| 56 | +export const resolveConfigPath = (scopeDir: string): string => join(scopeDir, "executor.jsonc"); | |
| 62 | 57 | |
| 63 | 58 | // --------------------------------------------------------------------------- |
| 64 | 59 | // Load config (sync, no Effect deps — runs at startup) |
| @@ -91,45 +86,53 @@ const addSourceFromConfig = ( · 26 unmodified lines | ||
| 91 | 86 | const scope = executor.scopes.at(-1)!.id; |
| 92 | 87 | switch (source.kind) { |
| 93 | 88 | case "openapi": |
| 94 | − return executor.openapi.addSpec({ | |
| 95 | − spec: source.spec, | |
| 96 | − scope, | |
| 97 | − baseUrl: source.baseUrl, | |
| 98 | − namespace: source.namespace, | |
| 99 | − headers: translateHeaders(source.headers), | |
| 100 | − }).pipe(Effect.asVoid); | |
| 89 | + return executor.openapi | |
| 90 | + .addSpec({ | |
| 91 | + spec: source.spec, | |
| 92 | + scope, | |
| 93 | + baseUrl: source.baseUrl, | |
| 94 | + namespace: source.namespace, | |
| 95 | + headers: translateHeaders(source.headers), | |
| 96 | + }) | |
| 97 | + .pipe(Effect.asVoid); | |
| 101 | 98 | |
| 102 | 99 | case "graphql": |
| 103 | − return executor.graphql.addSource({ | |
| 104 | − endpoint: source.endpoint, | |
| 105 | − scope, | |
| 106 | − namespace: source.namespace, | |
| 107 | − headers: translateHeaders(source.headers) as Record<string, string> | undefined, | |
| 108 | − }).pipe(Effect.asVoid); | |
| 100 | + return executor.graphql | |
| 101 | + .addSource({ | |
| 102 | + endpoint: source.endpoint, | |
| 103 | + scope, | |
| 104 | + namespace: source.namespace, | |
| 105 | + headers: translateHeaders(source.headers) as Record<string, string> | undefined, | |
| 106 | + }) | |
| 107 | + .pipe(Effect.asVoid); | |
| 109 | 108 | |
| 110 | 109 | case "mcp": |
| 111 | 110 | if (source.transport === "stdio") { |
| 112 | − return executor.mcp.addSource({ | |
| 113 | − transport: "stdio", | |
| 111 | + return executor.mcp | |
| 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 | 127 | scope, |
| 115 | 128 | name: source.name, |
| 116 | − command: source.command, | |
| 117 | − args: source.args ? [...source.args] : undefined, | |
| 118 | − env: source.env, | |
| 119 | − cwd: source.cwd, | |
| 129 | + endpoint: source.endpoint, | |
| 130 | + remoteTransport: source.remoteTransport, | |
| 131 | + queryParams: source.queryParams, | |
| 132 | + headers: source.headers, | |
| 120 | 133 | namespace: source.namespace, |
| 121 | − }).pipe(Effect.asVoid); | |
| 122 | − } | |
| 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); | |
| 134 | + }) | |
| 135 | + .pipe(Effect.asVoid); | |
| 133 | 136 | } |
| 134 | 137 | }; |
| 135 | 138 | |
| @@ -137,10 +140,7 @@ const addSourceFromConfig = ( · 1 unmodified line | ||
| 137 | 140 | * Read executor.jsonc and replay all sources into the executor. |
| 138 | 141 | * Each source is added independently — if one fails, the rest still load. |
| 139 | 142 | */ |
| 140 | −export const syncFromConfig = ( | |
| 141 | − executor: LocalExecutor, | |
| 142 | − configPath: string, | |
| 143 | −): Effect.Effect<void> => | |
| 143 | +export const syncFromConfig = (executor: LocalExecutor, configPath: string): Effect.Effect<void> => | |
| 144 | 144 | Effect.gen(function* () { |
| 145 | 145 | const config = loadConfigSync(configPath); |
| 146 | 146 | if (!config?.sources?.length) { |
| @@ -148,9 +148,7 @@ export const syncFromConfig = ( · 1 unmodified line | ||
| 148 | 148 | return; |
| 149 | 149 | } |
| 150 | 150 | |
| 151 | − console.log( | |
| 152 | − `[config-sync] syncing ${config.sources.length} source(s) from ${configPath}`, | |
| 153 | − ); | |
| 151 | + console.log(`[config-sync] syncing ${config.sources.length} source(s) from ${configPath}`); | |
| 154 | 152 | |
| 155 | 153 | const results = yield* Effect.forEach( |
| 156 | 154 | config.sources, |
| @@ -158,11 +156,9 @@ export const syncFromConfig = ( · 1 unmodified line | ||
| 158 | 156 | addSourceFromConfig(executor, source).pipe( |
| 159 | 157 | Effect.map(() => true as const), |
| 160 | 158 | Effect.catchCause((cause) => { |
| 161 | − const ns = "namespace" in source ? source.namespace : ("name" in source ? source.name : "unknown"); | |
| 162 | − console.warn( | |
| 163 | − `[config-sync] Failed to load source "${ns}":`, | |
| 164 | − Cause.pretty(cause), | |
| 165 | − ); | |
| 159 | + const ns = | |
| 160 | + "namespace" in source ? source.namespace : "name" in source ? source.name : "unknown"; | |
| 161 | + console.warn(`[config-sync] Failed to load source "${ns}":`, Cause.pretty(cause)); | |
| 166 | 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 | 156 | migrationsFolder: join(__dirname, "../../drizzle"), |
| 157 | 157 | }); |
| 158 | 158 | // migrate() should have produced the new schema — source now has scope_id. |
| 159 | − const cols = db | |
| 160 | − .prepare("PRAGMA table_info('source')") | |
| 161 | − .all() as ReadonlyArray<{ readonly name: string }>; | |
| 159 | + const cols = db.prepare("PRAGMA table_info('source')").all() as ReadonlyArray<{ | |
| 160 | + readonly name: string; | |
| 161 | + }>; | |
| 162 | 162 | expect(cols.some((c) => c.name === "scope_id")).toBe(true); |
| 163 | 163 | db.close(); |
| 164 | 164 | }); |
| @@ -169,12 +169,18 @@ describe("readLegacySecrets", () => { · 4 unmodified lines | ||
| 169 | 169 | const path = join(workDir, "data.db"); |
| 170 | 170 | seed(path, PRE_SCOPE_SCHEMA); |
| 171 | 171 | const db = new Database(path); |
| 172 | − db.prepare( | |
| 173 | − "INSERT INTO secret (id, name, provider, created_at) VALUES (?, ?, ?, ?)", | |
| 174 | − ).run("sec_1", "GitHub Token", "onepassword", 1700000000); | |
| 175 | − db.prepare( | |
| 176 | − "INSERT INTO secret (id, name, provider, created_at) VALUES (?, ?, ?, ?)", | |
| 177 | − ).run("sec_2", "Stripe", "keychain", 1700000001); | |
| 172 | + db.prepare("INSERT INTO secret (id, name, provider, created_at) VALUES (?, ?, ?, ?)").run( | |
| 173 | + "sec_1", | |
| 174 | + "GitHub Token", | |
| 175 | + "onepassword", | |
| 176 | + 1700000000, | |
| 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 | 184 | db.close(); |
| 179 | 185 | |
| 180 | 186 | const rows = readLegacySecrets(path); |
| @@ -248,9 +254,7 @@ describe("importLegacySecrets", () => { · 67 unmodified lines | ||
| 248 | 254 | it("uses INSERT OR IGNORE so a second import of the same ids is a no-op", () => { |
| 249 | 255 | const path = join(workDir, "data.db"); |
| 250 | 256 | const db = createScopedDb(path); |
| 251 | − const rows = [ | |
| 252 | − { id: "sec_1", name: "GH", provider: "onepassword", createdAt: 1 }, | |
| 253 | − ]; | |
| 257 | + const rows = [{ id: "sec_1", name: "GH", provider: "onepassword", createdAt: 1 }]; | |
| 254 | 258 | importLegacySecrets(db, "scope_a", rows); |
| 255 | 259 | // If the user's already re-registered the secret via a different |
| 256 | 260 | // provider, the legacy row must NOT clobber it. |
| @@ -27,9 +27,9 @@ export const isPreScopeSchema = (dbPath: string): boolean => { · 26 unmodified lines | ||
| 27 | 27 | .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='source'") |
| 28 | 28 | .get(); |
| 29 | 29 | if (!tableExists) return false; |
| 30 | − const columns = db | |
| 31 | − .prepare("PRAGMA table_info('source')") | |
| 32 | − .all() as ReadonlyArray<{ readonly name: string }>; | |
| 30 | + const columns = db.prepare("PRAGMA table_info('source')").all() as ReadonlyArray<{ | |
| 31 | + readonly name: string; | |
| 32 | + }>; | |
| 33 | 33 | return !columns.some((c) => c.name === "scope_id"); |
| 34 | 34 | } finally { |
| 35 | 35 | db.close(); |
| @@ -1,396 +1,505 @@ | ||
| 1 | 1 | import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core"; |
| 2 | 2 | |
| 3 | −export const source = sqliteTable("source", { | |
| 4 | − id: text('id').notNull(), | |
| 5 | − scope_id: text('scope_id').notNull(), | |
| 6 | − plugin_id: text('plugin_id').notNull(), | |
| 7 | − kind: text('kind').notNull(), | |
| 8 | − name: text('name').notNull(), | |
| 9 | − url: text('url'), | |
| 10 | − can_remove: integer('can_remove', { mode: 'boolean' }).default(true).notNull(), | |
| 11 | − can_refresh: integer('can_refresh', { mode: 'boolean' }).default(false).notNull(), | |
| 12 | − can_edit: integer('can_edit', { mode: 'boolean' }).default(false).notNull(), | |
| 13 | − created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(), | |
| 14 | − updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull() | |
| 15 | −}, (table) => [ | |
| 16 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 17 | − index("source_scope_id_idx").on(table.scope_id), | |
| 18 | − index("source_plugin_id_idx").on(table.plugin_id), | |
| 19 | −]); | |
| 3 | +export const source = sqliteTable( | |
| 4 | + "source", | |
| 5 | + { | |
| 6 | + id: text("id").notNull(), | |
| 7 | + scope_id: text("scope_id").notNull(), | |
| 8 | + plugin_id: text("plugin_id").notNull(), | |
| 9 | + kind: text("kind").notNull(), | |
| 10 | + name: text("name").notNull(), | |
| 11 | + url: text("url"), | |
| 12 | + can_remove: integer("can_remove", { mode: "boolean" }).default(true).notNull(), | |
| 13 | + can_refresh: integer("can_refresh", { mode: "boolean" }).default(false).notNull(), | |
| 14 | + can_edit: integer("can_edit", { mode: "boolean" }).default(false).notNull(), | |
| 15 | + created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), | |
| 16 | + updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(), | |
| 17 | + }, | |
| 18 | + (table) => [ | |
| 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", { | |
| 22 | − id: text('id').notNull(), | |
| 23 | − scope_id: text('scope_id').notNull(), | |
| 24 | − source_id: text('source_id').notNull(), | |
| 25 | − plugin_id: text('plugin_id').notNull(), | |
| 26 | − name: text('name').notNull(), | |
| 27 | − description: text('description').notNull(), | |
| 28 | − input_schema: text('input_schema', { mode: "json" }), | |
| 29 | − output_schema: text('output_schema', { mode: "json" }), | |
| 30 | − created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(), | |
| 31 | − updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull() | |
| 32 | −}, (table) => [ | |
| 33 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 34 | − index("tool_scope_id_idx").on(table.scope_id), | |
| 35 | − index("tool_source_id_idx").on(table.source_id), | |
| 36 | − index("tool_plugin_id_idx").on(table.plugin_id), | |
| 37 | −]); | |
| 25 | +export const tool = sqliteTable( | |
| 26 | + "tool", | |
| 27 | + { | |
| 28 | + id: text("id").notNull(), | |
| 29 | + scope_id: text("scope_id").notNull(), | |
| 30 | + source_id: text("source_id").notNull(), | |
| 31 | + plugin_id: text("plugin_id").notNull(), | |
| 32 | + name: text("name").notNull(), | |
| 33 | + description: text("description").notNull(), | |
| 34 | + input_schema: text("input_schema", { mode: "json" }), | |
| 35 | + output_schema: text("output_schema", { mode: "json" }), | |
| 36 | + created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), | |
| 37 | + updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(), | |
| 38 | + }, | |
| 39 | + (table) => [ | |
| 40 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 40 | − id: text('id').notNull(), | |
| 41 | − scope_id: text('scope_id').notNull(), | |
| 42 | − source_id: text('source_id').notNull(), | |
| 43 | − plugin_id: text('plugin_id').notNull(), | |
| 44 | − name: text('name').notNull(), | |
| 45 | − schema: text('schema', { mode: "json" }).notNull(), | |
| 46 | − created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull() | |
| 47 | −}, (table) => [ | |
| 48 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 49 | − index("definition_scope_id_idx").on(table.scope_id), | |
| 50 | − index("definition_source_id_idx").on(table.source_id), | |
| 51 | − index("definition_plugin_id_idx").on(table.plugin_id), | |
| 52 | −]); | |
| 47 | +export const definition = sqliteTable( | |
| 48 | + "definition", | |
| 49 | + { | |
| 50 | + id: text("id").notNull(), | |
| 51 | + scope_id: text("scope_id").notNull(), | |
| 52 | + source_id: text("source_id").notNull(), | |
| 53 | + plugin_id: text("plugin_id").notNull(), | |
| 54 | + name: text("name").notNull(), | |
| 55 | + schema: text("schema", { mode: "json" }).notNull(), | |
| 56 | + created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), | |
| 57 | + }, | |
| 58 | + (table) => [ | |
| 59 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 55 | − id: text('id').notNull(), | |
| 56 | − scope_id: text('scope_id').notNull(), | |
| 57 | − name: text('name').notNull(), | |
| 58 | − provider: text('provider').notNull(), | |
| 59 | − owned_by_connection_id: text('owned_by_connection_id'), | |
| 60 | − created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull() | |
| 61 | −}, (table) => [ | |
| 62 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 63 | − index("secret_scope_id_idx").on(table.scope_id), | |
| 64 | − index("secret_provider_idx").on(table.provider), | |
| 65 | − index("secret_owned_by_connection_id_idx").on(table.owned_by_connection_id), | |
| 66 | −]); | |
| 66 | +export const secret = sqliteTable( | |
| 67 | + "secret", | |
| 68 | + { | |
| 69 | + id: text("id").notNull(), | |
| 70 | + scope_id: text("scope_id").notNull(), | |
| 71 | + name: text("name").notNull(), | |
| 72 | + provider: text("provider").notNull(), | |
| 73 | + owned_by_connection_id: text("owned_by_connection_id"), | |
| 74 | + created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), | |
| 75 | + }, | |
| 76 | + (table) => [ | |
| 77 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 69 | − id: text('id').notNull(), | |
| 70 | − scope_id: text('scope_id').notNull(), | |
| 71 | − provider: text('provider').notNull(), | |
| 72 | − identity_label: text('identity_label'), | |
| 73 | − access_token_secret_id: text('access_token_secret_id').notNull(), | |
| 74 | − refresh_token_secret_id: text('refresh_token_secret_id'), | |
| 75 | − expires_at: integer('expires_at'), | |
| 76 | − scope: text('scope'), | |
| 77 | − provider_state: text('provider_state', { mode: "json" }), | |
| 78 | − created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(), | |
| 79 | − updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull() | |
| 80 | −}, (table) => [ | |
| 81 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 82 | − index("connection_scope_id_idx").on(table.scope_id), | |
| 83 | − index("connection_provider_idx").on(table.provider), | |
| 84 | −]); | |
| 84 | +export const connection = sqliteTable( | |
| 85 | + "connection", | |
| 86 | + { | |
| 87 | + id: text("id").notNull(), | |
| 88 | + scope_id: text("scope_id").notNull(), | |
| 89 | + provider: text("provider").notNull(), | |
| 90 | + identity_label: text("identity_label"), | |
| 91 | + access_token_secret_id: text("access_token_secret_id").notNull(), | |
| 92 | + refresh_token_secret_id: text("refresh_token_secret_id"), | |
| 93 | + expires_at: integer("expires_at"), | |
| 94 | + scope: text("scope"), | |
| 95 | + provider_state: text("provider_state", { mode: "json" }), | |
| 96 | + created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), | |
| 97 | + updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(), | |
| 98 | + }, | |
| 99 | + (table) => [ | |
| 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", { | |
| 87 | − id: text('id').notNull(), | |
| 88 | − scope_id: text('scope_id').notNull(), | |
| 89 | − plugin_id: text('plugin_id').notNull(), | |
| 90 | − strategy: text('strategy').notNull(), | |
| 91 | − connection_id: text('connection_id').notNull(), | |
| 92 | − token_scope: text('token_scope').notNull(), | |
| 93 | − redirect_url: text('redirect_url').notNull(), | |
| 94 | − payload: text('payload', { mode: "json" }).notNull(), | |
| 95 | − expires_at: integer('expires_at').notNull(), | |
| 96 | − created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull() | |
| 97 | −}, (table) => [ | |
| 98 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 99 | − index("oauth2_session_scope_id_idx").on(table.scope_id), | |
| 100 | − index("oauth2_session_plugin_id_idx").on(table.plugin_id), | |
| 101 | − index("oauth2_session_connection_id_idx").on(table.connection_id), | |
| 102 | −]); | |
| 106 | +export const oauth2_session = sqliteTable( | |
| 107 | + "oauth2_session", | |
| 108 | + { | |
| 109 | + id: text("id").notNull(), | |
| 110 | + scope_id: text("scope_id").notNull(), | |
| 111 | + plugin_id: text("plugin_id").notNull(), | |
| 112 | + strategy: text("strategy").notNull(), | |
| 113 | + connection_id: text("connection_id").notNull(), | |
| 114 | + token_scope: text("token_scope").notNull(), | |
| 115 | + redirect_url: text("redirect_url").notNull(), | |
| 116 | + payload: text("payload", { mode: "json" }).notNull(), | |
| 117 | + expires_at: integer("expires_at").notNull(), | |
| 118 | + created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), | |
| 119 | + }, | |
| 120 | + (table) => [ | |
| 121 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 105 | − id: text('id').notNull(), | |
| 106 | − scope_id: text('scope_id').notNull(), | |
| 107 | − pattern: text('pattern').notNull(), | |
| 108 | − action: text('action').notNull(), | |
| 109 | − position: text('position').notNull(), | |
| 110 | − created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(), | |
| 111 | − updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull() | |
| 112 | −}, (table) => [ | |
| 113 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 114 | − index("tool_policy_scope_id_position_idx").on(table.scope_id, table.position), | |
| 115 | −]); | |
| 128 | +export const tool_policy = sqliteTable( | |
| 129 | + "tool_policy", | |
| 130 | + { | |
| 131 | + id: text("id").notNull(), | |
| 132 | + scope_id: text("scope_id").notNull(), | |
| 133 | + pattern: text("pattern").notNull(), | |
| 134 | + action: text("action").notNull(), | |
| 135 | + position: text("position").notNull(), | |
| 136 | + created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), | |
| 137 | + updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(), | |
| 138 | + }, | |
| 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", { | |
| 118 | − id: text('id').notNull(), | |
| 119 | − scope_id: text('scope_id').notNull(), | |
| 120 | − name: text('name').notNull(), | |
| 121 | − spec: text('spec').notNull(), | |
| 122 | − source_url: text('source_url'), | |
| 123 | − base_url: text('base_url'), | |
| 124 | − headers: text('headers', { mode: "json" }), | |
| 125 | − oauth2: text('oauth2', { mode: "json" }) | |
| 126 | −}, (table) => [ | |
| 127 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 128 | − index("openapi_source_scope_id_idx").on(table.scope_id), | |
| 129 | −]); | |
| 145 | +export const openapi_source = sqliteTable( | |
| 146 | + "openapi_source", | |
| 147 | + { | |
| 148 | + id: text("id").notNull(), | |
| 149 | + scope_id: text("scope_id").notNull(), | |
| 150 | + name: text("name").notNull(), | |
| 151 | + spec: text("spec").notNull(), | |
| 152 | + source_url: text("source_url"), | |
| 153 | + base_url: text("base_url"), | |
| 154 | + headers: text("headers", { mode: "json" }), | |
| 155 | + oauth2: text("oauth2", { mode: "json" }), | |
| 156 | + }, | |
| 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", { | |
| 132 | − id: text('id').notNull(), | |
| 133 | − scope_id: text('scope_id').notNull(), | |
| 134 | − source_id: text('source_id').notNull(), | |
| 135 | − binding: text('binding', { mode: "json" }).notNull() | |
| 136 | −}, (table) => [ | |
| 137 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 138 | − index("openapi_operation_scope_id_idx").on(table.scope_id), | |
| 139 | − index("openapi_operation_source_id_idx").on(table.source_id), | |
| 140 | −]); | |
| 163 | +export const openapi_operation = sqliteTable( | |
| 164 | + "openapi_operation", | |
| 165 | + { | |
| 166 | + id: text("id").notNull(), | |
| 167 | + scope_id: text("scope_id").notNull(), | |
| 168 | + source_id: text("source_id").notNull(), | |
| 169 | + binding: text("binding", { mode: "json" }).notNull(), | |
| 170 | + }, | |
| 171 | + (table) => [ | |
| 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", { | |
| 143 | − id: text('id').primaryKey(), | |
| 144 | − source_id: text('source_id').notNull(), | |
| 145 | − source_scope_id: text('source_scope_id').notNull(), | |
| 146 | − target_scope_id: text('target_scope_id').notNull(), | |
| 147 | − slot: text('slot').notNull(), | |
| 148 | − kind: text({ enum: ['secret', 'connection', 'text'] }).notNull(), | |
| 149 | − secret_id: text('secret_id'), | |
| 150 | − connection_id: text('connection_id'), | |
| 151 | − text_value: text('text_value'), | |
| 152 | − created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(), | |
| 153 | − updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull() | |
| 154 | −}, (table) => [ | |
| 155 | − index("openapi_source_binding_source_id_idx").on(table.source_id), | |
| 156 | − index("openapi_source_binding_source_scope_id_idx").on(table.source_scope_id), | |
| 157 | − index("openapi_source_binding_target_scope_id_idx").on(table.target_scope_id), | |
| 158 | − index("openapi_source_binding_slot_idx").on(table.slot), | |
| 159 | − index("openapi_source_binding_secret_id_idx").on(table.secret_id), | |
| 160 | − index("openapi_source_binding_connection_id_idx").on(table.connection_id), | |
| 161 | −]); | |
| 178 | +export const openapi_source_binding = sqliteTable( | |
| 179 | + "openapi_source_binding", | |
| 180 | + { | |
| 181 | + id: text("id").primaryKey(), | |
| 182 | + source_id: text("source_id").notNull(), | |
| 183 | + source_scope_id: text("source_scope_id").notNull(), | |
| 184 | + target_scope_id: text("target_scope_id").notNull(), | |
| 185 | + slot: text("slot").notNull(), | |
| 186 | + kind: text({ enum: ["secret", "connection", "text"] }).notNull(), | |
| 187 | + secret_id: text("secret_id"), | |
| 188 | + connection_id: text("connection_id"), | |
| 189 | + text_value: text("text_value"), | |
| 190 | + created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), | |
| 191 | + updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(), | |
| 192 | + }, | |
| 193 | + (table) => [ | |
| 194 | + index("openapi_source_binding_source_id_idx").on(table.source_id), | |
| 195 | + index("openapi_source_binding_source_scope_id_idx").on(table.source_scope_id), | |
| 196 | + index("openapi_source_binding_target_scope_id_idx").on(table.target_scope_id), | |
| 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", { | |
| 164 | − id: text('id').notNull(), | |
| 165 | − scope_id: text('scope_id').notNull(), | |
| 166 | − source_id: text('source_id').notNull(), | |
| 167 | − name: text('name').notNull(), | |
| 168 | − kind: text({ enum: ['text', 'secret'] }).notNull(), | |
| 169 | − text_value: text('text_value'), | |
| 170 | − secret_id: text('secret_id'), | |
| 171 | − secret_prefix: text('secret_prefix') | |
| 172 | −}, (table) => [ | |
| 173 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 174 | − index("openapi_source_query_param_scope_id_idx").on(table.scope_id), | |
| 175 | − index("openapi_source_query_param_source_id_idx").on(table.source_id), | |
| 176 | − index("openapi_source_query_param_secret_id_idx").on(table.secret_id), | |
| 177 | −]); | |
| 203 | +export const openapi_source_query_param = sqliteTable( | |
| 204 | + "openapi_source_query_param", | |
| 205 | + { | |
| 206 | + id: text("id").notNull(), | |
| 207 | + scope_id: text("scope_id").notNull(), | |
| 208 | + source_id: text("source_id").notNull(), | |
| 209 | + name: text("name").notNull(), | |
| 210 | + kind: text({ enum: ["text", "secret"] }).notNull(), | |
| 211 | + text_value: text("text_value"), | |
| 212 | + secret_id: text("secret_id"), | |
| 213 | + secret_prefix: text("secret_prefix"), | |
| 214 | + }, | |
| 215 | + (table) => [ | |
| 216 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 180 | − id: text('id').notNull(), | |
| 181 | − scope_id: text('scope_id').notNull(), | |
| 182 | − source_id: text('source_id').notNull(), | |
| 183 | − name: text('name').notNull(), | |
| 184 | − kind: text({ enum: ['text', 'secret'] }).notNull(), | |
| 185 | − text_value: text('text_value'), | |
| 186 | − secret_id: text('secret_id'), | |
| 187 | − secret_prefix: text('secret_prefix') | |
| 188 | −}, (table) => [ | |
| 189 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 190 | − index("openapi_source_spec_fetch_header_scope_id_idx").on(table.scope_id), | |
| 191 | − index("openapi_source_spec_fetch_header_source_id_idx").on(table.source_id), | |
| 192 | − index("openapi_source_spec_fetch_header_secret_id_idx").on(table.secret_id), | |
| 193 | −]); | |
| 223 | +export const openapi_source_spec_fetch_header = sqliteTable( | |
| 224 | + "openapi_source_spec_fetch_header", | |
| 225 | + { | |
| 226 | + id: text("id").notNull(), | |
| 227 | + scope_id: text("scope_id").notNull(), | |
| 228 | + source_id: text("source_id").notNull(), | |
| 229 | + name: text("name").notNull(), | |
| 230 | + kind: text({ enum: ["text", "secret"] }).notNull(), | |
| 231 | + text_value: text("text_value"), | |
| 232 | + secret_id: text("secret_id"), | |
| 233 | + secret_prefix: text("secret_prefix"), | |
| 234 | + }, | |
| 235 | + (table) => [ | |
| 236 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 196 | − id: text('id').notNull(), | |
| 197 | − scope_id: text('scope_id').notNull(), | |
| 198 | − source_id: text('source_id').notNull(), | |
| 199 | − name: text('name').notNull(), | |
| 200 | − kind: text({ enum: ['text', 'secret'] }).notNull(), | |
| 201 | − text_value: text('text_value'), | |
| 202 | − secret_id: text('secret_id'), | |
| 203 | − secret_prefix: text('secret_prefix') | |
| 204 | −}, (table) => [ | |
| 205 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 206 | − index("openapi_source_spec_fetch_query_param_scope_id_idx").on(table.scope_id), | |
| 207 | − index("openapi_source_spec_fetch_query_param_source_id_idx").on(table.source_id), | |
| 208 | − index("openapi_source_spec_fetch_query_param_secret_id_idx").on(table.secret_id), | |
| 209 | −]); | |
| 243 | +export const openapi_source_spec_fetch_query_param = sqliteTable( | |
| 244 | + "openapi_source_spec_fetch_query_param", | |
| 245 | + { | |
| 246 | + id: text("id").notNull(), | |
| 247 | + scope_id: text("scope_id").notNull(), | |
| 248 | + source_id: text("source_id").notNull(), | |
| 249 | + name: text("name").notNull(), | |
| 250 | + kind: text({ enum: ["text", "secret"] }).notNull(), | |
| 251 | + text_value: text("text_value"), | |
| 252 | + secret_id: text("secret_id"), | |
| 253 | + secret_prefix: text("secret_prefix"), | |
| 254 | + }, | |
| 255 | + (table) => [ | |
| 256 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 212 | − id: text('id').notNull(), | |
| 213 | − scope_id: text('scope_id').notNull(), | |
| 214 | − name: text('name').notNull(), | |
| 215 | − config: text('config', { mode: "json" }).notNull(), | |
| 216 | − auth_kind: text({ enum: ['none', 'header', 'oauth2'] }).default("none").notNull(), | |
| 217 | − auth_header_name: text('auth_header_name'), | |
| 218 | − auth_secret_id: text('auth_secret_id'), | |
| 219 | − auth_secret_prefix: text('auth_secret_prefix'), | |
| 220 | − auth_connection_id: text('auth_connection_id'), | |
| 221 | − auth_client_id_secret_id: text('auth_client_id_secret_id'), | |
| 222 | − auth_client_secret_secret_id: text('auth_client_secret_secret_id'), | |
| 223 | − created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull() | |
| 224 | −}, (table) => [ | |
| 225 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 226 | − index("mcp_source_scope_id_idx").on(table.scope_id), | |
| 227 | − index("mcp_source_auth_secret_id_idx").on(table.auth_secret_id), | |
| 228 | − index("mcp_source_auth_connection_id_idx").on(table.auth_connection_id), | |
| 229 | − index("mcp_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id), | |
| 230 | − index("mcp_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id), | |
| 231 | −]); | |
| 263 | +export const mcp_source = sqliteTable( | |
| 264 | + "mcp_source", | |
| 265 | + { | |
| 266 | + id: text("id").notNull(), | |
| 267 | + scope_id: text("scope_id").notNull(), | |
| 268 | + name: text("name").notNull(), | |
| 269 | + config: text("config", { mode: "json" }).notNull(), | |
| 270 | + auth_kind: text({ enum: ["none", "header", "oauth2"] }) | |
| 271 | + .default("none") | |
| 272 | + .notNull(), | |
| 273 | + auth_header_name: text("auth_header_name"), | |
| 274 | + auth_secret_id: text("auth_secret_id"), | |
| 275 | + auth_secret_prefix: text("auth_secret_prefix"), | |
| 276 | + auth_connection_id: text("auth_connection_id"), | |
| 277 | + auth_client_id_secret_id: text("auth_client_id_secret_id"), | |
| 278 | + auth_client_secret_secret_id: text("auth_client_secret_secret_id"), | |
| 279 | + created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), | |
| 280 | + }, | |
| 281 | + (table) => [ | |
| 282 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 234 | − id: text('id').notNull(), | |
| 235 | − scope_id: text('scope_id').notNull(), | |
| 236 | − source_id: text('source_id').notNull(), | |
| 237 | − name: text('name').notNull(), | |
| 238 | − kind: text({ enum: ['text', 'secret'] }).notNull(), | |
| 239 | − text_value: text('text_value'), | |
| 240 | − secret_id: text('secret_id'), | |
| 241 | − secret_prefix: text('secret_prefix') | |
| 242 | −}, (table) => [ | |
| 243 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 244 | − index("mcp_source_header_scope_id_idx").on(table.scope_id), | |
| 245 | − index("mcp_source_header_source_id_idx").on(table.source_id), | |
| 246 | − index("mcp_source_header_secret_id_idx").on(table.secret_id), | |
| 247 | −]); | |
| 291 | +export const mcp_source_header = sqliteTable( | |
| 292 | + "mcp_source_header", | |
| 293 | + { | |
| 294 | + id: text("id").notNull(), | |
| 295 | + scope_id: text("scope_id").notNull(), | |
| 296 | + source_id: text("source_id").notNull(), | |
| 297 | + name: text("name").notNull(), | |
| 298 | + kind: text({ enum: ["text", "secret"] }).notNull(), | |
| 299 | + text_value: text("text_value"), | |
| 300 | + secret_id: text("secret_id"), | |
| 301 | + secret_prefix: text("secret_prefix"), | |
| 302 | + }, | |
| 303 | + (table) => [ | |
| 304 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 250 | − id: text('id').notNull(), | |
| 251 | − scope_id: text('scope_id').notNull(), | |
| 252 | − source_id: text('source_id').notNull(), | |
| 253 | − name: text('name').notNull(), | |
| 254 | − kind: text({ enum: ['text', 'secret'] }).notNull(), | |
| 255 | − text_value: text('text_value'), | |
| 256 | − secret_id: text('secret_id'), | |
| 257 | − secret_prefix: text('secret_prefix') | |
| 258 | −}, (table) => [ | |
| 259 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 260 | − index("mcp_source_query_param_scope_id_idx").on(table.scope_id), | |
| 261 | − index("mcp_source_query_param_source_id_idx").on(table.source_id), | |
| 262 | − index("mcp_source_query_param_secret_id_idx").on(table.secret_id), | |
| 263 | −]); | |
| 311 | +export const mcp_source_query_param = sqliteTable( | |
| 312 | + "mcp_source_query_param", | |
| 313 | + { | |
| 314 | + id: text("id").notNull(), | |
| 315 | + scope_id: text("scope_id").notNull(), | |
| 316 | + source_id: text("source_id").notNull(), | |
| 317 | + name: text("name").notNull(), | |
| 318 | + kind: text({ enum: ["text", "secret"] }).notNull(), | |
| 319 | + text_value: text("text_value"), | |
| 320 | + secret_id: text("secret_id"), | |
| 321 | + secret_prefix: text("secret_prefix"), | |
| 322 | + }, | |
| 323 | + (table) => [ | |
| 324 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 266 | − id: text('id').notNull(), | |
| 267 | − scope_id: text('scope_id').notNull(), | |
| 268 | − source_id: text('source_id').notNull(), | |
| 269 | − binding: text('binding', { mode: "json" }).notNull(), | |
| 270 | − created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull() | |
| 271 | −}, (table) => [ | |
| 272 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 273 | − index("mcp_binding_scope_id_idx").on(table.scope_id), | |
| 274 | − index("mcp_binding_source_id_idx").on(table.source_id), | |
| 275 | −]); | |
| 331 | +export const mcp_binding = sqliteTable( | |
| 332 | + "mcp_binding", | |
| 333 | + { | |
| 334 | + id: text("id").notNull(), | |
| 335 | + scope_id: text("scope_id").notNull(), | |
| 336 | + source_id: text("source_id").notNull(), | |
| 337 | + binding: text("binding", { mode: "json" }).notNull(), | |
| 338 | + created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), | |
| 339 | + }, | |
| 340 | + (table) => [ | |
| 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", { | |
| 278 | − id: text('id').notNull(), | |
| 279 | − scope_id: text('scope_id').notNull(), | |
| 280 | − name: text('name').notNull(), | |
| 281 | − config: text('config', { mode: "json" }).notNull(), | |
| 282 | − auth_kind: text({ enum: ['none', 'oauth2'] }).default("none").notNull(), | |
| 283 | − auth_connection_id: text('auth_connection_id'), | |
| 284 | − auth_client_id_secret_id: text('auth_client_id_secret_id'), | |
| 285 | − auth_client_secret_secret_id: text('auth_client_secret_secret_id'), | |
| 286 | − auth_scopes: text('auth_scopes', { mode: "json" }), | |
| 287 | − created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(), | |
| 288 | − updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull() | |
| 289 | −}, (table) => [ | |
| 290 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 291 | − index("google_discovery_source_scope_id_idx").on(table.scope_id), | |
| 292 | − index("google_discovery_source_auth_connection_id_idx").on(table.auth_connection_id), | |
| 293 | − index("google_discovery_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id), | |
| 294 | − index("google_discovery_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id), | |
| 295 | −]); | |
| 347 | +export const google_discovery_source = sqliteTable( | |
| 348 | + "google_discovery_source", | |
| 349 | + { | |
| 350 | + id: text("id").notNull(), | |
| 351 | + scope_id: text("scope_id").notNull(), | |
| 352 | + name: text("name").notNull(), | |
| 353 | + config: text("config", { mode: "json" }).notNull(), | |
| 354 | + auth_kind: text({ enum: ["none", "oauth2"] }) | |
| 355 | + .default("none") | |
| 356 | + .notNull(), | |
| 357 | + auth_connection_id: text("auth_connection_id"), | |
| 358 | + auth_client_id_secret_id: text("auth_client_id_secret_id"), | |
| 359 | + auth_client_secret_secret_id: text("auth_client_secret_secret_id"), | |
| 360 | + auth_scopes: text("auth_scopes", { mode: "json" }), | |
| 361 | + created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), | |
| 362 | + updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(), | |
| 363 | + }, | |
| 364 | + (table) => [ | |
| 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", { | |
| 298 | − id: text('id').notNull(), | |
| 299 | − scope_id: text('scope_id').notNull(), | |
| 300 | − source_id: text('source_id').notNull(), | |
| 301 | − name: text('name').notNull(), | |
| 302 | − kind: text({ enum: ['text', 'secret'] }).notNull(), | |
| 303 | − text_value: text('text_value'), | |
| 304 | − secret_id: text('secret_id'), | |
| 305 | − secret_prefix: text('secret_prefix') | |
| 306 | −}, (table) => [ | |
| 307 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 308 | − index("google_discovery_source_credential_header_scope_id_idx").on(table.scope_id), | |
| 309 | − index("google_discovery_source_credential_header_source_id_idx").on(table.source_id), | |
| 310 | − index("google_discovery_source_credential_header_secret_id_idx").on(table.secret_id), | |
| 311 | −]); | |
| 377 | +export const google_discovery_source_credential_header = sqliteTable( | |
| 378 | + "google_discovery_source_credential_header", | |
| 379 | + { | |
| 380 | + id: text("id").notNull(), | |
| 381 | + scope_id: text("scope_id").notNull(), | |
| 382 | + source_id: text("source_id").notNull(), | |
| 383 | + name: text("name").notNull(), | |
| 384 | + kind: text({ enum: ["text", "secret"] }).notNull(), | |
| 385 | + text_value: text("text_value"), | |
| 386 | + secret_id: text("secret_id"), | |
| 387 | + secret_prefix: text("secret_prefix"), | |
| 388 | + }, | |
| 389 | + (table) => [ | |
| 390 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 314 | − id: text('id').notNull(), | |
| 315 | − scope_id: text('scope_id').notNull(), | |
| 316 | − source_id: text('source_id').notNull(), | |
| 317 | − name: text('name').notNull(), | |
| 318 | − kind: text({ enum: ['text', 'secret'] }).notNull(), | |
| 319 | − text_value: text('text_value'), | |
| 320 | − secret_id: text('secret_id'), | |
| 321 | − secret_prefix: text('secret_prefix') | |
| 322 | −}, (table) => [ | |
| 323 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 324 | − index("google_discovery_source_credential_query_param_scope_id_idx").on(table.scope_id), | |
| 325 | − index("google_discovery_source_credential_query_param_source_id_idx").on(table.source_id), | |
| 326 | − index("google_discovery_source_credential_query_param_secret_id_idx").on(table.secret_id), | |
| 327 | −]); | |
| 397 | +export const google_discovery_source_credential_query_param = sqliteTable( | |
| 398 | + "google_discovery_source_credential_query_param", | |
| 399 | + { | |
| 400 | + id: text("id").notNull(), | |
| 401 | + scope_id: text("scope_id").notNull(), | |
| 402 | + source_id: text("source_id").notNull(), | |
| 403 | + name: text("name").notNull(), | |
| 404 | + kind: text({ enum: ["text", "secret"] }).notNull(), | |
| 405 | + text_value: text("text_value"), | |
| 406 | + secret_id: text("secret_id"), | |
| 407 | + secret_prefix: text("secret_prefix"), | |
| 408 | + }, | |
| 409 | + (table) => [ | |
| 410 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 330 | − id: text('id').notNull(), | |
| 331 | − scope_id: text('scope_id').notNull(), | |
| 332 | − source_id: text('source_id').notNull(), | |
| 333 | − binding: text('binding', { mode: "json" }).notNull(), | |
| 334 | − created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull() | |
| 335 | −}, (table) => [ | |
| 336 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 337 | − index("google_discovery_binding_scope_id_idx").on(table.scope_id), | |
| 338 | − index("google_discovery_binding_source_id_idx").on(table.source_id), | |
| 339 | −]); | |
| 417 | +export const google_discovery_binding = sqliteTable( | |
| 418 | + "google_discovery_binding", | |
| 419 | + { | |
| 420 | + id: text("id").notNull(), | |
| 421 | + scope_id: text("scope_id").notNull(), | |
| 422 | + source_id: text("source_id").notNull(), | |
| 423 | + binding: text("binding", { mode: "json" }).notNull(), | |
| 424 | + created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(), | |
| 425 | + }, | |
| 426 | + (table) => [ | |
| 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", { | |
| 342 | − id: text('id').notNull(), | |
| 343 | − scope_id: text('scope_id').notNull(), | |
| 344 | − name: text('name').notNull(), | |
| 345 | − endpoint: text('endpoint').notNull(), | |
| 346 | − auth_kind: text({ enum: ['none', 'oauth2'] }).default("none").notNull(), | |
| 347 | − auth_connection_id: text('auth_connection_id') | |
| 348 | −}, (table) => [ | |
| 349 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 350 | − index("graphql_source_scope_id_idx").on(table.scope_id), | |
| 351 | − index("graphql_source_auth_connection_id_idx").on(table.auth_connection_id), | |
| 352 | −]); | |
| 433 | +export const graphql_source = sqliteTable( | |
| 434 | + "graphql_source", | |
| 435 | + { | |
| 436 | + id: text("id").notNull(), | |
| 437 | + scope_id: text("scope_id").notNull(), | |
| 438 | + name: text("name").notNull(), | |
| 439 | + endpoint: text("endpoint").notNull(), | |
| 440 | + auth_kind: text({ enum: ["none", "oauth2"] }) | |
| 441 | + .default("none") | |
| 442 | + .notNull(), | |
| 443 | + auth_connection_id: text("auth_connection_id"), | |
| 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", { | |
| 355 | − id: text('id').notNull(), | |
| 356 | − scope_id: text('scope_id').notNull(), | |
| 357 | − source_id: text('source_id').notNull(), | |
| 358 | − name: text('name').notNull(), | |
| 359 | − kind: text({ enum: ['text', 'secret'] }).notNull(), | |
| 360 | − text_value: text('text_value'), | |
| 361 | − secret_id: text('secret_id'), | |
| 362 | − secret_prefix: text('secret_prefix') | |
| 363 | −}, (table) => [ | |
| 364 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 365 | − index("graphql_source_header_scope_id_idx").on(table.scope_id), | |
| 366 | − index("graphql_source_header_source_id_idx").on(table.source_id), | |
| 367 | − index("graphql_source_header_secret_id_idx").on(table.secret_id), | |
| 368 | −]); | |
| 452 | +export const graphql_source_header = sqliteTable( | |
| 453 | + "graphql_source_header", | |
| 454 | + { | |
| 455 | + id: text("id").notNull(), | |
| 456 | + scope_id: text("scope_id").notNull(), | |
| 457 | + source_id: text("source_id").notNull(), | |
| 458 | + name: text("name").notNull(), | |
| 459 | + kind: text({ enum: ["text", "secret"] }).notNull(), | |
| 460 | + text_value: text("text_value"), | |
| 461 | + secret_id: text("secret_id"), | |
| 462 | + secret_prefix: text("secret_prefix"), | |
| 463 | + }, | |
| 464 | + (table) => [ | |
| 465 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 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", { | |
| 371 | − id: text('id').notNull(), | |
| 372 | − scope_id: text('scope_id').notNull(), | |
| 373 | − source_id: text('source_id').notNull(), | |
| 374 | − name: text('name').notNull(), | |
| 375 | − kind: text({ enum: ['text', 'secret'] }).notNull(), | |
| 376 | − text_value: text('text_value'), | |
| 377 | − secret_id: text('secret_id'), | |
| 378 | − secret_prefix: text('secret_prefix') | |
| 379 | −}, (table) => [ | |
| 380 | − primaryKey({ columns: [table.scope_id, table.id] }), | |
| 381 | − index("graphql_source_query_param_scope_id_idx").on(table.scope_id), | |
| 382 | − index("graphql_source_query_param_source_id_idx").on(table.source_id), | |
| 383 | − index("graphql_source_query_param_secret_id_idx").on(table.secret_id), | |
| 384 | −]); | |
| 385 | − | |
| 386 | −export const graphql_operation = sqliteTable("graphql_operation", { | |
| 387 | − id: text('id').notNull(), | |
| 388 | − scope_id: text('scope_id').notNull(), | |
| 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 | −]); | |
| 472 | +export const graphql_source_query_param = sqliteTable( | |
| 473 | + "graphql_source_query_param", | |
| 474 | + { | |
| 475 | + id: text("id").notNull(), | |
| 476 | + scope_id: text("scope_id").notNull(), | |
| 477 | + source_id: text("source_id").notNull(), | |
| 478 | + name: text("name").notNull(), | |
| 479 | + kind: text({ enum: ["text", "secret"] }).notNull(), | |
| 480 | + text_value: text("text_value"), | |
| 481 | + secret_id: text("secret_id"), | |
| 482 | + secret_prefix: text("secret_prefix"), | |
| 483 | + }, | |
| 484 | + (table) => [ | |
| 485 | + primaryKey({ columns: [table.scope_id, table.id] }), | |
| 486 | + index("graphql_source_query_param_scope_id_idx").on(table.scope_id), | |
| 487 | + index("graphql_source_query_param_source_id_idx").on(table.source_id), | |
| 488 | + index("graphql_source_query_param_secret_id_idx").on(table.secret_id), | |
| 489 | + ], | |
| 490 | +); | |
| 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 | 16 | } from "./db-upgrade"; |
| 17 | 17 | import { migrateLegacyConnections } from "./migrate-connections"; |
| 18 | 18 | |
| 19 | −import { | |
| 20 | − Scope, | |
| 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"; | |
| 19 | +import { Scope, ScopeId, type AnyPlugin, collectSchemas, createExecutor } from "@executor-js/sdk"; | |
| 20 | +import { makeSqliteAdapter, makeSqliteBlobStore } from "@executor-js/storage-file"; | |
| 30 | 21 | import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"; |
| 31 | 22 | import { loadPluginsFromJsonc, makeFileConfigSink } from "@executor-js/config"; |
| 32 | 23 | import * as executorSchema from "./executor-schema"; |
| @@ -107,9 +98,7 @@ class LocalExecutorTag extends Context.Service<LocalExecutorTag, LocalExecutorBu · 74 unmodified lines | ||
| 107 | 98 | |
| 108 | 99 | export type LocalExecutor = LocalExecutorBundle["executor"]; |
| 109 | 100 | |
| 110 | −class LocalExecutorDisposeError extends Data.TaggedError( | |
| 111 | − "LocalExecutorDisposeError", | |
| 112 | −)<{ | |
| 101 | +class LocalExecutorDisposeError extends Data.TaggedError("LocalExecutorDisposeError")<{ | |
| 113 | 102 | readonly operation: "createHandle" | "disposeExecutor" | "disposeRuntime"; |
| 114 | 103 | readonly cause: unknown; |
| 115 | 104 | }> {} |
| @@ -131,13 +120,10 @@ const handleOrNull = (promise: ReturnType<typeof createExecutorHandle>) => · 15 unmodified lines | ||
| 131 | 120 | Effect.runPromise( |
| 132 | 121 | Effect.tryPromise({ |
| 133 | 122 | try: () => promise, |
| 134 | − catch: (cause) => | |
| 135 | − new LocalExecutorDisposeError({ operation: "createHandle", cause }), | |
| 123 | + catch: (cause) => new LocalExecutorDisposeError({ operation: "createHandle", cause }), | |
| 136 | 124 | }).pipe( |
| 137 | 125 | Effect.catch(() => |
| 138 | − Effect.succeed<Awaited<ReturnType<typeof createExecutorHandle>> | null>( | |
| 139 | − null, | |
| 140 | − ), | |
| 126 | + Effect.succeed<Awaited<ReturnType<typeof createExecutorHandle>> | null>(null), | |
| 141 | 127 | ), |
| 142 | 128 | ), |
| 143 | 129 | ); |
| @@ -145,7 +131,8 @@ const handleOrNull = (promise: ReturnType<typeof createExecutorHandle>) => · 1 unmodified line | ||
| 145 | 131 | const createLocalExecutorLayer = () => { |
| 146 | 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 | 136 | const sqlite = yield* Effect.acquireRelease( |
| 150 | 137 | Effect.sync(() => new Database(dbPath)), |
| 151 | 138 | (conn) => Effect.sync(() => conn.close()), |
| @@ -183,9 +170,7 @@ const createLocalExecutorLayer = () => { · 31 unmodified lines | ||
| 183 | 170 | // ordering. Without this, a package listed in both surfaces would |
| 184 | 171 | // boot twice (double routes, double in-memory storage). |
| 185 | 172 | const staticPackageNames = new Set( |
| 186 | − staticPlugins | |
| 187 | − .map((p) => p.packageName) | |
| 188 | − .filter((n): n is string => !!n), | |
| 173 | + staticPlugins.map((p) => p.packageName).filter((n): n is string => !!n), | |
| 189 | 174 | ); |
| 190 | 175 | const dedupedDynamic = dynamicPlugins.filter((p) => { |
| 191 | 176 | if (p.packageName && staticPackageNames.has(p.packageName)) { |
| @@ -261,9 +246,7 @@ export const disposeExecutor = async (): Promise<void> => { · 69 unmodified lines | ||
| 261 | 246 | const currentHandlePromise = sharedHandlePromise; |
| 262 | 247 | sharedHandlePromise = null; |
| 263 | 248 | |
| 264 | − const handle = currentHandlePromise | |
| 265 | − ? await handleOrNull(currentHandlePromise) | |
| 266 | − : null; | |
| 249 | + const handle = currentHandlePromise ? await handleOrNull(currentHandlePromise) : null; | |
| 267 | 250 | if (handle) { |
| 268 | 251 | await ignorePromiseFailure("disposeExecutor", () => handle.dispose()); |
| 269 | 252 | } |
| @@ -103,10 +103,12 @@ export const createServerHandlers = async (): Promise<ServerHandlers> => { · 102 unmodified lines | ||
| 103 | 103 | return { api: apiHandler, mcp }; |
| 104 | 104 | }; |
| 105 | 105 | |
| 106 | −export class ServerHandlersService extends Context.Service<ServerHandlersService, ServerHandlers | |
| 107 | −>()("@executor-js/local/ServerHandlersService") {} | |
| 106 | +export class ServerHandlersService extends Context.Service<ServerHandlersService, ServerHandlers>()( | |
| 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 | 112 | Effect.promise(() => createServerHandlers()), |
| 111 | 113 | (handlers) => Effect.promise(() => closeServerHandlers(handlers)), |
| 112 | 114 | ), |
| @@ -34,30 +34,14 @@ import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http"; · 33 unmodified lines | ||
| 34 | 34 | import { Effect, Layer, Option, Schema } from "effect"; |
| 35 | 35 | |
| 36 | 36 | import { addGroup, observabilityMiddleware } from "@executor-js/api"; |
| 37 | −import { | |
| 38 | − CoreHandlers, | |
| 39 | − ExecutionEngineService, | |
| 40 | − ExecutorService, | |
| 41 | −} from "@executor-js/api/server"; | |
| 37 | +import { CoreHandlers, ExecutionEngineService, ExecutorService } from "@executor-js/api/server"; | |
| 42 | 38 | import { createExecutionEngine } from "@executor-js/execution"; |
| 43 | 39 | import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs"; |
| 44 | −import { | |
| 45 | − Scope, | |
| 46 | − ScopeId, | |
| 47 | − collectSchemas, | |
| 48 | − createExecutor, | |
| 49 | −} from "@executor-js/sdk"; | |
| 50 | −import { | |
| 51 | − makeSqliteAdapter, | |
| 52 | − makeSqliteBlobStore, | |
| 53 | −} from "@executor-js/storage-file"; | |
| 40 | +import { Scope, ScopeId, collectSchemas, createExecutor } from "@executor-js/sdk"; | |
| 41 | +import { makeSqliteAdapter, makeSqliteBlobStore } from "@executor-js/storage-file"; | |
| 54 | 42 | import { fileSecretsPlugin } from "@executor-js/plugin-file-secrets"; |
| 55 | 43 | import { mcpPlugin } from "@executor-js/plugin-mcp"; |
| 56 | −import { | |
| 57 | − McpExtensionService, | |
| 58 | − McpGroup, | |
| 59 | − McpHandlers, | |
| 60 | −} from "@executor-js/plugin-mcp/api"; | |
| 44 | +import { McpExtensionService, McpGroup, McpHandlers } from "@executor-js/plugin-mcp/api"; | |
| 61 | 45 | |
| 62 | 46 | import * as executorSchema from "./executor-schema"; |
| 63 | 47 | import { ErrorCaptureLive } from "./observability"; |
| @@ -66,9 +50,10 @@ import { ErrorCaptureLive } from "./observability"; · 2 unmodified lines | ||
| 66 | 50 | // the top level so `observabilityMiddleware` can land its typed-error |
| 67 | 51 | // bridge on every endpoint. |
| 68 | 52 | const TestApi = addGroup(McpGroup); |
| 69 | −type TestApiShape = typeof TestApi extends HttpApi.HttpApi<infer _Id, infer Groups> | |
| 70 | − ? HttpApiClient.Client<Groups, never> | |
| 71 | − : never; | |
| 53 | +type TestApiShape = | |
| 54 | + typeof TestApi extends HttpApi.HttpApi<infer _Id, infer Groups> | |
| 55 | + ? HttpApiClient.Client<Groups, never> | |
| 56 | + : never; | |
| 72 | 57 | |
| 73 | 58 | // --------------------------------------------------------------------------- |
| 74 | 59 | // Fake OAuth + MCP server (mirrors the cloud test) |
| @@ -84,21 +69,15 @@ const RegistrationBody = Schema.Struct({ · 9 unmodified lines | ||
| 84 | 69 | grant_types: Schema.optional(Schema.Array(Schema.String)), |
| 85 | 70 | response_types: Schema.optional(Schema.Array(Schema.String)), |
| 86 | 71 | }); |
| 87 | −const decodeRegistrationBody = Schema.decodeUnknownOption( | |
| 88 | − Schema.fromJsonString(RegistrationBody), | |
| 89 | −); | |
| 72 | +const decodeRegistrationBody = Schema.decodeUnknownOption(Schema.fromJsonString(RegistrationBody)); | |
| 90 | 73 | |
| 91 | 74 | const startFakeServer = async (): Promise<FakeServer> => { |
| 92 | 75 | const clients = new Map<string, { redirect_uris: readonly string[] }>(); |
| 93 | − const codes = new Map< | |
| 94 | − string, | |
| 95 | − { readonly clientId: string; readonly codeChallenge: string } | |
| 96 | − >(); | |
| 76 | + const codes = new Map<string, { readonly clientId: string; readonly codeChallenge: string }>(); | |
| 97 | 77 | const accessTokens = new Map<string, { readonly refresh: string }>(); |
| 98 | 78 | const refreshTokens = new Map<string, string>(); |
| 99 | 79 | let seq = 0; |
| 100 | − const next = (p: string) => | |
| 101 | − `${p}_${++seq}_${randomBytes(6).toString("hex")}`; | |
| 80 | + const next = (p: string) => `${p}_${++seq}_${randomBytes(6).toString("hex")}`; | |
| 102 | 81 | |
| 103 | 82 | const readBody = (req: import("node:http").IncomingMessage): Promise<string> => |
| 104 | 83 | new Promise((resolve, reject) => { |
| @@ -110,15 +89,10 @@ const startFakeServer = async (): Promise<FakeServer> => { · 5 unmodified lines | ||
| 110 | 89 | |
| 111 | 90 | const server: Server = createServer(async (req, res) => { |
| 112 | 91 | const url = new URL(req.url!, `http://${req.headers.host}`); |
| 113 | − const send = ( | |
| 114 | − status: number, | |
| 115 | − body: unknown, | |
| 116 | − headers: Record<string, string> = {}, | |
| 117 | − ) => { | |
| 92 | + const send = (status: number, body: unknown, headers: Record<string, string> = {}) => { | |
| 118 | 93 | const payload = typeof body === "string" ? body : JSON.stringify(body); |
| 119 | 94 | res.writeHead(status, { |
| 120 | − "content-type": | |
| 121 | − typeof body === "string" ? "text/plain" : "application/json", | |
| 95 | + "content-type": typeof body === "string" ? "text/plain" : "application/json", | |
| 122 | 96 | ...headers, |
| 123 | 97 | }); |
| 124 | 98 | res.end(payload); |
| @@ -162,10 +136,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 37 unmodified lines | ||
| 162 | 136 | client_id: clientId, |
| 163 | 137 | client_id_issued_at: Math.floor(Date.now() / 1000), |
| 164 | 138 | redirect_uris: parsed.redirect_uris ?? [], |
| 165 | − grant_types: parsed.grant_types ?? [ | |
| 166 | − "authorization_code", | |
| 167 | − "refresh_token", | |
| 168 | − ], | |
| 139 | + grant_types: parsed.grant_types ?? ["authorization_code", "refresh_token"], | |
| 169 | 140 | response_types: parsed.response_types ?? ["code"], |
| 170 | 141 | token_endpoint_auth_method: "none", |
| 171 | 142 | }); |
| @@ -202,9 +173,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 30 unmodified lines | ||
| 202 | 173 | const record = codes.get(code); |
| 203 | 174 | if (!record) return send(400, { error: "invalid_grant" }); |
| 204 | 175 | codes.delete(code); |
| 205 | − const computed = createHash("sha256") | |
| 206 | − .update(verifier) | |
| 207 | − .digest("base64url"); | |
| 176 | + const computed = createHash("sha256").update(verifier).digest("base64url"); | |
| 208 | 177 | if (computed !== record.codeChallenge) { |
| 209 | 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) => | |
| 244 | − server.listen(0, "127.0.0.1", () => resolve()), | |
| 245 | − ); | |
| 212 | + await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", () => resolve())); | |
| 246 | 213 | const address = server.address() as AddressInfo; |
| 247 | 214 | |
| 248 | 215 | return { |
| @@ -328,15 +295,9 @@ const startHarness = async (tmpDir: string): Promise<Harness> => { · 79 unmodified lines | ||
| 328 | 295 | )) as typeof globalThis.fetch, |
| 329 | 296 | scopeId, |
| 330 | 297 | dispose: async () => { |
| 298 | + await Effect.runPromise(Effect.ignore(Effect.tryPromise(() => disposeHandler()))); | |
| 331 | 299 | await Effect.runPromise( |
| 332 | − Effect.ignore( | |
| 333 | − Effect.tryPromise(() => disposeHandler()), | |
| 334 | − ), | |
| 335 | − ); | |
| 336 | − await Effect.runPromise( | |
| 337 | − Effect.ignore( | |
| 338 | − Effect.tryPromise(() => Effect.runPromise(executor.close())), | |
| 339 | − ), | |
| 300 | + Effect.ignore(Effect.tryPromise(() => Effect.runPromise(executor.close()))), | |
| 340 | 301 | ); |
| 341 | 302 | sqlite.close(); |
| 342 | 303 | }, |
| @@ -400,9 +361,7 @@ describe("local mcp oauth (real OAuth + MCP server)", () => { · 57 unmodified lines | ||
| 400 | 361 | const redirectUrl = "http://local.test/api/mcp/oauth/callback"; |
| 401 | 362 | const scopeId = ScopeId.make(harness.scopeId); |
| 402 | 363 | |
| 403 | − const run = <A, E>( | |
| 404 | − body: (client: TestApiShape) => Effect.Effect<A, E>, | |
| 405 | − ): Effect.Effect<A, E> => | |
| 364 | + const run = <A, E>(body: (client: TestApiShape) => Effect.Effect<A, E>): Effect.Effect<A, E> => | |
| 406 | 365 | Effect.gen(function* () { |
| 407 | 366 | const client = yield* HttpApiClient.make(TestApi, { |
| 408 | 367 | baseUrl: TEST_BASE_URL, |
| @@ -40,9 +40,7 @@ const columnNames = (db: Database, table: string): ReadonlyArray<string> => · 39 unmodified lines | ||
| 40 | 40 | const MigratedMcpConfig = Schema.Struct({ |
| 41 | 41 | auth: Schema.optional(Schema.Unknown), |
| 42 | 42 | }); |
| 43 | −const decodeMigratedMcpConfig = Schema.decodeUnknownSync( | |
| 44 | − Schema.fromJsonString(MigratedMcpConfig), | |
| 45 | −); | |
| 43 | +const decodeMigratedMcpConfig = Schema.decodeUnknownSync(Schema.fromJsonString(MigratedMcpConfig)); | |
| 46 | 44 | |
| 47 | 45 | const MigratedOpenApiOAuth2 = Schema.Struct({ |
| 48 | 46 | kind: Schema.Literal("oauth2"), |
| @@ -130,9 +128,7 @@ describe("migrateLegacyConnections", () => { · 81 unmodified lines | ||
| 130 | 128 | expect(source.auth_connection_id).toBe("mcp-oauth2-remote-mcp"); |
| 131 | 129 | |
| 132 | 130 | const ownedSecrets = db |
| 133 | − .prepare( | |
| 134 | − "SELECT id, owned_by_connection_id FROM secret WHERE scope_id = ? ORDER BY id", | |
| 135 | − ) | |
| 131 | + .prepare("SELECT id, owned_by_connection_id FROM secret WHERE scope_id = ? ORDER BY id") | |
| 136 | 132 | .all("scope-1"); |
| 137 | 133 | expect(ownedSecrets).toEqual([ |
| 138 | 134 | { |
| @@ -208,9 +204,7 @@ describe("migrateLegacyConnections", () => { · 69 unmodified lines | ||
| 208 | 204 | await migrateLegacyConnections(db); |
| 209 | 205 | |
| 210 | 206 | const connection = db |
| 211 | − .prepare( | |
| 212 | − "SELECT id, provider, access_token_secret_id FROM connection WHERE scope_id = ?", | |
| 213 | − ) | |
| 207 | + .prepare("SELECT id, provider, access_token_secret_id FROM connection WHERE scope_id = ?") | |
| 214 | 208 | .get("scope-1") as |
| 215 | 209 | | { readonly id: string; readonly provider: string; readonly access_token_secret_id: string } |
| 216 | 210 | | undefined; |
| @@ -16,11 +16,7 @@ import { Database } from "bun:sqlite"; · 15 unmodified lines | ||
| 16 | 16 | import { randomUUID } from "node:crypto"; |
| 17 | 17 | import { Effect, Option, Result, Schema } from "effect"; |
| 18 | 18 | import { FetchHttpClient } from "effect/unstable/http"; |
| 19 | −import { | |
| 20 | − parse as parseOpenApi, | |
| 21 | − resolveSpecText, | |
| 22 | − OAuth2Auth, | |
| 23 | −} from "@executor-js/plugin-openapi"; | |
| 19 | +import { parse as parseOpenApi, resolveSpecText, OAuth2Auth } from "@executor-js/plugin-openapi"; | |
| 24 | 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 | 47 | /** Pre-flight: bail unless the drizzle migration that added the Connection |
| 52 | 48 | * table + `secret.owned_by_connection_id` has completed. */ |
| 53 | 49 | const connectionsReady = (sqlite: Database): boolean => { |
| 54 | − const secretColumns = sqlite | |
| 55 | − .prepare("PRAGMA table_info('secret')") | |
| 56 | − .all() as ReadonlyArray<{ readonly name: string }>; | |
| 50 | + const secretColumns = sqlite.prepare("PRAGMA table_info('secret')").all() as ReadonlyArray<{ | |
| 51 | + readonly name: string; | |
| 52 | + }>; | |
| 57 | 53 | if (!secretColumns.some((c) => c.name === "owned_by_connection_id")) return false; |
| 58 | 54 | const connectionTable = sqlite |
| 59 | 55 | .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='connection'") |
| @@ -103,16 +99,10 @@ const rewireSecrets = ( · 43 unmodified lines | ||
| 103 | 99 | ) VALUES (?, ?, ?, ?, ?, ?)`, |
| 104 | 100 | ); |
| 105 | 101 | |
| 106 | − const rows = secretIds.map( | |
| 107 | − (sid) => selectSecret.get(scopeId, sid) as SecretRow | undefined, | |
| 108 | − ); | |
| 102 | + const rows = secretIds.map((sid) => selectSecret.get(scopeId, sid) as SecretRow | undefined); | |
| 109 | 103 | const alreadyOwned = rows |
| 110 | 104 | .filter((r): r is SecretRow => !!r) |
| 111 | − .filter( | |
| 112 | − (r) => | |
| 113 | − r.owned_by_connection_id !== null && | |
| 114 | − r.owned_by_connection_id !== connectionId, | |
| 115 | − ); | |
| 105 | + .filter((r) => r.owned_by_connection_id !== null && r.owned_by_connection_id !== connectionId); | |
| 116 | 106 | if (alreadyOwned.length > 0) return "secret(s) already owned"; |
| 117 | 107 | |
| 118 | 108 | // Early-onboarded rows never got a `secret` routing row — pre-refactor |
| @@ -123,9 +113,7 @@ const rewireSecrets = ( · 4 unmodified lines | ||
| 123 | 113 | const missingCount = rows.filter((r) => r === undefined).length; |
| 124 | 114 | let fallbackProvider: string | null = null; |
| 125 | 115 | if (missingCount > 0) { |
| 126 | − const existing = selectAnySecretProvider.get(scopeId) as | |
| 127 | − | { provider: string } | |
| 128 | − | undefined; | |
| 116 | + const existing = selectAnySecretProvider.get(scopeId) as { provider: string } | undefined; | |
| 129 | 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 | 203 | const decodeOpenApiCurrent = Schema.decodeUnknownOption(OAuth2Auth); |
| 216 | −const decodeOpenApiLegacy = decodeUnknownOptionAs<LegacyOpenApiOAuth2>( | |
| 217 | − LegacyOpenApiOAuth2, | |
| 218 | −); | |
| 204 | +const decodeOpenApiLegacy = decodeUnknownOptionAs<LegacyOpenApiOAuth2>(LegacyOpenApiOAuth2); | |
| 219 | 205 | |
| 220 | 206 | const extractAuthorizationUrl = async ( |
| 221 | 207 | rawSpec: string, |
| @@ -234,19 +220,18 @@ const extractAuthorizationUrl = async ( · 12 unmodified lines | ||
| 234 | 220 | const spec = parsed.success as unknown; |
| 235 | 221 | if (!isRecord(spec)) return null; |
| 236 | 222 | const components = isRecord(spec.components) ? spec.components : null; |
| 237 | − const schemes = components && isRecord(components.securitySchemes) | |
| 238 | − ? components.securitySchemes | |
| 239 | − : null; | |
| 240 | − const scheme = schemes && isRecord(schemes[securitySchemeName]) | |
| 241 | − ? (schemes[securitySchemeName] as Record<string, unknown>) | |
| 242 | − : null; | |
| 223 | + const schemes = | |
| 224 | + components && isRecord(components.securitySchemes) ? components.securitySchemes : null; | |
| 225 | + const scheme = | |
| 226 | + schemes && isRecord(schemes[securitySchemeName]) | |
| 227 | + ? (schemes[securitySchemeName] as Record<string, unknown>) | |
| 228 | + : null; | |
| 243 | 229 | const flows = scheme && isRecord(scheme.flows) ? scheme.flows : null; |
| 244 | − const flowObj = flows && isRecord(flows.authorizationCode) | |
| 245 | − ? (flows.authorizationCode as Record<string, unknown>) | |
| 246 | − : null; | |
| 247 | − return flowObj && isString(flowObj.authorizationUrl) | |
| 248 | − ? flowObj.authorizationUrl | |
| 249 | − : null; | |
| 230 | + const flowObj = | |
| 231 | + flows && isRecord(flows.authorizationCode) | |
| 232 | + ? (flows.authorizationCode as Record<string, unknown>) | |
| 233 | + : null; | |
| 234 | + return flowObj && isString(flowObj.authorizationUrl) ? flowObj.authorizationUrl : null; | |
| 250 | 235 | }; |
| 251 | 236 | |
| 252 | 237 | type OpenApiRow = { |
| @@ -266,11 +251,7 @@ const migrateOpenApi = async (sqlite: Database): Promise<void> => { · 13 unmodified lines | ||
| 266 | 251 | // pointer. Pre-migration, both columns mirror each other; post- |
| 267 | 252 | // migration, only `oauth2` is left. We read both when available and |
| 268 | 253 | // fall back to `oauth2` so legacy data isn't silently skipped. |
| 269 | − const hasInvocationConfig = columnExists( | |
| 270 | − sqlite, | |
| 271 | − "openapi_source", | |
| 272 | − "invocation_config", | |
| 273 | − ); | |
| 254 | + const hasInvocationConfig = columnExists(sqlite, "openapi_source", "invocation_config"); | |
| 274 | 255 | const selectCols = hasInvocationConfig |
| 275 | 256 | ? "scope_id, id, name, spec, invocation_config, oauth2" |
| 276 | 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 | 264 | ? sqlite.prepare( |
| 284 | 265 | "UPDATE openapi_source SET oauth2 = ?, invocation_config = ? WHERE scope_id = ? AND id = ?", |
| 285 | 266 | ) |
| 286 | − : sqlite.prepare( | |
| 287 | − "UPDATE openapi_source SET oauth2 = ? WHERE scope_id = ? AND id = ?", | |
| 288 | − ); | |
| 267 | + : sqlite.prepare("UPDATE openapi_source SET oauth2 = ? WHERE scope_id = ? AND id = ?"); | |
| 289 | 268 | |
| 290 | 269 | for (const row of rows) { |
| 291 | 270 | let invocation: Record<string, unknown> = {}; |
| @@ -370,19 +349,13 @@ const migrateOpenApi = async (sqlite: Database): Promise<void> => { · 78 unmodified lines | ||
| 370 | 349 | row.id, |
| 371 | 350 | ); |
| 372 | 351 | } else { |
| 373 | − updateSource.run( | |
| 374 | − JSON.stringify(oauth2Pointer), | |
| 375 | − row.scope_id, | |
| 376 | − row.id, | |
| 377 | − ); | |
| 352 | + updateSource.run(JSON.stringify(oauth2Pointer), row.scope_id, row.id); | |
| 378 | 353 | } |
| 379 | 354 | }); |
| 380 | 355 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: bun:sqlite transaction reports rollback failures by throwing |
| 381 | 356 | try { |
| 382 | 357 | txn(); |
| 383 | − console.log( | |
| 384 | − `[migrate-connections] openapi ${row.scope_id}/${row.id} -> ${connectionId}`, | |
| 385 | − ); | |
| 358 | + console.log(`[migrate-connections] openapi ${row.scope_id}/${row.id} -> ${connectionId}`); | |
| 386 | 359 | } catch (err) { |
| 387 | 360 | console.warn( |
| 388 | 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 | 445 | const endpoint = typeof config.endpoint === "string" ? config.endpoint : null; |
| 473 | 446 | if (!endpoint) { |
| 474 | − console.warn( | |
| 475 | − `[migrate-connections] skip mcp ${row.scope_id}/${row.id}: endpoint missing`, | |
| 476 | − ); | |
| 447 | + console.warn(`[migrate-connections] skip mcp ${row.scope_id}/${row.id}: endpoint missing`); | |
| 477 | 448 | continue; |
| 478 | 449 | } |
| 479 | 450 | const connectionId = `mcp-oauth2-${row.id}`; |
| @@ -518,12 +489,7 @@ const migrateMcp = (sqlite: Database): void => { · 38 unmodified lines | ||
| 518 | 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 | 490 | if (err) throw new Error(err); |
| 520 | 491 | if (updateConfigAndAuth) { |
| 521 | − updateConfigAndAuth.run( | |
| 522 | − JSON.stringify(nextConfig), | |
| 523 | − connectionId, | |
| 524 | − row.scope_id, | |
| 525 | − row.id, | |
| 526 | − ); | |
| 492 | + updateConfigAndAuth.run(JSON.stringify(nextConfig), connectionId, row.scope_id, row.id); | |
| 527 | 493 | } else { |
| 528 | 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 | 497 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: bun:sqlite transaction reports rollback failures by throwing |
| 532 | 498 | try { |
| 533 | 499 | txn(); |
| 534 | − console.log( | |
| 535 | − `[migrate-connections] mcp ${row.scope_id}/${row.id} -> ${connectionId}`, | |
| 536 | − ); | |
| 500 | + console.log(`[migrate-connections] mcp ${row.scope_id}/${row.id} -> ${connectionId}`); | |
| 537 | 501 | } catch (err) { |
| 538 | 502 | console.warn( |
| 539 | 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 | 57 | const decodeHeaderRows = Schema.decodeUnknownSync(Schema.Array(GraphqlHeaderRow)); |
| 58 | 58 | const decodeQueryParamRow = Schema.decodeUnknownSync(GraphqlQueryParamRow); |
| 59 | 59 | const decodeCountRow = Schema.decodeUnknownSync(CountRow); |
| 60 | −const decodeHeaderIdRows = Schema.decodeUnknownSync( | |
| 61 | − Schema.Array(GraphqlHeaderIdRow), | |
| 62 | −); | |
| 60 | +const decodeHeaderIdRows = Schema.decodeUnknownSync(Schema.Array(GraphqlHeaderIdRow)); | |
| 63 | 61 | |
| 64 | 62 | let dir: string; |
| 65 | 63 | |
| @@ -96,19 +94,13 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 30 unmodified lines | ||
| 96 | 94 | const after = new Database(dbPath, { readonly: true }); |
| 97 | 95 | const row = decodeAuthRow( |
| 98 | 96 | after |
| 99 | − .prepare( | |
| 100 | − "SELECT auth_kind, auth_connection_id FROM graphql_source WHERE id = ?", | |
| 101 | − ) | |
| 97 | + .prepare("SELECT auth_kind, auth_connection_id FROM graphql_source WHERE id = ?") | |
| 102 | 98 | .get("github"), |
| 103 | 99 | ); |
| 104 | 100 | expect(row.auth_kind).toBe("oauth2"); |
| 105 | 101 | expect(row.auth_connection_id).toBe("conn-1"); |
| 106 | 102 | // Old json column is gone. |
| 107 | − const cols = decodeTableInfoRows( | |
| 108 | − after | |
| 109 | − .prepare("PRAGMA table_info('graphql_source')") | |
| 110 | − .all(), | |
| 111 | − ); | |
| 103 | + const cols = decodeTableInfoRows(after.prepare("PRAGMA table_info('graphql_source')").all()); | |
| 112 | 104 | expect(cols.some((c) => c.name === "auth")).toBe(false); |
| 113 | 105 | expect(cols.some((c) => c.name === "headers")).toBe(false); |
| 114 | 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 | 173 | const paramRow = decodeQueryParamRow( |
| 182 | 174 | after |
| 183 | − .prepare( | |
| 184 | − "SELECT kind, secret_id FROM graphql_source_query_param WHERE source_id = ?", | |
| 185 | − ) | |
| 175 | + .prepare("SELECT kind, secret_id FROM graphql_source_query_param WHERE source_id = ?") | |
| 186 | 176 | .get("example"), |
| 187 | 177 | ); |
| 188 | 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 | 186 | db.exec(PRE_0007_SQL); |
| 197 | 187 | stampPriorMigrationsApplied(db); |
| 198 | 188 | |
| 199 | − db.prepare( | |
| 200 | − "INSERT INTO graphql_source (scope_id, id, name, endpoint) VALUES (?, ?, ?, ?)", | |
| 201 | − ).run("default-scope", "bare", "Bare", "https://bare.example/graphql"); | |
| 189 | + db.prepare("INSERT INTO graphql_source (scope_id, id, name, endpoint) VALUES (?, ?, ?, ?)").run( | |
| 190 | + "default-scope", | |
| 191 | + "bare", | |
| 192 | + "Bare", | |
| 193 | + "https://bare.example/graphql", | |
| 194 | + ); | |
| 202 | 195 | db.close(); |
| 203 | 196 | |
| 204 | 197 | const drizzleDb = drizzle(new Database(dbPath)); |
| @@ -207,9 +200,7 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 2 unmodified lines | ||
| 207 | 200 | const after = new Database(dbPath, { readonly: true }); |
| 208 | 201 | const row = decodeAuthRow( |
| 209 | 202 | after |
| 210 | − .prepare( | |
| 211 | − "SELECT auth_kind, auth_connection_id FROM graphql_source WHERE id = ?", | |
| 212 | − ) | |
| 203 | + .prepare("SELECT auth_kind, auth_connection_id FROM graphql_source WHERE id = ?") | |
| 213 | 204 | .get("bare"), |
| 214 | 205 | ); |
| 215 | 206 | expect(row.auth_kind).toBe("none"); |
| @@ -217,9 +208,7 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 1 unmodified line | ||
| 217 | 208 | |
| 218 | 209 | const headerCount = decodeCountRow( |
| 219 | 210 | after |
| 220 | − .prepare( | |
| 221 | − "SELECT count(*) as n FROM graphql_source_header WHERE source_id = ?", | |
| 222 | − ) | |
| 211 | + .prepare("SELECT count(*) as n FROM graphql_source_header WHERE source_id = ?") | |
| 223 | 212 | .get("bare"), |
| 224 | 213 | ).n; |
| 225 | 214 | expect(headerCount).toBe(0); |
| @@ -283,9 +283,7 @@ describe("0007_normalize_plugin_secret_refs (openapi)", () => { · 282 unmodified lines | ||
| 283 | 283 | const after = openDatabase(dbPath, { readonly: true }); |
| 284 | 284 | const qpCount = Schema.decodeUnknownSync(CountRow)( |
| 285 | 285 | after |
| 286 | − .prepare( | |
| 287 | − "SELECT count(*) as n FROM openapi_source_query_param WHERE source_id = ?", | |
| 288 | − ) | |
| 286 | + .prepare("SELECT count(*) as n FROM openapi_source_query_param WHERE source_id = ?") | |
| 289 | 287 | .get("bare"), |
| 290 | 288 | ).n; |
| 291 | 289 | expect(qpCount).toBe(0); |
| @@ -24,7 +24,7 @@ export const ErrorCaptureLive: Layer.Layer<ErrorCapture> = Layer.succeed( · 23 unmodified lines | ||
| 24 | 24 | console.error( |
| 25 | 25 | `[executor ${traceId}]`, |
| 26 | 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 | 29 | console.error(`[executor ${traceId}] cause:`, Cause.pretty(cause)); |
| 30 | 30 | return traceId; |
| @@ -218,7 +218,9 @@ body { · 217 unmodified lines | ||
| 218 | 218 | text-decoration: underline; |
| 219 | 219 | text-decoration-color: color-mix(in srgb, var(--color-teal) 35%, transparent); |
| 220 | 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 | 226 | .legal-prose a:hover { |
| @@ -230,4 +232,3 @@ body { · 5 unmodified lines | ||
| 230 | 232 | color: var(--color-ink); |
| 231 | 233 | font-weight: 600; |
| 232 | 234 | } |
| 233 | − | |
| @@ -2,84 +2,84 @@ import { feature, item, plan } from "atmn"; · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 3 | // Features |
| 4 | 4 | export const executions = feature({ |
| 5 | − id: 'executions', | |
| 6 | − name: 'Executions', | |
| 7 | − type: 'metered', | |
| 8 | − consumable: true, | |
| 5 | + id: "executions", | |
| 6 | + name: "Executions", | |
| 7 | + type: "metered", | |
| 8 | + consumable: true, | |
| 9 | 9 | }); |
| 10 | 10 | |
| 11 | 11 | export const domainVerification = feature({ |
| 12 | − id: 'domain-verification', | |
| 13 | − name: 'Domain Verification', | |
| 14 | − type: 'boolean', | |
| 12 | + id: "domain-verification", | |
| 13 | + name: "Domain Verification", | |
| 14 | + type: "boolean", | |
| 15 | 15 | }); |
| 16 | 16 | |
| 17 | 17 | export const members = feature({ |
| 18 | − id: 'members', | |
| 19 | − name: 'Members', | |
| 20 | − type: 'metered', | |
| 21 | − consumable: false, | |
| 22 | − archived: true, | |
| 18 | + id: "members", | |
| 19 | + name: "Members", | |
| 20 | + type: "metered", | |
| 21 | + consumable: false, | |
| 22 | + archived: true, | |
| 23 | 23 | }); |
| 24 | 24 | |
| 25 | 25 | // Plans |
| 26 | 26 | export const free = plan({ |
| 27 | − id: 'free', | |
| 28 | − name: 'Free', | |
| 29 | − addOn: false, | |
| 30 | − autoEnable: true, | |
| 31 | − items: [ | |
| 32 | − item({ | |
| 33 | − featureId: executions.id, | |
| 34 | − included: 10000, | |
| 35 | − reset: { | |
| 36 | − interval: 'month', | |
| 37 | − }, | |
| 38 | − }), | |
| 39 | − ], | |
| 27 | + id: "free", | |
| 28 | + name: "Free", | |
| 29 | + addOn: false, | |
| 30 | + autoEnable: true, | |
| 31 | + items: [ | |
| 32 | + item({ | |
| 33 | + featureId: executions.id, | |
| 34 | + included: 10000, | |
| 35 | + reset: { | |
| 36 | + interval: "month", | |
| 37 | + }, | |
| 38 | + }), | |
| 39 | + ], | |
| 40 | 40 | }); |
| 41 | 41 | |
| 42 | 42 | export const freePayAsYouGo = plan({ |
| 43 | − id: 'free-pay-as-you-go', | |
| 44 | − name: 'Free Pay As You Go', | |
| 45 | − addOn: false, | |
| 46 | − autoEnable: false, | |
| 47 | − items: [ | |
| 48 | − item({ | |
| 49 | − featureId: executions.id, | |
| 50 | − included: 10000, | |
| 51 | − price: { | |
| 52 | − amount: 0.2, | |
| 53 | − billingUnits: 1000, | |
| 54 | − billingMethod: 'usage_based', | |
| 55 | − interval: 'month', | |
| 56 | − }, | |
| 57 | − }), | |
| 58 | − ], | |
| 43 | + id: "free-pay-as-you-go", | |
| 44 | + name: "Free Pay As You Go", | |
| 45 | + addOn: false, | |
| 46 | + autoEnable: false, | |
| 47 | + items: [ | |
| 48 | + item({ | |
| 49 | + featureId: executions.id, | |
| 50 | + included: 10000, | |
| 51 | + price: { | |
| 52 | + amount: 0.2, | |
| 53 | + billingUnits: 1000, | |
| 54 | + billingMethod: "usage_based", | |
| 55 | + interval: "month", | |
| 56 | + }, | |
| 57 | + }), | |
| 58 | + ], | |
| 59 | 59 | }); |
| 60 | 60 | |
| 61 | 61 | export const team = plan({ |
| 62 | − id: 'team', | |
| 63 | − name: 'Team', | |
| 64 | − addOn: false, | |
| 65 | − autoEnable: false, | |
| 66 | − price: { | |
| 67 | − amount: 49, | |
| 68 | − interval: 'month', | |
| 69 | − }, | |
| 70 | − items: [ | |
| 71 | − item({ | |
| 72 | − featureId: executions.id, | |
| 73 | − included: 250000, | |
| 74 | − price: { | |
| 75 | − amount: 0.2, | |
| 76 | − billingUnits: 1000, | |
| 77 | − billingMethod: 'usage_based', | |
| 78 | − interval: 'month', | |
| 79 | − }, | |
| 80 | − }), | |
| 81 | − item({ | |
| 82 | − featureId: domainVerification.id, | |
| 83 | − }), | |
| 84 | − ], | |
| 62 | + id: "team", | |
| 63 | + name: "Team", | |
| 64 | + addOn: false, | |
| 65 | + autoEnable: false, | |
| 66 | + price: { | |
| 67 | + amount: 49, | |
| 68 | + interval: "month", | |
| 69 | + }, | |
| 70 | + items: [ | |
| 71 | + item({ | |
| 72 | + featureId: executions.id, | |
| 73 | + included: 250000, | |
| 74 | + price: { | |
| 75 | + amount: 0.2, | |
| 76 | + billingUnits: 1000, | |
| 77 | + billingMethod: "usage_based", | |
| 78 | + interval: "month", | |
| 79 | + }, | |
| 80 | + }), | |
| 81 | + item({ | |
| 82 | + featureId: domainVerification.id, | |
| 83 | + }), | |
| 84 | + ], | |
| 85 | 85 | }); |
| @@ -141,17 +141,13 @@ const exampleOpenApiSpec = JSON.stringify({ · 140 unmodified lines | ||
| 141 | 141 | get: { |
| 142 | 142 | operationId: "items.get", |
| 143 | 143 | tags: ["items"], |
| 144 | − parameters: [ | |
| 145 | − { name: "id", in: "path", required: true, schema: { type: "string" } }, | |
| 146 | − ], | |
| 144 | + parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], | |
| 147 | 145 | responses: { "200": { description: "ok" } }, |
| 148 | 146 | }, |
| 149 | 147 | delete: { |
| 150 | 148 | operationId: "items.delete", |
| 151 | 149 | tags: ["items"], |
| 152 | − parameters: [ | |
| 153 | − { name: "id", in: "path", required: true, schema: { type: "string" } }, | |
| 154 | − ], | |
| 150 | + parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], | |
| 155 | 151 | responses: { "204": { description: "deleted" } }, |
| 156 | 152 | }, |
| 157 | 153 | }, |
| @@ -400,10 +396,7 @@ const program = Effect.gen(function* () { · 242 unmodified lines | ||
| 400 | 396 | console.log(" executor.keychain.isSupported:", executor.keychain.isSupported); |
| 401 | 397 | console.log(" executor.keychain.displayName:", executor.keychain.displayName); |
| 402 | 398 | |
| 403 | − console.log( | |
| 404 | − " executor.fileSecrets.filePath: ", | |
| 405 | − executor.fileSecrets.filePath, | |
| 406 | − ); | |
| 399 | + console.log(" executor.fileSecrets.filePath: ", executor.fileSecrets.filePath); | |
| 407 | 400 | |
| 408 | 401 | // executor.mcp.addSource({ connector: { kind: "remote", endpoint: "..." } }); |
| 409 | 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 | 64 | project, with workflows as the first consumer. |
| 65 | 65 | |
| 66 | 66 | ```ts |
| 67 | −type ArtifactKind = | |
| 68 | − | "workflow" | |
| 69 | − | "component" | |
| 70 | − | "ui" | |
| 71 | − | "template" | |
| 72 | − | "connector" | |
| 73 | − | "document"; | |
| 67 | +type ArtifactKind = "workflow" | "component" | "ui" | "template" | "connector" | "document"; | |
| 74 | 68 | |
| 75 | 69 | type ArtifactProject = { |
| 76 | 70 | id: string; |
| @@ -171,18 +165,13 @@ export interface ArtifactStore { · 94 unmodified lines | ||
| 171 | 165 | input: CreateArtifactProjectInput, |
| 172 | 166 | ) => Effect.Effect<ArtifactProject, ArtifactError>; |
| 173 | 167 | |
| 174 | − readonly readFile: ( | |
| 175 | − ref: ArtifactRef, | |
| 176 | − path: string, | |
| 177 | − ) => Effect.Effect<Uint8Array, ArtifactError>; | |
| 168 | + readonly readFile: (ref: ArtifactRef, path: string) => Effect.Effect<Uint8Array, ArtifactError>; | |
| 178 | 169 | |
| 179 | 170 | readonly writeFiles: ( |
| 180 | 171 | input: WriteArtifactFilesInput, |
| 181 | 172 | ) => Effect.Effect<ArtifactCommit, ArtifactError>; |
| 182 | 173 | |
| 183 | − readonly diff: ( | |
| 184 | − input: ArtifactDiffInput, | |
| 185 | − ) => Effect.Effect<ArtifactDiff, ArtifactError>; | |
| 174 | + readonly diff: (input: ArtifactDiffInput) => Effect.Effect<ArtifactDiff, ArtifactError>; | |
| 186 | 175 | |
| 187 | 176 | readonly forkProject: ( |
| 188 | 177 | input: ForkArtifactProjectInput, |
| @@ -365,10 +354,7 @@ export interface ArtifactStore { · 176 unmodified lines | ||
| 365 | 354 | name: string; |
| 366 | 355 | }) => Effect.Effect<ArtifactProject, ArtifactError>; |
| 367 | 356 | |
| 368 | − readonly readFile: ( | |
| 369 | − ref: ArtifactRef, | |
| 370 | − path: string, | |
| 371 | − ) => Effect.Effect<Uint8Array, ArtifactError>; | |
| 357 | + readonly readFile: (ref: ArtifactRef, path: string) => Effect.Effect<Uint8Array, ArtifactError>; | |
| 372 | 358 | |
| 373 | 359 | readonly writeFiles: (input: { |
| 374 | 360 | projectId: string; |
| @@ -422,10 +408,7 @@ Feature plugin: · 47 unmodified lines | ||
| 422 | 408 | ```ts |
| 423 | 409 | // @executor/workflows |
| 424 | 410 | import { definePlugin, type PluginCtx } from "@executor/sdk"; |
| 425 | −import { | |
| 426 | − ArtifactStoreProtocol, | |
| 427 | − type ArtifactsProtocolCtx, | |
| 428 | −} from "@executor/artifacts"; | |
| 411 | +import { ArtifactStoreProtocol, type ArtifactsProtocolCtx } from "@executor/artifacts"; | |
| 429 | 412 | |
| 430 | 413 | type WorkflowCtx<TStore> = PluginCtx<TStore, ArtifactsProtocolCtx>; |
| 431 | 414 | |
| @@ -546,56 +529,47 @@ export async function summarizeTicket(input: SupportEmail) { · 114 unmodified lines | ||
| 546 | 529 | E2E flow: |
| 547 | 530 | |
| 548 | 531 | ```ts |
| 549 | −it.effect( | |
| 550 | − "creates a code-first workflow artifact and indexes a visual graph", | |
| 551 | − () => | |
| 552 | − Effect.gen(function* () { | |
| 553 | − const executor = yield* createExecutor({ | |
| 554 | − scopes: [testScope], | |
| 555 | − adapter, | |
| 556 | − blobs, | |
| 557 | − plugins: [ | |
| 558 | − artifactsLocalGitPlugin({ rootDir: tempDir }), | |
| 559 | − workflowsPlugin(), | |
| 560 | − ] as const, | |
| 561 | − protocols: { | |
| 562 | − "executor.artifacts.store": "artifacts-local-git", | |
| 532 | +it.effect("creates a code-first workflow artifact and indexes a visual graph", () => | |
| 533 | + Effect.gen(function* () { | |
| 534 | + const executor = yield* createExecutor({ | |
| 535 | + scopes: [testScope], | |
| 536 | + adapter, | |
| 537 | + blobs, | |
| 538 | + plugins: [artifactsLocalGitPlugin({ rootDir: tempDir }), workflowsPlugin()] as const, | |
| 539 | + protocols: { | |
| 540 | + "executor.artifacts.store": "artifacts-local-git", | |
| 541 | + }, | |
| 542 | + }); | |
| 543 | + | |
| 544 | + const created = yield* executor.workflows.create({ | |
| 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 | − }); | |
| 565 | − | |
| 566 | − const created = yield* executor.workflows.create({ | |
| 567 | − name: "Support triage", | |
| 568 | − files: [ | |
| 569 | − { | |
| 570 | − type: "put", | |
| 571 | − path: "artifact.json", | |
| 572 | − contents: JSON.stringify({ | |
| 573 | − schemaVersion: 1, | |
| 574 | − kind: "workflow", | |
| 575 | − entrypoints: ["src/workflows/support-triage.workflow.ts"], | |
| 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 | − }), | |
| 561 | + ], | |
| 562 | + }); | |
| 563 | + | |
| 564 | + const source = yield* executor.artifacts.readFile( | |
| 565 | + { projectId: created.project.id, ref: created.commit.id }, | |
| 566 | + "src/workflows/support-triage.workflow.ts", | |
| 567 | + ); | |
| 568 | + expect(new TextDecoder().decode(source)).toContain('"use workflow"'); | |
| 569 | + | |
| 570 | + expect(created.manifest.entrypoints).toEqual(["src/workflows/support-triage.workflow.ts"]); | |
| 571 | + expect(created.manifest.graph.nodes.map((node) => node.kind)).toContain("step"); | |
| 572 | + }), | |
| 599 | 573 | ); |
| 600 | 574 | ``` |
| 601 | 575 | |
| @@ -89,7 +89,7 @@ Surveyed five plugin systems in `.reference/`: · 88 unmodified lines | ||
| 89 | 89 | - **emdash** — **closest match to our setup.** Astro/Vite + React, plugins are |
| 90 | 90 | npm packages with separate `./` and `./admin` exports, registered via |
| 91 | 91 | import-and-call in `astro.config.mjs`. `definePlugin({ id, hooks, routes, |
| 92 | − admin })` declarative shape. | |
| 92 | +admin })` declarative shape. | |
| 93 | 93 | - **dynamic-software** — most ambitious; Cloudflare Worker Loader for cloud |
| 94 | 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 | 110 | ```ts |
| 111 | 111 | // apps/local/executor.config.ts (after) |
| 112 | −import { defineExecutorConfig } from "@executor-js/sdk" | |
| 113 | −import { openApiPlugin } from "@executor-js/plugin-openapi" | |
| 114 | −import { mcpPlugin } from "@executor-js/plugin-mcp" | |
| 115 | −import { fileSecretsPlugin } from "@executor-js/plugin-file-secrets" | |
| 112 | +import { defineExecutorConfig } from "@executor-js/sdk"; | |
| 113 | +import { openApiPlugin } from "@executor-js/plugin-openapi"; | |
| 114 | +import { mcpPlugin } from "@executor-js/plugin-mcp"; | |
| 115 | +import { fileSecretsPlugin } from "@executor-js/plugin-file-secrets"; | |
| 116 | 116 | |
| 117 | 117 | export default defineExecutorConfig({ |
| 118 | 118 | dialect: "sqlite", |
| @@ -121,7 +121,7 @@ export default defineExecutorConfig({ · 2 unmodified lines | ||
| 121 | 121 | mcpPlugin({ dangerouslyAllowStdioMCP: true }), |
| 122 | 122 | fileSecretsPlugin(), |
| 123 | 123 | ] as const, |
| 124 | −}) | |
| 124 | +}); | |
| 125 | 125 | ``` |
| 126 | 126 | |
| 127 | 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 | 137 | "type": "module", |
| 138 | 138 | "exports": { |
| 139 | 139 | "./server": "./dist/server.js", |
| 140 | − "./client": "./dist/client.js" | |
| 140 | + "./client": "./dist/client.js", | |
| 141 | 141 | }, |
| 142 | 142 | "executor": { |
| 143 | 143 | "id": "foo", |
| 144 | − "version": "0.1.0" | |
| 145 | − } | |
| 144 | + "version": "0.1.0", | |
| 145 | + }, | |
| 146 | 146 | } |
| 147 | 147 | ``` |
| 148 | 148 | |
| @@ -171,7 +171,7 @@ Client side: separate primitive `defineClientPlugin` lives in · 22 unmodified lines | ||
| 171 | 171 | React types never leak into server bundles. |
| 172 | 172 | |
| 173 | 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 | 175 | authors should treat extension as the implementation and write handlers as |
| 176 | 176 | thin wrappers that delegate via the `self` parameter (same pattern as |
| 177 | 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 | 188 | utility for other plugins or scripts. CLI/embedded consumers use |
| 189 | 189 | `executor.<id>.method()`. Vite plugin notices no `./client` and skips the |
| 190 | 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 | 192 | openapi, mcp, anything that needs a frontend. Routes are thin wrappers |
| 193 | 193 | over extension methods. |
| 194 | 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 | 214 | `@executor-js/sdk` (server) and `@executor-js/sdk/client` (frontend). The SDK |
| 215 | 215 | re-exports `Schema`, `HttpApi`, `HttpApiGroup`, `HttpApiEndpoint`, |
| 216 | 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 | 218 | `effect` directly. This keeps the door open without forcing the dependency. |
| 219 | 219 | |
| 220 | 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 | 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 | 251 | same shape. When artifacts lands, that's `artifactStore: () => |
| 252 | 252 | ArtifactStore`. If connection providers want to be modeled this way, the |
| 253 | 253 | existing `connectionProviders` field already is. |
| @@ -335,24 +335,21 @@ server both import it: · 81 unmodified lines | ||
| 335 | 335 | |
| 336 | 336 | ```ts |
| 337 | 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 | 340 | export const Thing = Schema.Struct({ |
| 341 | 341 | id: Schema.String, |
| 342 | 342 | name: Schema.String, |
| 343 | −}) | |
| 343 | +}); | |
| 344 | 344 | |
| 345 | 345 | export const FooApi = HttpApiGroup.make("foo") |
| 346 | − .add( | |
| 347 | − HttpApiEndpoint.get("listThings")`/things` | |
| 348 | − .addSuccess(Schema.Array(Thing)), | |
| 349 | − ) | |
| 346 | + .add(HttpApiEndpoint.get("listThings")`/things`.addSuccess(Schema.Array(Thing))) | |
| 350 | 347 | .add( |
| 351 | 348 | HttpApiEndpoint.post("syncThing")`/sync/:id` |
| 352 | 349 | .setPath(Schema.Struct({ id: Schema.String })) |
| 353 | 350 | .addSuccess(Thing) |
| 354 | 351 | .addError(SyncError), |
| 355 | − ) | |
| 352 | + ); | |
| 356 | 353 | ``` |
| 357 | 354 | |
| 358 | 355 | ```ts |
| @@ -429,49 +426,47 @@ Lives in `@executor-js/sdk/client`. Server bundles cannot import this module. · 70 unmodified lines | ||
| 429 | 426 | ```ts |
| 430 | 427 | // packages/core/sdk/src/client.ts |
| 431 | 428 | export interface ClientPluginSpec<TId extends string = string> { |
| 432 | − readonly id: TId | |
| 429 | + readonly id: TId; | |
| 433 | 430 | |
| 434 | 431 | /** Pages contributed to the host's TanStack router. Mounted under |
| 435 | 432 | * /plugins/{id}/{path}. Sidebar nav metadata declared on the route. */ |
| 436 | − readonly pages?: readonly PageDecl[] | |
| 433 | + readonly pages?: readonly PageDecl[]; | |
| 437 | 434 | |
| 438 | 435 | /** Dashboard / overview widgets the host can render in known slots. */ |
| 439 | − readonly widgets?: readonly WidgetDecl[] | |
| 436 | + readonly widgets?: readonly WidgetDecl[]; | |
| 440 | 437 | |
| 441 | 438 | /** Components the host can render in named slots (e.g., source-detail |
| 442 | 439 | * panels, secret-picker variants). Slot names are part of the host |
| 443 | 440 | * contract — plugin opts in by registering. */ |
| 444 | − readonly slots?: Record<string, SlotComponent> | |
| 441 | + readonly slots?: Record<string, SlotComponent>; | |
| 445 | 442 | |
| 446 | 443 | /** Source-plugin contribution — populated by plugins that expose |
| 447 | 444 | * `kind` rows in the core `source` table (openapi, mcp, graphql, |
| 448 | 445 | * google-discovery). The host's sources page derives its provider |
| 449 | 446 | * list from the union of every loaded plugin's `sourcePlugin` |
| 450 | 447 | * via `useSourcePlugins()`. */ |
| 451 | − readonly sourcePlugin?: SourcePlugin | |
| 448 | + readonly sourcePlugin?: SourcePlugin; | |
| 452 | 449 | |
| 453 | 450 | /** Secret-provider-plugin contribution — populated by plugins that |
| 454 | 451 | * also ship a `secretProviders` server-side capability AND want to |
| 455 | 452 | * expose a settings card on the host's secrets page. Surfaced via |
| 456 | 453 | * `useSecretProviderPlugins()`. */ |
| 457 | − readonly secretProviderPlugin?: SecretProviderPlugin | |
| 454 | + readonly secretProviderPlugin?: SecretProviderPlugin; | |
| 458 | 455 | } |
| 459 | 456 | |
| 460 | 457 | type PageDecl = { |
| 461 | − path: string // "/", "/edit/$id" | |
| 462 | − component: ComponentType | |
| 463 | − nav?: { label: string; section?: string } | |
| 464 | −} | |
| 458 | + path: string; // "/", "/edit/$id" | |
| 459 | + component: ComponentType; | |
| 460 | + nav?: { label: string; section?: string }; | |
| 461 | +}; | |
| 465 | 462 | |
| 466 | 463 | type WidgetDecl = { |
| 467 | − id: string | |
| 468 | − component: ComponentType<WidgetProps> | |
| 469 | − size?: "half" | "full" | |
| 470 | −} | |
| 464 | + id: string; | |
| 465 | + component: ComponentType<WidgetProps>; | |
| 466 | + size?: "half" | "full"; | |
| 467 | +}; | |
| 471 | 468 | |
| 472 | −export const defineClientPlugin = <TId extends string>( | |
| 473 | − spec: ClientPluginSpec<TId>, | |
| 474 | −) => spec | |
| 469 | +export const defineClientPlugin = <TId extends string>(spec: ClientPluginSpec<TId>) => spec; | |
| 475 | 470 | ``` |
| 476 | 471 | |
| 477 | 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 | 479 | ```ts |
| 485 | 480 | // packages/core/sdk/src/client.ts (helper) |
| 486 | −import { HttpApi } from "effect/unstable/httpapi" | |
| 487 | −import { FetchHttpClient } from "effect/unstable/http" | |
| 488 | −import * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi" | |
| 481 | +import { HttpApi } from "effect/unstable/httpapi"; | |
| 482 | +import { FetchHttpClient } from "effect/unstable/http"; | |
| 483 | +import * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi"; | |
| 489 | 484 | |
| 490 | 485 | export const createPluginAtomClient = <G extends HttpApiGroup.Any>( |
| 491 | 486 | group: G, |
| 492 | 487 | opts: { pluginId: string }, |
| 493 | 488 | ) => { |
| 494 | − const bundle = HttpApi.make(`plugin-${opts.pluginId}`).add(group) | |
| 495 | − return AtomHttpApi.Service<`Plugin_${string}Client`>()( | |
| 496 | − `Plugin_${opts.pluginId}Client`, | |
| 497 | − { | |
| 498 | − api: bundle, | |
| 499 | − httpClient: FetchHttpClient.layer, | |
| 500 | − baseUrl: `/_executor/plugins/${opts.pluginId}`, | |
| 501 | − }, | |
| 502 | − ) | |
| 503 | −} | |
| 489 | + const bundle = HttpApi.make(`plugin-${opts.pluginId}`).add(group); | |
| 490 | + return AtomHttpApi.Service<`Plugin_${string}Client`>()(`Plugin_${opts.pluginId}Client`, { | |
| 491 | + api: bundle, | |
| 492 | + httpClient: FetchHttpClient.layer, | |
| 493 | + baseUrl: `/_executor/plugins/${opts.pluginId}`, | |
| 494 | + }); | |
| 495 | +}; | |
| 504 | 496 | ``` |
| 505 | 497 | |
| 506 | 498 | Plugin author writes: |
| @@ -513,39 +505,34 @@ import { · 6 unmodified lines | ||
| 513 | 505 | useAtomValue, |
| 514 | 506 | useAtomSet, |
| 515 | 507 | AsyncResult, |
| 516 | −} from "@executor-js/sdk/client" | |
| 517 | −import { FooApi } from "./shared" | |
| 508 | +} from "@executor-js/sdk/client"; | |
| 509 | +import { FooApi } from "./shared"; | |
| 518 | 510 | |
| 519 | −const FooClient = createPluginAtomClient(FooApi, { pluginId: "foo" }) | |
| 511 | +const FooClient = createPluginAtomClient(FooApi, { pluginId: "foo" }); | |
| 520 | 512 | |
| 521 | 513 | export const fooThingsAtom = FooClient.query("foo", "listThings", { |
| 522 | 514 | timeToLive: "30 seconds", |
| 523 | 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 | 520 | const FooPage = () => { |
| 529 | − const things = useAtomValue(fooThingsAtom) | |
| 530 | − const doSync = useAtomSet(fooSync, { mode: "promise" }) | |
| 521 | + const things = useAtomValue(fooThingsAtom); | |
| 522 | + const doSync = useAtomSet(fooSync, { mode: "promise" }); | |
| 531 | 523 | |
| 532 | 524 | return AsyncResult.match(things, { |
| 533 | 525 | onInitial: () => <Skeleton />, |
| 534 | 526 | onFailure: () => <p>Failed to load</p>, |
| 535 | − onSuccess: ({ value }) => ( | |
| 536 | − <Table | |
| 537 | − rows={value} | |
| 538 | − onSync={(id) => doSync({ path: { id } })} | |
| 539 | − /> | |
| 540 | − ), | |
| 541 | − }) | |
| 542 | −} | |
| 527 | + onSuccess: ({ value }) => <Table rows={value} onSync={(id) => doSync({ path: { id } })} />, | |
| 528 | + }); | |
| 529 | +}; | |
| 543 | 530 | |
| 544 | 531 | export default defineClientPlugin({ |
| 545 | 532 | id: "foo" as const, |
| 546 | 533 | pages: [{ path: "/", component: FooPage, nav: { label: "Foo" } }], |
| 547 | 534 | widgets: [{ id: "foo-status", component: FooStatus, size: "half" }], |
| 548 | −}) | |
| 535 | +}); | |
| 549 | 536 | ``` |
| 550 | 537 | |
| 551 | 538 | Type inference: `FooClient.query("foo", "listThings", ...)` is fully typed |
| @@ -608,15 +595,17 @@ Single config, two consumers. · 56 unmodified lines | ||
| 608 | 595 | |
| 609 | 596 | ```ts |
| 610 | 597 | // apps/local/src/server/executor.ts (after) |
| 611 | −import config from "../../executor.config.ts" | |
| 612 | − | |
| 613 | −const executor = yield* createExecutor({ | |
| 614 | − scopes: [scope], | |
| 615 | − adapter, | |
| 616 | − blobs, | |
| 617 | − plugins: config.plugins, | |
| 618 | − onElicitation: "accept-all", | |
| 619 | −}) | |
| 598 | +import config from "../../executor.config.ts"; | |
| 599 | + | |
| 600 | +const executor = | |
| 601 | + yield * | |
| 602 | + createExecutor({ | |
| 603 | + scopes: [scope], | |
| 604 | + adapter, | |
| 605 | + blobs, | |
| 606 | + plugins: config.plugins, | |
| 607 | + onElicitation: "accept-all", | |
| 608 | + }); | |
| 620 | 609 | ``` |
| 621 | 610 | |
| 622 | 611 | The plugins are already configured (factory called) by the time |
| @@ -631,18 +620,16 @@ export default function executorVite(): Plugin { · 8 unmodified lines | ||
| 631 | 620 | return { |
| 632 | 621 | name: "executor-plugins", |
| 633 | 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 | 625 | async load(id) { |
| 637 | − if (id !== "\0virtual:executor/plugins-client") return | |
| 638 | − const config = await loadExecutorConfig() | |
| 639 | − const imports = config.plugins | |
| 640 | − .map((p, i) => `import p${i} from "${p.id}/client"`) | |
| 641 | − .join("\n") | |
| 642 | − const list = config.plugins.map((_, i) => `p${i}`).join(", ") | |
| 643 | − return `${imports}\nexport const plugins = [${list}]` | |
| 626 | + if (id !== "\0virtual:executor/plugins-client") return; | |
| 627 | + const config = await loadExecutorConfig(); | |
| 628 | + const imports = config.plugins.map((p, i) => `import p${i} from "${p.id}/client"`).join("\n"); | |
| 629 | + const list = config.plugins.map((_, i) => `p${i}`).join(", "); | |
| 630 | + 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 | 643 | ```tsx |
| 657 | 644 | // apps/local/src/routes/__root.tsx |
| 658 | −import { createRootRoute } from "@tanstack/react-router" | |
| 659 | −import { ExecutorProvider } from "@executor-js/react/api/provider" | |
| 660 | −import { ExecutorPluginsProvider } from "@executor-js/sdk/client" | |
| 661 | −import { plugins as clientPlugins } from "virtual:executor/plugins-client" | |
| 662 | −import { Shell } from "../web/shell" | |
| 645 | +import { createRootRoute } from "@tanstack/react-router"; | |
| 646 | +import { ExecutorProvider } from "@executor-js/react/api/provider"; | |
| 647 | +import { ExecutorPluginsProvider } from "@executor-js/sdk/client"; | |
| 648 | +import { plugins as clientPlugins } from "virtual:executor/plugins-client"; | |
| 649 | +import { Shell } from "../web/shell"; | |
| 663 | 650 | |
| 664 | 651 | export const Route = createRootRoute({ |
| 665 | 652 | component: () => ( |
| @@ -669,15 +656,15 @@ export const Route = createRootRoute({ · 3 unmodified lines | ||
| 669 | 656 | </ExecutorPluginsProvider> |
| 670 | 657 | </ExecutorProvider> |
| 671 | 658 | ), |
| 672 | −}) | |
| 659 | +}); | |
| 673 | 660 | ``` |
| 674 | 661 | |
| 675 | 662 | ```tsx |
| 676 | 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 | 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 | 689 | ```ts |
| 703 | 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 | 693 | export const Greeting = Schema.Struct({ |
| 707 | 694 | message: Schema.String, |
| 708 | 695 | count: Schema.Number, |
| 709 | −}) | |
| 710 | −export type Greeting = typeof Greeting.Type | |
| 696 | +}); | |
| 697 | +export type Greeting = typeof Greeting.Type; | |
| 711 | 698 | |
| 712 | 699 | export const ExampleApi = HttpApiGroup.make("example").add( |
| 713 | 700 | HttpApiEndpoint.post("greet", "/greet", { |
| 714 | 701 | payload: Schema.Struct({ name: Schema.String }), |
| 715 | 702 | success: Greeting, |
| 716 | 703 | }), |
| 717 | −) | |
| 704 | +); | |
| 718 | 705 | ``` |
| 719 | 706 | |
| 720 | 707 | ```ts |
| 721 | 708 | // src/server.ts |
| 722 | −import { | |
| 723 | − Context, definePlugin, Effect, HttpApi, HttpApiBuilder, | |
| 724 | −} from "@executor-js/sdk" | |
| 725 | −import { ExampleApi } from "./shared" | |
| 709 | +import { Context, definePlugin, Effect, HttpApi, HttpApiBuilder } from "@executor-js/sdk"; | |
| 710 | +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 | 714 | interface ExampleExtension { |
| 730 | 715 | readonly greet: ( |
| 731 | 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 | 720 | export class ExampleExtensionService extends Context.Service< |
| 736 | − ExampleExtensionService, ExampleExtension, | |
| 721 | + ExampleExtensionService, | |
| 722 | + ExampleExtension | |
| 737 | 723 | >()("ExampleExtensionService") {} |
| 738 | 724 | |
| 739 | 725 | const ExampleHandlers = HttpApiBuilder.group(ExampleApiBundle, "example", (h) => |
| 740 | 726 | h.handle("greet", ({ payload }) => |
| 741 | 727 | Effect.gen(function* () { |
| 742 | − const ext = yield* ExampleExtensionService | |
| 743 | − return yield* ext.greet(payload.name) | |
| 728 | + const ext = yield* ExampleExtensionService; | |
| 729 | + return yield* ext.greet(payload.name); | |
| 744 | 730 | }), |
| 745 | 731 | ), |
| 746 | −) | |
| 732 | +); | |
| 747 | 733 | |
| 748 | 734 | export const examplePlugin = definePlugin(() => ({ |
| 749 | 735 | id: "example" as const, |
| @@ -754,57 +740,57 @@ export const examplePlugin = definePlugin(() => ({ · 4 unmodified lines | ||
| 754 | 740 | extension: (ctx): ExampleExtension => ({ |
| 755 | 741 | greet: (name: string) => |
| 756 | 742 | Effect.sync(() => { |
| 757 | − ctx.storage.count += 1 | |
| 743 | + ctx.storage.count += 1; | |
| 758 | 744 | return { |
| 759 | 745 | message: `hello ${name}`, |
| 760 | 746 | count: ctx.storage.count, |
| 761 | − } | |
| 747 | + }; | |
| 762 | 748 | }), |
| 763 | 749 | }), |
| 764 | 750 | |
| 765 | 751 | routes: () => ExampleApi, |
| 766 | 752 | handlers: () => ExampleHandlers, |
| 767 | 753 | extensionService: ExampleExtensionService, |
| 768 | −})) | |
| 754 | +})); | |
| 769 | 755 | |
| 770 | −export default examplePlugin | |
| 756 | +export default examplePlugin; | |
| 771 | 757 | ``` |
| 772 | 758 | |
| 773 | 759 | ```tsx |
| 774 | 760 | // src/client.tsx |
| 775 | −import { | |
| 776 | − defineClientPlugin, | |
| 777 | − createPluginAtomClient, | |
| 778 | − useAtomSet, | |
| 779 | −} from "@executor-js/sdk/client" | |
| 780 | −import { useState } from "react" | |
| 781 | −import { ExampleApi } from "./shared" | |
| 761 | +import { defineClientPlugin, createPluginAtomClient, useAtomSet } from "@executor-js/sdk/client"; | |
| 762 | +import { useState } from "react"; | |
| 763 | +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 | 769 | const ExamplePage = () => { |
| 788 | − const [name, setName] = useState("world") | |
| 789 | − const [result, setResult] = useState<string>() | |
| 790 | − const doGreet = useAtomSet(greetAtom, { mode: "promise" }) | |
| 770 | + const [name, setName] = useState("world"); | |
| 771 | + const [result, setResult] = useState<string>(); | |
| 772 | + const doGreet = useAtomSet(greetAtom, { mode: "promise" }); | |
| 791 | 773 | |
| 792 | 774 | return ( |
| 793 | 775 | <div> |
| 794 | 776 | <input value={name} onChange={(e) => setName(e.target.value)} /> |
| 795 | − <button onClick={async () => { | |
| 796 | − const g = await doGreet({ payload: { name } }) | |
| 797 | − setResult(`${g.message} (#${g.count})`) | |
| 798 | − }}>Greet</button> | |
| 777 | + <button | |
| 778 | + onClick={async () => { | |
| 779 | + const g = await doGreet({ payload: { name } }); | |
| 780 | + setResult(`${g.message} (#${g.count})`); | |
| 781 | + }} | |
| 782 | + > | |
| 783 | + Greet | |
| 784 | + </button> | |
| 799 | 785 | {result && <pre>{result}</pre>} |
| 800 | 786 | </div> |
| 801 | − ) | |
| 802 | −} | |
| 787 | + ); | |
| 788 | +}; | |
| 803 | 789 | |
| 804 | 790 | export default defineClientPlugin({ |
| 805 | 791 | id: "example" as const, |
| 806 | 792 | pages: [{ path: "/", component: ExamplePage, nav: { label: "Example" } }], |
| 807 | −}) | |
| 793 | +}); | |
| 808 | 794 | ``` |
| 809 | 795 | |
| 810 | 796 | ```jsonc |
| @@ -814,13 +800,13 @@ export default defineClientPlugin({ · 3 unmodified lines | ||
| 814 | 800 | "type": "module", |
| 815 | 801 | "exports": { |
| 816 | 802 | "./server": "./dist/server.js", |
| 817 | − "./client": "./dist/client.js" | |
| 803 | + "./client": "./dist/client.js", | |
| 818 | 804 | }, |
| 819 | 805 | "executor": { "id": "example", "version": "0.1.0" }, |
| 820 | 806 | "peerDependencies": { |
| 821 | 807 | "@executor-js/sdk": "workspace:*", |
| 822 | − "react": "catalog:" | |
| 823 | − } | |
| 808 | + "react": "catalog:", | |
| 809 | + }, | |
| 824 | 810 | } |
| 825 | 811 | ``` |
| 826 | 812 | |
| @@ -828,8 +814,8 @@ User adds it: · 1 unmodified line | ||
| 828 | 814 | |
| 829 | 815 | ```ts |
| 830 | 816 | // apps/local/executor.config.ts |
| 831 | −import { examplePlugin } from "@executor-js/plugin-example/server" | |
| 832 | −plugins: [/* ... */, examplePlugin()] | |
| 817 | +import { examplePlugin } from "@executor-js/plugin-example/server"; | |
| 818 | +plugins: [, /* ... */ examplePlugin()]; | |
| 833 | 819 | ``` |
| 834 | 820 | |
| 835 | 821 | ## SDK-only plugin example |
| @@ -852,81 +838,82 @@ file with no `./client` export, no `routes`, no `handlers`. · 16 unmodified lines | ||
| 852 | 838 | "type": "module", |
| 853 | 839 | "exports": { "./server": "./dist/server.js" }, |
| 854 | 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 | 845 | ```ts |
| 860 | 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 | 849 | interface RateLimiterConfig { |
| 864 | − defaultLimit?: number | |
| 850 | + defaultLimit?: number; | |
| 865 | 851 | } |
| 866 | 852 | |
| 867 | −class RateLimitExceeded extends Schema.TaggedError<RateLimitExceeded>()( | |
| 868 | − "RateLimitExceeded", | |
| 869 | − { key: Schema.String, retryAfterMs: Schema.Number }, | |
| 870 | −) {} | |
| 871 | − | |
| 872 | −export const rateLimiterPlugin = definePlugin( | |
| 873 | − (opts: RateLimiterConfig = {}) => ({ | |
| 874 | − id: "rateLimiter" as const, | |
| 875 | − | |
| 876 | − schema: defineSchema({ | |
| 877 | − rate_buckets: { | |
| 878 | − fields: { | |
| 879 | − key: { type: "string", primary: true }, | |
| 880 | − tokens: { type: "number" }, | |
| 881 | − updated_at: { type: "number" }, | |
| 882 | − }, | |
| 853 | +class RateLimitExceeded extends Schema.TaggedError<RateLimitExceeded>()("RateLimitExceeded", { | |
| 854 | + key: Schema.String, | |
| 855 | + retryAfterMs: Schema.Number, | |
| 856 | +}) {} | |
| 857 | + | |
| 858 | +export const rateLimiterPlugin = definePlugin((opts: RateLimiterConfig = {}) => ({ | |
| 859 | + id: "rateLimiter" as const, | |
| 860 | + | |
| 861 | + schema: defineSchema({ | |
| 862 | + rate_buckets: { | |
| 863 | + fields: { | |
| 864 | + key: { type: "string", primary: true }, | |
| 865 | + tokens: { type: "number" }, | |
| 866 | + updated_at: { type: "number" }, | |
| 883 | 867 | }, |
| 884 | − }), | |
| 868 | + }, | |
| 869 | + }), | |
| 885 | 870 | |
| 886 | − storage: ({ adapter }) => ({ | |
| 887 | − adapter, | |
| 888 | − defaultLimit: opts.defaultLimit ?? 60, | |
| 889 | − }), | |
| 871 | + storage: ({ adapter }) => ({ | |
| 872 | + adapter, | |
| 873 | + defaultLimit: opts.defaultLimit ?? 60, | |
| 874 | + }), | |
| 890 | 875 | |
| 891 | − extension: (ctx) => ({ | |
| 892 | − check: (key: string, cost = 1) => | |
| 893 | − Effect.gen(function* () { | |
| 894 | − const bucket = yield* readOrCreateBucket(ctx.storage, key) | |
| 895 | − const refilled = refill(bucket, ctx.storage.defaultLimit) | |
| 896 | − if (refilled.tokens < cost) { | |
| 897 | − return yield* new RateLimitExceeded({ | |
| 898 | − key, | |
| 899 | − retryAfterMs: estimateRetry(refilled), | |
| 900 | − }) | |
| 901 | − } | |
| 902 | − yield* writeBucket(ctx.storage, key, refilled.tokens - cost) | |
| 903 | − return { allowed: true, remaining: refilled.tokens - cost } | |
| 904 | − }), | |
| 876 | + extension: (ctx) => ({ | |
| 877 | + check: (key: string, cost = 1) => | |
| 878 | + Effect.gen(function* () { | |
| 879 | + const bucket = yield* readOrCreateBucket(ctx.storage, key); | |
| 880 | + const refilled = refill(bucket, ctx.storage.defaultLimit); | |
| 881 | + if (refilled.tokens < cost) { | |
| 882 | + return yield* new RateLimitExceeded({ | |
| 883 | + key, | |
| 884 | + retryAfterMs: estimateRetry(refilled), | |
| 885 | + }); | |
| 886 | + } | |
| 887 | + yield* writeBucket(ctx.storage, key, refilled.tokens - cost); | |
| 888 | + return { allowed: true, remaining: refilled.tokens - cost }; | |
| 889 | + }), | |
| 905 | 890 | |
| 906 | − reset: (key: string) => | |
| 907 | − Effect.tryPromise(() => | |
| 908 | − ctx.storage.adapter.delete({ | |
| 909 | − model: "rate_buckets", | |
| 910 | − where: [["key", "=", key]], | |
| 911 | − }), | |
| 912 | − ), | |
| 913 | − }), | |
| 891 | + reset: (key: string) => | |
| 892 | + Effect.tryPromise(() => | |
| 893 | + ctx.storage.adapter.delete({ | |
| 894 | + model: "rate_buckets", | |
| 895 | + where: [["key", "=", key]], | |
| 896 | + }), | |
| 897 | + ), | |
| 914 | 898 | }), |
| 915 | −) | |
| 899 | +})); | |
| 916 | 900 | ``` |
| 917 | 901 | |
| 918 | 902 | Pure programmatic consumption — works identically from CLI, tests, |
| 919 | 903 | embedded library use, or another plugin's `extension`: |
| 920 | 904 | |
| 921 | 905 | ```ts |
| 922 | −const result = yield* executor.rateLimiter.check("user-123", 5) | |
| 906 | +const result = yield * executor.rateLimiter.check("user-123", 5); | |
| 923 | 907 | // ^? { allowed: true; remaining: number } |
| 924 | 908 | |
| 925 | −yield* executor.rateLimiter.check("user-123", 1000).pipe( | |
| 926 | − Effect.catchTag("RateLimitExceeded", (err) => | |
| 927 | − Effect.log(`hit limit on ${err.key}, retry in ${err.retryAfterMs}ms`), | |
| 928 | − ), | |
| 929 | −) | |
| 909 | +yield * | |
| 910 | + executor.rateLimiter | |
| 911 | + .check("user-123", 1000) | |
| 912 | + .pipe( | |
| 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 | 919 | What the host does with this plugin: |
| @@ -1000,6 +987,7 @@ Build order — all shipped. · 67 unmodified lines | ||
| 1000 | 987 | ## References |
| 1001 | 988 | |
| 1002 | 989 | **Reference research (pre-design):** |
| 990 | + | |
| 1003 | 991 | - emdash: `astro.config.mjs` import-and-call pattern, `package.json` exports |
| 1004 | 992 | for separate admin entry — see `.reference/emdash/demos/plugins-demo/`. |
| 1005 | 993 | - dynamic-software: PluginManifest + capability declarations idea (deferred); |
| @@ -1008,6 +996,7 @@ Build order — all shipped. · 2 unmodified lines | ||
| 1008 | 996 | when we add capability enforcement). |
| 1009 | 997 | |
| 1010 | 998 | **Shipped code:** |
| 999 | + | |
| 1011 | 1000 | - Plugin contract: `packages/core/sdk/src/plugin.ts` (`PluginSpec`, |
| 1012 | 1001 | `definePlugin`). |
| 1013 | 1002 | - Client contract: `packages/core/sdk/src/client.ts` |
| @@ -1030,6 +1019,7 @@ Build order — all shipped. · 16 unmodified lines | ||
| 1030 | 1019 | follows the same per-capability-typed-field pattern. |
| 1031 | 1020 | |
| 1032 | 1021 | **Adjacent plans:** |
| 1022 | + | |
| 1033 | 1023 | - `notes/artifacts-workflows-and-generated-ui.md` — the artifacts/workflows |
| 1034 | 1024 | plan. Uses "protocol" terminology that we're not adopting; read its |
| 1035 | 1025 | "ArtifactStoreProtocol" as "the typed interface that goes into the |
| @@ -4,7 +4,7 @@ · 3 unmodified lines | ||
| 4 | 4 | |
| 5 | 5 | The MCP plugin **already pools connections within a session DO**. Production |
| 6 | 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 | 8 | not redundant in-session handshakes. No structural change is required; |
| 9 | 9 | this PR ships a strict regression test that pins the existing contract |
| 10 | 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 | 68 | ## Re-reading the production trace |
| 69 | 69 | |
| 70 | 70 | Trace `b7102047bed975da461c0519d1251de4`: |
| 71 | + | |
| 71 | 72 | - `mcp.plugin.resolve_connector` 2.72s |
| 72 | −- `plugin.mcp.connection.acquire` 2.72s | |
| 73 | +- `plugin.mcp.connection.acquire` 2.72s | |
| 73 | 74 | - `plugin.mcp.connection.handshake` 2.52s |
| 74 | −- `executor.storage.transaction` 1.02s (token persist) | |
| 75 | −- `plugin.mcp.client.call_tool` 1.48s | |
| 75 | +- `executor.storage.transaction` 1.02s (token persist) | |
| 76 | +- `plugin.mcp.client.call_tool` 1.48s | |
| 76 | 77 | |
| 77 | 78 | Span nesting plus `resolve_connector ≈ acquire ≈ handshake` durations is |
| 78 | 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 | 81 | happens on a miss. |
| 81 | 82 | |
| 82 | 83 | 8h aggregate: |
| 84 | + | |
| 83 | 85 | - `plugin.mcp.connection.handshake` count 4 |
| 84 | −- `plugin.mcp.connection.acquire` count 24 | |
| 85 | −- `mcp.plugin.resolve_connector` count 4 | |
| 86 | +- `plugin.mcp.connection.acquire` count 24 | |
| 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 | 90 | calls. The 4 misses are cold-start sessions (each MCP session DO's first |
| 89 | 91 | tool call, plus any session where the connection went stale and was |
| 90 | 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 | 22 | 1. `INSERT INTO connection` with the new pointer's `connectionId`, |
| 23 | 23 | `provider = "openapi:oauth2"`, and `provider_state` = `{flow, |
| 24 | − tokenUrl, clientIdSecretId, clientSecretSecretId, scopes}` lifted | |
| 24 | +tokenUrl, clientIdSecretId, clientSecretSecretId, scopes}` lifted | |
| 25 | 25 | from the legacy row. `access_token_secret_id` + `refresh_token_secret_id` |
| 26 | 26 | reuse the legacy secret ids — tokens themselves never move, only |
| 27 | 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 | 88 | ```ts |
| 89 | 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 | 130 | unions look like: |
| 131 | 131 | |
| 132 | 132 | ```ts |
| 133 | −Effect.Effect<X, MyPluginTypedError | StorageError, never> | |
| 133 | +Effect.Effect<X, MyPluginTypedError | StorageError, never>; | |
| 134 | 134 | ``` |
| 135 | 135 | |
| 136 | 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 | 153 | .addError(InternalError) |
| 154 | 154 | .addError(McpOAuthError) |
| 155 | − .addError(McpConnectionError) | |
| 155 | + .addError(McpConnectionError) { | |
| 156 | 156 | // … |
| 157 | −{} | |
| 157 | +} | |
| 158 | 158 | ``` |
| 159 | 159 | |
| 160 | 160 | No per-endpoint `addError`. The framework encodes each tagged error by |
| @@ -54,7 +54,7 @@ source: { · 53 unmodified lines | ||
| 54 | 54 | Effect.catchAll((e) => { |
| 55 | 55 | const message = e instanceof Error ? e.message : String(e); |
| 56 | 56 | return executor.sources.recordError({ source, message }).pipe(Effect.asVoid); |
| 57 | −}) | |
| 57 | +}); | |
| 58 | 58 | ``` |
| 59 | 59 | |
| 60 | 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 | 76 | ## Scope |
| 77 | 77 | |
| 78 | 78 | Not for the scope-refactor PR. Separate change: |
| 79 | + | |
| 79 | 80 | - Schema migration adds two columns. |
| 80 | 81 | - `executor.sources.recordError` on the core SDK. |
| 81 | 82 | - Wire up in config-sync. |
| @@ -25,7 +25,7 @@ Tables affected (all drop candidates): · 24 unmodified lines | ||
| 25 | 25 | - `workos_vault_metadata_scope_id_idx` |
| 26 | 26 | |
| 27 | 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 | 29 | a single-column index on a column that's already the leftmost column |
| 30 | 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 | 6 | ## Suites at a glance |
| 7 | 7 | |
| 8 | −| file | pool | drives | what it proves | | |
| 9 | −|---|---|---|---| | |
| 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 | | |
| 8 | +| file | pool | drives | what it proves | | |
| 9 | +| ----------------------------------------------- | ----------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------- | | |
| 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 | | |
| 12 | 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 | 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 | 86 | 3. Read the bound port from `HttpServer.HttpServer.address` (TcpAddress tag) |
| 87 | 87 | inside a `Layer.effect` that produces the test service. |
| 88 | 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 | 90 | 5. SDK Client advertises `capabilities: { elicitation: { form: {} } }` and |
| 91 | 91 | registers `client.setRequestHandler(ElicitRequestSchema, ...)` that returns |
| 92 | 92 | `{ action: "accept", content: {} }`. |
| 93 | 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 | 96 | **Tool id format inside the `execute` sandbox.** |
| 97 | 97 | `tools.<sourceId>.<group>.<operation>` for openapi. The Effect |
| @@ -35,7 +35,9 @@ export interface CoreStore { · 34 unmodified lines | ||
| 35 | 35 | |
| 36 | 36 | // definitions |
| 37 | 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 | 41 | readonly removeDefinitionsBySource: (sourceId: string) => Effect.Effect<void, Error>; |
| 40 | 42 | |
| 41 | 43 | // secrets (routing rows; provider-specific data lives in provider) |
| @@ -45,10 +47,9 @@ export interface CoreStore { · 3 unmodified lines | ||
| 45 | 47 | readonly removeSecret: (id: string) => Effect.Effect<void, Error>; |
| 46 | 48 | } |
| 47 | 49 | |
| 48 | −export const makeDefaultCoreStore = (deps: { | |
| 49 | − adapter: DBAdapter; | |
| 50 | − scope: Scope; | |
| 51 | −}): CoreStore => { /* typedAdapter<CoreSchema> wiring, one place */ }; | |
| 50 | +export const makeDefaultCoreStore = (deps: { adapter: DBAdapter; scope: Scope }): CoreStore => { | |
| 51 | + /* typedAdapter<CoreSchema> wiring, one place */ | |
| 52 | +}; | |
| 52 | 53 | ``` |
| 53 | 54 | |
| 54 | 55 | ## Wiring — defaults passed in deps, force the spread |
| @@ -70,9 +71,7 @@ export interface ExecutorConfig<TPlugins> { · 15 unmodified lines | ||
| 70 | 71 | |
| 71 | 72 | // Inside createExecutor: |
| 72 | 73 | const defaults = makeDefaultCoreStore({ adapter, scope }); |
| 73 | −const core = config.coreStorage | |
| 74 | − ? config.coreStorage({ adapter, scope, defaults }) | |
| 75 | − : defaults; | |
| 74 | +const core = config.coreStorage ? config.coreStorage({ adapter, scope, defaults }) : defaults; | |
| 76 | 75 | // Executor now calls core.putTools(...), core.removeSource(...), etc. |
| 77 | 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 | 82 | with no type help. |
| 84 | 83 | - Anyone implementing from scratch (test doubles, a non-drizzle backend) |
| 85 | 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 | 86 | keeps the full `CoreStore` return type enforced. |
| 88 | 87 | |
| 89 | 88 | ## What it unlocks |
| @@ -91,30 +90,39 @@ const core = config.coreStorage · 1 unmodified line | ||
| 91 | 90 | **Algolia sidecar — no string-match, just method overrides:** |
| 92 | 91 | |
| 93 | 92 | ```ts |
| 94 | −const executor = yield* createExecutor({ | |
| 95 | − scope, adapter, blobs, plugins, | |
| 96 | − | |
| 97 | − coreStorage: ({ defaults }) => ({ | |
| 98 | − ...defaults, | |
| 99 | − putTools: (tools) => | |
| 100 | − Effect.gen(function* () { | |
| 101 | − yield* defaults.putTools(tools); | |
| 102 | − yield* Effect.promise(() => algolia.saveObjects({ | |
| 103 | − indexName: `tools_${scope.id}`, | |
| 104 | − objects: tools.map((t) => ({ objectID: t.id, ...t })), | |
| 105 | − })); | |
| 106 | − }), | |
| 107 | − removeToolsBySource: (sourceId) => | |
| 108 | − Effect.gen(function* () { | |
| 109 | − const affected = yield* defaults.listTools(); | |
| 110 | − yield* defaults.removeToolsBySource(sourceId); | |
| 111 | − yield* Effect.promise(() => algolia.deleteObjects({ | |
| 112 | − indexName: `tools_${scope.id}`, | |
| 113 | − objectIDs: affected.filter((t) => t.sourceId === sourceId).map((t) => t.id), | |
| 114 | − })); | |
| 115 | − }), | |
| 116 | − }), | |
| 117 | −}); | |
| 93 | +const executor = | |
| 94 | + yield * | |
| 95 | + createExecutor({ | |
| 96 | + scope, | |
| 97 | + adapter, | |
| 98 | + blobs, | |
| 99 | + plugins, | |
| 100 | + | |
| 101 | + coreStorage: ({ defaults }) => ({ | |
| 102 | + ...defaults, | |
| 103 | + putTools: (tools) => | |
| 104 | + Effect.gen(function* () { | |
| 105 | + yield* defaults.putTools(tools); | |
| 106 | + yield* Effect.promise(() => | |
| 107 | + algolia.saveObjects({ | |
| 108 | + indexName: `tools_${scope.id}`, | |
| 109 | + objects: tools.map((t) => ({ objectID: t.id, ...t })), | |
| 110 | + }), | |
| 111 | + ); | |
| 112 | + }), | |
| 113 | + removeToolsBySource: (sourceId) => | |
| 114 | + Effect.gen(function* () { | |
| 115 | + const affected = yield* defaults.listTools(); | |
| 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 | 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 | 137 | ```ts |
| 130 | 138 | // before |
| 131 | −storage: (deps) => makeDefaultOpenapiStore(deps) | |
| 139 | +storage: (deps) => makeDefaultOpenapiStore(deps); | |
| 132 | 140 | |
| 133 | 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 | 144 | // override one method |
| 137 | −storage: ({ defaults }) => ({ ...defaults, upsertSource: myUpsert }) | |
| 145 | +storage: ({ defaults }) => ({ ...defaults, upsertSource: myUpsert }); | |
| 138 | 146 | ``` |
| 139 | 147 | |
| 140 | 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 | 160 | factory renamed to follow the `defaults` contract. |
| 153 | 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 | 164 | depends on this refactor to avoid the `if (model === "tool")` antipattern |
| 157 | 165 | I'd otherwise be stuck with. |
| @@ -6,7 +6,7 @@ · 5 unmodified lines | ||
| 6 | 6 | `Effect.runPromise(...)`. That works but it's lossy at the Promise |
| 7 | 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 | 10 | `try/catch`). Consumers have to `instanceof InternalError` / |
| 11 | 11 | `err._tag === "..."` to discriminate, even though the underlying |
| 12 | 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 | 43 | or wrap them in a promise-native `Result<A, E>`? |
| 44 | 44 | |
| 45 | 45 | **Expose `Exit`/`Cause`:** |
| 46 | + | |
| 46 | 47 | - Pros: one fewer abstraction, no translation layer to maintain, cause |
| 47 | 48 | preserves parallel/sequential composition. |
| 48 | 49 | - Cons: consumers depend on `effect`'s API even though they're using a |
| 49 | 50 | "Promise" SDK — partly defeats the abstraction. |
| 50 | 51 | |
| 51 | 52 | **Wrap in a `Result<A, E>`:** |
| 53 | + | |
| 52 | 54 | - Pros: `@executor-js/sdk/promise` consumers don't import `effect` at all, |
| 53 | 55 | surface stays small. |
| 54 | 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 | 54 | takes): |
| 55 | 55 | |
| 56 | 56 | ```ts |
| 57 | −yield* sql`SET LOCAL app.scope_chain = ${toPgArray(chain)}`; | |
| 58 | −yield* sql`SET LOCAL app.write_scope = ${writeScope}`; | |
| 57 | +yield * sql`SET LOCAL app.scope_chain = ${toPgArray(chain)}`; | |
| 58 | +yield * sql`SET LOCAL app.write_scope = ${writeScope}`; | |
| 59 | 59 | ``` |
| 60 | 60 | |
| 61 | 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 | 69 | tables is whatever `executor-schema.ts` exports with a `scope_id` |
| 70 | 70 | column — same set the scope adapter already knows about. |
| 71 | 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 | 73 | gives us the transaction boundary. |
| 74 | 74 | 3. Leave `BYPASSRLS` off the worker's role so we actually get the |
| 75 | 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 | 20 | scope stack: |
| 21 | 21 | |
| 22 | 22 | ```ts |
| 23 | −[user, workspace, org] | |
| 23 | +[user, workspace, org]; | |
| 24 | 24 | ``` |
| 25 | 25 | |
| 26 | 26 | Rows still belong to exactly one scope. Resolution walks the stack |
| @@ -68,12 +68,7 @@ For example: · 41 unmodified lines | ||
| 68 | 68 | |
| 69 | 69 | ```ts |
| 70 | 70 | oauth2: { |
| 71 | − flow: "clientCredentials", | |
| 72 | − tokenUrl, | |
| 73 | − clientIdSlot, | |
| 74 | − clientSecretSlot, | |
| 75 | − connectionSlot, | |
| 76 | − scopes | |
| 71 | + flow: ("clientCredentials", tokenUrl, clientIdSlot, clientSecretSlot, connectionSlot, scopes); | |
| 77 | 72 | } |
| 78 | 73 | ``` |
| 79 | 74 | |
| @@ -10,15 +10,15 @@ scopes by construction. · 9 unmodified lines | ||
| 10 | 10 | |
| 11 | 11 | - **SDK** — `createExecutor({ scope: Scope, adapter, blobs, plugins })`. |
| 12 | 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 | 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 | 16 | into the payload. Tx handles passed into transaction callbacks are |
| 17 | 17 | also wrapped, so nested writes stay inside the same scope. |
| 18 | 18 | |
| 19 | 19 | - **Plugins** — see a plain `DBAdapter`. They do not know or care about |
| 20 | 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 | 22 | column means the adapter passes the plugin's reads/writes through |
| 23 | 23 | unscoped (documented failure mode, not silent) — tests catch the |
| 24 | 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 | 45 | ```ts |
| 46 | 46 | interface ScopeContext { |
| 47 | − readonly read: readonly string[]; // precedence-ordered, innermost first | |
| 48 | − readonly write: string; // exactly one scope for writes | |
| 47 | + readonly read: readonly string[]; // precedence-ordered, innermost first | |
| 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 | 91 | there. When a user invokes Gmail, secret resolution walks the scope |
| 92 | 92 | stack (user → workspace → org) and only finds a token if that user |
| 93 | 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 | 95 | workspace-level tokens even if one existed. |
| 96 | 96 | |
| 97 | 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 | 10 | 1. `executor.tools.list()` — `SELECT * FROM tool WHERE scope_id = ?`. |
| 11 | 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 | 13 | phrase boosts, filter, sort, slice. |
| 14 | 14 | |
| 15 | 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 | 74 | ## Override shapes |
| 75 | 75 | |
| 76 | 76 | **Replace entirely:** |
| 77 | + | |
| 77 | 78 | ```ts |
| 78 | 79 | search: ({ listTools }) => algoliaSearch({ listTools }), |
| 79 | 80 | ``` |
| 80 | 81 | |
| 81 | 82 | **Decorate — mix in logging/timing:** |
| 83 | + | |
| 82 | 84 | ```ts |
| 83 | 85 | search: ({ defaults }) => ({ |
| 84 | 86 | query: (q) => Effect.gen(function* () { |
| @@ -91,6 +93,7 @@ search: ({ defaults }) => ({ · 6 unmodified lines | ||
| 91 | 93 | ``` |
| 92 | 94 | |
| 93 | 95 | **Hybrid — try Algolia, fall back to local:** |
| 96 | + | |
| 94 | 97 | ```ts |
| 95 | 98 | search: ({ listTools, defaults }) => ({ |
| 96 | 99 | query: (q) => |
| @@ -109,6 +112,7 @@ it's a storage concern. Handled by wrapping the core store (see · 12 unmodified lines | ||
| 109 | 112 | delegate to the default and mirror to the external index. |
| 110 | 113 | |
| 111 | 114 | The two halves are independent: |
| 115 | + | |
| 112 | 116 | - Indexing only (manual reindex): override `coreStorage`, leave `search` |
| 113 | 117 | as default. |
| 114 | 118 | - Search only (some other populates the index): override `search`, leave |
| @@ -122,7 +126,7 @@ scorer: · 7 unmodified lines | ||
| 122 | 126 | 1. **Projection on `tools.list`** — expose a `select` option so search |
| 123 | 127 | doesn't pull `input_schema` / `output_schema`. Biggest per-query win. |
| 124 | 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 | 130 | ranking, fuzzy via `pg_trgm`. Would replace the in-memory scorer for |
| 127 | 131 | the cloud app. |
| 128 | 132 | 3. **Per-scope LRU** — cache scored results; invalidate on source |
| @@ -3,11 +3,13 @@ · 2 unmodified lines | ||
| 3 | 3 | ## What was done |
| 4 | 4 | |
| 5 | 5 | ### 1. DBSchema alignment with better-auth |
| 6 | + | |
| 6 | 7 | - Vendored better-auth's `BetterAuthPluginDBSchema` pattern into `storage-core/src/schema.ts` |
| 7 | 8 | - Made `modelName` optional (falls back to the key), renamed `disableMigrations` → `disableMigration`, dropped `order` |
| 8 | 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 | 11 | ### 2. @executor-js/cli package (packages/core/cli/) |
| 12 | + | |
| 11 | 13 | - Mirrors better-auth's CLI approach: load config → collect plugin schemas → generate drizzle TS |
| 12 | 14 | - `executor generate --config ./executor.config.ts --output ./src/services/executor-schema.ts` |
| 13 | 15 | - Generator ported from better-auth's drizzle generator, adapted for our DBSchema |
| @@ -15,11 +17,13 @@ · 1 unmodified line | ||
| 15 | 17 | - Uses jiti for config loading (supports TS configs) |
| 16 | 18 | |
| 17 | 19 | ### 3. apps/cloud wired up |
| 20 | + | |
| 18 | 21 | - `executor.config.ts` defines plugins with stub credentials (only used for schema shape) |
| 19 | 22 | - `drizzle.config.ts` points at both `schema.ts` (cloud tables) and `executor-schema.ts` (executor tables) |
| 20 | 23 | - Fresh `drizzle/0000_initial.sql` with all tables (cloud + core + plugin + blob), indexes, FKs |
| 21 | 24 | |
| 22 | 25 | ### 4. apps/local wired up |
| 26 | + | |
| 23 | 27 | - `executor.config.ts` defines plugins + dialect sqlite |
| 24 | 28 | - `drizzle.config.ts` for drizzle-kit generate/push |
| 25 | 29 | - Generated `executor-schema.ts` with all sqlite tables + blob table |
| @@ -27,6 +31,7 @@ · 1 unmodified line | ||
| 27 | 31 | - At startup: `migrate(db, { migrationsFolder })` from `drizzle-orm/bun-sqlite/migrator` applies pending migrations before constructing the adapter |
| 28 | 32 | |
| 29 | 33 | ### 5. Adapter DDL removed |
| 34 | + | |
| 30 | 35 | - `makeSqliteAdapter` and `makePostgresAdapter` no longer run DDL — they return `DBAdapter` directly (not `Effect.Effect<DBAdapter>`) |
| 31 | 36 | - `makeSqliteBlobStore` and `makePostgresBlobStore` no longer run DDL — they return `BlobStore` directly |
| 32 | 37 | - `buildCreateTableStatements` deleted from `storage-file/compile.ts` |
| @@ -39,6 +44,7 @@ · 6 unmodified lines | ||
| 39 | 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 | 46 | ### Local app migration |
| 47 | + | |
| 42 | 48 | 1. On first run, `migrate()` creates all tables from the initial migration |
| 43 | 49 | 2. Add a **sync-from-config step** after executor creation: |
| 44 | 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 | 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 | 55 | ### What won't survive (acceptable losses) |
| 56 | + | |
| 50 | 57 | - Tool invocation history / blobs (old KV format) |
| 51 | 58 | - OAuth sessions (ephemeral, users re-auth) |
| 52 | 59 | - Stale source state (re-fetching is actually a benefit) |
| 53 | 60 | |
| 54 | 61 | ### Cloud migration |
| 62 | + | |
| 55 | 63 | - Only ~2 users, just drop old tables and re-add manually |
| 56 | 64 | - Or write a one-off SQL migration |
| 57 | 65 | |
| 58 | 66 | ## Key files |
| 59 | 67 | |
| 60 | −| File | Role | | |
| 61 | −|------|------| | |
| 62 | −| `packages/core/cli/src/commands/generate.ts` | CLI generate command | | |
| 63 | −| `packages/core/cli/src/generators/drizzle.ts` | Drizzle schema generator | | |
| 64 | −| `packages/core/sdk/src/config.ts` | `defineExecutorConfig` helper | | |
| 65 | −| `packages/core/config/src/schema.ts` | `executor.jsonc` schema | | |
| 66 | −| `packages/core/config/src/config-store.ts` | Plugin store → config file decorator | | |
| 67 | −| `packages/core/config/src/load.ts` | Config loading | | |
| 68 | −| `apps/cloud/executor.config.ts` | Cloud config for CLI | | |
| 69 | −| `apps/cloud/src/services/executor-schema.ts` | Generated drizzle schema | | |
| 70 | −| `apps/cloud/drizzle/0000_*.sql` | Fresh migration | | |
| 71 | −| `apps/local/executor.config.ts` | Local config for CLI | | |
| 72 | −| `apps/local/src/server/executor-schema.ts` | Generated drizzle schema | | |
| 73 | −| `apps/local/drizzle/0000_*.sql` | Fresh migration | | |
| 74 | −| `apps/local/src/server/executor.ts` | Local executor bootstrap with `migrate()` | | |
| 68 | +| File | Role | | |
| 69 | +| --------------------------------------------- | ----------------------------------------- | | |
| 70 | +| `packages/core/cli/src/commands/generate.ts` | CLI generate command | | |
| 71 | +| `packages/core/cli/src/generators/drizzle.ts` | Drizzle schema generator | | |
| 72 | +| `packages/core/sdk/src/config.ts` | `defineExecutorConfig` helper | | |
| 73 | +| `packages/core/config/src/schema.ts` | `executor.jsonc` schema | | |
| 74 | +| `packages/core/config/src/config-store.ts` | Plugin store → config file decorator | | |
| 75 | +| `packages/core/config/src/load.ts` | Config loading | | |
| 76 | +| `apps/cloud/executor.config.ts` | Cloud config for CLI | | |
| 77 | +| `apps/cloud/src/services/executor-schema.ts` | Generated drizzle schema | | |
| 78 | +| `apps/cloud/drizzle/0000_*.sql` | Fresh migration | | |
| 79 | +| `apps/local/executor.config.ts` | Local config for CLI | | |
| 80 | +| `apps/local/src/server/executor-schema.ts` | Generated drizzle schema | | |
| 81 | +| `apps/local/drizzle/0000_*.sql` | Fresh migration | | |
| 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 | 73 | ```ts |
| 74 | 74 | type PluginManifest = { |
| 75 | − schemaVersion: string | |
| 76 | − id: string | |
| 77 | − name: string | |
| 78 | − version: string | |
| 79 | − description?: string | |
| 80 | − publisher?: PluginPublisher | |
| 81 | − license?: string | |
| 82 | − homepage?: string | |
| 83 | − repository?: string | |
| 84 | − categories?: PluginCategory[] | |
| 85 | − compatibility: PluginCompatibility | |
| 86 | − entrypoints: PluginEntrypoints | |
| 87 | − capabilities?: PluginCapabilities | |
| 88 | − extensions?: PluginExtensions | |
| 89 | − storage?: PluginStorageDeclaration | |
| 90 | − package?: PluginPackageDeclaration | |
| 91 | − metadata?: Record<string, unknown> | |
| 92 | −} | |
| 75 | + schemaVersion: string; | |
| 76 | + id: string; | |
| 77 | + name: string; | |
| 78 | + version: string; | |
| 79 | + description?: string; | |
| 80 | + publisher?: PluginPublisher; | |
| 81 | + license?: string; | |
| 82 | + homepage?: string; | |
| 83 | + repository?: string; | |
| 84 | + categories?: PluginCategory[]; | |
| 85 | + compatibility: PluginCompatibility; | |
| 86 | + entrypoints: PluginEntrypoints; | |
| 87 | + capabilities?: PluginCapabilities; | |
| 88 | + extensions?: PluginExtensions; | |
| 89 | + storage?: PluginStorageDeclaration; | |
| 90 | + package?: PluginPackageDeclaration; | |
| 91 | + metadata?: Record<string, unknown>; | |
| 92 | +}; | |
| 93 | 93 | ``` |
| 94 | 94 | |
| 95 | 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 | 135 | User-facing install config can stay simple: |
| 136 | 136 | |
| 137 | 137 | ```ts |
| 138 | −type PluginInstallSpec = | |
| 139 | − | string | |
| 140 | − | [string, Record<string, unknown>] | |
| 138 | +type PluginInstallSpec = string | [string, Record<string, unknown>]; | |
| 141 | 139 | ``` |
| 142 | 140 | |
| 143 | 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 | 145 | ```ts |
| 148 | 146 | type PluginPackageDeclaration = { |
| 149 | − source?: "npm" | "git" | "github" | "local" | "registry" | |
| 150 | − packageName?: string | |
| 151 | − repository?: string | |
| 152 | −} | |
| 147 | + source?: "npm" | "git" | "github" | "local" | "registry"; | |
| 148 | + packageName?: string; | |
| 149 | + repository?: string; | |
| 150 | +}; | |
| 153 | 151 | ``` |
| 154 | 152 | |
| 155 | 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 | 159 | ```ts |
| 162 | 160 | type PluginCompatibility = { |
| 163 | − executor: string | |
| 164 | − runtimeApi?: string | |
| 165 | − platforms?: PluginPlatform[] | |
| 166 | −} | |
| 161 | + executor: string; | |
| 162 | + runtimeApi?: string; | |
| 163 | + platforms?: PluginPlatform[]; | |
| 164 | +}; | |
| 167 | 165 | |
| 168 | −type PluginPlatform = "local" | "cloud" | |
| 166 | +type PluginPlatform = "local" | "cloud"; | |
| 169 | 167 | ``` |
| 170 | 168 | |
| 171 | 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 | 181 | ```ts |
| 184 | 182 | type PluginEntrypoints = { |
| 185 | − main?: PluginCodeEntrypoint | |
| 186 | − sdk?: PluginCodeEntrypoint | |
| 187 | − source?: PluginCodeEntrypoint | |
| 188 | − secretStore?: PluginCodeEntrypoint | |
| 189 | − apiGroup?: PluginCodeEntrypoint | |
| 190 | − apiHandlers?: PluginCodeEntrypoint | |
| 191 | − routeHandlers?: Record<string, PluginCodeEntrypoint> | |
| 192 | − backgroundServices?: Record<string, PluginCodeEntrypoint> | |
| 193 | − reactSource?: PluginUiEntrypoint | |
| 194 | − reactSecretProvider?: PluginUiEntrypoint | |
| 195 | − ui?: PluginUiEntrypoint | |
| 196 | − adminUi?: PluginUiEntrypoint | |
| 197 | −} | |
| 183 | + main?: PluginCodeEntrypoint; | |
| 184 | + sdk?: PluginCodeEntrypoint; | |
| 185 | + source?: PluginCodeEntrypoint; | |
| 186 | + secretStore?: PluginCodeEntrypoint; | |
| 187 | + apiGroup?: PluginCodeEntrypoint; | |
| 188 | + apiHandlers?: PluginCodeEntrypoint; | |
| 189 | + routeHandlers?: Record<string, PluginCodeEntrypoint>; | |
| 190 | + backgroundServices?: Record<string, PluginCodeEntrypoint>; | |
| 191 | + reactSource?: PluginUiEntrypoint; | |
| 192 | + reactSecretProvider?: PluginUiEntrypoint; | |
| 193 | + ui?: PluginUiEntrypoint; | |
| 194 | + adminUi?: PluginUiEntrypoint; | |
| 195 | +}; | |
| 198 | 196 | |
| 199 | 197 | type PluginCodeEntrypoint = { |
| 200 | − module: string | |
| 201 | − export?: string | |
| 202 | − runtime?: PluginRuntimeTarget | |
| 203 | −} | |
| 198 | + module: string; | |
| 199 | + export?: string; | |
| 200 | + runtime?: PluginRuntimeTarget; | |
| 201 | +}; | |
| 204 | 202 | |
| 205 | −type PluginRuntimeTarget = "worker" | "isolate" | "host-trusted" | |
| 203 | +type PluginRuntimeTarget = "worker" | "isolate" | "host-trusted"; | |
| 206 | 204 | ``` |
| 207 | 205 | |
| 208 | 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 | 235 | ```ts |
| 238 | 236 | type PluginLayerDeclaration = { |
| 239 | 237 | sdk?: { |
| 240 | − pluginId: string | |
| 241 | − entrypoint: keyof PluginEntrypoints | |
| 242 | − } | |
| 238 | + pluginId: string; | |
| 239 | + entrypoint: keyof PluginEntrypoints; | |
| 240 | + }; | |
| 243 | 241 | api?: { |
| 244 | − requiresSdkPlugin: string | |
| 245 | − groupEntrypoint?: keyof PluginEntrypoints | |
| 246 | − handlersEntrypoint?: keyof PluginEntrypoints | |
| 247 | − } | |
| 242 | + requiresSdkPlugin: string; | |
| 243 | + groupEntrypoint?: keyof PluginEntrypoints; | |
| 244 | + handlersEntrypoint?: keyof PluginEntrypoints; | |
| 245 | + }; | |
| 248 | 246 | react?: Array<{ |
| 249 | − kind: "source" | "secret-provider" | "settings" | "admin" | |
| 250 | − requiresSdkPlugin?: string | |
| 251 | − requiresApi?: boolean | |
| 252 | − entrypoint: keyof PluginEntrypoints | |
| 253 | − }> | |
| 254 | −} | |
| 247 | + kind: "source" | "secret-provider" | "settings" | "admin"; | |
| 248 | + requiresSdkPlugin?: string; | |
| 249 | + requiresApi?: boolean; | |
| 250 | + entrypoint: keyof PluginEntrypoints; | |
| 251 | + }>; | |
| 252 | +}; | |
| 255 | 253 | ``` |
| 256 | 254 | |
| 257 | 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 | 263 | ```ts |
| 266 | 264 | type PluginExtensions = { |
| 267 | − sources?: SourceExtensionDeclaration[] | |
| 268 | − secretStores?: SecretStoreExtensionDeclaration[] | |
| 269 | − tools?: ToolExtensionDeclaration[] | |
| 270 | − routes?: RouteExtensionDeclaration[] | |
| 271 | − uiSurfaces?: UiSurfaceDeclaration[] | |
| 272 | − backgroundServices?: BackgroundServiceDeclaration[] | |
| 273 | −} | |
| 265 | + sources?: SourceExtensionDeclaration[]; | |
| 266 | + secretStores?: SecretStoreExtensionDeclaration[]; | |
| 267 | + tools?: ToolExtensionDeclaration[]; | |
| 268 | + routes?: RouteExtensionDeclaration[]; | |
| 269 | + uiSurfaces?: UiSurfaceDeclaration[]; | |
| 270 | + backgroundServices?: BackgroundServiceDeclaration[]; | |
| 271 | +}; | |
| 274 | 272 | ``` |
| 275 | 273 | |
| 276 | 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 | 282 | ```ts |
| 285 | 283 | type PluginCapabilities = { |
| 286 | − tools?: ToolCapabilityDeclaration[] | |
| 287 | − sources?: SourceCapabilityDeclaration[] | |
| 288 | − secrets?: SecretCapabilityDeclaration[] | |
| 289 | − network?: NetworkCapabilityDeclaration[] | |
| 290 | − storage?: StorageCapabilityDeclaration[] | |
| 291 | − scopes?: ScopeCapabilityDeclaration[] | |
| 292 | − filesystem?: FilesystemCapabilityDeclaration[] | |
| 293 | − execution?: ExecutionCapabilityDeclaration[] | |
| 294 | − host?: HostCapabilityDeclaration[] | |
| 295 | −} | |
| 284 | + tools?: ToolCapabilityDeclaration[]; | |
| 285 | + sources?: SourceCapabilityDeclaration[]; | |
| 286 | + secrets?: SecretCapabilityDeclaration[]; | |
| 287 | + network?: NetworkCapabilityDeclaration[]; | |
| 288 | + storage?: StorageCapabilityDeclaration[]; | |
| 289 | + scopes?: ScopeCapabilityDeclaration[]; | |
| 290 | + filesystem?: FilesystemCapabilityDeclaration[]; | |
| 291 | + execution?: ExecutionCapabilityDeclaration[]; | |
| 292 | + host?: HostCapabilityDeclaration[]; | |
| 293 | +}; | |
| 296 | 294 | ``` |
| 297 | 295 | |
| 298 | 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 | 342 | ```ts |
| 345 | 343 | type PluginStorageDeclaration = { |
| 346 | 344 | collections?: Array<{ |
| 347 | − name: string | |
| 348 | − kind: "kv" | "document" | "sql" | |
| 349 | − description?: string | |
| 350 | − }> | |
| 351 | −} | |
| 345 | + name: string; | |
| 346 | + kind: "kv" | "document" | "sql"; | |
| 347 | + description?: string; | |
| 348 | + }>; | |
| 349 | +}; | |
| 352 | 350 | ``` |
| 353 | 351 | |
| 354 | 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 | 362 | ```ts |
| 365 | 363 | type PluginArtifacts = { |
| 366 | 364 | files?: Array<{ |
| 367 | − path: string | |
| 368 | − size?: number | |
| 369 | − sha256?: string | |
| 370 | − kind?: "code" | "asset" | "manifest" | "source-map" | |
| 371 | − }> | |
| 365 | + path: string; | |
| 366 | + size?: number; | |
| 367 | + sha256?: string; | |
| 368 | + kind?: "code" | "asset" | "manifest" | "source-map"; | |
| 369 | + }>; | |
| 372 | 370 | bundle?: { |
| 373 | − format: "esm" | |
| 374 | − main: string | |
| 375 | − size?: number | |
| 376 | − sha256?: string | |
| 377 | − } | |
| 378 | −} | |
| 371 | + format: "esm"; | |
| 372 | + main: string; | |
| 373 | + size?: number; | |
| 374 | + sha256?: string; | |
| 375 | + }; | |
| 376 | +}; | |
| 379 | 377 | ``` |
| 380 | 378 | |
| 381 | 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 | 1 | import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"; |
| 2 | 2 | import { Schema } from "effect"; |
| 3 | 3 | |
| 4 | −import { | |
| 5 | − ConnectionId, | |
| 6 | − ConnectionInUseError, | |
| 7 | − ScopeId, | |
| 8 | − Usage, | |
| 9 | −} from "@executor-js/sdk"; | |
| 4 | +import { ConnectionId, ConnectionInUseError, ScopeId, Usage } from "@executor-js/sdk"; | |
| 10 | 5 | |
| 11 | 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 | 51 | .add( |
| 57 | − HttpApiEndpoint.get( | |
| 58 | − "usages", | |
| 59 | − "/scopes/:scopeId/connections/:connectionId/usages", | |
| 60 | − { | |
| 61 | − params: ConnectionParams, | |
| 62 | − success: Schema.Array(Usage), | |
| 63 | − error: InternalError, | |
| 64 | − }, | |
| 65 | − ), | |
| 52 | + HttpApiEndpoint.get("usages", "/scopes/:scopeId/connections/:connectionId/usages", { | |
| 53 | + params: ConnectionParams, | |
| 54 | + success: Schema.Array(Usage), | |
| 55 | + error: InternalError, | |
| 56 | + }), | |
| 66 | 57 | ); |
| @@ -18,35 +18,32 @@ const refToResponse = (ref: ConnectionRef) => ({ · 17 unmodified lines | ||
| 18 | 18 | updatedAt: ref.updatedAt.getTime(), |
| 19 | 19 | }); |
| 20 | 20 | |
| 21 | −export const ConnectionsHandlers = HttpApiBuilder.group( | |
| 22 | − ExecutorApi, | |
| 23 | − "connections", | |
| 24 | − (handlers) => | |
| 25 | − handlers | |
| 26 | − .handle("list", () => | |
| 27 | − capture( | |
| 28 | − Effect.gen(function* () { | |
| 29 | − const executor = yield* ExecutorService; | |
| 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 | − ), | |
| 21 | +export const ConnectionsHandlers = HttpApiBuilder.group(ExecutorApi, "connections", (handlers) => | |
| 22 | + handlers | |
| 23 | + .handle("list", () => | |
| 24 | + capture( | |
| 25 | + Effect.gen(function* () { | |
| 26 | + const executor = yield* ExecutorService; | |
| 27 | + const refs = yield* executor.connections.list(); | |
| 28 | + return refs.map(refToResponse); | |
| 29 | + }), | |
| 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 | 9 | export const ExecutionsHandlers = HttpApiBuilder.group(ExecutorApi, "executions", (handlers) => |
| 10 | 10 | handlers |
| 11 | 11 | .handle("execute", ({ payload }) => |
| 12 | − capture(Effect.gen(function* () { | |
| 13 | − const engine = yield* ExecutionEngineService; | |
| 14 | − const outcome = yield* captureEngineError(engine.executeWithPause(payload.code)); | |
| 12 | + capture( | |
| 13 | + Effect.gen(function* () { | |
| 14 | + const engine = yield* ExecutionEngineService; | |
| 15 | + const outcome = yield* captureEngineError(engine.executeWithPause(payload.code)); | |
| 15 | 16 | |
| 16 | − if (outcome.status === "completed") { | |
| 17 | − const formatted = formatExecuteResult(outcome.result); | |
| 17 | + if (outcome.status === "completed") { | |
| 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 | 28 | return { |
| 19 | − status: "completed" as const, | |
| 29 | + status: "paused" as const, | |
| 20 | 30 | text: formatted.text, |
| 21 | 31 | structured: formatted.structured, |
| 22 | − isError: formatted.isError, | |
| 23 | 32 | }; |
| 24 | − } | |
| 25 | − | |
| 26 | − const formatted = formatPausedExecution(outcome.execution); | |
| 27 | − return { | |
| 28 | − status: "paused" as const, | |
| 29 | − text: formatted.text, | |
| 30 | − structured: formatted.structured, | |
| 31 | − }; | |
| 32 | − })), | |
| 33 | + }), | |
| 34 | + ), | |
| 33 | 35 | ) |
| 34 | 36 | .handle("resume", ({ params: path, payload }) => |
| 35 | − capture(Effect.gen(function* () { | |
| 36 | − const engine = yield* ExecutionEngineService; | |
| 37 | − const result = yield* captureEngineError( | |
| 38 | − engine.resume(path.executionId, { | |
| 39 | − action: payload.action, | |
| 40 | − content: payload.content as Record<string, unknown> | undefined, | |
| 41 | − }), | |
| 42 | − ); | |
| 37 | + capture( | |
| 38 | + Effect.gen(function* () { | |
| 39 | + const engine = yield* ExecutionEngineService; | |
| 40 | + const result = yield* captureEngineError( | |
| 41 | + engine.resume(path.executionId, { | |
| 42 | + action: payload.action, | |
| 43 | + content: payload.content as Record<string, unknown> | undefined, | |
| 44 | + }), | |
| 45 | + ); | |
| 43 | 46 | |
| 44 | − if (!result) { | |
| 45 | − return yield* Effect.fail({ | |
| 46 | − _tag: "ExecutionNotFoundError" as const, | |
| 47 | − executionId: path.executionId, | |
| 48 | − }); | |
| 49 | − } | |
| 47 | + if (!result) { | |
| 48 | + return yield* Effect.fail({ | |
| 49 | + _tag: "ExecutionNotFoundError" as const, | |
| 50 | + executionId: path.executionId, | |
| 51 | + }); | |
| 52 | + } | |
| 50 | 53 | |
| 51 | − if (result.status === "completed") { | |
| 52 | − const formatted = formatExecuteResult(result.result); | |
| 54 | + if (result.status === "completed") { | |
| 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 | 64 | return { |
| 54 | 65 | text: formatted.text, |
| 55 | 66 | structured: formatted.structured, |
| 56 | − isError: formatted.isError, | |
| 67 | + isError: false, | |
| 57 | 68 | }; |
| 58 | − } | |
| 59 | − | |
| 60 | − const formatted = formatPausedExecution(result.execution); | |
| 61 | − return { | |
| 62 | − text: formatted.text, | |
| 63 | − structured: formatted.structured, | |
| 64 | − isError: false, | |
| 65 | − }; | |
| 66 | − })), | |
| 69 | + }), | |
| 70 | + ), | |
| 67 | 71 | ), |
| 68 | 72 | ); |
| @@ -67,7 +67,8 @@ const toPopupErrorMessage = (error: unknown): string => { · 66 unmodified lines | ||
| 67 | 67 | if (message) return message; |
| 68 | 68 | |
| 69 | 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 | 72 | return "Authentication failed"; |
| 72 | 73 | }; |
| 73 | 74 | |
| @@ -7,23 +7,25 @@ import { capture } from "@executor-js/api"; · 6 unmodified lines | ||
| 7 | 7 | |
| 8 | 8 | export const ScopeHandlers = HttpApiBuilder.group(ExecutorApi, "scope", (handlers) => |
| 9 | 9 | handlers.handle("info", () => |
| 10 | − capture(Effect.gen(function* () { | |
| 11 | − const executor = yield* ExecutorService; | |
| 12 | − // `id` / `name` / `dir` continue to point at the outermost scope so | |
| 13 | − // existing clients keep their source writes org/workspace-scoped. | |
| 14 | − // `stack` exposes the full innermost-first scope stack so the UI can | |
| 15 | − // deliberately target per-user secret writes when binding credentials. | |
| 16 | − const scope = executor.scopes.at(-1)!; | |
| 17 | − return { | |
| 18 | − id: scope.id, | |
| 19 | − name: scope.name, | |
| 20 | − dir: scope.name, | |
| 21 | − stack: executor.scopes.map((entry) => ({ | |
| 22 | − id: entry.id, | |
| 23 | − name: entry.name, | |
| 24 | − dir: entry.name, | |
| 25 | − })), | |
| 26 | − }; | |
| 27 | − })), | |
| 10 | + capture( | |
| 11 | + Effect.gen(function* () { | |
| 12 | + const executor = yield* ExecutorService; | |
| 13 | + // `id` / `name` / `dir` continue to point at the outermost scope so | |
| 14 | + // existing clients keep their source writes org/workspace-scoped. | |
| 15 | + // `stack` exposes the full innermost-first scope stack so the UI can | |
| 16 | + // deliberately target per-user secret writes when binding credentials. | |
| 17 | + const scope = executor.scopes.at(-1)!; | |
| 18 | + return { | |
| 19 | + id: scope.id, | |
| 20 | + name: scope.name, | |
| 21 | + dir: scope.name, | |
| 22 | + stack: executor.scopes.map((entry) => ({ | |
| 23 | + id: entry.id, | |
| 24 | + name: entry.name, | |
| 25 | + dir: entry.name, | |
| 26 | + })), | |
| 27 | + }; | |
| 28 | + }), | |
| 29 | + ), | |
| 28 | 30 | ), |
| 29 | 31 | ); |
| @@ -17,45 +17,55 @@ const refToResponse = (ref: SecretRef) => ({ · 16 unmodified lines | ||
| 17 | 17 | export const SecretsHandlers = HttpApiBuilder.group(ExecutorApi, "secrets", (handlers) => |
| 18 | 18 | handlers |
| 19 | 19 | .handle("list", () => |
| 20 | − capture(Effect.gen(function* () { | |
| 21 | − const executor = yield* ExecutorService; | |
| 22 | − const refs = yield* executor.secrets.list(); | |
| 23 | − return refs.map(refToResponse); | |
| 24 | − })), | |
| 20 | + capture( | |
| 21 | + Effect.gen(function* () { | |
| 22 | + const executor = yield* ExecutorService; | |
| 23 | + const refs = yield* executor.secrets.list(); | |
| 24 | + return refs.map(refToResponse); | |
| 25 | + }), | |
| 26 | + ), | |
| 25 | 27 | ) |
| 26 | 28 | .handle("status", ({ params: path }) => |
| 27 | − capture(Effect.gen(function* () { | |
| 28 | − const executor = yield* ExecutorService; | |
| 29 | − const status = yield* executor.secrets.status(path.secretId); | |
| 30 | − return { secretId: path.secretId, status }; | |
| 31 | − })), | |
| 29 | + capture( | |
| 30 | + Effect.gen(function* () { | |
| 31 | + const executor = yield* ExecutorService; | |
| 32 | + const status = yield* executor.secrets.status(path.secretId); | |
| 33 | + return { secretId: path.secretId, status }; | |
| 34 | + }), | |
| 35 | + ), | |
| 32 | 36 | ) |
| 33 | 37 | .handle("set", ({ params: path, payload }) => |
| 34 | − capture(Effect.gen(function* () { | |
| 35 | − const executor = yield* ExecutorService; | |
| 36 | − const ref = yield* executor.secrets.set( | |
| 37 | − new SetSecretInput({ | |
| 38 | − id: payload.id, | |
| 39 | − scope: path.scopeId, | |
| 40 | − name: payload.name, | |
| 41 | − value: payload.value, | |
| 42 | − provider: payload.provider, | |
| 43 | − }), | |
| 44 | − ); | |
| 45 | − return refToResponse(ref); | |
| 46 | − })), | |
| 38 | + capture( | |
| 39 | + Effect.gen(function* () { | |
| 40 | + const executor = yield* ExecutorService; | |
| 41 | + const ref = yield* executor.secrets.set( | |
| 42 | + new SetSecretInput({ | |
| 43 | + id: payload.id, | |
| 44 | + scope: path.scopeId, | |
| 45 | + name: payload.name, | |
| 46 | + value: payload.value, | |
| 47 | + provider: payload.provider, | |
| 48 | + }), | |
| 49 | + ); | |
| 50 | + return refToResponse(ref); | |
| 51 | + }), | |
| 52 | + ), | |
| 47 | 53 | ) |
| 48 | 54 | .handle("remove", ({ params: path }) => |
| 49 | − capture(Effect.gen(function* () { | |
| 50 | − const executor = yield* ExecutorService; | |
| 51 | − yield* executor.secrets.remove(path.secretId); | |
| 52 | − return { removed: true }; | |
| 53 | − })), | |
| 55 | + capture( | |
| 56 | + Effect.gen(function* () { | |
| 57 | + const executor = yield* ExecutorService; | |
| 58 | + yield* executor.secrets.remove(path.secretId); | |
| 59 | + return { removed: true }; | |
| 60 | + }), | |
| 61 | + ), | |
| 54 | 62 | ) |
| 55 | 63 | .handle("usages", ({ params: path }) => |
| 56 | − capture(Effect.gen(function* () { | |
| 57 | − const executor = yield* ExecutorService; | |
| 58 | − return yield* executor.secrets.usages(path.secretId); | |
| 59 | − })), | |
| 64 | + capture( | |
| 65 | + Effect.gen(function* () { | |
| 66 | + const executor = yield* ExecutorService; | |
| 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 | 9 | export const SourcesHandlers = HttpApiBuilder.group(ExecutorApi, "sources", (handlers) => |
| 10 | 10 | handlers |
| 11 | 11 | .handle("list", () => |
| 12 | − capture(Effect.gen(function* () { | |
| 13 | − const executor = yield* ExecutorService; | |
| 14 | − const sources = yield* executor.sources.list(); | |
| 15 | − return sources.map((s) => ({ | |
| 16 | − id: s.id, | |
| 17 | − scopeId: s.scopeId ? ScopeId.make(s.scopeId) : undefined, | |
| 18 | − name: s.name, | |
| 19 | − kind: s.kind, | |
| 20 | − url: s.url, | |
| 21 | − runtime: s.runtime, | |
| 22 | − canRemove: s.canRemove, | |
| 23 | − canRefresh: s.canRefresh, | |
| 24 | − canEdit: s.canEdit, | |
| 25 | − })); | |
| 26 | − })), | |
| 12 | + capture( | |
| 13 | + Effect.gen(function* () { | |
| 14 | + const executor = yield* ExecutorService; | |
| 15 | + const sources = yield* executor.sources.list(); | |
| 16 | + return sources.map((s) => ({ | |
| 17 | + id: s.id, | |
| 18 | + scopeId: s.scopeId ? ScopeId.make(s.scopeId) : undefined, | |
| 19 | + name: s.name, | |
| 20 | + kind: s.kind, | |
| 21 | + url: s.url, | |
| 22 | + runtime: s.runtime, | |
| 23 | + canRemove: s.canRemove, | |
| 24 | + canRefresh: s.canRefresh, | |
| 25 | + canEdit: s.canEdit, | |
| 26 | + })); | |
| 27 | + }), | |
| 28 | + ), | |
| 27 | 29 | ) |
| 28 | 30 | .handle("remove", ({ params: path }) => |
| 29 | − capture(Effect.gen(function* () { | |
| 30 | − const executor = yield* ExecutorService; | |
| 31 | − yield* executor.sources.remove(path.sourceId); | |
| 32 | − return { removed: true }; | |
| 33 | − })), | |
| 31 | + capture( | |
| 32 | + Effect.gen(function* () { | |
| 33 | + const executor = yield* ExecutorService; | |
| 34 | + yield* executor.sources.remove(path.sourceId); | |
| 35 | + return { removed: true }; | |
| 36 | + }), | |
| 37 | + ), | |
| 34 | 38 | ) |
| 35 | 39 | .handle("refresh", ({ params: path }) => |
| 36 | − capture(Effect.gen(function* () { | |
| 37 | − const executor = yield* ExecutorService; | |
| 38 | − yield* executor.sources.refresh(path.sourceId); | |
| 39 | − return { refreshed: true }; | |
| 40 | − })), | |
| 40 | + capture( | |
| 41 | + Effect.gen(function* () { | |
| 42 | + const executor = yield* ExecutorService; | |
| 43 | + yield* executor.sources.refresh(path.sourceId); | |
| 44 | + return { refreshed: true }; | |
| 45 | + }), | |
| 46 | + ), | |
| 41 | 47 | ) |
| 42 | 48 | .handle("tools", ({ params: path }) => |
| 43 | − capture(Effect.gen(function* () { | |
| 44 | − const executor = yield* ExecutorService; | |
| 45 | − // Source detail is a management view — include policy-blocked | |
| 46 | − // tools so users can see and unblock them from the same place | |
| 47 | − // they review the source's other tools. Annotations are loaded | |
| 48 | − // so the UI can show the plugin's default approval state for | |
| 49 | − // tools that have no user policy override. | |
| 50 | − const tools = yield* executor.tools.list({ | |
| 51 | − sourceId: path.sourceId, | |
| 52 | − includeAnnotations: true, | |
| 53 | − includeBlocked: true, | |
| 54 | − }); | |
| 55 | − return tools.map((t) => ({ | |
| 56 | − id: ToolId.make(t.id), | |
| 57 | − pluginId: t.pluginId, | |
| 58 | − sourceId: t.sourceId, | |
| 59 | − name: t.name, | |
| 60 | − description: t.description, | |
| 61 | − mayElicit: t.annotations?.mayElicit, | |
| 62 | − requiresApproval: t.annotations?.requiresApproval, | |
| 63 | − approvalDescription: t.annotations?.approvalDescription, | |
| 64 | − })); | |
| 65 | − })), | |
| 49 | + capture( | |
| 50 | + Effect.gen(function* () { | |
| 51 | + const executor = yield* ExecutorService; | |
| 52 | + // Source detail is a management view — include policy-blocked | |
| 53 | + // tools so users can see and unblock them from the same place | |
| 54 | + // they review the source's other tools. Annotations are loaded | |
| 55 | + // so the UI can show the plugin's default approval state for | |
| 56 | + // tools that have no user policy override. | |
| 57 | + const tools = yield* executor.tools.list({ | |
| 58 | + sourceId: path.sourceId, | |
| 59 | + includeAnnotations: true, | |
| 60 | + includeBlocked: true, | |
| 61 | + }); | |
| 62 | + return tools.map((t) => ({ | |
| 63 | + id: ToolId.make(t.id), | |
| 64 | + pluginId: t.pluginId, | |
| 65 | + sourceId: t.sourceId, | |
| 66 | + name: t.name, | |
| 67 | + description: t.description, | |
| 68 | + mayElicit: t.annotations?.mayElicit, | |
| 69 | + requiresApproval: t.annotations?.requiresApproval, | |
| 70 | + approvalDescription: t.annotations?.approvalDescription, | |
| 71 | + })); | |
| 72 | + }), | |
| 73 | + ), | |
| 66 | 74 | ) |
| 67 | 75 | .handle("detect", ({ payload }) => |
| 68 | − capture(Effect.gen(function* () { | |
| 69 | − const executor = yield* ExecutorService; | |
| 70 | − const results = yield* executor.sources.detect(payload.url); | |
| 71 | − return results.map((r) => ({ | |
| 72 | − kind: r.kind, | |
| 73 | − confidence: r.confidence, | |
| 74 | − endpoint: r.endpoint, | |
| 75 | − name: r.name, | |
| 76 | − namespace: r.namespace, | |
| 77 | − })); | |
| 78 | − })), | |
| 76 | + capture( | |
| 77 | + Effect.gen(function* () { | |
| 78 | + const executor = yield* ExecutorService; | |
| 79 | + const results = yield* executor.sources.detect(payload.url); | |
| 80 | + return results.map((r) => ({ | |
| 81 | + kind: r.kind, | |
| 82 | + confidence: r.confidence, | |
| 83 | + endpoint: r.endpoint, | |
| 84 | + name: r.name, | |
| 85 | + namespace: r.namespace, | |
| 86 | + })); | |
| 87 | + }), | |
| 88 | + ), | |
| 79 | 89 | ), |
| 80 | 90 | ); |
| @@ -9,35 +9,39 @@ import { capture } from "@executor-js/api"; · 8 unmodified lines | ||
| 9 | 9 | export const ToolsHandlers = HttpApiBuilder.group(ExecutorApi, "tools", (handlers) => |
| 10 | 10 | handlers |
| 11 | 11 | .handle("list", () => |
| 12 | − capture(Effect.gen(function* () { | |
| 13 | − const executor = yield* ExecutorService; | |
| 14 | − // Tools page is a management view — include policy-blocked tools | |
| 15 | − // (so users can unblock them) and load annotations so the row can | |
| 16 | − // show the plugin's default approval state when no user rule | |
| 17 | − // matches. Mirrors the per-source `sources.tools` handler. | |
| 18 | − const tools = yield* executor.tools.list({ | |
| 19 | − includeAnnotations: true, | |
| 20 | − includeBlocked: true, | |
| 21 | − }); | |
| 22 | − return tools.map((t) => ({ | |
| 23 | − id: ToolId.make(t.id), | |
| 24 | − pluginId: t.pluginId, | |
| 25 | − sourceId: t.sourceId, | |
| 26 | − name: t.name, | |
| 27 | − description: t.description, | |
| 28 | − mayElicit: t.annotations?.mayElicit, | |
| 29 | − requiresApproval: t.annotations?.requiresApproval, | |
| 30 | − })); | |
| 31 | − })), | |
| 12 | + capture( | |
| 13 | + Effect.gen(function* () { | |
| 14 | + const executor = yield* ExecutorService; | |
| 15 | + // Tools page is a management view — include policy-blocked tools | |
| 16 | + // (so users can unblock them) and load annotations so the row can | |
| 17 | + // show the plugin's default approval state when no user rule | |
| 18 | + // matches. Mirrors the per-source `sources.tools` handler. | |
| 19 | + const tools = yield* executor.tools.list({ | |
| 20 | + includeAnnotations: true, | |
| 21 | + includeBlocked: true, | |
| 22 | + }); | |
| 23 | + return tools.map((t) => ({ | |
| 24 | + id: ToolId.make(t.id), | |
| 25 | + pluginId: t.pluginId, | |
| 26 | + sourceId: t.sourceId, | |
| 27 | + name: t.name, | |
| 28 | + description: t.description, | |
| 29 | + mayElicit: t.annotations?.mayElicit, | |
| 30 | + requiresApproval: t.annotations?.requiresApproval, | |
| 31 | + })); | |
| 32 | + }), | |
| 33 | + ), | |
| 32 | 34 | ) |
| 33 | 35 | .handle("schema", ({ params: path }) => |
| 34 | − capture(Effect.gen(function* () { | |
| 35 | − const executor = yield* ExecutorService; | |
| 36 | − const schema = yield* executor.tools.schema(path.toolId); | |
| 37 | − if (schema === null) { | |
| 38 | − return yield* new ToolNotFoundError({ toolId: path.toolId }); | |
| 39 | − } | |
| 40 | − return schema; | |
| 41 | − })), | |
| 36 | + capture( | |
| 37 | + Effect.gen(function* () { | |
| 38 | + const executor = yield* ExecutorService; | |
| 39 | + const schema = yield* executor.tools.schema(path.toolId); | |
| 40 | + if (schema === null) { | |
| 41 | + return yield* new ToolNotFoundError({ toolId: path.toolId }); | |
| 42 | + } | |
| 43 | + return schema; | |
| 44 | + }), | |
| 45 | + ), | |
| 42 | 46 | ), |
| 43 | 47 | ); |
| @@ -172,10 +172,12 @@ describe("runOAuthCallback", () => { · 171 unmodified lines | ||
| 172 | 172 | class DomainError extends Data.TaggedError("DomainError")<{ |
| 173 | 173 | readonly message: string; |
| 174 | 174 | }> {} |
| 175 | − const isDomainError = Schema.is(Schema.Struct({ | |
| 176 | − _tag: Schema.Literal("DomainError"), | |
| 177 | − message: Schema.String, | |
| 178 | − })); | |
| 175 | + const isDomainError = Schema.is( | |
| 176 | + Schema.Struct({ | |
| 177 | + _tag: Schema.Literal("DomainError"), | |
| 178 | + message: Schema.String, | |
| 179 | + }), | |
| 180 | + ); | |
| 179 | 181 | const html = await Effect.runPromise( |
| 180 | 182 | runOAuthCallback<GoogleAuth, DomainError, never>({ |
| 181 | 183 | complete: () => Effect.fail(new DomainError({ message: "Code expired" })), |
| @@ -12,10 +12,7 @@ · 11 unmodified lines | ||
| 12 | 12 | |
| 13 | 13 | import { Cause, Effect } from "effect"; |
| 14 | 14 | |
| 15 | −import { | |
| 16 | − OAUTH_POPUP_MESSAGE_TYPE, | |
| 17 | − type OAuthPopupResult, | |
| 18 | −} from "@executor-js/sdk"; | |
| 15 | +import { OAUTH_POPUP_MESSAGE_TYPE, type OAuthPopupResult } from "@executor-js/sdk"; | |
| 19 | 16 | |
| 20 | 17 | export { OAUTH_POPUP_MESSAGE_TYPE, isOAuthPopupResult } from "@executor-js/sdk"; |
| 21 | 18 | export type { OAuthPopupResult } from "@executor-js/sdk"; |
| @@ -36,9 +36,7 @@ const ProbePayload = Schema.Struct({ · 35 unmodified lines | ||
| 36 | 36 | const ProbeResponse = Schema.Struct({ |
| 37 | 37 | resourceMetadata: Schema.NullOr(Schema.Record(Schema.String, Schema.Unknown)), |
| 38 | 38 | resourceMetadataUrl: Schema.NullOr(Schema.String), |
| 39 | − authorizationServerMetadata: Schema.NullOr( | |
| 40 | − Schema.Record(Schema.String, Schema.Unknown), | |
| 41 | − ), | |
| 39 | + authorizationServerMetadata: Schema.NullOr(Schema.Record(Schema.String, Schema.Unknown)), | |
| 42 | 40 | authorizationServerMetadataUrl: Schema.NullOr(Schema.String), |
| 43 | 41 | authorizationServerUrl: Schema.NullOr(Schema.String), |
| 44 | 42 | supportsDynamicRegistration: Schema.Boolean, |
| @@ -20,9 +20,7 @@ const makeRecorder = (traceId = "trace-xyz") => · 19 unmodified lines | ||
| 20 | 20 | ErrorCapture, |
| 21 | 21 | ErrorCapture.of({ |
| 22 | 22 | captureException: (cause) => |
| 23 | − Ref.update(seen, (prev) => [...prev, cause]).pipe( | |
| 24 | − Effect.as(traceId), | |
| 25 | − ), | |
| 23 | + Ref.update(seen, (prev) => [...prev, cause]).pipe(Effect.as(traceId)), | |
| 26 | 24 | }), |
| 27 | 25 | ); |
| 28 | 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)", () => | |
| 62 | − Effect.gen(function* () { | |
| 63 | − const err = new UniqueViolationError({ model: "thing" }); | |
| 64 | − const exit = yield* Effect.exit(capture(Effect.fail(err))); | |
| 65 | − expect(Exit.isFailure(exit)).toBe(true); | |
| 66 | − if (!Exit.isFailure(exit)) return; | |
| 67 | − const defect = Cause.findDefect(exit.cause); | |
| 68 | − expect(Result.isSuccess(defect) ? defect.success : undefined).toBeInstanceOf( | |
| 69 | − UniqueViolationError, | |
| 70 | − ); | |
| 71 | − }), | |
| 59 | + it.effect( | |
| 60 | + "UniqueViolationError dies (becomes a defect — plugins should catchTag before returning)", | |
| 61 | + () => | |
| 62 | + Effect.gen(function* () { | |
| 63 | + const err = new UniqueViolationError({ model: "thing" }); | |
| 64 | + const exit = yield* Effect.exit(capture(Effect.fail(err))); | |
| 65 | + expect(Exit.isFailure(exit)).toBe(true); | |
| 66 | + if (!Exit.isFailure(exit)) return; | |
| 67 | + const defect = Cause.findDefect(exit.cause); | |
| 68 | + expect(Result.isSuccess(defect) ? defect.success : undefined).toBeInstanceOf( | |
| 69 | + UniqueViolationError, | |
| 70 | + ); | |
| 71 | + }), | |
| 72 | 72 | ); |
| 73 | 73 | |
| 74 | 74 | it.effect("non-storage typed failures pass through unchanged", () => |
| @@ -76,10 +76,7 @@ describe("capture", () => { · 1 unmodified line | ||
| 76 | 76 | class DomainError { |
| 77 | 77 | readonly _tag = "DomainError" as const; |
| 78 | 78 | } |
| 79 | − const eff = Effect.fail(new DomainError()) as Effect.Effect< | |
| 80 | − never, | |
| 81 | − DomainError | |
| 82 | − >; | |
| 79 | + const eff = Effect.fail(new DomainError()) as Effect.Effect<never, DomainError>; | |
| 83 | 80 | const result = yield* Effect.flip(capture(eff)); |
| 84 | 81 | expect(result).toBeInstanceOf(DomainError); |
| 85 | 82 | }), |
| @@ -51,19 +51,16 @@ export interface ErrorCaptureShape { · 50 unmodified lines | ||
| 51 | 51 | * can later look up. Implementations (Sentry, console, etc.) decide |
| 52 | 52 | * how to persist it. |
| 53 | 53 | */ |
| 54 | − readonly captureException: ( | |
| 55 | − cause: Cause.Cause<unknown>, | |
| 56 | − ) => Effect.Effect<string>; | |
| 54 | + readonly captureException: (cause: Cause.Cause<unknown>) => Effect.Effect<string>; | |
| 57 | 55 | } |
| 58 | 56 | |
| 59 | 57 | export class ErrorCapture extends Context.Service<ErrorCapture, ErrorCaptureShape>()( |
| 60 | 58 | "@executor-js/api/ErrorCapture", |
| 61 | 59 | ) { |
| 62 | 60 | /** No-op — used where capture isn't wired. Traces back as empty string. */ |
| 63 | − static readonly NoOp: Layer.Layer<ErrorCapture> = Layer.succeed( | |
| 64 | − ErrorCapture, | |
| 65 | − { captureException: () => Effect.succeed("") }, | |
| 66 | − ); | |
| 61 | + static readonly NoOp: Layer.Layer<ErrorCapture> = Layer.succeed(ErrorCapture, { | |
| 62 | + captureException: () => Effect.succeed(""), | |
| 63 | + }); | |
| 67 | 64 | } |
| 68 | 65 | |
| 69 | 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 | 68 | // typecheck; if it's there, we use it; if not, trace ids are empty. |
| 72 | 69 | const resolveCapture = Effect.serviceOption(ErrorCapture).pipe( |
| 73 | 70 | Effect.map((opt) => |
| 74 | − Option.isSome(opt) | |
| 75 | − ? opt.value | |
| 76 | − : ({ captureException: () => Effect.succeed("") } as const), | |
| 71 | + Option.isSome(opt) ? opt.value : ({ 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 | 97 | Effect.catchTag("StorageError", (err) => |
| 103 | 98 | resolveCapture.pipe( |
| 104 | 99 | Effect.flatMap((c) => c.captureException(Cause.fail(err))), |
| 105 | − Effect.flatMap((traceId) => | |
| 106 | − Effect.fail(new InternalError({ traceId })), | |
| 107 | − ), | |
| 100 | + Effect.flatMap((traceId) => Effect.fail(new InternalError({ traceId }))), | |
| 108 | 101 | ), |
| 109 | 102 | ), |
| 110 | 103 | ) as Effect.Effect<A, Exclude<E, StorageFailure> | InternalError, R>; |
| @@ -131,10 +124,8 @@ export const captureEngineError = <A, R>( · 20 unmodified lines | ||
| 131 | 124 | ? Effect.fail(err) |
| 132 | 125 | : resolveCapture.pipe( |
| 133 | 126 | Effect.flatMap((c) => c.captureException(Cause.fail(err))), |
| 134 | − Effect.flatMap((traceId) => | |
| 135 | − Effect.fail(new InternalError({ traceId })), | |
| 136 | − ), | |
| 137 | − ), | |
| 127 | + Effect.flatMap((traceId) => Effect.fail(new InternalError({ traceId }))), | |
| 128 | + ), | |
| 138 | 129 | ), |
| 139 | 130 | ); |
| 140 | 131 | |
| @@ -160,20 +151,18 @@ export class ObservabilityMiddleware extends HttpApiMiddleware.Service<Observabi · 19 unmodified lines | ||
| 160 | 151 | export const observabilityMiddleware = <Id extends string, Groups extends HttpApiGroup.Any>( |
| 161 | 152 | _api: HttpApi.HttpApi<Id, Groups>, |
| 162 | 153 | ): Layer.Layer<ObservabilityMiddleware> => |
| 163 | − Layer.succeed( | |
| 164 | − ObservabilityMiddleware, | |
| 165 | − (httpApp) => | |
| 166 | − Effect.catchCause(httpApp, (cause) => | |
| 167 | − Effect.gen(function* () { | |
| 168 | − const defect = Cause.findDefect(cause); | |
| 169 | − if (Result.isFailure(defect)) { | |
| 170 | − return yield* Effect.failCause(cause); | |
| 171 | − } | |
| 172 | − const c = yield* resolveCapture; | |
| 173 | − const traceId = yield* c.captureException(cause); | |
| 174 | − return HttpServerResponse.jsonUnsafe(new InternalError({ traceId }), { | |
| 175 | − status: 500, | |
| 176 | − }); | |
| 177 | − }), | |
| 178 | − ), | |
| 154 | + Layer.succeed(ObservabilityMiddleware, (httpApp) => | |
| 155 | + Effect.catchCause(httpApp, (cause) => | |
| 156 | + Effect.gen(function* () { | |
| 157 | + const defect = Cause.findDefect(cause); | |
| 158 | + if (Result.isFailure(defect)) { | |
| 159 | + return yield* Effect.failCause(cause); | |
| 160 | + } | |
| 161 | + const c = yield* resolveCapture; | |
| 162 | + const traceId = yield* c.captureException(cause); | |
| 163 | + return HttpServerResponse.jsonUnsafe(new InternalError({ traceId }), { | |
| 164 | + status: 500, | |
| 165 | + }); | |
| 166 | + }), | |
| 167 | + ), | |
| 179 | 168 | ); |
| @@ -40,8 +40,9 @@ import { CoreExecutorApi } from "./api"; · 39 unmodified lines | ||
| 40 | 40 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 41 | 41 | type ExtractServiceId<S> = S extends Context.Service<infer Id, any> ? Id : never; |
| 42 | 42 | |
| 43 | −export type PluginExtensionServices<TPlugins extends readonly AnyPlugin[]> = | |
| 44 | − ExtractServiceId<NonNullable<TPlugins[number]["extensionService"]>>; | |
| 43 | +export type PluginExtensionServices<TPlugins extends readonly AnyPlugin[]> = ExtractServiceId< | |
| 44 | + NonNullable<TPlugins[number]["extensionService"]> | |
| 45 | +>; | |
| 45 | 46 | |
| 46 | 47 | /** Extract the precise `HttpApiGroup` type carried by a plugin's |
| 47 | 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 | 54 | /** Union of every plugin's contributed group — combined with the core |
| 54 | 55 | * executor groups to type `composePluginApi(plugins)` precisely. */ |
| 55 | −export type PluginGroups<TPlugins extends readonly AnyPlugin[]> = | |
| 56 | − ExtractPluginGroup<TPlugins[number]>; | |
| 56 | +export type PluginGroups<TPlugins extends readonly AnyPlugin[]> = ExtractPluginGroup< | |
| 57 | + TPlugins[number] | |
| 58 | +>; | |
| 57 | 59 | |
| 58 | 60 | /** Group identities baked into `CoreExecutorApi` (tools, sources, secrets, |
| 59 | 61 | * …). Extracted via inference so adding a core group flows through |
| @@ -64,8 +66,10 @@ type CoreGroups = · 4 unmodified lines | ||
| 64 | 66 | |
| 65 | 67 | /** Result of `composePluginApi(plugins)` — the core API extended with |
| 66 | 68 | * every plugin group from `TPlugins`. */ |
| 67 | −export type ComposedExecutorApi<TPlugins extends readonly AnyPlugin[]> = | |
| 68 | − HttpApi.HttpApi<"executor", CoreGroups | PluginGroups<TPlugins>>; | |
| 69 | +export type ComposedExecutorApi<TPlugins extends readonly AnyPlugin[]> = HttpApi.HttpApi< | |
| 70 | + "executor", | |
| 71 | + CoreGroups | PluginGroups<TPlugins> | |
| 72 | +>; | |
| 69 | 73 | |
| 70 | 74 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 71 | 75 | type AnyLayer = Layer.Layer<any, any, any>; |
| @@ -73,18 +77,18 @@ type AnyLayer = Layer.Layer<any, any, any>; · 1 unmodified line | ||
| 73 | 77 | // Use the field accessor + NonNullable rather than `extends { handlers: ... }` |
| 74 | 78 | // because the spec marks `handlers` optional (`handlers?:`); the conditional |
| 75 | 79 | // form would fail the match because the field type includes `undefined`. |
| 76 | −type ExtractHandlerLayer<P> = NonNullable< | |
| 77 | − P extends { readonly handlers?: infer F } ? F : never | |
| 78 | −> extends () => infer L | |
| 79 | − ? L | |
| 80 | − : never; | |
| 80 | +type ExtractHandlerLayer<P> = | |
| 81 | + NonNullable<P extends { readonly handlers?: infer F } ? F : never> extends () => infer L | |
| 82 | + ? L | |
| 83 | + : never; | |
| 81 | 84 | |
| 82 | 85 | // Compute the union of every plugin's handler-Layer type. Each plugin's |
| 83 | 86 | // `handlers()` returns a specific `Layer<Group, never, ExtensionService>`; |
| 84 | 87 | // we union them so `Layer.mergeAll(...)`'s output type can be extracted |
| 85 | 88 | // without erasing per-plugin requirements. |
| 86 | −type PluginHandlerLayers<TPlugins extends readonly AnyPlugin[]> = | |
| 87 | − ExtractHandlerLayer<TPlugins[number]>; | |
| 89 | +type PluginHandlerLayers<TPlugins extends readonly AnyPlugin[]> = ExtractHandlerLayer< | |
| 90 | + TPlugins[number] | |
| 91 | +>; | |
| 88 | 92 | |
| 89 | 93 | // Distribute over the union of handler layers to extract each channel |
| 90 | 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 | 178 | * `*ExtensionService` Tags) so the host's `HttpRouter.middleware` |
| 175 | 179 | * recognises them as per-request requires. |
| 176 | 180 | */ |
| 177 | −export const composePluginHandlerLayer = < | |
| 178 | − TPlugins extends readonly AnyPlugin[], | |
| 179 | −>( | |
| 181 | +export const composePluginHandlerLayer = <TPlugins extends readonly AnyPlugin[]>( | |
| 180 | 182 | plugins: TPlugins, |
| 181 | 183 | ): MergedHandlerLayer<TPlugins> => { |
| 182 | 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 | 50 | const SecretNotFound = SecretNotFoundError.annotate({ httpApiStatus: 404 }); |
| 51 | −const SecretResolution = SecretResolutionError.annotate( | |
| 52 | − { httpApiStatus: 500 }, | |
| 53 | −); | |
| 54 | −const SecretOwnedByConnection = SecretOwnedByConnectionError.annotate( | |
| 55 | − { httpApiStatus: 409 }, | |
| 56 | −); | |
| 51 | +const SecretResolution = SecretResolutionError.annotate({ httpApiStatus: 500 }); | |
| 52 | +const SecretOwnedByConnection = SecretOwnedByConnectionError.annotate({ httpApiStatus: 409 }); | |
| 57 | 53 | const SecretInUse = SecretInUseError.annotate({ httpApiStatus: 409 }); |
| 58 | 54 | |
| 59 | 55 | // --------------------------------------------------------------------------- |
| @@ -1,10 +1,6 @@ | ||
| 1 | 1 | import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"; |
| 2 | 2 | import { Schema } from "effect"; |
| 3 | −import { | |
| 4 | − ScopeId, | |
| 5 | − SourceRemovalNotAllowedError, | |
| 6 | − ToolId, | |
| 7 | −} from "@executor-js/sdk"; | |
| 3 | +import { ScopeId, SourceRemovalNotAllowedError, ToolId } from "@executor-js/sdk"; | |
| 8 | 4 | |
| 9 | 5 | import { InternalError } from "../observability"; |
| 10 | 6 | |
| @@ -68,9 +64,7 @@ const DetectResultResponse = Schema.Struct({ · 57 unmodified lines | ||
| 68 | 64 | // Error schemas with HTTP status annotations |
| 69 | 65 | // --------------------------------------------------------------------------- |
| 70 | 66 | |
| 71 | −const SourceRemovalNotAllowed = SourceRemovalNotAllowedError.annotate( | |
| 72 | − { httpApiStatus: 409 }, | |
| 73 | −); | |
| 67 | +const SourceRemovalNotAllowed = SourceRemovalNotAllowedError.annotate({ httpApiStatus: 409 }); | |
| 74 | 68 | |
| 75 | 69 | // --------------------------------------------------------------------------- |
| 76 | 70 | // Group |
| @@ -31,9 +31,7 @@ const ToolSchemaResponse = Schema.Struct({ · 30 unmodified lines | ||
| 31 | 31 | id: ToolId, |
| 32 | 32 | inputTypeScript: Schema.optional(Schema.String), |
| 33 | 33 | outputTypeScript: Schema.optional(Schema.String), |
| 34 | − typeScriptDefinitions: Schema.optional( | |
| 35 | − Schema.Record(Schema.String, Schema.String), | |
| 36 | − ), | |
| 34 | + typeScriptDefinitions: Schema.optional(Schema.Record(Schema.String, Schema.String)), | |
| 37 | 35 | inputSchema: Schema.optional(Schema.Unknown), |
| 38 | 36 | outputSchema: Schema.optional(Schema.Unknown), |
| 39 | 37 | }); |
| @@ -12,13 +12,13 @@ · 11 unmodified lines | ||
| 12 | 12 | "url": "git+https://github.com/RhysSullivan/executor.git", |
| 13 | 13 | "directory": "packages/core/cli" |
| 14 | 14 | }, |
| 15 | − "type": "module", | |
| 16 | 15 | "bin": { |
| 17 | 16 | "executor": "./dist/index.js" |
| 18 | 17 | }, |
| 19 | 18 | "files": [ |
| 20 | 19 | "dist" |
| 21 | 20 | ], |
| 21 | + "type": "module", | |
| 22 | 22 | "exports": { |
| 23 | 23 | ".": "./src/index.ts" |
| 24 | 24 | }, |
| @@ -6,11 +6,7 @@ import { collectSchemas } from "@executor-js/sdk/core"; · 5 unmodified lines | ||
| 6 | 6 | import { getConfig } from "../utils/get-config.js"; |
| 7 | 7 | import { generateDrizzleSchema } from "../generators/drizzle.js"; |
| 8 | 8 | |
| 9 | −async function generateAction(opts: { | |
| 10 | − cwd: string; | |
| 11 | − config?: string; | |
| 12 | − output?: string; | |
| 13 | −}) { | |
| 9 | +async function generateAction(opts: { cwd: string; config?: string; output?: string }) { | |
| 14 | 10 | const cwd = path.resolve(opts.cwd); |
| 15 | 11 | if (!existsSync(cwd)) { |
| 16 | 12 | console.error(`The directory "${cwd}" does not exist.`); |
| @@ -55,17 +51,7 @@ async function generateAction(opts: { · 38 unmodified lines | ||
| 55 | 51 | |
| 56 | 52 | export const generate = new Command("generate") |
| 57 | 53 | .description("Generate a drizzle schema file from the executor config") |
| 58 | − .option( | |
| 59 | − "-c, --cwd <cwd>", | |
| 60 | − "the working directory", | |
| 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 | − ) | |
| 54 | + .option("-c, --cwd <cwd>", "the working directory", process.cwd()) | |
| 55 | + .option("--config <config>", "path to the executor config file") | |
| 56 | + .option("--output <output>", "output file path for the generated schema") | |
| 71 | 57 | .action(generateAction); |
| @@ -15,14 +15,9 @@ import type { SchemaGenerator } from "./types.js"; · 14 unmodified lines | ||
| 15 | 15 | |
| 16 | 16 | type Dialect = "pg" | "sqlite" | "mysql"; |
| 17 | 17 | |
| 18 | −const getModelName = (key: string, def: DBSchema[string]): string => | |
| 19 | − def.modelName ?? key; | |
| 20 | − | |
| 21 | −const getType = ( | |
| 22 | − name: string, | |
| 23 | − field: DBFieldAttribute, | |
| 24 | − dialect: Dialect, | |
| 25 | −): string => { | |
| 18 | +const getModelName = (key: string, def: DBSchema[string]): string => def.modelName ?? key; | |
| 19 | + | |
| 20 | +const getType = (name: string, field: DBFieldAttribute, dialect: Dialect): string => { | |
| 26 | 21 | if (field.references?.field === "id") { |
| 27 | 22 | return `text('${name}')`; |
| 28 | 23 | } |
| @@ -39,9 +34,7 @@ const getType = ( · 10 unmodified lines | ||
| 39 | 34 | }[dialect]; |
| 40 | 35 | } |
| 41 | 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( | |
| 43 | − `Invalid field type for field ${name}`, | |
| 44 | − ); | |
| 37 | + throw new TypeError(`Invalid field type for field ${name}`); | |
| 45 | 38 | } |
| 46 | 39 | |
| 47 | 40 | const typeMap: Record<string, Record<Dialect, string>> = { |
| @@ -65,12 +58,8 @@ const getType = ( · 17 unmodified lines | ||
| 65 | 58 | }, |
| 66 | 59 | number: { |
| 67 | 60 | sqlite: `integer('${name}')`, |
| 68 | − pg: field.bigint | |
| 69 | − ? `bigint('${name}', { mode: 'number' })` | |
| 70 | − : `integer('${name}')`, | |
| 71 | − mysql: field.bigint | |
| 72 | − ? `bigint('${name}', { mode: 'number' })` | |
| 73 | − : `int('${name}')`, | |
| 61 | + pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`, | |
| 62 | + mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`, | |
| 74 | 63 | }, |
| 75 | 64 | date: { |
| 76 | 65 | sqlite: `integer('${name}', { mode: 'timestamp_ms' })`, |
| @@ -99,9 +88,7 @@ const getType = ( · 22 unmodified lines | ||
| 99 | 88 | const dbTypeMap = typeMap[type]; |
| 100 | 89 | if (!dbTypeMap) { |
| 101 | 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( | |
| 103 | − `Unsupported field type '${field.type}' for field '${name}'.`, | |
| 104 | − ); | |
| 91 | + throw new Error(`Unsupported field type '${field.type}' for field '${name}'.`); | |
| 105 | 92 | } |
| 106 | 93 | return dbTypeMap[dialect]; |
| 107 | 94 | }; |
| @@ -110,11 +97,7 @@ const getType = ( · 2 unmodified lines | ||
| 110 | 97 | // Generator |
| 111 | 98 | // --------------------------------------------------------------------------- |
| 112 | 99 | |
| 113 | −export const generateDrizzleSchema: SchemaGenerator = async ({ | |
| 114 | − schema, | |
| 115 | − dialect, | |
| 116 | − file, | |
| 117 | −}) => { | |
| 100 | +export const generateDrizzleSchema: SchemaGenerator = async ({ schema, dialect, file }) => { | |
| 118 | 101 | const filePath = file || "./executor-schema.ts"; |
| 119 | 102 | const fileExist = existsSync(filePath); |
| 120 | 103 | |
| @@ -159,69 +142,62 @@ export const generateDrizzleSchema: SchemaGenerator = async ({ · 38 unmodified lines | ||
| 159 | 142 | } |
| 160 | 143 | |
| 161 | 144 | const fieldLines = Object.entries(fields) |
| 162 | − .filter(([fieldName]) => fieldName !== "id") | |
| 163 | − .map(([fieldName, attr]) => { | |
| 164 | − const physical = attr.fieldName ?? fieldName; | |
| 165 | − | |
| 166 | − const isToolPolicyCompositeField = | |
| 167 | − tableKey === "tool_policy" && | |
| 168 | − (physical === "scope_id" || physical === "position"); | |
| 169 | − | |
| 170 | − if (attr.index && !attr.unique && !isToolPolicyCompositeField) { | |
| 171 | − extras.push({ | |
| 172 | − kind: "index", | |
| 173 | − name: `${tableKey}_${physical}_idx`, | |
| 174 | − on: physical, | |
| 175 | − }); | |
| 176 | − } else if (attr.index && attr.unique) { | |
| 177 | − extras.push({ | |
| 178 | − kind: "uniqueIndex", | |
| 179 | − name: `${tableKey}_${physical}_uidx`, | |
| 180 | − on: physical, | |
| 181 | − }); | |
| 182 | − } | |
| 145 | + .filter(([fieldName]) => fieldName !== "id") | |
| 146 | + .map(([fieldName, attr]) => { | |
| 147 | + const physical = attr.fieldName ?? fieldName; | |
| 148 | + | |
| 149 | + const isToolPolicyCompositeField = | |
| 150 | + tableKey === "tool_policy" && (physical === "scope_id" || physical === "position"); | |
| 151 | + | |
| 152 | + if (attr.index && !attr.unique && !isToolPolicyCompositeField) { | |
| 153 | + extras.push({ | |
| 154 | + kind: "index", | |
| 155 | + name: `${tableKey}_${physical}_idx`, | |
| 156 | + on: physical, | |
| 157 | + }); | |
| 158 | + } else if (attr.index && attr.unique) { | |
| 159 | + extras.push({ | |
| 160 | + kind: "uniqueIndex", | |
| 161 | + name: `${tableKey}_${physical}_uidx`, | |
| 162 | + on: physical, | |
| 163 | + }); | |
| 164 | + } | |
| 165 | + | |
| 166 | + let col = getType(physical, attr, dialect); | |
| 183 | 167 | |
| 184 | − let col = getType(physical, attr, dialect); | |
| 185 | − | |
| 186 | − if ( | |
| 187 | − attr.defaultValue !== null && | |
| 188 | − typeof attr.defaultValue !== "undefined" | |
| 189 | − ) { | |
| 190 | − if (typeof attr.defaultValue === "function") { | |
| 191 | − if ( | |
| 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()`; | |
| 168 | + if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") { | |
| 169 | + if (typeof attr.defaultValue === "function") { | |
| 170 | + if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) { | |
| 171 | + if (dialect === "sqlite") { | |
| 172 | + col += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`; | |
| 173 | + } else { | |
| 174 | + col += `.defaultNow()`; | |
| 175 | + } | |
| 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") { | |
| 209 | − if (typeof attr.onUpdate === "function") { | |
| 210 | − col += `.$onUpdate(${attr.onUpdate})`; | |
| 184 | + if (attr.onUpdate && attr.type === "date") { | |
| 185 | + if (typeof attr.onUpdate === "function") { | |
| 186 | + col += `.$onUpdate(${attr.onUpdate})`; | |
| 187 | + } | |
| 211 | 188 | } |
| 212 | − } | |
| 213 | 189 | |
| 214 | − return `${physical}: ${col}${attr.required !== false ? ".notNull()" : ""}${ | |
| 215 | − attr.unique ? ".unique()" : "" | |
| 216 | − }${ | |
| 217 | − attr.references | |
| 218 | − ? `.references(()=> ${attr.references.model}.${attr.references.field ?? "id"}, { onDelete: '${ | |
| 219 | − attr.references.onDelete || "cascade" | |
| 220 | − }' })` | |
| 221 | − : "" | |
| 222 | − }`; | |
| 223 | − }) | |
| 224 | − .join(",\n "); | |
| 190 | + return `${physical}: ${col}${attr.required !== false ? ".notNull()" : ""}${ | |
| 191 | + attr.unique ? ".unique()" : "" | |
| 192 | + }${ | |
| 193 | + attr.references | |
| 194 | + ? `.references(()=> ${attr.references.model}.${attr.references.field ?? "id"}, { onDelete: '${ | |
| 195 | + attr.references.onDelete || "cascade" | |
| 196 | + }' })` | |
| 197 | + : "" | |
| 198 | + }`; | |
| 199 | + }) | |
| 200 | + .join(",\n "); | |
| 225 | 201 | |
| 226 | 202 | if (tableKey === "tool_policy") { |
| 227 | 203 | extras.push({ |
| @@ -339,12 +315,7 @@ export const generateDrizzleSchema: SchemaGenerator = async ({ · 111 unmodified lines | ||
| 339 | 315 | const hasMany = manyRelations.length > 0; |
| 340 | 316 | |
| 341 | 317 | if (hasOne || hasMany) { |
| 342 | − const destructured = [ | |
| 343 | − hasOne ? "one" : "", | |
| 344 | − hasMany ? "many" : "", | |
| 345 | − ] | |
| 346 | − .filter(Boolean) | |
| 347 | − .join(", "); | |
| 318 | + const destructured = [hasOne ? "one" : "", hasMany ? "many" : ""].filter(Boolean).join(", "); | |
| 348 | 319 | |
| 349 | 320 | const body = [ |
| 350 | 321 | ...singleRelations |
| @@ -353,9 +324,7 @@ export const generateDrizzleSchema: SchemaGenerator = async ({ · 2 unmodified lines | ||
| 353 | 324 | (r) => |
| 354 | 325 | ` ${r.key}: one(${r.model}, {\n fields: [${r.reference!.field}],\n references: [${r.reference!.references}],\n })`, |
| 355 | 326 | ), |
| 356 | − ...manyRelations.map( | |
| 357 | − ({ key, model }) => ` ${key}: many(${model})`, | |
| 358 | − ), | |
| 327 | + ...manyRelations.map(({ key, model }) => ` ${key}: many(${model})`), | |
| 359 | 328 | ].join(",\n"); |
| 360 | 329 | |
| 361 | 330 | const block = `export const ${modelName}Relations = relations(${modelName}, ({ ${destructured} }) => ({ |
| @@ -378,13 +347,7 @@ ${body} · 16 unmodified lines | ||
| 378 | 347 | // Import generation — only emit what's actually used |
| 379 | 348 | // --------------------------------------------------------------------------- |
| 380 | 349 | |
| 381 | −function generateImport({ | |
| 382 | − dialect, | |
| 383 | − schema, | |
| 384 | −}: { | |
| 385 | − dialect: Dialect; | |
| 386 | − schema: DBSchema; | |
| 387 | −}) { | |
| 350 | +function generateImport({ dialect, schema }: { dialect: Dialect; schema: DBSchema }) { | |
| 388 | 351 | const rootImports: string[] = []; |
| 389 | 352 | const coreImports: string[] = []; |
| 390 | 353 | |
| @@ -403,10 +366,7 @@ function generateImport({ · 12 unmodified lines | ||
| 403 | 366 | if (field.bigint) hasBigint = true; |
| 404 | 367 | if (field.type === "json") hasJson = true; |
| 405 | 368 | if (field.type === "boolean") hasBoolean = true; |
| 406 | − if ( | |
| 407 | − (field.type === "number" && !field.bigint) || | |
| 408 | − field.type === "number[]" | |
| 409 | − ) { | |
| 369 | + if ((field.type === "number" && !field.bigint) || field.type === "number[]") { | |
| 410 | 370 | hasNumber = true; |
| 411 | 371 | } |
| 412 | 372 | if (field.type === "date") hasDate = true; |
| @@ -482,9 +442,7 @@ function generateImport({ · 69 unmodified lines | ||
| 482 | 442 | // produce relation blocks (see relationsString generation). |
| 483 | 443 | if (hasReferences) rootImports.push("relations"); |
| 484 | 444 | |
| 485 | − const filteredCore = coreImports | |
| 486 | − .map((x) => x.trim()) | |
| 487 | − .filter((x) => x !== ""); | |
| 445 | + const filteredCore = coreImports.map((x) => x.trim()).filter((x) => x !== ""); | |
| 488 | 446 | |
| 489 | 447 | // Deduplicate |
| 490 | 448 | const uniqueCore = [...new Set(filteredCore)]; |
| @@ -7,10 +7,7 @@ const generators: Record<string, SchemaGenerator> = { · 6 unmodified lines | ||
| 7 | 7 | drizzle: generateDrizzleSchema, |
| 8 | 8 | }; |
| 9 | 9 | |
| 10 | −export const generateSchema = ( | |
| 11 | − adapter: string, | |
| 12 | − ...args: Parameters<SchemaGenerator> | |
| 13 | −) => { | |
| 10 | +export const generateSchema = (adapter: string, ...args: Parameters<SchemaGenerator>) => { | |
| 14 | 11 | const generator = generators[adapter]; |
| 15 | 12 | if (!generator) { |
| 16 | 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 | 8 | dts: false, |
| 9 | 9 | sourcemap: true, |
| 10 | 10 | clean: true, |
| 11 | − external: [ | |
| 12 | − /^effect/, | |
| 13 | − /^@effect\//, | |
| 14 | − "commander", | |
| 15 | − "jiti", | |
| 16 | − ], | |
| 11 | + external: [/^effect/, /^@effect\//, "commander", "jiti"], | |
| 17 | 12 | }); |
| @@ -25,8 +25,4 @@ export { · 24 unmodified lines | ||
| 25 | 25 | } from "./write"; |
| 26 | 26 | |
| 27 | 27 | export type { ConfigFileSink, ConfigFileSinkOptions } from "./sink"; |
| 28 | −export { | |
| 29 | − makeFileConfigSink, | |
| 30 | − headerToConfigValue, | |
| 31 | − headersToConfigValues, | |
| 32 | −} from "./sink"; | |
| 28 | +export { makeFileConfigSink, headerToConfigValue, headersToConfigValues } from "./sink"; | |
| @@ -25,9 +25,7 @@ interface FixturePlugin { · 24 unmodified lines | ||
| 25 | 25 | readonly __optionsReceived: Record<string, unknown> | null; |
| 26 | 26 | } |
| 27 | 27 | |
| 28 | −const asFixturePlugins = ( | |
| 29 | − plugins: readonly AnyPlugin[] | null, | |
| 30 | −): readonly FixturePlugin[] => { | |
| 28 | +const asFixturePlugins = (plugins: readonly AnyPlugin[] | null): readonly FixturePlugin[] => { | |
| 31 | 29 | // Fixture servers return shapes wider than AnyPlugin (they carry |
| 32 | 30 | // __optionsReceived for test assertions); narrow once here. |
| 33 | 31 | // oxlint-disable-next-line executor/no-double-cast |
| @@ -71,9 +69,7 @@ describe("loadPluginsFromJsonc", () => { · 37 unmodified lines | ||
| 71 | 69 | |
| 72 | 70 | it("throws on JSONC parse errors", async () => { |
| 73 | 71 | const file = writeJsonc("parse-error", `{ "plugins": [`); |
| 74 | − await expect(loadPluginsFromJsonc({ path: file })).rejects.toThrow( | |
| 75 | − /failed to parse/i, | |
| 76 | − ); | |
| 72 | + await expect(loadPluginsFromJsonc({ path: file })).rejects.toThrow(/failed to parse/i); | |
| 77 | 73 | }); |
| 78 | 74 | |
| 79 | 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 | 67 | export const loadPluginsFromJsonc = async ( |
| 68 | 68 | options: LoadPluginsFromJsoncOptions, |
| 69 | −): Promise<readonly AnyPlugin[] | null> => | |
| 70 | − Effect.runPromise(loadPluginsFromJsoncEffect(options)); | |
| 69 | +): Promise<readonly AnyPlugin[] | null> => Effect.runPromise(loadPluginsFromJsoncEffect(options)); | |
| 71 | 70 | |
| 72 | 71 | const loadPluginsFromJsoncEffect = ( |
| 73 | 72 | options: LoadPluginsFromJsoncOptions, |
| @@ -122,9 +121,7 @@ const loadPluginsFromJsoncEffect = ( · 48 unmodified lines | ||
| 122 | 121 | // require.resolve is anchored to the jsonc's directory so plugin |
| 123 | 122 | // packages resolve from the host app's `node_modules` regardless of |
| 124 | 123 | // CWD. |
| 125 | − const require = createRequire( | |
| 126 | − isAbsolute(path) ? path : resolvePath(fromDir, "_anchor.js"), | |
| 127 | − ); | |
| 124 | + const require = createRequire(isAbsolute(path) ? path : resolvePath(fromDir, "_anchor.js")); | |
| 128 | 125 | |
| 129 | 126 | const loaded: AnyPlugin[] = []; |
| 130 | 127 | for (const entry of entries) { |
| @@ -146,9 +143,7 @@ const loadPluginsFromJsoncEffect = ( · 15 unmodified lines | ||
| 146 | 143 | message: `[load-plugins] failed to import "${serverEntry}" from ${resolved}.`, |
| 147 | 144 | cause, |
| 148 | 145 | }), |
| 149 | − })) as | |
| 150 | − | { default?: LooseConfiguredPlugin } | |
| 151 | − | LooseConfiguredPlugin; | |
| 146 | + })) as { default?: LooseConfiguredPlugin } | LooseConfiguredPlugin; | |
| 152 | 147 | const factory = ( |
| 153 | 148 | typeof mod === "function" ? mod : (mod.default ?? null) |
| 154 | 149 | ) as LooseConfiguredPlugin | null; |
| @@ -22,9 +22,7 @@ import { addSourceToConfig, removeSourceFromConfig } from "./write"; · 21 unmodified lines | ||
| 22 | 22 | // refs) into the config file's `secret-public-ref:<id>` string form. |
| 23 | 23 | type PluginHeaderValue = string | { secretId: string; prefix?: string }; |
| 24 | 24 | |
| 25 | −export const headerToConfigValue = ( | |
| 26 | − value: PluginHeaderValue, | |
| 27 | −): ConfigHeaderValue => { | |
| 25 | +export const headerToConfigValue = (value: PluginHeaderValue): ConfigHeaderValue => { | |
| 28 | 26 | if (typeof value === "string") return value; |
| 29 | 27 | const ref = `${SECRET_REF_PREFIX}${value.secretId}`; |
| 30 | 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 | 53 | console.warn(`[config-sink] ${op} failed`, err); |
| 56 | 54 | }; |
| 57 | 55 | |
| 58 | −export const makeFileConfigSink = ( | |
| 59 | − options: ConfigFileSinkOptions, | |
| 60 | −): ConfigFileSink => { | |
| 56 | +export const makeFileConfigSink = (options: ConfigFileSinkOptions): ConfigFileSink => { | |
| 61 | 57 | const { path, fsLayer, onError = defaultOnError } = options; |
| 62 | 58 | |
| 63 | 59 | return { |
| @@ -56,54 +56,44 @@ const slackPlugin = definePlugin(() => ({ · 55 unmodified lines | ||
| 56 | 56 | })); |
| 57 | 57 | |
| 58 | 58 | describe("buildExecuteDescription", () => { |
| 59 | − it.effect( | |
| 60 | − "renders real source ids as namespaces (sorted) through the real executor flow", | |
| 61 | − () => | |
| 62 | − Effect.gen(function* () { | |
| 63 | − // Intentionally register in non-alphabetical order — the formatter | |
| 64 | − // is expected to sort by source id. | |
| 65 | − const executor = yield* createExecutor( | |
| 66 | − makeTestConfig({ plugins: [slackPlugin(), githubPlugin()] as const }), | |
| 67 | − ); | |
| 59 | + it.effect("renders real source ids as namespaces (sorted) through the real executor flow", () => | |
| 60 | + Effect.gen(function* () { | |
| 61 | + // Intentionally register in non-alphabetical order — the formatter | |
| 62 | + // is expected to sort by source id. | |
| 63 | + const executor = yield* createExecutor( | |
| 64 | + makeTestConfig({ plugins: [slackPlugin(), githubPlugin()] as const }), | |
| 65 | + ); | |
| 68 | 66 | |
| 69 | − const description = yield* buildExecuteDescription(executor); | |
| 67 | + const description = yield* buildExecuteDescription(executor); | |
| 70 | 68 | |
| 71 | − // Stable anchor from the workflow preamble. | |
| 72 | − expect(description).toContain( | |
| 73 | − "Execute TypeScript in a sandboxed runtime", | |
| 74 | − ); | |
| 75 | − // The namespaces section header. | |
| 76 | − expect(description).toContain("## Available namespaces"); | |
| 77 | − // Each source renders with its ACTUAL id (not pluginId / name / UUID). | |
| 78 | − expect(description).toContain("`github` — GitHub"); | |
| 79 | − expect(description).toContain("`slack` — Slack Workspace"); | |
| 80 | − // And the plugin ids must NOT leak into the namespace list. | |
| 81 | − expect(description).not.toContain("`github-plugin`"); | |
| 82 | − expect(description).not.toContain("`slack-plugin`"); | |
| 69 | + // Stable anchor from the workflow preamble. | |
| 70 | + expect(description).toContain("Execute TypeScript in a sandboxed runtime"); | |
| 71 | + // The namespaces section header. | |
| 72 | + expect(description).toContain("## Available namespaces"); | |
| 73 | + // Each source renders with its ACTUAL id (not pluginId / name / UUID). | |
| 74 | + expect(description).toContain("`github` — GitHub"); | |
| 75 | + expect(description).toContain("`slack` — Slack Workspace"); | |
| 76 | + // And the plugin ids must NOT leak into the namespace list. | |
| 77 | + expect(description).not.toContain("`github-plugin`"); | |
| 78 | + expect(description).not.toContain("`slack-plugin`"); | |
| 83 | 79 | |
| 84 | − // Sort order: `github` before `slack`. | |
| 85 | − const githubIdx = description.indexOf("`github`"); | |
| 86 | − const slackIdx = description.indexOf("`slack`"); | |
| 87 | − expect(githubIdx).toBeGreaterThan(-1); | |
| 88 | − expect(slackIdx).toBeGreaterThan(-1); | |
| 89 | − expect(githubIdx).toBeLessThan(slackIdx); | |
| 90 | − }), | |
| 80 | + // Sort order: `github` before `slack`. | |
| 81 | + const githubIdx = description.indexOf("`github`"); | |
| 82 | + const slackIdx = description.indexOf("`slack`"); | |
| 83 | + expect(githubIdx).toBeGreaterThan(-1); | |
| 84 | + expect(slackIdx).toBeGreaterThan(-1); | |
| 85 | + expect(githubIdx).toBeLessThan(slackIdx); | |
| 86 | + }), | |
| 91 | 87 | ); |
| 92 | 88 | |
| 93 | − it.effect( | |
| 94 | − "omits the Available namespaces section when no plugins register sources", | |
| 95 | − () => | |
| 96 | − Effect.gen(function* () { | |
| 97 | − const executor = yield* createExecutor( | |
| 98 | − makeTestConfig({ plugins: [] as const }), | |
| 99 | − ); | |
| 89 | + it.effect("omits the Available namespaces section when no plugins register sources", () => | |
| 90 | + Effect.gen(function* () { | |
| 91 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [] as const })); | |
| 100 | 92 | |
| 101 | − const description = yield* buildExecuteDescription(executor); | |
| 93 | + const description = yield* buildExecuteDescription(executor); | |
| 102 | 94 | |
| 103 | − expect(description).toContain( | |
| 104 | − "Execute TypeScript in a sandboxed runtime", | |
| 105 | − ); | |
| 106 | − expect(description).not.toContain("## Available namespaces"); | |
| 107 | − }), | |
| 95 | + expect(description).toContain("Execute TypeScript in a sandboxed runtime"); | |
| 96 | + expect(description).not.toContain("## Available namespaces"); | |
| 97 | + }), | |
| 108 | 98 | ); |
| 109 | 99 | }); |
| @@ -10,13 +10,11 @@ import type { Executor, Source } from "@executor-js/sdk/core"; · 9 unmodified lines | ||
| 10 | 10 | */ |
| 11 | 11 | export const buildExecuteDescription = (executor: Executor): Effect.Effect<string> => |
| 12 | 12 | Effect.gen(function* () { |
| 13 | − const sources: readonly Source[] = yield* executor.sources | |
| 14 | − .list() | |
| 15 | − .pipe( | |
| 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 | |
| 17 | − Effect.orDie, | |
| 18 | − Effect.withSpan("executor.sources.list"), | |
| 19 | − ); | |
| 13 | + const sources: readonly Source[] = yield* executor.sources.list().pipe( | |
| 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 | + Effect.orDie, | |
| 16 | + Effect.withSpan("executor.sources.list"), | |
| 17 | + ); | |
| 20 | 18 | |
| 21 | 19 | const description = yield* Effect.sync(() => formatDescription(sources)).pipe( |
| 22 | 20 | Effect.withSpan("schema.compile.description", { |
| @@ -20,13 +20,11 @@ class FakeRuntimeError extends Data.TaggedError("FakeRuntimeError")<{ · 19 unmodified lines | ||
| 20 | 20 | }> {} |
| 21 | 21 | |
| 22 | 22 | const failingExecutor: CodeExecutor<FakeRuntimeError> = { |
| 23 | − execute: () => | |
| 24 | − Effect.fail(new FakeRuntimeError({ message: "Unexpected token ':'" })), | |
| 23 | + execute: () => Effect.fail(new FakeRuntimeError({ message: "Unexpected token ':'" })), | |
| 25 | 24 | }; |
| 26 | 25 | |
| 27 | 26 | const succeedingExecutor: CodeExecutor<FakeRuntimeError> = { |
| 28 | − execute: () => | |
| 29 | − Effect.succeed({ result: "ok", logs: [] } satisfies ExecuteResult), | |
| 27 | + execute: () => Effect.succeed({ result: "ok", logs: [] } satisfies ExecuteResult), | |
| 30 | 28 | }; |
| 31 | 29 | |
| 32 | 30 | const emptyPlugin = definePlugin(() => ({ |
| @@ -30,13 +30,9 @@ import { · 29 unmodified lines | ||
| 30 | 30 | type ResumeResponse, |
| 31 | 31 | } from "./engine"; |
| 32 | 32 | |
| 33 | −export type ElicitationHandler = ( | |
| 34 | − ctx: ElicitationContext, | |
| 35 | −) => Promise<ElicitationResponse>; | |
| 33 | +export type ElicitationHandler = (ctx: ElicitationContext) => Promise<ElicitationResponse>; | |
| 36 | 34 | |
| 37 | −export type ExecutionEngineConfig< | |
| 38 | − E extends Cause.YieldableError = CodeExecutionError, | |
| 39 | −> = { | |
| 35 | +export type ExecutionEngineConfig<E extends Cause.YieldableError = CodeExecutionError> = { | |
| 40 | 36 | readonly executor: PromiseExecutor; |
| 41 | 37 | readonly codeExecutor: CodeExecutor<E>; |
| 42 | 38 | }; |
| @@ -64,9 +60,7 @@ const fromPromise = <A>(try_: () => Promise<A>): Effect.Effect<A> => · 21 unmodified lines | ||
| 64 | 60 | type EffectInvokeOptions = Parameters<EffectExecutor["tools"]["invoke"]>[2]; |
| 65 | 61 | type PromiseInvokeOptions = Parameters<PromiseExecutor["tools"]["invoke"]>[2]; |
| 66 | 62 | |
| 67 | −const toPromiseInvokeOptions = ( | |
| 68 | − options: EffectInvokeOptions, | |
| 69 | −): PromiseInvokeOptions => { | |
| 63 | +const toPromiseInvokeOptions = (options: EffectInvokeOptions): PromiseInvokeOptions => { | |
| 70 | 64 | const onElicitation = options?.onElicitation; |
| 71 | 65 | if (!onElicitation) return undefined; |
| 72 | 66 | if (onElicitation === "accept-all") return { onElicitation }; |
| @@ -154,9 +148,7 @@ export const toPromiseExecutionEngine = <E extends Cause.YieldableError>( · 81 unmodified lines | ||
| 154 | 148 | getDescription: () => Effect.runPromise(engine.getDescription), |
| 155 | 149 | }); |
| 156 | 150 | |
| 157 | −export const createExecutionEngine = < | |
| 158 | − E extends Cause.YieldableError = CodeExecutionError, | |
| 159 | −>( | |
| 151 | +export const createExecutionEngine = <E extends Cause.YieldableError = CodeExecutionError>( | |
| 160 | 152 | config: ExecutionEngineConfig<E>, |
| 161 | 153 | ): ExecutionEngine => |
| 162 | 154 | toPromiseExecutionEngine( |
| @@ -133,9 +133,7 @@ const errorPlugin = definePlugin(() => ({ · 132 unmodified lines | ||
| 133 | 133 | })); |
| 134 | 134 | |
| 135 | 135 | const makeSearchExecutor = () => |
| 136 | − createExecutor( | |
| 137 | − makeTestConfig({ plugins: [githubPlugin(), crmPlugin()] as const }), | |
| 138 | − ); | |
| 136 | + createExecutor(makeTestConfig({ plugins: [githubPlugin(), crmPlugin()] as const })); | |
| 139 | 137 | |
| 140 | 138 | describe("tool discovery", () => { |
| 141 | 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 | 154 | const crmMatches = yield* searchTools(executor, "crm create contact", 5); |
| 157 | 155 | expect(crmMatches.items[0]?.path).toBe("crm.createContact"); |
| 158 | − expect(crmMatches.items[0]?.score ?? 0).toBeGreaterThan( | |
| 159 | − crmMatches.items[1]?.score ?? 0, | |
| 160 | − ); | |
| 156 | + expect(crmMatches.items[0]?.score ?? 0).toBeGreaterThan(crmMatches.items[1]?.score ?? 0); | |
| 161 | 157 | }), |
| 162 | 158 | ); |
| 163 | 159 | |
| @@ -218,9 +214,7 @@ describe("tool discovery", () => { · 54 unmodified lines | ||
| 218 | 214 | const githubOnly = yield* searchTools(executor, "list", 5, { |
| 219 | 215 | namespace: "github", |
| 220 | 216 | }); |
| 221 | − expect(githubOnly.items.map((match) => match.path)).toEqual([ | |
| 222 | − "github.listRepositoryIssues", | |
| 223 | − ]); | |
| 217 | + expect(githubOnly.items.map((match) => match.path)).toEqual(["github.listRepositoryIssues"]); | |
| 224 | 218 | |
| 225 | 219 | const crmOnly = yield* searchTools(executor, "list", 5, { |
| 226 | 220 | namespace: "crm", |
| @@ -426,16 +420,12 @@ describe("tool discovery", () => { · 199 unmodified lines | ||
| 426 | 420 | |
| 427 | 421 | it.effect("converts message-bearing tool error results into execution errors", () => |
| 428 | 422 | Effect.gen(function* () { |
| 429 | − const executor = yield* createExecutor( | |
| 430 | − makeTestConfig({ plugins: [errorPlugin()] as const }), | |
| 431 | − ); | |
| 423 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [errorPlugin()] as const })); | |
| 432 | 424 | const invoker = makeExecutorToolInvoker(executor, { |
| 433 | 425 | invokeOptions: { onElicitation: acceptAll }, |
| 434 | 426 | }); |
| 435 | 427 | |
| 436 | − const error = yield* Effect.flip( | |
| 437 | − invoker.invoke({ path: "records.queryRows", args: {} }), | |
| 438 | − ); | |
| 428 | + const error = yield* Effect.flip(invoker.invoke({ path: "records.queryRows", args: {} })); | |
| 439 | 429 | |
| 440 | 430 | expect(error).toEqual( |
| 441 | 431 | expect.objectContaining({ |
| @@ -552,10 +542,11 @@ describe("pause/resume with multiple elicitations", () => { · 110 unmodified lines | ||
| 552 | 542 | |
| 553 | 543 | // `execution.fiber` is on `InternalPausedExecution`; the exported |
| 554 | 544 | // `PausedExecution` type doesn't carry it. Cast to read. |
| 555 | − const pausedWithFiber = (value: unknown): { | |
| 545 | + const pausedWithFiber = ( | |
| 546 | + value: unknown, | |
| 547 | + ): { | |
| 556 | 548 | readonly fiber: Fiber.Fiber<unknown, unknown>; |
| 557 | − } => | |
| 558 | − value as { readonly fiber: Fiber.Fiber<unknown, unknown> }; | |
| 549 | + } => value as { readonly fiber: Fiber.Fiber<unknown, unknown> }; | |
| 559 | 550 | const sandboxFiber = pausedWithFiber(paused1.execution).fiber; |
| 560 | 551 | const exitProbe = await Effect.runPromise( |
| 561 | 552 | Effect.race( |
| @@ -60,9 +60,7 @@ type ToolResultEnvelope = { · 59 unmodified lines | ||
| 60 | 60 | }; |
| 61 | 61 | |
| 62 | 62 | const isToolResultEnvelope = (value: unknown): value is ToolResultEnvelope => |
| 63 | − value !== null && | |
| 64 | − typeof value === "object" && | |
| 65 | − ("error" in value || "data" in value); | |
| 63 | + value !== null && typeof value === "object" && ("error" in value || "data" in value); | |
| 66 | 64 | |
| 67 | 65 | const hasToolResultError = ( |
| 68 | 66 | value: ToolResultEnvelope, |
| @@ -129,7 +127,11 @@ export const makeExecutorToolInvoker = ( · 60 unmodified lines | ||
| 129 | 127 | |
| 130 | 128 | const isElicitationDeclinedError = ( |
| 131 | 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 | 135 | Predicate.isTagged(value, "ElicitationDeclinedError") && |
| 134 | 136 | value !== null && |
| 135 | 137 | typeof value === "object" && |
| @@ -83,35 +83,27 @@ interface MemorySecretsConfig { · 82 unmodified lines | ||
| 83 | 83 | |
| 84 | 84 | // definePlugin takes a factory and returns a configured-plugin function |
| 85 | 85 | // that the consumer calls with options. |
| 86 | −export const memorySecretsPlugin = definePlugin( | |
| 87 | − (options?: MemorySecretsConfig) => { | |
| 88 | − const map = new Map<string, string>( | |
| 89 | − Object.entries(options?.initial ?? {}), | |
| 90 | − ); | |
| 91 | − const provider: SecretProvider = { | |
| 92 | − key: "memory", | |
| 93 | − writable: true, | |
| 94 | − get: (id: string) => Effect.sync(() => map.get(id) ?? null), | |
| 95 | − has: (id: string) => Effect.sync(() => map.has(id)), | |
| 96 | − set: (id: string, value: string) => | |
| 97 | − Effect.sync(() => void map.set(id, value)), | |
| 98 | − delete: (id: string) => Effect.sync(() => map.delete(id)), | |
| 99 | − list: () => | |
| 100 | − Effect.sync(() => | |
| 101 | − Array.from(map.keys()).map((k) => ({ id: k, name: k })), | |
| 102 | − ), | |
| 103 | − }; | |
| 104 | − | |
| 105 | − return { | |
| 106 | − id: "memorySecrets" as const, | |
| 107 | − storage: () => ({}), | |
| 108 | − extension: () => ({ | |
| 109 | − label: "in-memory secrets", | |
| 110 | − }), | |
| 111 | − secretProviders: () => [provider], | |
| 112 | − }; | |
| 113 | − }, | |
| 114 | −); | |
| 86 | +export const memorySecretsPlugin = definePlugin((options?: MemorySecretsConfig) => { | |
| 87 | + const map = new Map<string, string>(Object.entries(options?.initial ?? {})); | |
| 88 | + const provider: SecretProvider = { | |
| 89 | + key: "memory", | |
| 90 | + writable: true, | |
| 91 | + get: (id: string) => Effect.sync(() => map.get(id) ?? null), | |
| 92 | + has: (id: string) => Effect.sync(() => map.has(id)), | |
| 93 | + set: (id: string, value: string) => Effect.sync(() => void map.set(id, value)), | |
| 94 | + delete: (id: string) => Effect.sync(() => map.delete(id)), | |
| 95 | + list: () => Effect.sync(() => Array.from(map.keys()).map((k) => ({ id: k, name: k }))), | |
| 96 | + }; | |
| 97 | + | |
| 98 | + return { | |
| 99 | + id: "memorySecrets" as const, | |
| 100 | + storage: () => ({}), | |
| 101 | + extension: () => ({ | |
| 102 | + label: "in-memory secrets", | |
| 103 | + }), | |
| 104 | + secretProviders: () => [provider], | |
| 105 | + }; | |
| 106 | +}); | |
| 115 | 107 | |
| 116 | 108 | // End users compose the executor exactly the same way as above — |
| 117 | 109 | // the plugin's extension is reachable as `executor.memorySecrets`. |
| @@ -53,8 +53,18 @@ · 52 unmodified lines | ||
| 53 | 53 | "dependencies": { |
| 54 | 54 | "@executor-js/storage-core": "workspace:*", |
| 55 | 55 | "effect": "catalog:", |
| 56 | − "oauth4webapi": "^3.8.5", | |
| 57 | − "fractional-indexing": "^3.2.0" | |
| 56 | + "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 | 69 | "peerDependencies": { |
| 60 | 70 | "@effect/atom-react": "catalog:", |
| @@ -67,15 +77,5 @@ · 6 unmodified lines | ||
| 67 | 77 | "react": { |
| 68 | 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 | 75 | Effect.gen(function* () { |
| 76 | 76 | const store = makeInMemoryBlobStore(); |
| 77 | 77 | const plugin = pluginBlobStore(store, ["inner", "outer"], "my-plugin"); |
| 78 | − const err = yield* plugin | |
| 79 | − .put("k", "v", { scope: "not-in-stack" }) | |
| 80 | − .pipe(Effect.flip); | |
| 78 | + const err = yield* plugin.put("k", "v", { scope: "not-in-stack" }).pipe(Effect.flip); | |
| 81 | 79 | expect(err).toBeInstanceOf(StorageError); |
| 82 | 80 | expect(err).toMatchObject({ |
| 83 | 81 | message: expect.stringContaining("not in the"), |
| @@ -91,9 +89,7 @@ describe("pluginBlobStore", () => { · 7 unmodified lines | ||
| 91 | 89 | Effect.gen(function* () { |
| 92 | 90 | const store = makeInMemoryBlobStore(); |
| 93 | 91 | const plugin = pluginBlobStore(store, ["inner"], "my-plugin"); |
| 94 | − const err = yield* plugin | |
| 95 | − .delete("k", { scope: "not-in-stack" }) | |
| 96 | − .pipe(Effect.flip); | |
| 92 | + const err = yield* plugin.delete("k", { scope: "not-in-stack" }).pipe(Effect.flip); | |
| 97 | 93 | expect(err).toBeInstanceOf(StorageError); |
| 98 | 94 | }), |
| 99 | 95 | ); |
| @@ -21,10 +21,7 @@ import { Effect } from "effect"; · 20 unmodified lines | ||
| 21 | 21 | import { StorageError } from "@executor-js/storage-core"; |
| 22 | 22 | |
| 23 | 23 | export interface BlobStore { |
| 24 | − readonly get: ( | |
| 25 | − namespace: string, | |
| 26 | − key: string, | |
| 27 | − ) => Effect.Effect<string | null, StorageError>; | |
| 24 | + readonly get: (namespace: string, key: string) => Effect.Effect<string | null, StorageError>; | |
| 28 | 25 | /** Multi-namespace lookup for a single key. Backends issue one query |
| 29 | 26 | * (`WHERE namespace IN (...) AND key = ?`) and return the hits keyed |
| 30 | 27 | * by namespace — the caller applies its own precedence. Lets |
| @@ -39,14 +36,8 @@ export interface BlobStore { · 8 unmodified lines | ||
| 39 | 36 | key: string, |
| 40 | 37 | value: string, |
| 41 | 38 | ) => Effect.Effect<void, StorageError>; |
| 42 | − readonly delete: ( | |
| 43 | − namespace: string, | |
| 44 | − key: string, | |
| 45 | − ) => Effect.Effect<void, StorageError>; | |
| 46 | − readonly has: ( | |
| 47 | − namespace: string, | |
| 48 | − key: string, | |
| 49 | − ) => Effect.Effect<boolean, StorageError>; | |
| 39 | + readonly delete: (namespace: string, key: string) => Effect.Effect<void, StorageError>; | |
| 40 | + readonly has: (namespace: string, key: string) => Effect.Effect<boolean, StorageError>; | |
| 50 | 41 | } |
| 51 | 42 | |
| 52 | 43 | export interface PluginBlobStore { |
| @@ -69,10 +60,7 @@ export interface PluginBlobStore { · 16 unmodified lines | ||
| 69 | 60 | readonly has: (key: string) => Effect.Effect<boolean, StorageError>; |
| 70 | 61 | } |
| 71 | 62 | |
| 72 | −const assertScope = ( | |
| 73 | − scope: string, | |
| 74 | − scopes: readonly string[], | |
| 75 | −): Effect.Effect<void, StorageError> => | |
| 63 | +const assertScope = (scope: string, scopes: readonly string[]): Effect.Effect<void, StorageError> => | |
| 76 | 64 | scopes.includes(scope) |
| 77 | 65 | ? Effect.void |
| 78 | 66 | : Effect.fail( |
| @@ -35,12 +35,7 @@ export * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; · 34 unmodified lines | ||
| 35 | 35 | export * as Atom from "effect/unstable/reactivity/Atom"; |
| 36 | 36 | export * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi"; |
| 37 | 37 | |
| 38 | −export { | |
| 39 | − useAtomValue, | |
| 40 | − useAtomSet, | |
| 41 | − useAtomMount, | |
| 42 | − useAtomRefresh, | |
| 43 | −} from "@effect/atom-react"; | |
| 38 | +export { useAtomValue, useAtomSet, useAtomMount, useAtomRefresh } from "@effect/atom-react"; | |
| 44 | 39 | |
| 45 | 40 | // --------------------------------------------------------------------------- |
| 46 | 41 | // defineClientPlugin — declarative spec for the frontend half of a plugin. |
| @@ -219,14 +214,11 @@ export const createPluginAtomClient = < · 172 unmodified lines | ||
| 219 | 214 | const { baseUrl = "/api" } = options; |
| 220 | 215 | const pluginId = group.identifier; |
| 221 | 216 | const bundle = HttpApi.make(`plugin-${pluginId}`).add(group); |
| 222 | − return AtomHttpApi.Service<`Plugin_${G["identifier"]}Client`>()( | |
| 223 | − `Plugin_${pluginId}Client`, | |
| 224 | − { | |
| 225 | − api: bundle, | |
| 226 | − httpClient: FetchHttpClient.layer, | |
| 227 | − baseUrl, | |
| 228 | − }, | |
| 229 | − ); | |
| 217 | + return AtomHttpApi.Service<`Plugin_${G["identifier"]}Client`>()(`Plugin_${pluginId}Client`, { | |
| 218 | + api: bundle, | |
| 219 | + httpClient: FetchHttpClient.layer, | |
| 220 | + baseUrl, | |
| 221 | + }); | |
| 230 | 222 | }; |
| 231 | 223 | |
| 232 | 224 | // --------------------------------------------------------------------------- |
| @@ -250,9 +242,7 @@ interface ExecutorPluginsContextValue { · 17 unmodified lines | ||
| 250 | 242 | readonly secretProviderPlugins: readonly SecretProviderPlugin[]; |
| 251 | 243 | } |
| 252 | 244 | |
| 253 | −const ExecutorPluginsContext = createContext< | |
| 254 | − ExecutorPluginsContextValue | null | |
| 255 | −>(null); | |
| 245 | +const ExecutorPluginsContext = createContext<ExecutorPluginsContextValue | null>(null); | |
| 256 | 246 | ExecutorPluginsContext.displayName = "ExecutorPluginsContext"; |
| 257 | 247 | |
| 258 | 248 | export interface ExecutorPluginsProviderProps { |
| @@ -286,9 +276,7 @@ const usePluginsCtx = (hookName: string): ExecutorPluginsContextValue => { · 27 unmodified lines | ||
| 286 | 276 | const ctx = useContext(ExecutorPluginsContext); |
| 287 | 277 | if (!ctx) { |
| 288 | 278 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: React hook invariant |
| 289 | − throw new Error( | |
| 290 | − `${hookName} must be called inside an <ExecutorPluginsProvider>.`, | |
| 291 | − ); | |
| 279 | + throw new Error(`${hookName} must be called inside an <ExecutorPluginsProvider>.`); | |
| 292 | 280 | } |
| 293 | 281 | return ctx; |
| 294 | 282 | }; |
| @@ -162,9 +162,7 @@ describe("connections", () => { · 161 unmodified lines | ||
| 162 | 162 | }), |
| 163 | 163 | ) |
| 164 | 164 | .pipe(Effect.flip); |
| 165 | − expect( | |
| 166 | − Predicate.isTagged(err, "ConnectionProviderNotRegisteredError"), | |
| 167 | − ).toBe(true); | |
| 165 | + expect(Predicate.isTagged(err, "ConnectionProviderNotRegisteredError")).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( | |
| 204 | − "secrets.list hides connection-owned secrets but surfaces bare ones", | |
| 205 | − () => | |
| 206 | − Effect.gen(function* () { | |
| 207 | − const { provider } = makeConnectionProvider({ key: "spotify" }); | |
| 208 | − const executor = yield* createExecutor( | |
| 209 | − makeTestConfig({ | |
| 210 | − plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, | |
| 211 | − }), | |
| 212 | − ); | |
| 201 | + it.effect("secrets.list hides connection-owned secrets but surfaces bare ones", () => | |
| 202 | + Effect.gen(function* () { | |
| 203 | + const { provider } = makeConnectionProvider({ key: "spotify" }); | |
| 204 | + const executor = yield* createExecutor( | |
| 205 | + makeTestConfig({ | |
| 206 | + plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, | |
| 207 | + }), | |
| 208 | + ); | |
| 213 | 209 | |
| 214 | − yield* executor.secrets.set( | |
| 215 | − new SetSecretInput({ | |
| 216 | − id: sid("bare-api"), | |
| 217 | − scope: scpid("test-scope"), | |
| 218 | − name: "bare API key", | |
| 219 | − value: "bare", | |
| 220 | − }), | |
| 221 | − ); | |
| 210 | + yield* executor.secrets.set( | |
| 211 | + new SetSecretInput({ | |
| 212 | + id: sid("bare-api"), | |
| 213 | + scope: scpid("test-scope"), | |
| 214 | + name: "bare API key", | |
| 215 | + value: "bare", | |
| 216 | + }), | |
| 217 | + ); | |
| 222 | 218 | |
| 223 | − yield* executor.connections.create( | |
| 224 | − new CreateConnectionInput({ | |
| 225 | − id: cid("conn-1"), | |
| 226 | − scope: scpid("test-scope"), | |
| 227 | − provider: "spotify", | |
| 228 | − identityLabel: null, | |
| 229 | − accessToken: new TokenMaterial({ | |
| 230 | − secretId: sid("conn-1.access"), | |
| 231 | − name: "access", | |
| 232 | − 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, | |
| 219 | + yield* executor.connections.create( | |
| 220 | + new CreateConnectionInput({ | |
| 221 | + id: cid("conn-1"), | |
| 222 | + scope: scpid("test-scope"), | |
| 223 | + provider: "spotify", | |
| 224 | + identityLabel: null, | |
| 225 | + accessToken: new TokenMaterial({ | |
| 226 | + secretId: sid("conn-1.access"), | |
| 227 | + name: "access", | |
| 228 | + value: "a", | |
| 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(); | |
| 246 | − const ids = list.map((s) => String(s.id)); | |
| 247 | − expect(ids).toContain("bare-api"); | |
| 248 | − expect(ids).not.toContain("conn-1.access"); | |
| 249 | − expect(ids).not.toContain("conn-1.refresh"); | |
| 250 | − }), | |
| 241 | + const list = yield* executor.secrets.list(); | |
| 242 | + const ids = list.map((s) => String(s.id)); | |
| 243 | + expect(ids).toContain("bare-api"); | |
| 244 | + expect(ids).not.toContain("conn-1.access"); | |
| 245 | + expect(ids).not.toContain("conn-1.refresh"); | |
| 246 | + }), | |
| 251 | 247 | ); |
| 252 | 248 | |
| 253 | 249 | it.effect( |
| @@ -279,72 +275,55 @@ describe("connections", () => { · 25 unmodified lines | ||
| 279 | 275 | }), |
| 280 | 276 | ); |
| 281 | 277 | |
| 282 | − const err = yield* executor.secrets | |
| 283 | − .remove("conn-1.access") | |
| 284 | − .pipe(Effect.flip); | |
| 285 | − expect(Predicate.isTagged(err, "SecretOwnedByConnectionError")).toBe( | |
| 286 | − true, | |
| 287 | − ); | |
| 278 | + const err = yield* executor.secrets.remove("conn-1.access").pipe(Effect.flip); | |
| 279 | + expect(Predicate.isTagged(err, "SecretOwnedByConnectionError")).toBe(true); | |
| 288 | 280 | }), |
| 289 | 281 | ); |
| 290 | 282 | |
| 291 | − it.effect( | |
| 292 | − "connections.remove cascades through providers and deletes the core row", | |
| 293 | − () => | |
| 294 | − Effect.gen(function* () { | |
| 295 | − const secretProvider = makeMemoryProvider(); | |
| 296 | − const { provider } = makeConnectionProvider({ key: "spotify" }); | |
| 297 | − const executor = yield* createExecutor( | |
| 298 | − makeTestConfig({ | |
| 299 | − plugins: [ | |
| 300 | − memorySecretsPlugin(secretProvider), | |
| 301 | − connPlugin(provider), | |
| 302 | − ] as const, | |
| 303 | − }), | |
| 304 | − ); | |
| 283 | + it.effect("connections.remove cascades through providers and deletes the core row", () => | |
| 284 | + Effect.gen(function* () { | |
| 285 | + const secretProvider = makeMemoryProvider(); | |
| 286 | + const { provider } = makeConnectionProvider({ key: "spotify" }); | |
| 287 | + const executor = yield* createExecutor( | |
| 288 | + makeTestConfig({ | |
| 289 | + plugins: [memorySecretsPlugin(secretProvider), connPlugin(provider)] as const, | |
| 290 | + }), | |
| 291 | + ); | |
| 305 | 292 | |
| 306 | − yield* executor.connections.create( | |
| 307 | − new CreateConnectionInput({ | |
| 308 | − id: cid("conn-1"), | |
| 309 | − scope: scpid("test-scope"), | |
| 310 | − provider: "spotify", | |
| 311 | − identityLabel: null, | |
| 312 | − accessToken: new TokenMaterial({ | |
| 313 | − secretId: sid("conn-1.access"), | |
| 314 | − name: "access", | |
| 315 | − 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, | |
| 293 | + yield* executor.connections.create( | |
| 294 | + new CreateConnectionInput({ | |
| 295 | + id: cid("conn-1"), | |
| 296 | + scope: scpid("test-scope"), | |
| 297 | + provider: "spotify", | |
| 298 | + identityLabel: null, | |
| 299 | + accessToken: new TokenMaterial({ | |
| 300 | + secretId: sid("conn-1.access"), | |
| 301 | + name: "access", | |
| 302 | + value: "access-v1", | |
| 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. | |
| 329 | − expect( | |
| 330 | − yield* secretProvider.get!("conn-1.access", "test-scope"), | |
| 331 | − ).toBe("access-v1"); | |
| 332 | − expect( | |
| 333 | − yield* secretProvider.get!("conn-1.refresh", "test-scope"), | |
| 334 | − ).toBe("refresh-v1"); | |
| 335 | − | |
| 336 | − yield* executor.connections.remove("conn-1"); | |
| 337 | − | |
| 338 | − // Connection row gone. | |
| 339 | − expect(yield* executor.connections.get("conn-1")).toBeNull(); | |
| 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 | − }), | |
| 315 | + // Pre-check: backing provider holds the tokens. | |
| 316 | + expect(yield* secretProvider.get!("conn-1.access", "test-scope")).toBe("access-v1"); | |
| 317 | + expect(yield* secretProvider.get!("conn-1.refresh", "test-scope")).toBe("refresh-v1"); | |
| 318 | + | |
| 319 | + yield* executor.connections.remove("conn-1"); | |
| 320 | + | |
| 321 | + // Connection row gone. | |
| 322 | + expect(yield* executor.connections.get("conn-1")).toBeNull(); | |
| 323 | + // Backing secret values gone from the provider. | |
| 324 | + expect(yield* secretProvider.get!("conn-1.access", "test-scope")).toBeNull(); | |
| 325 | + expect(yield* secretProvider.get!("conn-1.refresh", "test-scope")).toBeNull(); | |
| 326 | + }), | |
| 348 | 327 | ); |
| 349 | 328 | |
| 350 | 329 | it.effect("accessToken returns the stored value when not near expiry", () => |
| @@ -407,10 +386,7 @@ describe("connections", () => { · 56 unmodified lines | ||
| 407 | 386 | }); |
| 408 | 387 | const executor = yield* createExecutor( |
| 409 | 388 | makeTestConfig({ |
| 410 | − plugins: [ | |
| 411 | − memorySecretsPlugin(secretProvider), | |
| 412 | − connPlugin(provider), | |
| 413 | − ] as const, | |
| 389 | + plugins: [memorySecretsPlugin(secretProvider), connPlugin(provider)] as const, | |
| 414 | 390 | }), |
| 415 | 391 | ); |
| 416 | 392 | |
| @@ -445,12 +421,10 @@ describe("connections", () => { · 28 unmodified lines | ||
| 445 | 421 | expect(calls[0]!.providerState).toEqual({ rotation: "fresh" }); |
| 446 | 422 | |
| 447 | 423 | // Backing secrets got rewritten at the same ids. |
| 448 | − expect( | |
| 449 | − yield* secretProvider.get!("conn-1.access", "test-scope"), | |
| 450 | − ).toBe("rotated-refresh-v1"); | |
| 451 | − expect( | |
| 452 | − yield* secretProvider.get!("conn-1.refresh", "test-scope"), | |
| 453 | − ).toBe("refresh-v2"); | |
| 424 | + expect(yield* secretProvider.get!("conn-1.access", "test-scope")).toBe( | |
| 425 | + "rotated-refresh-v1", | |
| 426 | + ); | |
| 427 | + expect(yield* secretProvider.get!("conn-1.refresh", "test-scope")).toBe("refresh-v2"); | |
| 454 | 428 | |
| 455 | 429 | const got = yield* executor.connections.get("conn-1"); |
| 456 | 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( | |
| 462 | − "accessToken dedupes concurrent refreshes into a single provider call", | |
| 463 | − () => | |
| 464 | − Effect.gen(function* () { | |
| 465 | − // A gated refresh provider. Every concurrent caller that lands | |
| 466 | − // inside the skew window must converge on the single pending | |
| 467 | − // refresh instead of hitting the token endpoint N times. The | |
| 468 | − // `entered` Deferred signals that the leader fiber is parked | |
| 469 | − // inside `refresh`; we only release the `gate` once every | |
| 470 | − // caller has had a chance to register. | |
| 471 | − const gate = yield* Deferred.make<void>(); | |
| 472 | − const entered = yield* Deferred.make<void>(); | |
| 473 | − const calls: ConnectionRefreshInput[] = []; | |
| 474 | − let responseCounter = 0; | |
| 475 | − const provider: ConnectionProvider = { | |
| 476 | − key: "spotify", | |
| 477 | − refresh: ( | |
| 478 | − input, | |
| 479 | − ): Effect.Effect<ConnectionRefreshResult, ConnectionRefreshError> => | |
| 480 | − Effect.gen(function* () { | |
| 481 | − calls.push(input); | |
| 482 | − const n = ++responseCounter; | |
| 483 | − yield* Deferred.succeed(entered, undefined as void); | |
| 484 | − // Block the leader inside `refresh` until the test | |
| 485 | − // releases the gate. Any other fiber that concurrently | |
| 486 | − // calls `accessToken` must observe the in-flight | |
| 487 | − // Deferred instead of entering this handler. | |
| 488 | − yield* Deferred.await(gate); | |
| 489 | − return { | |
| 490 | − accessToken: `rotated-${n}`, | |
| 491 | − refreshToken: `refresh-${n}`, | |
| 492 | − expiresAt: Date.now() + 3_600_000, | |
| 493 | − oauthScope: "user-read", | |
| 494 | − providerState: null, | |
| 495 | − }; | |
| 496 | − }), | |
| 497 | − }; | |
| 435 | + it.effect("accessToken dedupes concurrent refreshes into a single provider call", () => | |
| 436 | + Effect.gen(function* () { | |
| 437 | + // A gated refresh provider. Every concurrent caller that lands | |
| 438 | + // inside the skew window must converge on the single pending | |
| 439 | + // refresh instead of hitting the token endpoint N times. The | |
| 440 | + // `entered` Deferred signals that the leader fiber is parked | |
| 441 | + // inside `refresh`; we only release the `gate` once every | |
| 442 | + // caller has had a chance to register. | |
| 443 | + const gate = yield* Deferred.make<void>(); | |
| 444 | + const entered = yield* Deferred.make<void>(); | |
| 445 | + const calls: ConnectionRefreshInput[] = []; | |
| 446 | + let responseCounter = 0; | |
| 447 | + const provider: ConnectionProvider = { | |
| 448 | + key: "spotify", | |
| 449 | + refresh: (input): Effect.Effect<ConnectionRefreshResult, ConnectionRefreshError> => | |
| 450 | + Effect.gen(function* () { | |
| 451 | + calls.push(input); | |
| 452 | + const n = ++responseCounter; | |
| 453 | + yield* Deferred.succeed(entered, undefined as void); | |
| 454 | + // Block the leader inside `refresh` until the test | |
| 455 | + // releases the gate. Any other fiber that concurrently | |
| 456 | + // calls `accessToken` must observe the in-flight | |
| 457 | + // Deferred instead of entering this handler. | |
| 458 | + yield* Deferred.await(gate); | |
| 459 | + return { | |
| 460 | + accessToken: `rotated-${n}`, | |
| 461 | + refreshToken: `refresh-${n}`, | |
| 462 | + expiresAt: Date.now() + 3_600_000, | |
| 463 | + oauthScope: "user-read", | |
| 464 | + providerState: null, | |
| 465 | + }; | |
| 466 | + }), | |
| 467 | + }; | |
| 498 | 468 | |
| 499 | − const executor = yield* createExecutor( | |
| 500 | − makeTestConfig({ | |
| 501 | − plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, | |
| 502 | − }), | |
| 503 | − ); | |
| 469 | + const executor = yield* createExecutor( | |
| 470 | + makeTestConfig({ | |
| 471 | + plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, | |
| 472 | + }), | |
| 473 | + ); | |
| 504 | 474 | |
| 505 | − yield* executor.connections.create( | |
| 506 | − new CreateConnectionInput({ | |
| 507 | − id: cid("conn-1"), | |
| 508 | − scope: scpid("test-scope"), | |
| 509 | − provider: "spotify", | |
| 510 | − identityLabel: null, | |
| 511 | − accessToken: new TokenMaterial({ | |
| 512 | − secretId: sid("conn-1.access"), | |
| 513 | − name: "access", | |
| 514 | − 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, | |
| 475 | + yield* executor.connections.create( | |
| 476 | + new CreateConnectionInput({ | |
| 477 | + id: cid("conn-1"), | |
| 478 | + scope: scpid("test-scope"), | |
| 479 | + provider: "spotify", | |
| 480 | + identityLabel: null, | |
| 481 | + accessToken: new TokenMaterial({ | |
| 482 | + secretId: sid("conn-1.access"), | |
| 483 | + name: "access", | |
| 484 | + value: "stale", | |
| 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 | |
| 529 | − // `refresh`. Any subsequent caller is guaranteed to see the | |
| 530 | − // in-flight Deferred the leader just registered. | |
| 531 | − const leaderFiber = yield* Effect.forkDetach( | |
| 532 | − executor.connections.accessToken("conn-1"), | |
| 533 | − { startImmediately: true }, | |
| 534 | − ); | |
| 535 | − yield* Deferred.await(entered); | |
| 498 | + // Kick off the leader first and wait for it to park inside | |
| 499 | + // `refresh`. Any subsequent caller is guaranteed to see the | |
| 500 | + // in-flight Deferred the leader just registered. | |
| 501 | + const leaderFiber = yield* Effect.forkDetach(executor.connections.accessToken("conn-1"), { | |
| 502 | + startImmediately: true, | |
| 503 | + }); | |
| 504 | + yield* Deferred.await(entered); | |
| 536 | 505 | |
| 537 | − const followerFibers = yield* Effect.forEach( | |
| 538 | − [1, 2, 3, 4], | |
| 539 | − () => | |
| 540 | − Effect.forkDetach(executor.connections.accessToken("conn-1"), { | |
| 541 | − startImmediately: true, | |
| 542 | − }), | |
| 543 | − { concurrency: "unbounded" }, | |
| 544 | − ); | |
| 545 | − yield* Effect.forEach([1, 2, 3, 4], () => Effect.yieldNow); | |
| 506 | + const followerFibers = yield* Effect.forEach( | |
| 507 | + [1, 2, 3, 4], | |
| 508 | + () => | |
| 509 | + Effect.forkDetach(executor.connections.accessToken("conn-1"), { | |
| 510 | + startImmediately: true, | |
| 511 | + }), | |
| 512 | + { concurrency: "unbounded" }, | |
| 513 | + ); | |
| 514 | + yield* Effect.forEach([1, 2, 3, 4], () => Effect.yieldNow); | |
| 546 | 515 | |
| 547 | − // Every follower is queued on the leader's Deferred. Release | |
| 548 | − // the gate — the leader resolves, waiters wake up with the | |
| 549 | − // same token, no extra `refresh` is invoked. | |
| 550 | − yield* Deferred.succeed(gate, undefined as void); | |
| 516 | + // Every follower is queued on the leader's Deferred. Release | |
| 517 | + // the gate — the leader resolves, waiters wake up with the | |
| 518 | + // same token, no extra `refresh` is invoked. | |
| 519 | + yield* Deferred.succeed(gate, undefined as void); | |
| 551 | 520 | |
| 552 | − const leaderResult = yield* Fiber.join(leaderFiber); | |
| 553 | − const followerResults = yield* Effect.all( | |
| 554 | − followerFibers.map((f) => Fiber.await(f)), | |
| 555 | − { concurrency: "unbounded" }, | |
| 556 | − ); | |
| 557 | − expect(leaderResult).toBe("rotated-1"); | |
| 558 | − for (const result of followerResults) { | |
| 559 | − expect(Exit.isSuccess(result)).toBe(true); | |
| 560 | − if (!Exit.isSuccess(result)) continue; | |
| 561 | − expect(result.value).toBe("rotated-1"); | |
| 562 | − } | |
| 563 | − expect(calls).toHaveLength(1); | |
| 564 | − }), | |
| 521 | + const leaderResult = yield* Fiber.join(leaderFiber); | |
| 522 | + const followerResults = yield* Effect.all( | |
| 523 | + followerFibers.map((f) => Fiber.await(f)), | |
| 524 | + { concurrency: "unbounded" }, | |
| 525 | + ); | |
| 526 | + expect(leaderResult).toBe("rotated-1"); | |
| 527 | + for (const result of followerResults) { | |
| 528 | + expect(Exit.isSuccess(result)).toBe(true); | |
| 529 | + if (!Exit.isSuccess(result)) continue; | |
| 530 | + expect(result.value).toBe("rotated-1"); | |
| 531 | + } | |
| 532 | + expect(calls).toHaveLength(1); | |
| 533 | + }), | |
| 565 | 534 | ); |
| 566 | 535 | |
| 567 | 536 | it.effect( |
| @@ -609,12 +578,8 @@ describe("connections", () => { · 41 unmodified lines | ||
| 609 | 578 | }), |
| 610 | 579 | ); |
| 611 | 580 | |
| 612 | − const flipped = yield* executor.connections | |
| 613 | − .accessToken("conn-1") | |
| 614 | − .pipe(Effect.flip); | |
| 615 | − expect( | |
| 616 | − Predicate.isTagged(flipped, "ConnectionReauthRequiredError"), | |
| 617 | − ).toBe(true); | |
| 581 | + const flipped = yield* executor.connections.accessToken("conn-1").pipe(Effect.flip); | |
| 582 | + expect(Predicate.isTagged(flipped, "ConnectionReauthRequiredError")).toBe(true); | |
| 618 | 583 | if (!Predicate.isTagged(flipped, "ConnectionReauthRequiredError")) { |
| 619 | 584 | return; |
| 620 | 585 | } |
| @@ -623,57 +588,53 @@ describe("connections", () => { · 2 unmodified lines | ||
| 623 | 588 | }), |
| 624 | 589 | ); |
| 625 | 590 | |
| 626 | − it.effect( | |
| 627 | − "accessToken preserves ConnectionRefreshError for non-reauth failures", | |
| 628 | − () => | |
| 629 | − Effect.gen(function* () { | |
| 630 | − // Transient failure path — the provider failed but not with a | |
| 631 | − // terminal RFC 6749 code. The SDK must keep it as-is so | |
| 632 | − // callers can tell "retry later" from "prompt for sign-in". | |
| 633 | − const provider: ConnectionProvider = { | |
| 634 | − key: "spotify", | |
| 635 | − refresh: (input) => | |
| 636 | − Effect.fail( | |
| 637 | − new ConnectionRefreshError({ | |
| 638 | − connectionId: input.connectionId, | |
| 639 | − message: "network flake", | |
| 640 | − }), | |
| 641 | − ), | |
| 642 | − }; | |
| 591 | + it.effect("accessToken preserves ConnectionRefreshError for non-reauth failures", () => | |
| 592 | + Effect.gen(function* () { | |
| 593 | + // Transient failure path — the provider failed but not with a | |
| 594 | + // terminal RFC 6749 code. The SDK must keep it as-is so | |
| 595 | + // callers can tell "retry later" from "prompt for sign-in". | |
| 596 | + const provider: ConnectionProvider = { | |
| 597 | + key: "spotify", | |
| 598 | + refresh: (input) => | |
| 599 | + Effect.fail( | |
| 600 | + new ConnectionRefreshError({ | |
| 601 | + connectionId: input.connectionId, | |
| 602 | + message: "network flake", | |
| 603 | + }), | |
| 604 | + ), | |
| 605 | + }; | |
| 643 | 606 | |
| 644 | − const executor = yield* createExecutor( | |
| 645 | − makeTestConfig({ | |
| 646 | − plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, | |
| 647 | − }), | |
| 648 | − ); | |
| 607 | + const executor = yield* createExecutor( | |
| 608 | + makeTestConfig({ | |
| 609 | + plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, | |
| 610 | + }), | |
| 611 | + ); | |
| 649 | 612 | |
| 650 | − yield* executor.connections.create( | |
| 651 | − new CreateConnectionInput({ | |
| 652 | − id: cid("conn-1"), | |
| 653 | − scope: scpid("test-scope"), | |
| 654 | − provider: "spotify", | |
| 655 | − identityLabel: null, | |
| 656 | − accessToken: new TokenMaterial({ | |
| 657 | − secretId: sid("conn-1.access"), | |
| 658 | − name: "access", | |
| 659 | − 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, | |
| 613 | + yield* executor.connections.create( | |
| 614 | + new CreateConnectionInput({ | |
| 615 | + id: cid("conn-1"), | |
| 616 | + scope: scpid("test-scope"), | |
| 617 | + provider: "spotify", | |
| 618 | + identityLabel: null, | |
| 619 | + accessToken: new TokenMaterial({ | |
| 620 | + secretId: sid("conn-1.access"), | |
| 621 | + name: "access", | |
| 622 | + value: "stale", | |
| 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 | |
| 673 | − .accessToken("conn-1") | |
| 674 | − .pipe(Effect.flip); | |
| 675 | − expect(Predicate.isTagged(err, "ConnectionRefreshError")).toBe(true); | |
| 676 | − }), | |
| 635 | + const err = yield* executor.connections.accessToken("conn-1").pipe(Effect.flip); | |
| 636 | + expect(Predicate.isTagged(err, "ConnectionRefreshError")).toBe(true); | |
| 637 | + }), | |
| 677 | 638 | ); |
| 678 | 639 | |
| 679 | 640 | it.effect( |
| @@ -705,116 +666,97 @@ describe("connections", () => { · 25 unmodified lines | ||
| 705 | 666 | }), |
| 706 | 667 | ); |
| 707 | 668 | |
| 708 | − const err = yield* executor.connections | |
| 709 | − .accessToken("conn-1") | |
| 710 | − .pipe(Effect.flip); | |
| 711 | − expect( | |
| 712 | − Predicate.isTagged(err, "ConnectionRefreshNotSupportedError"), | |
| 713 | − ).toBe(true); | |
| 669 | + const err = yield* executor.connections.accessToken("conn-1").pipe(Effect.flip); | |
| 670 | + expect(Predicate.isTagged(err, "ConnectionRefreshNotSupportedError")).toBe(true); | |
| 714 | 671 | }), |
| 715 | 672 | ); |
| 716 | 673 | |
| 717 | − it.effect( | |
| 718 | − "accessToken fails with ConnectionNotFoundError for an unknown id", | |
| 719 | − () => | |
| 720 | − Effect.gen(function* () { | |
| 721 | − const executor = yield* createExecutor( | |
| 722 | − makeTestConfig({ | |
| 723 | − plugins: [memorySecretsPlugin()] as const, | |
| 724 | − }), | |
| 725 | − ); | |
| 674 | + it.effect("accessToken fails with ConnectionNotFoundError for an unknown id", () => | |
| 675 | + Effect.gen(function* () { | |
| 676 | + const executor = yield* createExecutor( | |
| 677 | + makeTestConfig({ | |
| 678 | + plugins: [memorySecretsPlugin()] as const, | |
| 679 | + }), | |
| 680 | + ); | |
| 726 | 681 | |
| 727 | − const err = yield* executor.connections | |
| 728 | − .accessToken("does-not-exist") | |
| 729 | − .pipe(Effect.flip); | |
| 730 | − expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true); | |
| 731 | − }), | |
| 682 | + const err = yield* executor.connections.accessToken("does-not-exist").pipe(Effect.flip); | |
| 683 | + expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true); | |
| 684 | + }), | |
| 732 | 685 | ); |
| 733 | 686 | |
| 734 | − it.effect( | |
| 735 | − "updateTokens writes new values but does not rotate secret ids", | |
| 736 | − () => | |
| 737 | − Effect.gen(function* () { | |
| 738 | − const secretProvider = makeMemoryProvider(); | |
| 739 | − const { provider } = makeConnectionProvider({ key: "spotify" }); | |
| 740 | − const executor = yield* createExecutor( | |
| 741 | − makeTestConfig({ | |
| 742 | − plugins: [ | |
| 743 | − memorySecretsPlugin(secretProvider), | |
| 744 | − connPlugin(provider), | |
| 745 | − ] as const, | |
| 746 | − }), | |
| 747 | − ); | |
| 687 | + it.effect("updateTokens writes new values but does not rotate secret ids", () => | |
| 688 | + Effect.gen(function* () { | |
| 689 | + const secretProvider = makeMemoryProvider(); | |
| 690 | + const { provider } = makeConnectionProvider({ key: "spotify" }); | |
| 691 | + const executor = yield* createExecutor( | |
| 692 | + makeTestConfig({ | |
| 693 | + plugins: [memorySecretsPlugin(secretProvider), connPlugin(provider)] as const, | |
| 694 | + }), | |
| 695 | + ); | |
| 748 | 696 | |
| 749 | − yield* executor.connections.create( | |
| 750 | − new CreateConnectionInput({ | |
| 751 | − id: cid("conn-1"), | |
| 752 | − scope: scpid("test-scope"), | |
| 753 | − provider: "spotify", | |
| 754 | − identityLabel: null, | |
| 755 | − accessToken: new TokenMaterial({ | |
| 756 | − secretId: sid("conn-1.access"), | |
| 757 | − name: "access", | |
| 758 | − 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, | |
| 697 | + yield* executor.connections.create( | |
| 698 | + new CreateConnectionInput({ | |
| 699 | + id: cid("conn-1"), | |
| 700 | + scope: scpid("test-scope"), | |
| 701 | + provider: "spotify", | |
| 702 | + identityLabel: null, | |
| 703 | + accessToken: new TokenMaterial({ | |
| 704 | + secretId: sid("conn-1.access"), | |
| 705 | + name: "access", | |
| 706 | + value: "v1", | |
| 768 | 707 | }), |
| 769 | − ); | |
| 770 | − | |
| 771 | − const updated = yield* executor.connections.updateTokens( | |
| 772 | − new UpdateConnectionTokensInput({ | |
| 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" }, | |
| 708 | + refreshToken: new TokenMaterial({ | |
| 709 | + secretId: sid("conn-1.refresh"), | |
| 710 | + name: "refresh", | |
| 711 | + value: "r1", | |
| 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")); | |
| 783 | − expect(updated.refreshTokenSecretId).toBe(sid("conn-1.refresh")); | |
| 784 | − expect(updated.expiresAt).toBe(1_700_000_000_000); | |
| 785 | − expect(updated.oauthScope).toBe("new-scope"); | |
| 786 | − expect(updated.providerState).toEqual({ rotation: "next" }); | |
| 787 | − | |
| 788 | − expect( | |
| 789 | − yield* secretProvider.get!("conn-1.access", "test-scope"), | |
| 790 | − ).toBe("v2"); | |
| 791 | − expect( | |
| 792 | − yield* secretProvider.get!("conn-1.refresh", "test-scope"), | |
| 793 | − ).toBe("r2"); | |
| 794 | − }), | |
| 719 | + const updated = yield* executor.connections.updateTokens( | |
| 720 | + new UpdateConnectionTokensInput({ | |
| 721 | + id: cid("conn-1"), | |
| 722 | + accessToken: "v2", | |
| 723 | + refreshToken: "r2", | |
| 724 | + expiresAt: 1_700_000_000_000, | |
| 725 | + oauthScope: "new-scope", | |
| 726 | + providerState: { rotation: "next" }, | |
| 727 | + }), | |
| 728 | + ); | |
| 729 | + | |
| 730 | + expect(updated.accessTokenSecretId).toBe(sid("conn-1.access")); | |
| 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( | |
| 798 | − "updateTokens fails with ConnectionNotFoundError for unknown id", | |
| 799 | − () => | |
| 800 | − Effect.gen(function* () { | |
| 801 | − const { provider } = makeConnectionProvider({ key: "spotify" }); | |
| 802 | − const executor = yield* createExecutor( | |
| 803 | − makeTestConfig({ | |
| 804 | − plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, | |
| 805 | − }), | |
| 806 | − ); | |
| 741 | + it.effect("updateTokens fails with ConnectionNotFoundError for unknown id", () => | |
| 742 | + Effect.gen(function* () { | |
| 743 | + const { provider } = makeConnectionProvider({ key: "spotify" }); | |
| 744 | + const executor = yield* createExecutor( | |
| 745 | + makeTestConfig({ | |
| 746 | + plugins: [memorySecretsPlugin(), connPlugin(provider)] as const, | |
| 747 | + }), | |
| 748 | + ); | |
| 807 | 749 | |
| 808 | − const err = yield* executor.connections | |
| 809 | − .updateTokens( | |
| 810 | − new UpdateConnectionTokensInput({ | |
| 811 | − id: cid("nope"), | |
| 812 | − accessToken: "x", | |
| 813 | − }), | |
| 814 | − ) | |
| 815 | − .pipe(Effect.flip); | |
| 816 | − expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true); | |
| 817 | − }), | |
| 750 | + const err = yield* executor.connections | |
| 751 | + .updateTokens( | |
| 752 | + new UpdateConnectionTokensInput({ | |
| 753 | + id: cid("nope"), | |
| 754 | + accessToken: "x", | |
| 755 | + }), | |
| 756 | + ) | |
| 757 | + .pipe(Effect.flip); | |
| 758 | + expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true); | |
| 759 | + }), | |
| 818 | 760 | ); |
| 819 | 761 | |
| 820 | 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( | |
| 854 | − "setIdentityLabel fails with ConnectionNotFoundError for unknown id", | |
| 855 | − () => | |
| 856 | − Effect.gen(function* () { | |
| 857 | − const executor = yield* createExecutor( | |
| 858 | − makeTestConfig({ | |
| 859 | − plugins: [memorySecretsPlugin()] as const, | |
| 860 | − }), | |
| 861 | − ); | |
| 795 | + it.effect("setIdentityLabel fails with ConnectionNotFoundError for unknown id", () => | |
| 796 | + Effect.gen(function* () { | |
| 797 | + const executor = yield* createExecutor( | |
| 798 | + makeTestConfig({ | |
| 799 | + plugins: [memorySecretsPlugin()] as const, | |
| 800 | + }), | |
| 801 | + ); | |
| 862 | 802 | |
| 863 | − const err = yield* executor.connections | |
| 864 | − .setIdentityLabel("does-not-exist", "x") | |
| 865 | − .pipe(Effect.flip); | |
| 866 | − expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true); | |
| 867 | − }), | |
| 803 | + const err = yield* executor.connections | |
| 804 | + .setIdentityLabel("does-not-exist", "x") | |
| 805 | + .pipe(Effect.flip); | |
| 806 | + expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true); | |
| 807 | + }), | |
| 868 | 808 | ); |
| 869 | 809 | |
| 870 | 810 | it.effect("providers() returns every registered connection provider key", () => |
| @@ -936,8 +876,7 @@ const makeLayeredConnExecutors = () => · 65 unmodified lines | ||
| 936 | 876 | describe("connections — multi-scope behaviour", () => { |
| 937 | 877 | it.effect("get picks the innermost-scope row when the same id exists at two scopes", () => |
| 938 | 878 | Effect.gen(function* () { |
| 939 | − const { execOuter, execInner, outerId, innerId } = | |
| 940 | − yield* makeLayeredConnExecutors(); | |
| 879 | + const { execOuter, execInner, outerId, innerId } = yield* makeLayeredConnExecutors(); | |
| 941 | 880 | |
| 942 | 881 | yield* execOuter.connections.create( |
| 943 | 882 | new CreateConnectionInput({ |
| @@ -991,52 +930,49 @@ describe("connections — multi-scope behaviour", () => { · 47 unmodified lines | ||
| 991 | 930 | }), |
| 992 | 931 | ); |
| 993 | 932 | |
| 994 | − it.effect( | |
| 995 | − "remove at the inner scope does not wipe the outer-scope connection", | |
| 996 | − () => | |
| 997 | − Effect.gen(function* () { | |
| 998 | − const { execOuter, execInner, outerId, innerId } = | |
| 999 | − yield* makeLayeredConnExecutors(); | |
| 933 | + it.effect("remove at the inner scope does not wipe the outer-scope connection", () => | |
| 934 | + Effect.gen(function* () { | |
| 935 | + const { execOuter, execInner, outerId, innerId } = yield* makeLayeredConnExecutors(); | |
| 1000 | 936 | |
| 1001 | − yield* execOuter.connections.create( | |
| 1002 | − new CreateConnectionInput({ | |
| 1003 | − id: cid("shared"), | |
| 1004 | − scope: outerId, | |
| 1005 | − provider: "spotify", | |
| 1006 | − identityLabel: "outer", | |
| 1007 | − accessToken: new TokenMaterial({ | |
| 1008 | − secretId: sid("shared.access.outer"), | |
| 1009 | − name: "access", | |
| 1010 | − value: "outer-access", | |
| 1011 | − }), | |
| 1012 | − refreshToken: null, | |
| 1013 | − expiresAt: null, | |
| 1014 | − oauthScope: null, | |
| 1015 | − providerState: null, | |
| 937 | + yield* execOuter.connections.create( | |
| 938 | + new CreateConnectionInput({ | |
| 939 | + id: cid("shared"), | |
| 940 | + scope: outerId, | |
| 941 | + provider: "spotify", | |
| 942 | + identityLabel: "outer", | |
| 943 | + accessToken: new TokenMaterial({ | |
| 944 | + secretId: sid("shared.access.outer"), | |
| 945 | + name: "access", | |
| 946 | + value: "outer-access", | |
| 1016 | 947 | }), |
| 1017 | − ); | |
| 1018 | − yield* execInner.connections.create( | |
| 1019 | − new CreateConnectionInput({ | |
| 1020 | − id: cid("shared"), | |
| 1021 | − scope: innerId, | |
| 1022 | − provider: "spotify", | |
| 1023 | − identityLabel: "inner", | |
| 1024 | − accessToken: new TokenMaterial({ | |
| 1025 | − secretId: sid("shared.access.inner"), | |
| 1026 | − name: "access", | |
| 1027 | − value: "inner-access", | |
| 1028 | − }), | |
| 1029 | − refreshToken: null, | |
| 1030 | − expiresAt: null, | |
| 1031 | − oauthScope: null, | |
| 1032 | − providerState: null, | |
| 948 | + refreshToken: null, | |
| 949 | + expiresAt: null, | |
| 950 | + oauthScope: null, | |
| 951 | + providerState: null, | |
| 952 | + }), | |
| 953 | + ); | |
| 954 | + yield* execInner.connections.create( | |
| 955 | + new CreateConnectionInput({ | |
| 956 | + id: cid("shared"), | |
| 957 | + scope: innerId, | |
| 958 | + provider: "spotify", | |
| 959 | + identityLabel: "inner", | |
| 960 | + accessToken: new TokenMaterial({ | |
| 961 | + secretId: sid("shared.access.inner"), | |
| 962 | + name: "access", | |
| 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"); | |
| 1039 | − expect(outerStill?.identityLabel).toBe("outer"); | |
| 1040 | − }), | |
| 974 | + const outerStill = yield* execOuter.connections.get("shared"); | |
| 975 | + expect(outerStill?.identityLabel).toBe("outer"); | |
| 976 | + }), | |
| 1041 | 977 | ); |
| 1042 | 978 | }); |
| @@ -87,9 +87,7 @@ export class CreateConnectionInput extends Schema.Class<CreateConnectionInput>( · 86 unmodified lines | ||
| 87 | 87 | // the SDK rethrows it from `ctx.connections.accessToken` callers. |
| 88 | 88 | // --------------------------------------------------------------------------- |
| 89 | 89 | |
| 90 | −export class ConnectionRefreshError extends Data.TaggedError( | |
| 91 | − "ConnectionRefreshError", | |
| 92 | −)<{ | |
| 90 | +export class ConnectionRefreshError extends Data.TaggedError("ConnectionRefreshError")<{ | |
| 93 | 91 | readonly connectionId: ConnectionId; |
| 94 | 92 | readonly message: string; |
| 95 | 93 | /** |
| @@ -10,10 +10,7 @@ · 9 unmodified lines | ||
| 10 | 10 | // dynamic (runtime-registered) rows. |
| 11 | 11 | // --------------------------------------------------------------------------- |
| 12 | 12 | |
| 13 | −import type { | |
| 14 | − DBSchema, | |
| 15 | − InferDBFieldsOutput, | |
| 16 | −} from "@executor-js/storage-core"; | |
| 13 | +import type { DBSchema, InferDBFieldsOutput } from "@executor-js/storage-core"; | |
| 17 | 14 | |
| 18 | 15 | export const coreSchema = { |
| 19 | 16 | source: { |
| @@ -209,25 +206,18 @@ export type CoreSchema = typeof coreSchema; · 189 unmodified lines | ||
| 209 | 206 | export type SourceRow = InferDBFieldsOutput<CoreSchema["source"]["fields"]> & |
| 210 | 207 | Record<string, unknown>; |
| 211 | 208 | |
| 212 | −export type ToolRow = InferDBFieldsOutput<CoreSchema["tool"]["fields"]> & | |
| 213 | − Record<string, unknown>; | |
| 209 | +export type ToolRow = InferDBFieldsOutput<CoreSchema["tool"]["fields"]> & Record<string, unknown>; | |
| 214 | 210 | |
| 215 | −export type DefinitionRow = InferDBFieldsOutput< | |
| 216 | − CoreSchema["definition"]["fields"] | |
| 217 | −> & | |
| 211 | +export type DefinitionRow = InferDBFieldsOutput<CoreSchema["definition"]["fields"]> & | |
| 218 | 212 | Record<string, unknown>; |
| 219 | 213 | |
| 220 | 214 | export type SecretRow = InferDBFieldsOutput<CoreSchema["secret"]["fields"]> & |
| 221 | 215 | Record<string, unknown>; |
| 222 | 216 | |
| 223 | −export type ConnectionRow = InferDBFieldsOutput< | |
| 224 | − CoreSchema["connection"]["fields"] | |
| 225 | −> & | |
| 217 | +export type ConnectionRow = InferDBFieldsOutput<CoreSchema["connection"]["fields"]> & | |
| 226 | 218 | Record<string, unknown>; |
| 227 | 219 | |
| 228 | −export type ToolPolicyRow = InferDBFieldsOutput< | |
| 229 | − CoreSchema["tool_policy"]["fields"] | |
| 230 | −> & | |
| 220 | +export type ToolPolicyRow = InferDBFieldsOutput<CoreSchema["tool_policy"]["fields"]> & | |
| 231 | 221 | Record<string, unknown>; |
| 232 | 222 | |
| 233 | 223 | // --------------------------------------------------------------------------- |
| @@ -251,8 +241,7 @@ export const TOOL_POLICY_ACTIONS = [ · 17 unmodified lines | ||
| 251 | 241 | ] as const satisfies readonly ToolPolicyAction[]; |
| 252 | 242 | |
| 253 | 243 | export const isToolPolicyAction = (value: unknown): value is ToolPolicyAction => |
| 254 | − typeof value === "string" && | |
| 255 | − (TOOL_POLICY_ACTIONS as readonly string[]).includes(value); | |
| 244 | + typeof value === "string" && (TOOL_POLICY_ACTIONS as readonly string[]).includes(value); | |
| 256 | 245 | |
| 257 | 246 | // --------------------------------------------------------------------------- |
| 258 | 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 | 45 | const makeFailingAdapter = (failure: StorageFailure): DBAdapter => { |
| 46 | − const fail = () => | |
| 47 | − Effect.fail(failure) as Effect.Effect<never, StorageFailure, never>; | |
| 46 | + const fail = () => Effect.fail(failure) as Effect.Effect<never, StorageFailure, never>; | |
| 48 | 47 | return { |
| 49 | 48 | id: "failing", |
| 50 | 49 | create: fail as DBAdapter["create"], |
| @@ -91,9 +90,7 @@ describe("typed-error edge model — SDK", () => { · 40 unmodified lines | ||
| 91 | 90 | message: "backend lost its mind", |
| 92 | 91 | cause: driverCause, |
| 93 | 92 | }); |
| 94 | − const executor = yield* createExecutor( | |
| 95 | − baseConfig(makeFailingAdapter(failure)), | |
| 96 | − ); | |
| 93 | + const executor = yield* createExecutor(baseConfig(makeFailingAdapter(failure))); | |
| 97 | 94 | |
| 98 | 95 | const result = yield* executor.tools.list().pipe(Effect.flip); |
| 99 | 96 | expect(result).toBeInstanceOf(StorageError); |
| @@ -140,9 +137,7 @@ describe("typed-error edge model — SDK", () => { · 40 unmodified lines | ||
| 140 | 137 | model: err.model ?? null, |
| 141 | 138 | }), |
| 142 | 139 | ), |
| 143 | − Effect.map((r) => | |
| 144 | − "caught" in r ? r : { caught: false as const, model: null }, | |
| 145 | − ), | |
| 140 | + Effect.map((r) => ("caught" in r ? r : { caught: false as const, model: null })), | |
| 146 | 141 | ), |
| 147 | 142 | }), |
| 148 | 143 | extension: (ctx) => ({ |
| @@ -172,9 +167,7 @@ describe("typed-error edge model — SDK", () => { · 23 unmodified lines | ||
| 172 | 167 | // type-check (R would leak) nor run (would crash at construction). |
| 173 | 168 | const executor = yield* createExecutor( |
| 174 | 169 | baseConfig( |
| 175 | − makeFailingAdapter( | |
| 176 | − new StorageError({ message: "doesn't matter", cause: undefined }), | |
| 177 | − ), | |
| 170 | + makeFailingAdapter(new StorageError({ message: "doesn't matter", cause: undefined })), | |
| 178 | 171 | ), |
| 179 | 172 | ); |
| 180 | 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 | 32 | * the plugin only declares static tools and this one's id matched |
| 33 | 33 | * dynamically somehow. Shouldn't happen in practice; guards against |
| 34 | 34 | * programmer error. */ |
| 35 | −export class NoHandlerError extends Schema.TaggedErrorClass<NoHandlerError>()( | |
| 36 | − "NoHandlerError", | |
| 37 | − { | |
| 38 | − toolId: ToolId, | |
| 39 | − pluginId: Schema.String, | |
| 40 | − }, | |
| 41 | −) {} | |
| 35 | +export class NoHandlerError extends Schema.TaggedErrorClass<NoHandlerError>()("NoHandlerError", { | |
| 36 | + toolId: ToolId, | |
| 37 | + pluginId: Schema.String, | |
| 38 | +}) {} | |
| 42 | 39 | |
| 43 | 40 | /** Tool invocation was rejected because a workspace `tool_policy` rule |
| 44 | 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 | 7 | import { makeInMemoryBlobStore } from "./blob"; |
| 8 | 8 | import { CreateConnectionInput, TokenMaterial } from "./connections"; |
| 9 | 9 | import { collectSchemas, createExecutor } from "./executor"; |
| 10 | −import { | |
| 11 | − ElicitationResponse, | |
| 12 | − FormElicitation, | |
| 13 | − UrlElicitation, | |
| 14 | −} from "./elicitation"; | |
| 10 | +import { ElicitationResponse, FormElicitation, UrlElicitation } from "./elicitation"; | |
| 15 | 11 | import { defineSchema, definePlugin } from "./plugin"; |
| 16 | 12 | import { SetSecretInput } from "./secrets"; |
| 17 | 13 | import { makeTestConfig } from "./testing"; |
| @@ -29,10 +25,7 @@ class TestPluginError extends Data.TaggedError("TestPluginError")<{ · 11 unmodified lines | ||
| 29 | 25 | readonly message: string; |
| 30 | 26 | }> {} |
| 31 | 27 | |
| 32 | −const recordFindMany = ( | |
| 33 | − adapter: DBAdapter, | |
| 34 | − calls: FindManyCall[], | |
| 35 | −): DBAdapter => ({ | |
| 28 | +const recordFindMany = (adapter: DBAdapter, calls: FindManyCall[]): DBAdapter => ({ | |
| 36 | 29 | ...adapter, |
| 37 | 30 | findMany: (data) => { |
| 38 | 31 | calls.push({ model: data.model, where: data.where }); |
| @@ -244,9 +237,7 @@ const opaqueRouteSecretsPlugin = definePlugin(() => ({ · 205 unmodified lines | ||
| 244 | 237 | describe("createExecutor", () => { |
| 245 | 238 | it.effect("invokes a static tool via the in-memory pool", () => |
| 246 | 239 | Effect.gen(function* () { |
| 247 | − const executor = yield* createExecutor( | |
| 248 | − makeTestConfig({ plugins: [testPlugin()] as const }), | |
| 249 | − ); | |
| 240 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); | |
| 250 | 241 | const result = yield* executor.tools.invoke( |
| 251 | 242 | "test.control.echo", |
| 252 | 243 | { text: "hi" }, |
| @@ -258,9 +249,7 @@ describe("createExecutor", () => { · 5 unmodified lines | ||
| 258 | 249 | |
| 259 | 250 | it.effect("lists static tools alongside dynamic ones", () => |
| 260 | 251 | Effect.gen(function* () { |
| 261 | − const executor = yield* createExecutor( | |
| 262 | − makeTestConfig({ plugins: [testPlugin()] as const }), | |
| 263 | − ); | |
| 252 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); | |
| 264 | 253 | yield* executor.test.addThing("thing1", "hello"); |
| 265 | 254 | |
| 266 | 255 | const tools = yield* executor.tools.list(); |
| @@ -273,9 +262,7 @@ describe("createExecutor", () => { · 6 unmodified lines | ||
| 273 | 262 | |
| 274 | 263 | it.effect("filters tools by query", () => |
| 275 | 264 | Effect.gen(function* () { |
| 276 | − const executor = yield* createExecutor( | |
| 277 | − makeTestConfig({ plugins: [testPlugin()] as const }), | |
| 278 | − ); | |
| 265 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); | |
| 279 | 266 | yield* executor.test.addThing("thing1", "hello"); |
| 280 | 267 | |
| 281 | 268 | const tools = yield* executor.tools.list({ query: "echo" }); |
| @@ -298,24 +285,17 @@ describe("createExecutor", () => { · 16 unmodified lines | ||
| 298 | 285 | findManyCalls.length = 0; |
| 299 | 286 | const tools = yield* executor.tools.list({ sourceId: "thing1" }); |
| 300 | 287 | |
| 301 | − expect(tools.map((t) => t.id).sort()).toEqual([ | |
| 302 | − "thing1.read", | |
| 303 | − "thing1.write", | |
| 304 | − ]); | |
| 288 | + expect(tools.map((t) => t.id).sort()).toEqual(["thing1.read", "thing1.write"]); | |
| 305 | 289 | const toolRead = findManyCalls.find((call) => call.model === "tool"); |
| 306 | 290 | expect(toolRead?.where).toEqual( |
| 307 | − expect.arrayContaining([ | |
| 308 | − expect.objectContaining({ field: "source_id", value: "thing1" }), | |
| 309 | − ]), | |
| 291 | + expect.arrayContaining([expect.objectContaining({ field: "source_id", value: "thing1" })]), | |
| 310 | 292 | ); |
| 311 | 293 | }), |
| 312 | 294 | ); |
| 313 | 295 | |
| 314 | 296 | it.effect("can list tools without resolving dynamic annotations", () => |
| 315 | 297 | Effect.gen(function* () { |
| 316 | − const executor = yield* createExecutor( | |
| 317 | − makeTestConfig({ plugins: [testPlugin()] as const }), | |
| 318 | − ); | |
| 298 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); | |
| 319 | 299 | yield* executor.test.addThing("thing1", "hello"); |
| 320 | 300 | testAnnotationResolveCount = 0; |
| 321 | 301 | |
| @@ -325,19 +305,14 @@ describe("createExecutor", () => { · 3 unmodified lines | ||
| 325 | 305 | }); |
| 326 | 306 | |
| 327 | 307 | expect(testAnnotationResolveCount).toBe(0); |
| 328 | − expect(tools.map((t) => t.id).sort()).toEqual([ | |
| 329 | − "thing1.read", | |
| 330 | − "thing1.write", | |
| 331 | − ]); | |
| 308 | + expect(tools.map((t) => t.id).sort()).toEqual(["thing1.read", "thing1.write"]); | |
| 332 | 309 | expect(tools.every((tool) => tool.annotations === undefined)).toBe(true); |
| 333 | 310 | }), |
| 334 | 311 | ); |
| 335 | 312 | |
| 336 | 313 | it.effect("invokes a dynamic tool through plugin.invokeTool", () => |
| 337 | 314 | Effect.gen(function* () { |
| 338 | − const executor = yield* createExecutor( | |
| 339 | − makeTestConfig({ plugins: [testPlugin()] as const }), | |
| 340 | − ); | |
| 315 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); | |
| 341 | 316 | yield* executor.test.addThing("thing1", "hello"); |
| 342 | 317 | |
| 343 | 318 | const result = yield* executor.tools.invoke( |
| @@ -351,9 +326,7 @@ describe("createExecutor", () => { · 7 unmodified lines | ||
| 351 | 326 | |
| 352 | 327 | it.effect("enforces tool annotations before invoking", () => |
| 353 | 328 | Effect.gen(function* () { |
| 354 | − const executor = yield* createExecutor( | |
| 355 | − makeTestConfig({ plugins: [testPlugin()] as const }), | |
| 356 | − ); | |
| 329 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); | |
| 357 | 330 | yield* executor.test.addThing("thing1", "hello"); |
| 358 | 331 | |
| 359 | 332 | // requiresApproval: true → declined → ElicitationDeclinedError |
| @@ -362,8 +335,7 @@ describe("createExecutor", () => { · 2 unmodified lines | ||
| 362 | 335 | "thing1.write", |
| 363 | 336 | { value: "updated" }, |
| 364 | 337 | { |
| 365 | − onElicitation: () => | |
| 366 | − Effect.succeed(new ElicitationResponse({ action: "decline" })), | |
| 338 | + onElicitation: () => Effect.succeed(new ElicitationResponse({ action: "decline" })), | |
| 367 | 339 | }, |
| 368 | 340 | ) |
| 369 | 341 | .pipe(Effect.flip); |
| @@ -381,9 +353,7 @@ describe("createExecutor", () => { · 11 unmodified lines | ||
| 381 | 353 | |
| 382 | 354 | it.effect("sources.list unions static runtime sources and dynamic ones", () => |
| 383 | 355 | Effect.gen(function* () { |
| 384 | − const executor = yield* createExecutor( | |
| 385 | − makeTestConfig({ plugins: [testPlugin()] as const }), | |
| 386 | − ); | |
| 356 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); | |
| 387 | 357 | yield* executor.test.addThing("thing1", "hello"); |
| 388 | 358 | |
| 389 | 359 | const sources = yield* executor.sources.list(); |
| @@ -444,12 +414,8 @@ describe("createExecutor", () => { · 54 unmodified lines | ||
| 444 | 414 | |
| 445 | 415 | it.effect("rejects remove of a static source", () => |
| 446 | 416 | Effect.gen(function* () { |
| 447 | − const executor = yield* createExecutor( | |
| 448 | − makeTestConfig({ plugins: [testPlugin()] as const }), | |
| 449 | − ); | |
| 450 | − const err = yield* executor.sources | |
| 451 | − .remove("test.control") | |
| 452 | − .pipe(Effect.flip); | |
| 417 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); | |
| 418 | + const err = yield* executor.sources.remove("test.control").pipe(Effect.flip); | |
| 453 | 419 | expect(Predicate.isTagged(err, "SourceRemovalNotAllowedError")).toBe(true); |
| 454 | 420 | }), |
| 455 | 421 | ); |
| @@ -546,9 +512,7 @@ describe("createExecutor", () => { · 90 unmodified lines | ||
| 546 | 512 | |
| 547 | 513 | it.effect("ctx.transaction commits all nested writes on success", () => |
| 548 | 514 | Effect.gen(function* () { |
| 549 | − const executor = yield* createExecutor( | |
| 550 | − makeTestConfig({ plugins: [testPlugin()] as const }), | |
| 551 | − ); | |
| 515 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); | |
| 552 | 516 | // addThing wraps storage + sources.register in ctx.transaction. |
| 553 | 517 | yield* executor.test.addThing("thing1", "hello"); |
| 554 | 518 | |
| @@ -560,13 +524,9 @@ describe("createExecutor", () => { · 5 unmodified lines | ||
| 560 | 524 | expect(tools).toContain("thing1.write"); |
| 561 | 525 | |
| 562 | 526 | // plugin storage row committed too |
| 563 | − expect( | |
| 564 | − yield* executor.tools.invoke( | |
| 565 | − "thing1.read", | |
| 566 | − {}, | |
| 567 | − { onElicitation: "accept-all" }, | |
| 568 | − ), | |
| 569 | − ).toBe("hello"); | |
| 527 | + expect(yield* executor.tools.invoke("thing1.read", {}, { onElicitation: "accept-all" })).toBe( | |
| 528 | + "hello", | |
| 529 | + ); | |
| 570 | 530 | }), |
| 571 | 531 | ); |
| 572 | 532 | |
| @@ -612,9 +572,7 @@ describe("createExecutor", () => { · 39 unmodified lines | ||
| 612 | 572 | makeTestConfig({ plugins: [rollbackPlugin()] as const }), |
| 613 | 573 | ); |
| 614 | 574 | |
| 615 | − const result = yield* executor.rollback | |
| 616 | − .doFailingTx() | |
| 617 | − .pipe(Effect.result); | |
| 575 | + const result = yield* executor.rollback.doFailingTx().pipe(Effect.result); | |
| 618 | 576 | expect(Result.isFailure(result)).toBe(true); |
| 619 | 577 | |
| 620 | 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 | |
| 688 | − .get("conn-owned.access_token") | |
| 689 | − .pipe(Effect.flip); | |
| 690 | − expect(Predicate.isTagged(leaked, "SecretOwnedByConnectionError")).toBe( | |
| 691 | − true, | |
| 692 | − ); | |
| 645 | + const leaked = yield* executor.secrets.get("conn-owned.access_token").pipe(Effect.flip); | |
| 646 | + expect(Predicate.isTagged(leaked, "SecretOwnedByConnectionError")).toBe(true); | |
| 693 | 647 | |
| 694 | 648 | const status = yield* executor.secrets.status("conn-owned.access_token"); |
| 695 | 649 | expect(status).toBe("missing"); |
| 696 | − const visibleIds = (yield* executor.secrets.list()).map( | |
| 697 | − (s) => String(s.id), | |
| 698 | − ); | |
| 650 | + const visibleIds = (yield* executor.secrets.list()).map((s) => String(s.id)); | |
| 699 | 651 | expect(visibleIds).not.toContain("conn-owned.access_token"); |
| 700 | 652 | |
| 701 | 653 | const token = yield* executor.connections.accessToken("conn-owned"); |
| @@ -764,9 +716,7 @@ describe("createExecutor", () => { · 62 unmodified lines | ||
| 764 | 716 | close: () => Effect.sync(() => void closed++), |
| 765 | 717 | })); |
| 766 | 718 | |
| 767 | − const executor = yield* createExecutor( | |
| 768 | − makeTestConfig({ plugins: [closeable()] as const }), | |
| 769 | − ); | |
| 719 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [closeable()] as const })); | |
| 770 | 720 | |
| 771 | 721 | yield* executor.close(); |
| 772 | 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( | |
| 816 | − makeTestConfig({ plugins: [loginPlugin()] as const }), | |
| 817 | − ); | |
| 765 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [loginPlugin()] as const })); | |
| 818 | 766 | |
| 819 | 767 | const result = yield* executor.tools.invoke( |
| 820 | 768 | "login.ctl.signIn", |
| 821 | 769 | {}, |
| 822 | 770 | { |
| 823 | 771 | onElicitation: (ctx) => { |
| 824 | − expect(Predicate.isTagged(ctx.request, "FormElicitation")).toBe( | |
| 825 | − true, | |
| 826 | − ); | |
| 772 | + expect(Predicate.isTagged(ctx.request, "FormElicitation")).toBe(true); | |
| 827 | 773 | return Effect.succeed( |
| 828 | 774 | new ElicitationResponse({ |
| 829 | 775 | action: "accept", |
| @@ -872,18 +818,14 @@ describe("createExecutor", () => { · 42 unmodified lines | ||
| 872 | 818 | ], |
| 873 | 819 | })); |
| 874 | 820 | |
| 875 | − const executor = yield* createExecutor( | |
| 876 | − makeTestConfig({ plugins: [oauthPlugin()] as const }), | |
| 877 | − ); | |
| 821 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [oauthPlugin()] as const })); | |
| 878 | 822 | |
| 879 | 823 | const result = yield* executor.tools.invoke( |
| 880 | 824 | "oauth.ctl.connect", |
| 881 | 825 | {}, |
| 882 | 826 | { |
| 883 | 827 | onElicitation: (ctx) => { |
| 884 | − expect(Predicate.isTagged(ctx.request, "UrlElicitation")).toBe( | |
| 885 | − true, | |
| 886 | − ); | |
| 828 | + expect(Predicate.isTagged(ctx.request, "UrlElicitation")).toBe(true); | |
| 887 | 829 | return Effect.succeed( |
| 888 | 830 | new ElicitationResponse({ |
| 889 | 831 | action: "accept", |
| @@ -932,9 +874,7 @@ describe("createExecutor", () => { · 42 unmodified lines | ||
| 932 | 874 | ], |
| 933 | 875 | })); |
| 934 | 876 | |
| 935 | − const executor = yield* createExecutor( | |
| 936 | − makeTestConfig({ plugins: [elicitOnly()] as const }), | |
| 937 | − ); | |
| 877 | + const executor = yield* createExecutor(makeTestConfig({ plugins: [elicitOnly()] as const })); | |
| 938 | 878 | |
| 939 | 879 | const action = yield* executor.tools.invoke( |
| 940 | 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( | |
| 989 | − "DB_PASSWORD", | |
| 990 | − "correct-horse-battery-staple", | |
| 991 | − ); | |
| 928 | + const result = yield* executor.rotate.rotate("DB_PASSWORD", "correct-horse-battery-staple"); | |
| 992 | 929 | expect(result).toEqual({ |
| 993 | 930 | oldValue: "hunter2", |
| 994 | 931 | newValue: "correct-horse-battery-staple", |
| @@ -1348,8 +1285,7 @@ describe("cross-scope write preservation (SDK)", () => { · 353 unmodified lines | ||
| 1348 | 1285 | "secrets.set at the inner scope does not wipe an outer-scope row with the same id", |
| 1349 | 1286 | () => |
| 1350 | 1287 | Effect.gen(function* () { |
| 1351 | − const { execOuter, execInner, outerId, innerId } = | |
| 1352 | − yield* makeLayeredExecutors(); | |
| 1288 | + const { execOuter, execInner, outerId, innerId } = yield* makeLayeredExecutors(); | |
| 1353 | 1289 | |
| 1354 | 1290 | // Admin-equivalent writes the org-wide secret at the outer scope. |
| 1355 | 1291 | yield* execInner.secrets.set( |
| @@ -1384,9 +1320,7 @@ describe("cross-scope write preservation (SDK)", () => { · 28 unmodified lines | ||
| 1384 | 1320 | // are the shared, observable source of truth. |
| 1385 | 1321 | const outerRefs = yield* execOuter.secrets.list(); |
| 1386 | 1322 | expect(outerRefs.map((r) => r.id)).toContain("api-token"); |
| 1387 | − expect(outerRefs.find((r) => r.id === "api-token")?.scopeId).toBe( | |
| 1388 | − outerId, | |
| 1389 | − ); | |
| 1323 | + expect(outerRefs.find((r) => r.id === "api-token")?.scopeId).toBe(outerId); | |
| 1390 | 1324 | |
| 1391 | 1325 | // Inner executor's list is de-duplicated by id (innermost wins), |
| 1392 | 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 | 1466 | describe("cross-scope read precedence + remove isolation (SDK)", () => { |
| 1533 | − it.effect( | |
| 1534 | − "secrets.remove at the inner scope does not wipe outer-scope row with same id", | |
| 1535 | − () => | |
| 1536 | − Effect.gen(function* () { | |
| 1537 | − const { execOuter, execInner, outerId, innerId } = | |
| 1538 | − yield* makeLayeredExecutors(); | |
| 1467 | + it.effect("secrets.remove at the inner scope does not wipe outer-scope row with same id", () => | |
| 1468 | + Effect.gen(function* () { | |
| 1469 | + const { execOuter, execInner, outerId, innerId } = yield* makeLayeredExecutors(); | |
| 1539 | 1470 | |
| 1540 | − yield* execInner.secrets.set( | |
| 1541 | − new SetSecretInput({ | |
| 1542 | − id: SecretId.make("api-token"), | |
| 1543 | − scope: outerId, | |
| 1544 | − name: "Org default", | |
| 1545 | − value: "org-default", | |
| 1546 | − }), | |
| 1547 | − ); | |
| 1548 | − yield* execInner.secrets.set( | |
| 1549 | − new SetSecretInput({ | |
| 1550 | − id: SecretId.make("api-token"), | |
| 1551 | − scope: innerId, | |
| 1552 | − name: "Personal override", | |
| 1553 | − value: "personal-override", | |
| 1554 | − }), | |
| 1555 | − ); | |
| 1471 | + yield* execInner.secrets.set( | |
| 1472 | + new SetSecretInput({ | |
| 1473 | + id: SecretId.make("api-token"), | |
| 1474 | + scope: outerId, | |
| 1475 | + name: "Org default", | |
| 1476 | + value: "org-default", | |
| 1477 | + }), | |
| 1478 | + ); | |
| 1479 | + yield* execInner.secrets.set( | |
| 1480 | + new SetSecretInput({ | |
| 1481 | + id: SecretId.make("api-token"), | |
| 1482 | + scope: innerId, | |
| 1483 | + name: "Personal override", | |
| 1484 | + value: "personal-override", | |
| 1485 | + }), | |
| 1486 | + ); | |
| 1556 | 1487 | |
| 1557 | − // Inner caller removes — should only drop the inner override. | |
| 1558 | − yield* execInner.secrets.remove("api-token"); | |
| 1488 | + // Inner caller removes — should only drop the inner override. | |
| 1489 | + yield* execInner.secrets.remove("api-token"); | |
| 1559 | 1490 | |
| 1560 | − // Outer-only executor must still see its org-scope row. | |
| 1561 | − const outerRefs = yield* execOuter.secrets.list(); | |
| 1562 | − expect(outerRefs.map((r) => r.id)).toContain("api-token"); | |
| 1563 | − expect(outerRefs.find((r) => r.id === "api-token")?.scopeId).toBe( | |
| 1564 | − outerId, | |
| 1565 | − ); | |
| 1566 | − }), | |
| 1491 | + // Outer-only executor must still see its org-scope row. | |
| 1492 | + const outerRefs = yield* execOuter.secrets.list(); | |
| 1493 | + expect(outerRefs.map((r) => r.id)).toContain("api-token"); | |
| 1494 | + expect(outerRefs.find((r) => r.id === "api-token")?.scopeId).toBe(outerId); | |
| 1495 | + }), | |
| 1567 | 1496 | ); |
| 1568 | 1497 | |
| 1569 | 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( | |
| 1588 | − "ctx.core.sources.unregister at the inner scope does not wipe outer-scope row", | |
| 1589 | − () => | |
| 1590 | − Effect.gen(function* () { | |
| 1591 | − const { execOuter, execInner } = yield* makeLayeredExecutors(); | |
| 1516 | + it.effect("ctx.core.sources.unregister at the inner scope does not wipe outer-scope row", () => | |
| 1517 | + Effect.gen(function* () { | |
| 1518 | + const { execOuter, execInner } = yield* makeLayeredExecutors(); | |
| 1592 | 1519 | |
| 1593 | − yield* execOuter.tenant.addSource("shared"); | |
| 1594 | − yield* execInner.tenant.addSource("shared"); | |
| 1520 | + yield* execOuter.tenant.addSource("shared"); | |
| 1521 | + yield* execInner.tenant.addSource("shared"); | |
| 1595 | 1522 | |
| 1596 | − // Plugin-owned unregister path (ctx.core.sources.unregister) fires | |
| 1597 | − // via a dedicated extension method. We drive it by calling | |
| 1598 | − // `sources.remove` — which routes through the same deleteSourceById | |
| 1599 | − // helper — but the real regression is the findOne-before-delete | |
| 1600 | − // picking the wrong scope's row. The outer row must survive. | |
| 1601 | − yield* execInner.sources.remove("shared"); | |
| 1523 | + // Plugin-owned unregister path (ctx.core.sources.unregister) fires | |
| 1524 | + // via a dedicated extension method. We drive it by calling | |
| 1525 | + // `sources.remove` — which routes through the same deleteSourceById | |
| 1526 | + // helper — but the real regression is the findOne-before-delete | |
| 1527 | + // picking the wrong scope's row. The outer row must survive. | |
| 1528 | + yield* execInner.sources.remove("shared"); | |
| 1602 | 1529 | |
| 1603 | − const outerSources = yield* execOuter.sources.list(); | |
| 1604 | − expect(outerSources.filter((s) => s.id === "shared")).toHaveLength(1); | |
| 1605 | − }), | |
| 1530 | + const outerSources = yield* execOuter.sources.list(); | |
| 1531 | + expect(outerSources.filter((s) => s.id === "shared")).toHaveLength(1); | |
| 1532 | + }), | |
| 1606 | 1533 | ); |
| 1607 | 1534 | |
| 1608 | 1535 | it.effect( |
| 1609 | 1536 | "tools.invoke picks the innermost tool when the same tool id exists at two scopes", |
| 1610 | 1537 | () => |
| 1611 | 1538 | Effect.gen(function* () { |
| 1612 | − const { execOuter, execInner, outerId, innerId } = | |
| 1613 | − yield* makeMarkerExecutors(); | |
| 1539 | + const { execOuter, execInner, outerId, innerId } = yield* makeMarkerExecutors(); | |
| 1614 | 1540 | |
| 1615 | 1541 | yield* execOuter.marker.register("shared", "outer"); |
| 1616 | 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( | |
| 1641 | − "tools.schema — innermost shadow returns the inner description", | |
| 1642 | − () => | |
| 1643 | − Effect.gen(function* () { | |
| 1644 | − const { execOuter, execInner } = yield* makeMarkerExecutors(); | |
| 1566 | + it.effect("tools.schema — innermost shadow returns the inner description", () => | |
| 1567 | + Effect.gen(function* () { | |
| 1568 | + const { execOuter, execInner } = yield* makeMarkerExecutors(); | |
| 1645 | 1569 | |
| 1646 | − yield* execOuter.marker.register("shared", "outer-desc"); | |
| 1647 | − yield* execInner.marker.register("shared", "inner-desc"); | |
| 1570 | + yield* execOuter.marker.register("shared", "outer-desc"); | |
| 1571 | + yield* execInner.marker.register("shared", "inner-desc"); | |
| 1648 | 1572 | |
| 1649 | − const schema = yield* execInner.tools.schema("shared.t"); | |
| 1650 | − expect(schema?.description).toBe("inner-desc"); | |
| 1651 | − }), | |
| 1573 | + const schema = yield* execInner.tools.schema("shared.t"); | |
| 1574 | + expect(schema?.description).toBe("inner-desc"); | |
| 1575 | + }), | |
| 1652 | 1576 | ); |
| 1653 | 1577 | |
| 1654 | − it.effect( | |
| 1655 | − "tools.list dedupes by id, keeping the innermost row", | |
| 1656 | − () => | |
| 1657 | − Effect.gen(function* () { | |
| 1658 | − const { execOuter, execInner } = yield* makeMarkerExecutors(); | |
| 1578 | + it.effect("tools.list dedupes by id, keeping the innermost row", () => | |
| 1579 | + Effect.gen(function* () { | |
| 1580 | + const { execOuter, execInner } = yield* makeMarkerExecutors(); | |
| 1659 | 1581 | |
| 1660 | − yield* execOuter.marker.register("shared", "outer-desc"); | |
| 1661 | − yield* execInner.marker.register("shared", "inner-desc"); | |
| 1582 | + yield* execOuter.marker.register("shared", "outer-desc"); | |
| 1583 | + yield* execInner.marker.register("shared", "inner-desc"); | |
| 1662 | 1584 | |
| 1663 | − const tools = yield* execInner.tools.list(); | |
| 1664 | − const shared = tools.filter((t) => t.id === "shared.t"); | |
| 1665 | − expect(shared).toHaveLength(1); | |
| 1666 | − expect(shared[0]?.description).toBe("inner-desc"); | |
| 1667 | − }), | |
| 1585 | + const tools = yield* execInner.tools.list(); | |
| 1586 | + const shared = tools.filter((t) => t.id === "shared.t"); | |
| 1587 | + expect(shared).toHaveLength(1); | |
| 1588 | + expect(shared[0]?.description).toBe("inner-desc"); | |
| 1589 | + }), | |
| 1668 | 1590 | ); |
| 1669 | 1591 | |
| 1670 | − it.effect( | |
| 1671 | − "sources.list dedupes by id, keeping the innermost row", | |
| 1672 | − () => | |
| 1673 | − Effect.gen(function* () { | |
| 1674 | − const { execOuter, execInner } = yield* makeMarkerExecutors(); | |
| 1592 | + it.effect("sources.list dedupes by id, keeping the innermost row", () => | |
| 1593 | + Effect.gen(function* () { | |
| 1594 | + const { execOuter, execInner } = yield* makeMarkerExecutors(); | |
| 1675 | 1595 | |
| 1676 | − yield* execOuter.marker.register("shared", "outer-name"); | |
| 1677 | − yield* execInner.marker.register("shared", "inner-name"); | |
| 1596 | + yield* execOuter.marker.register("shared", "outer-name"); | |
| 1597 | + yield* execInner.marker.register("shared", "inner-name"); | |
| 1678 | 1598 | |
| 1679 | − const sources = yield* execInner.sources.list(); | |
| 1680 | − const shared = sources.filter((s) => s.id === "shared"); | |
| 1681 | − expect(shared).toHaveLength(1); | |
| 1682 | − expect(shared[0]?.name).toBe("inner-name"); | |
| 1683 | − }), | |
| 1599 | + const sources = yield* execInner.sources.list(); | |
| 1600 | + const shared = sources.filter((s) => s.id === "shared"); | |
| 1601 | + expect(shared).toHaveLength(1); | |
| 1602 | + expect(shared[0]?.name).toBe("inner-name"); | |
| 1603 | + }), | |
| 1684 | 1604 | ); |
| 1685 | 1605 | |
| 1686 | − it.effect( | |
| 1687 | − "tools.definitions dedupes by (source_id, name), keeping the innermost row", | |
| 1688 | − () => | |
| 1689 | − Effect.gen(function* () { | |
| 1690 | − const { execOuter, execInner } = yield* makeLayeredExecutors(); | |
| 1691 | − | |
| 1692 | − // Register inner first, outer second. Without precedence-aware | |
| 1693 | − // dedup, a naive "iterate rows, last-one-wins" map would end up | |
| 1694 | − // keyed to the outer description just because outer was inserted | |
| 1695 | − // into the store last. | |
| 1696 | − yield* execInner["tenant-defs"].addDefs("S", { | |
| 1697 | − Shared: { type: "string", description: "inner" }, | |
| 1698 | − }); | |
| 1699 | − yield* execOuter["tenant-defs"].addDefs("S", { | |
| 1700 | − Shared: { type: "string", description: "outer" }, | |
| 1701 | − }); | |
| 1606 | + it.effect("tools.definitions dedupes by (source_id, name), keeping the innermost row", () => | |
| 1607 | + Effect.gen(function* () { | |
| 1608 | + const { execOuter, execInner } = yield* makeLayeredExecutors(); | |
| 1609 | + | |
| 1610 | + // Register inner first, outer second. Without precedence-aware | |
| 1611 | + // dedup, a naive "iterate rows, last-one-wins" map would end up | |
| 1612 | + // keyed to the outer description just because outer was inserted | |
| 1613 | + // into the store last. | |
| 1614 | + yield* execInner["tenant-defs"].addDefs("S", { | |
| 1615 | + Shared: { type: "string", description: "inner" }, | |
| 1616 | + }); | |
| 1617 | + yield* execOuter["tenant-defs"].addDefs("S", { | |
| 1618 | + Shared: { type: "string", description: "outer" }, | |
| 1619 | + }); | |
| 1702 | 1620 | |
| 1703 | − const defs = yield* execInner.tools.definitions(); | |
| 1704 | − const shared = defs.S?.Shared as { description?: string } | undefined; | |
| 1705 | − expect(shared?.description).toBe("inner"); | |
| 1706 | − }), | |
| 1621 | + const defs = yield* execInner.tools.definitions(); | |
| 1622 | + const shared = defs.S?.Shared as { description?: string } | undefined; | |
| 1623 | + expect(shared?.description).toBe("inner"); | |
| 1624 | + }), | |
| 1707 | 1625 | ); |
| 1708 | 1626 | |
| 1709 | − it.effect( | |
| 1710 | − "tools.schema attaches innermost $defs when shadowed across scopes", | |
| 1711 | − () => | |
| 1712 | − Effect.gen(function* () { | |
| 1713 | − // Source id "S" with a tool that references $defs/Shared, plus a | |
| 1714 | − // definition "Shared" registered at both scopes. Schema's attached | |
| 1715 | − // $defs should come from the inner scope. | |
| 1716 | − const plugins = [ | |
| 1717 | − tenantPlugin(), | |
| 1718 | − tenantPluginWithDefs(), | |
| 1719 | − definePlugin(() => ({ | |
| 1720 | − id: "ref" as const, | |
| 1721 | − storage: () => ({}), | |
| 1722 | − extension: (ctx) => ({ | |
| 1723 | − register: (sourceId: string) => | |
| 1724 | − ctx.transaction( | |
| 1725 | − ctx.core.sources.register({ | |
| 1726 | − id: sourceId, | |
| 1727 | − scope: ctx.scopes[0]!.id, | |
| 1728 | − kind: "ref", | |
| 1729 | − name: sourceId, | |
| 1730 | − canRemove: true, | |
| 1731 | − tools: [ | |
| 1732 | − { | |
| 1733 | − name: "use", | |
| 1734 | − description: "uses $defs/Shared", | |
| 1735 | − inputSchema: { | |
| 1736 | − type: "object", | |
| 1737 | − properties: { x: { $ref: "#/$defs/Shared" } }, | |
| 1738 | − }, | |
| 1627 | + it.effect("tools.schema attaches innermost $defs when shadowed across scopes", () => | |
| 1628 | + Effect.gen(function* () { | |
| 1629 | + // Source id "S" with a tool that references $defs/Shared, plus a | |
| 1630 | + // definition "Shared" registered at both scopes. Schema's attached | |
| 1631 | + // $defs should come from the inner scope. | |
| 1632 | + const plugins = [ | |
| 1633 | + tenantPlugin(), | |
| 1634 | + tenantPluginWithDefs(), | |
| 1635 | + definePlugin(() => ({ | |
| 1636 | + id: "ref" as const, | |
| 1637 | + storage: () => ({}), | |
| 1638 | + extension: (ctx) => ({ | |
| 1639 | + register: (sourceId: string) => | |
| 1640 | + ctx.transaction( | |
| 1641 | + ctx.core.sources.register({ | |
| 1642 | + id: sourceId, | |
| 1643 | + scope: ctx.scopes[0]!.id, | |
| 1644 | + kind: "ref", | |
| 1645 | + name: sourceId, | |
| 1646 | + canRemove: true, | |
| 1647 | + tools: [ | |
| 1648 | + { | |
| 1649 | + name: "use", | |
| 1650 | + description: "uses $defs/Shared", | |
| 1651 | + inputSchema: { | |
| 1652 | + type: "object", | |
| 1653 | + properties: { x: { $ref: "#/$defs/Shared" } }, | |
| 1739 | 1654 | }, |
| 1740 | − ], | |
| 1741 | − }), | |
| 1742 | − ), | |
| 1743 | − }), | |
| 1744 | − }))(), | |
| 1745 | − ] as const; | |
| 1746 | − const schema = collectSchemas(plugins); | |
| 1747 | − const adapter = makeMemoryAdapter({ schema }); | |
| 1748 | − const blobs = makeInMemoryBlobStore(); | |
| 1749 | − | |
| 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 | − }); | |
| 1655 | + }, | |
| 1656 | + ], | |
| 1657 | + }), | |
| 1658 | + ), | |
| 1659 | + }), | |
| 1660 | + }))(), | |
| 1661 | + ] as const; | |
| 1662 | + const schema = collectSchemas(plugins); | |
| 1663 | + const adapter = makeMemoryAdapter({ schema }); | |
| 1664 | + const blobs = makeInMemoryBlobStore(); | |
| 1771 | 1665 | |
| 1772 | − yield* execOuter.ref.register("S"); | |
| 1773 | − yield* execInner.ref.register("S"); | |
| 1666 | + const outerId = ScopeId.make("org"); | |
| 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", { | |
| 1776 | − Shared: { type: "string", description: "inner" }, | |
| 1777 | − }); | |
| 1778 | − yield* execOuter["tenant-defs"].addDefs("S", { | |
| 1779 | − Shared: { type: "string", description: "outer" }, | |
| 1780 | − }); | |
| 1686 | + yield* execOuter.ref.register("S"); | |
| 1687 | + yield* execInner.ref.register("S"); | |
| 1781 | 1688 | |
| 1782 | − const view = yield* execInner.tools.schema("S.use"); | |
| 1783 | − const input = view?.inputSchema as | |
| 1784 | − | { $defs?: { Shared?: { description?: string } } } | |
| 1785 | − | undefined; | |
| 1786 | − expect(input?.$defs?.Shared?.description).toBe("inner"); | |
| 1787 | − }), | |
| 1689 | + yield* execInner["tenant-defs"].addDefs("S", { | |
| 1690 | + Shared: { type: "string", description: "inner" }, | |
| 1691 | + }); | |
| 1692 | + yield* execOuter["tenant-defs"].addDefs("S", { | |
| 1693 | + Shared: { type: "string", description: "outer" }, | |
| 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 | 121 | } from "./secret-backed-value"; |
| 122 | 122 | |
| 123 | 123 | // Usage tracking — secret/connection refs across plugins |
| 124 | −export { | |
| 125 | − Usage, | |
| 126 | − type UsagesForSecretInput, | |
| 127 | − type UsagesForConnectionInput, | |
| 128 | −} from "./usages"; | |
| 124 | +export { Usage, type UsagesForSecretInput, type UsagesForConnectionInput } from "./usages"; | |
| 129 | 125 | |
| 130 | 126 | // Connections |
| 131 | 127 | export { |
| @@ -15,23 +15,19 @@ const DcrRequestBody = Schema.Struct({ · 14 unmodified lines | ||
| 15 | 15 | redirect_uris: Schema.Array(Schema.String), |
| 16 | 16 | token_endpoint_auth_method: Schema.String, |
| 17 | 17 | }); |
| 18 | −const decodeDcrRequestBody = Schema.decodeUnknownSync( | |
| 19 | − Schema.fromJsonString(DcrRequestBody), | |
| 20 | −); | |
| 18 | +const decodeDcrRequestBody = Schema.decodeUnknownSync(Schema.fromJsonString(DcrRequestBody)); | |
| 21 | 19 | |
| 22 | 20 | const installFetchRouter = ( |
| 23 | 21 | handlers: readonly { match: (url: string) => boolean; handle: Handler }[], |
| 24 | 22 | ): { calls: Array<{ url: string; init: RequestInit }> } => { |
| 25 | 23 | const calls: Array<{ url: string; init: RequestInit }> = []; |
| 26 | − globalThis.fetch = vi | |
| 27 | − .fn() | |
| 28 | − .mockImplementation(async (url: string, init: RequestInit = {}) => { | |
| 29 | − calls.push({ url, init }); | |
| 30 | − for (const h of handlers) { | |
| 31 | − if (h.match(url)) return h.handle(url, init); | |
| 32 | − } | |
| 33 | − return new Response(null, { status: 404 }); | |
| 34 | − }) as typeof fetch; | |
| 24 | + globalThis.fetch = vi.fn().mockImplementation(async (url: string, init: RequestInit = {}) => { | |
| 25 | + calls.push({ url, init }); | |
| 26 | + for (const h of handlers) { | |
| 27 | + if (h.match(url)) return h.handle(url, init); | |
| 28 | + } | |
| 29 | + return new Response(null, { status: 404 }); | |
| 30 | + }) as typeof fetch; | |
| 35 | 31 | return { calls }; |
| 36 | 32 | }; |
| 37 | 33 | |
| @@ -45,14 +41,11 @@ describe("discoverProtectedResourceMetadata", () => { · 7 unmodified lines | ||
| 45 | 41 | it("fetches RFC 9728 well-known metadata on the resource's origin", async () => { |
| 46 | 42 | installFetchRouter([ |
| 47 | 43 | { |
| 48 | − match: (u) => | |
| 49 | − u === | |
| 50 | − "https://api.example.com/.well-known/oauth-protected-resource/graphql", | |
| 44 | + match: (u) => u === "https://api.example.com/.well-known/oauth-protected-resource/graphql", | |
| 51 | 45 | handle: () => new Response(null, { status: 404 }), |
| 52 | 46 | }, |
| 53 | 47 | { |
| 54 | − match: (u) => | |
| 55 | − u === "https://api.example.com/.well-known/oauth-protected-resource", | |
| 48 | + match: (u) => u === "https://api.example.com/.well-known/oauth-protected-resource", | |
| 56 | 49 | handle: () => |
| 57 | 50 | new Response( |
| 58 | 51 | JSON.stringify({ |
| @@ -69,18 +62,14 @@ describe("discoverProtectedResourceMetadata", () => { · 10 unmodified lines | ||
| 69 | 62 | discoverProtectedResourceMetadata("https://api.example.com/graphql"), |
| 70 | 63 | ); |
| 71 | 64 | expect(result).not.toBeNull(); |
| 72 | − expect(result!.metadata.authorization_servers?.[0]).toBe( | |
| 73 | − "https://api.example.com", | |
| 74 | − ); | |
| 65 | + expect(result!.metadata.authorization_servers?.[0]).toBe("https://api.example.com"); | |
| 75 | 66 | expect(result!.metadataUrl).toBe( |
| 76 | 67 | "https://api.example.com/.well-known/oauth-protected-resource", |
| 77 | 68 | ); |
| 78 | 69 | }); |
| 79 | 70 | |
| 80 | 71 | it("returns null when every well-known candidate 404s", async () => { |
| 81 | − installFetchRouter([ | |
| 82 | − { match: () => true, handle: () => new Response(null, { status: 404 }) }, | |
| 83 | − ]); | |
| 72 | + installFetchRouter([{ match: () => true, handle: () => new Response(null, { status: 404 }) }]); | |
| 84 | 73 | |
| 85 | 74 | const result = await Effect.runPromise( |
| 86 | 75 | discoverProtectedResourceMetadata("https://api.example.com/graphql"), |
| @@ -117,13 +106,11 @@ describe("discoverAuthorizationServerMetadata", () => { · 30 unmodified lines | ||
| 117 | 106 | it("falls back to openid-configuration when oauth-authorization-server is absent", async () => { |
| 118 | 107 | installFetchRouter([ |
| 119 | 108 | { |
| 120 | − match: (u) => | |
| 121 | − u === "https://as.example.com/.well-known/oauth-authorization-server", | |
| 109 | + match: (u) => u === "https://as.example.com/.well-known/oauth-authorization-server", | |
| 122 | 110 | handle: () => new Response(null, { status: 404 }), |
| 123 | 111 | }, |
| 124 | 112 | { |
| 125 | − match: (u) => | |
| 126 | − u === "https://as.example.com/.well-known/openid-configuration", | |
| 113 | + match: (u) => u === "https://as.example.com/.well-known/openid-configuration", | |
| 127 | 114 | handle: () => |
| 128 | 115 | new Response( |
| 129 | 116 | JSON.stringify({ |
| @@ -142,9 +129,7 @@ describe("discoverAuthorizationServerMetadata", () => { · 12 unmodified lines | ||
| 142 | 129 | discoverAuthorizationServerMetadata("https://as.example.com"), |
| 143 | 130 | ); |
| 144 | 131 | expect(result).not.toBeNull(); |
| 145 | − expect(result!.metadata.token_endpoint).toBe( | |
| 146 | − "https://as.example.com/token", | |
| 147 | − ); | |
| 132 | + expect(result!.metadata.token_endpoint).toBe("https://as.example.com/token"); | |
| 148 | 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( | |
| 163 | − discoverAuthorizationServerMetadata("https://as"), | |
| 164 | − ); | |
| 147 | + const exit = await Effect.runPromiseExit(discoverAuthorizationServerMetadata("https://as")); | |
| 165 | 148 | expect(Exit.isFailure(exit)).toBe(true); |
| 166 | 149 | }); |
| 167 | 150 | }); |
| @@ -260,11 +243,13 @@ describe("registerDynamicClient", () => { · 92 unmodified lines | ||
| 260 | 243 | if (!Exit.isFailure(exit)) return; |
| 261 | 244 | const reason = exit.cause.reasons.find(Cause.isFailReason); |
| 262 | 245 | const error = reason?.error; |
| 263 | − expect(error).toEqual(expect.objectContaining({ | |
| 264 | − _tag: "OAuthDiscoveryError", | |
| 265 | − status: 400, | |
| 266 | − message: expect.stringMatching(/invalid_client_metadata/), | |
| 267 | − })); | |
| 246 | + expect(error).toEqual( | |
| 247 | + expect.objectContaining({ | |
| 248 | + _tag: "OAuthDiscoveryError", | |
| 249 | + status: 400, | |
| 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 | 264 | installFetchRouter([ |
| 280 | 265 | { |
| 281 | 266 | match: (u) => |
| 282 | − u === | |
| 283 | − "https://backboard.railway.com/.well-known/oauth-protected-resource/graphql/v2", | |
| 267 | + u === "https://backboard.railway.com/.well-known/oauth-protected-resource/graphql/v2", | |
| 284 | 268 | handle: () => new Response(null, { status: 404 }), |
| 285 | 269 | }, |
| 286 | 270 | { |
| 287 | − match: (u) => | |
| 288 | − u === | |
| 289 | − "https://backboard.railway.com/.well-known/oauth-protected-resource", | |
| 271 | + match: (u) => u === "https://backboard.railway.com/.well-known/oauth-protected-resource", | |
| 290 | 272 | handle: () => |
| 291 | 273 | new Response( |
| 292 | 274 | JSON.stringify({ |
| @@ -304,18 +286,14 @@ describe("beginDynamicAuthorization", () => { · 11 unmodified lines | ||
| 304 | 286 | ), |
| 305 | 287 | }, |
| 306 | 288 | { |
| 307 | − match: (u) => | |
| 308 | − u === | |
| 309 | − "https://backboard.railway.com/.well-known/oauth-authorization-server", | |
| 289 | + match: (u) => u === "https://backboard.railway.com/.well-known/oauth-authorization-server", | |
| 310 | 290 | handle: () => |
| 311 | 291 | new Response( |
| 312 | 292 | JSON.stringify({ |
| 313 | 293 | issuer: "https://backboard.railway.com", |
| 314 | − authorization_endpoint: | |
| 315 | − "https://backboard.railway.com/oauth/auth", | |
| 294 | + authorization_endpoint: "https://backboard.railway.com/oauth/auth", | |
| 316 | 295 | token_endpoint: "https://backboard.railway.com/oauth/token", |
| 317 | − registration_endpoint: | |
| 318 | − "https://backboard.railway.com/oauth/register", | |
| 296 | + registration_endpoint: "https://backboard.railway.com/oauth/register", | |
| 319 | 297 | scopes_supported: [ |
| 320 | 298 | "openid", |
| 321 | 299 | "profile", |
| @@ -356,27 +334,19 @@ describe("beginDynamicAuthorization", () => { · 34 unmodified lines | ||
| 356 | 334 | ); |
| 357 | 335 | |
| 358 | 336 | const url = new URL(result.authorizationUrl); |
| 359 | − expect(url.origin + url.pathname).toBe( | |
| 360 | − "https://backboard.railway.com/oauth/auth", | |
| 361 | − ); | |
| 337 | + expect(url.origin + url.pathname).toBe("https://backboard.railway.com/oauth/auth"); | |
| 362 | 338 | expect(url.searchParams.get("client_id")).toBe("dyn-client-42"); |
| 363 | − expect(url.searchParams.get("redirect_uri")).toBe( | |
| 364 | − "https://app.example/cb", | |
| 365 | − ); | |
| 339 | + expect(url.searchParams.get("redirect_uri")).toBe("https://app.example/cb"); | |
| 366 | 340 | expect(url.searchParams.get("response_type")).toBe("code"); |
| 367 | 341 | expect(url.searchParams.get("code_challenge_method")).toBe("S256"); |
| 368 | 342 | expect(url.searchParams.get("state")).toBe("state-xyz"); |
| 369 | 343 | expect(result.codeVerifier.length).toBeGreaterThanOrEqual(43); |
| 370 | − expect(result.state.authorizationServerUrl).toBe( | |
| 371 | − "https://backboard.railway.com", | |
| 372 | − ); | |
| 344 | + expect(result.state.authorizationServerUrl).toBe("https://backboard.railway.com"); | |
| 373 | 345 | expect(result.state.authorizationServerMetadata.token_endpoint).toBe( |
| 374 | 346 | "https://backboard.railway.com/oauth/token", |
| 375 | 347 | ); |
| 376 | 348 | expect(result.state.clientInformation.client_id).toBe("dyn-client-42"); |
| 377 | − expect(result.state.resourceMetadata?.resource).toBe( | |
| 378 | − "https://backboard.railway.com", | |
| 379 | − ); | |
| 349 | + expect(result.state.resourceMetadata?.resource).toBe("https://backboard.railway.com"); | |
| 380 | 350 | }); |
| 381 | 351 | |
| 382 | 352 | it("skips discovery + DCR when previousState is provided", async () => { |
| @@ -422,8 +392,7 @@ describe("beginDynamicAuthorization", () => { · 39 unmodified lines | ||
| 422 | 392 | new Response( |
| 423 | 393 | JSON.stringify({ |
| 424 | 394 | issuer: "https://legacy.example.com", |
| 425 | − authorization_endpoint: | |
| 426 | − "https://legacy.example.com/authorize", | |
| 395 | + authorization_endpoint: "https://legacy.example.com/authorize", | |
| 427 | 396 | token_endpoint: "https://legacy.example.com/token", |
| 428 | 397 | code_challenge_methods_supported: ["plain"], |
| 429 | 398 | response_types_supported: ["code"], |
| @@ -454,8 +423,7 @@ describe("beginDynamicAuthorization", () => { · 24 unmodified lines | ||
| 454 | 423 | new Response( |
| 455 | 424 | JSON.stringify({ |
| 456 | 425 | issuer: "https://static.example.com", |
| 457 | − authorization_endpoint: | |
| 458 | − "https://static.example.com/authorize", | |
| 426 | + authorization_endpoint: "https://static.example.com/authorize", | |
| 459 | 427 | token_endpoint: "https://static.example.com/token", |
| 460 | 428 | code_challenge_methods_supported: ["S256"], |
| 461 | 429 | response_types_supported: ["code"], |
| @@ -109,7 +109,10 @@ describe("buildAuthorizationUrl", () => { · 108 unmodified lines | ||
| 109 | 109 | |
| 110 | 110 | it("preserves pre-existing query params on the authorization URL", () => { |
| 111 | 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 | 117 | expect(url.searchParams.get("tenant")).toBe("acme"); |
| 115 | 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 | 128 | const captureFetch = (response: Response): { calls: FetchArgs[] } => { |
| 126 | 129 | const calls: FetchArgs[] = []; |
| 127 | − globalThis.fetch = vi | |
| 128 | − .fn() | |
| 129 | − .mockImplementation(async (url: string, init: RequestInit) => { | |
| 130 | − calls.push({ url, init }); | |
| 131 | − return response; | |
| 132 | − }) as typeof fetch; | |
| 130 | + globalThis.fetch = vi.fn().mockImplementation(async (url: string, init: RequestInit) => { | |
| 131 | + calls.push({ url, init }); | |
| 132 | + return response; | |
| 133 | + }) as typeof fetch; | |
| 133 | 134 | return { calls }; |
| 134 | 135 | }; |
| 135 | 136 | |
| @@ -138,10 +139,8 @@ const originalFetch = globalThis.fetch; · 2 unmodified lines | ||
| 138 | 139 | const jwtPart = (value: unknown): string => |
| 139 | 140 | Buffer.from(JSON.stringify(value)).toString("base64url"); |
| 140 | 141 | |
| 141 | −const unsignedJwt = ( | |
| 142 | − claims: Record<string, unknown>, | |
| 143 | − alg = "RS256", | |
| 144 | −): string => `${jwtPart({ alg, typ: "JWT" })}.${jwtPart(claims)}.sig`; | |
| 142 | +const unsignedJwt = (claims: Record<string, unknown>, alg = "RS256"): string => | |
| 143 | + `${jwtPart({ alg, typ: "JWT" })}.${jwtPart(claims)}.sig`; | |
| 145 | 144 | |
| 146 | 145 | describe("exchangeAuthorizationCode", () => { |
| 147 | 146 | afterEach(() => { |
| @@ -61,8 +61,7 @@ export const OAUTH2_DEFAULT_TIMEOUT_MS = 20_000; · 60 unmodified lines | ||
| 61 | 61 | // PKCE (RFC 7636) — straight delegation to `oauth4webapi` |
| 62 | 62 | // --------------------------------------------------------------------------- |
| 63 | 63 | |
| 64 | −export const createPkceCodeVerifier = (): string => | |
| 65 | − oauth.generateRandomCodeVerifier(); | |
| 64 | +export const createPkceCodeVerifier = (): string => oauth.generateRandomCodeVerifier(); | |
| 66 | 65 | |
| 67 | 66 | export const createPkceCodeChallenge = (verifier: string): Promise<string> => |
| 68 | 67 | oauth.calculatePKCECodeChallenge(verifier); |
| @@ -165,9 +164,7 @@ const asFromTokenUrlAndIssuer = ( · 96 unmodified lines | ||
| 165 | 164 | return options.idTokenSigningAlgValuesSupported |
| 166 | 165 | ? { |
| 167 | 166 | ...withIssuer, |
| 168 | − id_token_signing_alg_values_supported: [ | |
| 169 | − ...options.idTokenSigningAlgValuesSupported, | |
| 170 | − ], | |
| 167 | + id_token_signing_alg_values_supported: [...options.idTokenSigningAlgValuesSupported], | |
| 171 | 168 | } |
| 172 | 169 | : withIssuer; |
| 173 | 170 | }; |
| @@ -194,9 +191,7 @@ const oauth4webapiRequestOptions = ( · 20 unmodified lines | ||
| 194 | 191 | signal: AbortSignal.timeout(timeoutMs ?? OAUTH2_DEFAULT_TIMEOUT_MS), |
| 195 | 192 | }; |
| 196 | 193 | if (isLoopbackHttpUrl(targetUrl)) { |
| 197 | − (options as { [oauth.allowInsecureRequests]?: boolean })[ | |
| 198 | − oauth.allowInsecureRequests | |
| 199 | − ] = true; | |
| 194 | + (options as { [oauth.allowInsecureRequests]?: boolean })[oauth.allowInsecureRequests] = true; | |
| 200 | 195 | } |
| 201 | 196 | return options; |
| 202 | 197 | }; |
| @@ -211,9 +206,7 @@ const pickClientAuth = ( · 8 unmodified lines | ||
| 211 | 206 | : oauth.ClientSecretPost(clientSecret); |
| 212 | 207 | }; |
| 213 | 208 | |
| 214 | −const tokenResponseFrom = ( | |
| 215 | − r: oauth.TokenEndpointResponse, | |
| 216 | −): OAuth2TokenResponse => ({ | |
| 209 | +const tokenResponseFrom = (r: oauth.TokenEndpointResponse): OAuth2TokenResponse => ({ | |
| 217 | 210 | access_token: r.access_token, |
| 218 | 211 | token_type: r.token_type, |
| 219 | 212 | refresh_token: r.refresh_token, |
| @@ -227,15 +220,14 @@ const tokenResponseFrom = ( · 7 unmodified lines | ||
| 227 | 220 | // its claims against the AS metadata and rejects mismatches we don't care |
| 228 | 221 | // about. Strip the field before delegation. |
| 229 | 222 | const stripIdToken = async (response: Response): Promise<Response> => { |
| 230 | − const body = await response.clone().json().then( | |
| 231 | − (value: unknown) => value, | |
| 232 | − () => null, | |
| 233 | − ); | |
| 234 | − if ( | |
| 235 | − !body || | |
| 236 | − typeof body !== "object" || | |
| 237 | − !("id_token" in (body as Record<string, unknown>)) | |
| 238 | − ) { | |
| 223 | + const body = await response | |
| 224 | + .clone() | |
| 225 | + .json() | |
| 226 | + .then( | |
| 227 | + (value: unknown) => value, | |
| 228 | + () => null, | |
| 229 | + ); | |
| 230 | + if (!body || typeof body !== "object" || !("id_token" in (body as Record<string, unknown>))) { | |
| 239 | 231 | return response; |
| 240 | 232 | } |
| 241 | 233 | const { id_token: _ignored, ...rest } = body as Record<string, unknown>; |
| @@ -252,11 +244,7 @@ const processTokenEndpointResponse = async ( · 10 unmodified lines | ||
| 252 | 244 | response: Response, |
| 253 | 245 | ): Promise<OAuth2TokenResponse> => |
| 254 | 246 | tokenResponseFrom( |
| 255 | − await oauth.processGenericTokenEndpointResponse( | |
| 256 | − as, | |
| 257 | − client, | |
| 258 | − await stripIdToken(response), | |
| 259 | − ), | |
| 247 | + await oauth.processGenericTokenEndpointResponse(as, client, await stripIdToken(response)), | |
| 260 | 248 | ); |
| 261 | 249 | |
| 262 | 250 | // --------------------------------------------------------------------------- |
| @@ -282,14 +270,10 @@ export const exchangeAuthorizationCode = ( · 19 unmodified lines | ||
| 282 | 270 | Effect.tryPromise({ |
| 283 | 271 | try: async () => { |
| 284 | 272 | const as = asFromTokenUrlAndIssuer(input.tokenUrl, input.issuerUrl, { |
| 285 | − idTokenSigningAlgValuesSupported: | |
| 286 | − input.idTokenSigningAlgValuesSupported, | |
| 273 | + idTokenSigningAlgValuesSupported: input.idTokenSigningAlgValuesSupported, | |
| 287 | 274 | }); |
| 288 | 275 | const client: oauth.Client = { client_id: input.clientId }; |
| 289 | − const clientAuth = pickClientAuth( | |
| 290 | − input.clientSecret, | |
| 291 | − input.clientAuth ?? "body", | |
| 292 | − ); | |
| 276 | + const clientAuth = pickClientAuth(input.clientSecret, input.clientAuth ?? "body"); | |
| 293 | 277 | // `authorizationCodeGrantRequest` requires its `callbackParameters` |
| 294 | 278 | // to have been returned from `validateAuthResponse`. Our public API |
| 295 | 279 | // takes the `code` directly (the UI already validated `state` by |
| @@ -334,10 +318,7 @@ export const exchangeClientCredentials = ( · 38 unmodified lines | ||
| 334 | 318 | try: async () => { |
| 335 | 319 | const as = asFromTokenUrl(input.tokenUrl); |
| 336 | 320 | const client: oauth.Client = { client_id: input.clientId }; |
| 337 | − const clientAuth = pickClientAuth( | |
| 338 | − input.clientSecret, | |
| 339 | − input.clientAuth ?? "body", | |
| 340 | − ); | |
| 321 | + const clientAuth = pickClientAuth(input.clientSecret, input.clientAuth ?? "body"); | |
| 341 | 322 | const params = new URLSearchParams(); |
| 342 | 323 | if (input.scopes && input.scopes.length > 0) { |
| 343 | 324 | params.set("scope", input.scopes.join(input.scopeSeparator ?? " ")); |
| @@ -349,11 +330,7 @@ export const exchangeClientCredentials = ( · 5 unmodified lines | ||
| 349 | 330 | params, |
| 350 | 331 | oauth4webapiRequestOptions(input.tokenUrl, input.timeoutMs), |
| 351 | 332 | ); |
| 352 | − const result = await oauth.processClientCredentialsResponse( | |
| 353 | − as, | |
| 354 | − client, | |
| 355 | − response, | |
| 356 | − ); | |
| 333 | + const result = await oauth.processClientCredentialsResponse(as, client, response); | |
| 357 | 334 | return tokenResponseFrom(result); |
| 358 | 335 | }, |
| 359 | 336 | catch: toOAuth2Error, |
| @@ -382,14 +359,10 @@ export const refreshAccessToken = ( · 22 unmodified lines | ||
| 382 | 359 | Effect.tryPromise({ |
| 383 | 360 | try: async () => { |
| 384 | 361 | const as = asFromTokenUrlAndIssuer(input.tokenUrl, input.issuerUrl, { |
| 385 | − idTokenSigningAlgValuesSupported: | |
| 386 | − input.idTokenSigningAlgValuesSupported, | |
| 362 | + idTokenSigningAlgValuesSupported: input.idTokenSigningAlgValuesSupported, | |
| 387 | 363 | }); |
| 388 | 364 | const client: oauth.Client = { client_id: input.clientId }; |
| 389 | − const clientAuth = pickClientAuth( | |
| 390 | − input.clientSecret, | |
| 391 | − input.clientAuth ?? "body", | |
| 392 | − ); | |
| 365 | + const clientAuth = pickClientAuth(input.clientSecret, input.clientAuth ?? "body"); | |
| 393 | 366 | const additionalParameters = |
| 394 | 367 | input.scopes && input.scopes.length > 0 |
| 395 | 368 | ? new URLSearchParams({ |
| @@ -22,9 +22,7 @@ export type OAuthPopupResult<TAuth> = · 21 unmodified lines | ||
| 22 | 22 | readonly error: string; |
| 23 | 23 | }; |
| 24 | 24 | |
| 25 | −export const isOAuthPopupResult = <TAuth>( | |
| 26 | − value: unknown, | |
| 27 | −): value is OAuthPopupResult<TAuth> => | |
| 25 | +export const isOAuthPopupResult = <TAuth>(value: unknown): value is OAuthPopupResult<TAuth> => | |
| 28 | 26 | typeof value === "object" && |
| 29 | 27 | value !== null && |
| 30 | 28 | (value as { type?: unknown }).type === OAUTH_POPUP_MESSAGE_TYPE; |
| @@ -69,15 +69,12 @@ export const OAuthAuthorizationCodeStrategy = Schema.Struct({ · 68 unmodified lines | ||
| 69 | 69 | scopeSeparator: Schema.optional(Schema.String), |
| 70 | 70 | /** Provider-specific params injected at authorization URL build time |
| 71 | 71 | * (Google's `access_type=offline`, `prompt=consent`, ...). */ |
| 72 | − extraAuthorizationParams: Schema.optional( | |
| 73 | − Schema.Record(Schema.String, Schema.String), | |
| 74 | − ), | |
| 72 | + extraAuthorizationParams: Schema.optional(Schema.Record(Schema.String, Schema.String)), | |
| 75 | 73 | /** `"body"` (default) sends client creds in the form body; `"basic"` |
| 76 | 74 | * uses HTTP Basic auth. Stripe-style servers require basic. */ |
| 77 | 75 | clientAuth: Schema.optional(Schema.Literals(["body", "basic"])), |
| 78 | 76 | }); |
| 79 | −export type OAuthAuthorizationCodeStrategy = | |
| 80 | − typeof OAuthAuthorizationCodeStrategy.Type; | |
| 77 | +export type OAuthAuthorizationCodeStrategy = typeof OAuthAuthorizationCodeStrategy.Type; | |
| 81 | 78 | |
| 82 | 79 | /** RFC 6749 §4.4 client credentials — no user redirect, no PKCE. Used |
| 83 | 80 | * for server-to-server integrations where the plugin has both |
| @@ -92,8 +89,7 @@ export const OAuthClientCredentialsStrategy = Schema.Struct({ · 8 unmodified lines | ||
| 92 | 89 | scopeSeparator: Schema.optional(Schema.String), |
| 93 | 90 | clientAuth: Schema.optional(Schema.Literals(["body", "basic"])), |
| 94 | 91 | }); |
| 95 | −export type OAuthClientCredentialsStrategy = | |
| 96 | − typeof OAuthClientCredentialsStrategy.Type; | |
| 92 | +export type OAuthClientCredentialsStrategy = typeof OAuthClientCredentialsStrategy.Type; | |
| 97 | 93 | |
| 98 | 94 | /** Tagged union of every start-time strategy shape. A new strategy (e.g. |
| 99 | 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 | 117 | issuerUrl: Schema.optional(Schema.NullOr(Schema.String)), |
| 122 | 118 | authorizationServerUrl: Schema.optional(Schema.NullOr(Schema.String)), |
| 123 | 119 | authorizationServerMetadataUrl: Schema.NullOr(Schema.String), |
| 124 | − idTokenSigningAlgValuesSupported: Schema.optional( | |
| 125 | − Schema.Array(Schema.String), | |
| 126 | − ), | |
| 120 | + idTokenSigningAlgValuesSupported: Schema.optional(Schema.Array(Schema.String)), | |
| 127 | 121 | /** DCR-minted client_id. Embedded inline (not a secret) — DCR |
| 128 | 122 | * clients are public-ish by design; the secret part (if the AS |
| 129 | 123 | * issued one) is a separate secret row. */ |
| @@ -266,21 +260,15 @@ export interface OAuthCompleteResult { · 136 unmodified lines | ||
| 266 | 260 | // capable plugin group `.addError(OAuthStartError)` etc. and the HTTP |
| 267 | 261 | // edge renders them with the annotated status. |
| 268 | 262 | |
| 269 | −export class OAuthProbeError extends Schema.TaggedErrorClass<OAuthProbeError>()( | |
| 270 | − "OAuthProbeError", | |
| 271 | − { | |
| 272 | − message: Schema.String, | |
| 273 | − }, | |
| 274 | −) { | |
| 263 | +export class OAuthProbeError extends Schema.TaggedErrorClass<OAuthProbeError>()("OAuthProbeError", { | |
| 264 | + message: Schema.String, | |
| 265 | +}) { | |
| 275 | 266 | static annotations = { httpApiStatus: 400 }; |
| 276 | 267 | } |
| 277 | 268 | |
| 278 | −export class OAuthStartError extends Schema.TaggedErrorClass<OAuthStartError>()( | |
| 279 | − "OAuthStartError", | |
| 280 | − { | |
| 281 | − message: Schema.String, | |
| 282 | − }, | |
| 283 | −) { | |
| 269 | +export class OAuthStartError extends Schema.TaggedErrorClass<OAuthStartError>()("OAuthStartError", { | |
| 270 | + message: Schema.String, | |
| 271 | +}) { | |
| 284 | 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 | 302 | export interface OAuthService { |
| 315 | − readonly probe: ( | |
| 316 | − input: OAuthProbeInput, | |
| 317 | − ) => Effect.Effect<OAuthProbeResult, OAuthProbeError>; | |
| 303 | + readonly probe: (input: OAuthProbeInput) => Effect.Effect<OAuthProbeResult, OAuthProbeError>; | |
| 318 | 304 | readonly start: ( |
| 319 | 305 | input: OAuthStartInput, |
| 320 | 306 | ) => Effect.Effect<OAuthStartResult, OAuthStartError | StorageFailure>; |
| @@ -326,10 +312,7 @@ export interface OAuthService { · 5 unmodified lines | ||
| 326 | 312 | >; |
| 327 | 313 | /** Drop an in-flight session without completing — used when the |
| 328 | 314 | * user cancels the popup or the source is deleted mid-onboarding. */ |
| 329 | − readonly cancel: ( | |
| 330 | − sessionId: string, | |
| 331 | − tokenScope?: string, | |
| 332 | − ) => Effect.Effect<void, StorageFailure>; | |
| 315 | + readonly cancel: (sessionId: string, tokenScope?: string) => Effect.Effect<void, StorageFailure>; | |
| 333 | 316 | } |
| 334 | 317 | |
| 335 | 318 | // --------------------------------------------------------------------------- |
| @@ -1,11 +1,6 @@ | ||
| 1 | 1 | import type { Context, Effect, Layer } from "effect"; |
| 2 | 2 | import type { HttpApiGroup } from "effect/unstable/httpapi"; |
| 3 | −import type { | |
| 4 | − DBAdapter, | |
| 5 | − DBSchema, | |
| 6 | − StorageFailure, | |
| 7 | − TypedAdapter, | |
| 8 | −} from "@executor-js/storage-core"; | |
| 3 | +import type { DBAdapter, DBSchema, StorageFailure, TypedAdapter } from "@executor-js/storage-core"; | |
| 9 | 4 | |
| 10 | 5 | import type { PluginBlobStore } from "./blob"; |
| 11 | 6 | import type { |
| @@ -15,12 +10,7 @@ import type { · 3 unmodified lines | ||
| 15 | 10 | CreateConnectionInput, |
| 16 | 11 | UpdateConnectionTokensInput, |
| 17 | 12 | } from "./connections"; |
| 18 | −import type { | |
| 19 | − DefinitionsInput, | |
| 20 | − SourceInput, | |
| 21 | − ToolAnnotations, | |
| 22 | − ToolRow, | |
| 23 | −} from "./core-schema"; | |
| 13 | +import type { DefinitionsInput, SourceInput, ToolAnnotations, ToolRow } from "./core-schema"; | |
| 24 | 14 | import type { SourceDetectionResult } from "./types"; |
| 25 | 15 | import type { |
| 26 | 16 | ElicitationDeclinedError, |
| @@ -70,9 +60,7 @@ export interface StorageDeps<TSchema extends DBSchema | undefined = undefined> { · 43 unmodified lines | ||
| 70 | 60 | * `@executor-js/api` `withCapture`) is the one place that |
| 71 | 61 | * translates it to the opaque `InternalError({ traceId })`. |
| 72 | 62 | */ |
| 73 | − readonly adapter: TSchema extends DBSchema | |
| 74 | − ? TypedAdapter<TSchema, StorageFailure> | |
| 75 | − : DBAdapter; | |
| 63 | + readonly adapter: TSchema extends DBSchema ? TypedAdapter<TSchema, StorageFailure> : DBAdapter; | |
| 76 | 64 | readonly blobs: PluginBlobStore; |
| 77 | 65 | } |
| 78 | 66 | |
| @@ -114,12 +102,8 @@ export interface PluginCtx<TStore = unknown> { · 35 unmodified lines | ||
| 114 | 102 | |
| 115 | 103 | readonly core: { |
| 116 | 104 | readonly sources: { |
| 117 | − readonly register: ( | |
| 118 | − input: SourceInput, | |
| 119 | − ) => Effect.Effect<void, StorageFailure>; | |
| 120 | − readonly unregister: ( | |
| 121 | − sourceId: string, | |
| 122 | − ) => Effect.Effect<void, StorageFailure>; | |
| 105 | + readonly register: (input: SourceInput) => Effect.Effect<void, StorageFailure>; | |
| 106 | + readonly unregister: (sourceId: string) => Effect.Effect<void, StorageFailure>; | |
| 123 | 107 | readonly update: (input: { |
| 124 | 108 | readonly id: string; |
| 125 | 109 | readonly scope: string; |
| @@ -134,9 +118,7 @@ export interface PluginCtx<TStore = unknown> { · 8 unmodified lines | ||
| 134 | 118 | * same `ctx.transaction` as `sources.register` for atomicity. |
| 135 | 119 | * Replaces any existing defs for the given sourceId. */ |
| 136 | 120 | readonly definitions: { |
| 137 | − readonly register: ( | |
| 138 | − input: DefinitionsInput, | |
| 139 | − ) => Effect.Effect<void, StorageFailure>; | |
| 121 | + readonly register: (input: DefinitionsInput) => Effect.Effect<void, StorageFailure>; | |
| 140 | 122 | }; |
| 141 | 123 | }; |
| 142 | 124 | |
| @@ -157,9 +139,7 @@ export interface PluginCtx<TStore = unknown> { · 14 unmodified lines | ||
| 157 | 139 | * `executor.secrets.set` on the host surface, but OAuth2 refresh |
| 158 | 140 | * and one-shot token capture from plugin-owned flows need it here |
| 159 | 141 | * too. Same routing rules as the host-level setter. */ |
| 160 | − readonly set: ( | |
| 161 | − input: SetSecretInput, | |
| 162 | − ) => Effect.Effect<SecretRef, StorageFailure>; | |
| 142 | + readonly set: (input: SetSecretInput) => Effect.Effect<SecretRef, StorageFailure>; | |
| 163 | 143 | /** Delete a secret from its pinned provider and the core table. |
| 164 | 144 | * Rejects with `SecretOwnedByConnectionError` if the row is owned |
| 165 | 145 | * by a connection — callers must go through `connections.remove` |
| @@ -168,10 +148,7 @@ export interface PluginCtx<TStore = unknown> { · 2 unmodified lines | ||
| 168 | 148 | * the user to detach the listed sources first. */ |
| 169 | 149 | readonly remove: ( |
| 170 | 150 | id: string, |
| 171 | − ) => Effect.Effect< | |
| 172 | − void, | |
| 173 | − SecretOwnedByConnectionError | SecretInUseError | StorageFailure | |
| 174 | − >; | |
| 151 | + ) => Effect.Effect<void, SecretOwnedByConnectionError | SecretInUseError | StorageFailure>; | |
| 175 | 152 | }; |
| 176 | 153 | |
| 177 | 154 | /** Connections — product-level sign-in state. Owns backing secret |
| @@ -180,22 +157,14 @@ export interface PluginCtx<TStore = unknown> { · 2 unmodified lines | ||
| 180 | 157 | * fresh token (the SDK handles refresh via the registered provider |
| 181 | 158 | * keyed by `connection.provider`). */ |
| 182 | 159 | readonly connections: { |
| 183 | − readonly get: ( | |
| 184 | − id: string, | |
| 185 | − ) => Effect.Effect<ConnectionRef | null, StorageFailure>; | |
| 160 | + readonly get: (id: string) => Effect.Effect<ConnectionRef | null, StorageFailure>; | |
| 186 | 161 | readonly list: () => Effect.Effect<readonly ConnectionRef[], StorageFailure>; |
| 187 | 162 | readonly create: ( |
| 188 | 163 | input: CreateConnectionInput, |
| 189 | − ) => Effect.Effect< | |
| 190 | − ConnectionRef, | |
| 191 | − ConnectionProviderNotRegisteredError | StorageFailure | |
| 192 | − >; | |
| 164 | + ) => Effect.Effect<ConnectionRef, ConnectionProviderNotRegisteredError | StorageFailure>; | |
| 193 | 165 | readonly updateTokens: ( |
| 194 | 166 | input: UpdateConnectionTokensInput, |
| 195 | − ) => Effect.Effect< | |
| 196 | − ConnectionRef, | |
| 197 | − ConnectionNotFoundError | StorageFailure | |
| 198 | − >; | |
| 167 | + ) => Effect.Effect<ConnectionRef, ConnectionNotFoundError | StorageFailure>; | |
| 199 | 168 | readonly setIdentityLabel: ( |
| 200 | 169 | id: string, |
| 201 | 170 | label: string | null, |
| @@ -217,9 +186,7 @@ export interface PluginCtx<TStore = unknown> { · 15 unmodified lines | ||
| 217 | 186 | /** Refuses with `ConnectionInUseError` if any plugin reports the |
| 218 | 187 | * connection as in use. Caller surfaces the `usages` list to the |
| 219 | 188 | * user. */ |
| 220 | − readonly remove: ( | |
| 221 | − id: string, | |
| 222 | − ) => Effect.Effect<void, ConnectionInUseError | StorageFailure>; | |
| 189 | + readonly remove: (id: string) => Effect.Effect<void, ConnectionInUseError | StorageFailure>; | |
| 223 | 190 | }; |
| 224 | 191 | |
| 225 | 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 | 197 | * adapter's transaction method. Use this in extension methods that |
| 231 | 198 | * need atomicity across plugin storage writes AND core source/tool |
| 232 | 199 | * registration. */ |
| 233 | − readonly transaction: <A, E>( | |
| 234 | − effect: Effect.Effect<A, E>, | |
| 235 | − ) => Effect.Effect<A, E | StorageFailure>; | |
| 200 | + readonly transaction: <A, E>(effect: Effect.Effect<A, E>) => Effect.Effect<A, E | StorageFailure>; | |
| 236 | 201 | } |
| 237 | 202 | |
| 238 | 203 | // --------------------------------------------------------------------------- |
| @@ -265,9 +230,7 @@ export interface StaticToolDecl<TStore = unknown> { · 26 unmodified lines | ||
| 265 | 230 | * Inline because static tools have no plugin storage to resolve from; |
| 266 | 231 | * the plugin author literally writes this at definition time. */ |
| 267 | 232 | readonly annotations?: ToolAnnotations; |
| 268 | − readonly handler: ( | |
| 269 | − input: StaticToolHandlerInput<TStore>, | |
| 270 | − ) => Effect.Effect<unknown, unknown>; | |
| 233 | + readonly handler: (input: StaticToolHandlerInput<TStore>) => Effect.Effect<unknown, unknown>; | |
| 271 | 234 | } |
| 272 | 235 | |
| 273 | 236 | export interface StaticSourceDecl<TStore = unknown> { |
| @@ -388,9 +351,7 @@ export interface PluginSpec< · 114 unmodified lines | ||
| 388 | 351 | * Handlers close over `self` via the closure, so a control tool |
| 389 | 352 | * that delegates to the plugin's real API is a one-liner: |
| 390 | 353 | * `({ args }) => self.addSpec(args)`. */ |
| 391 | − readonly staticSources?: ( | |
| 392 | − self: NoInfer<TExtension>, | |
| 393 | − ) => readonly StaticSourceDecl<TStore>[]; | |
| 354 | + readonly staticSources?: (self: NoInfer<TExtension>) => readonly StaticSourceDecl<TStore>[]; | |
| 394 | 355 | |
| 395 | 356 | /** HttpApiGroup contributed by this plugin. Composed into the host's |
| 396 | 357 | * `HttpApi` via the `addGroup` helper at runtime. The host mounts |
| @@ -446,9 +407,7 @@ export interface PluginSpec< · 49 unmodified lines | ||
| 446 | 407 | * map doesn't have the toolId. The plugin reads its own enrichment |
| 447 | 408 | * via `ctx.storage` and returns the result. Optional — plugins with |
| 448 | 409 | * only static tools can omit it. */ |
| 449 | − readonly invokeTool?: ( | |
| 450 | − input: InvokeToolInput<TStore>, | |
| 451 | − ) => Effect.Effect<unknown, unknown>; | |
| 410 | + readonly invokeTool?: (input: InvokeToolInput<TStore>) => Effect.Effect<unknown, unknown>; | |
| 452 | 411 | |
| 453 | 412 | /** Bulk resolve annotations (requiresApproval, approvalDescription, |
| 454 | 413 | * mayElicit) for a set of tool rows under a single source. Called |
| @@ -497,13 +456,9 @@ export interface PluginSpec< · 42 unmodified lines | ||
| 497 | 456 | * by this plugin. Plugin-side cleanup only; the executor deletes |
| 498 | 457 | * the core source/tool rows after this callback returns, inside |
| 499 | 458 | * the same transaction. */ |
| 500 | − readonly removeSource?: ( | |
| 501 | − input: SourceLifecycleInput<TStore>, | |
| 502 | − ) => Effect.Effect<void, unknown>; | |
| 459 | + readonly removeSource?: (input: SourceLifecycleInput<TStore>) => Effect.Effect<void, unknown>; | |
| 503 | 460 | |
| 504 | − readonly refreshSource?: ( | |
| 505 | − input: SourceLifecycleInput<TStore>, | |
| 506 | − ) => Effect.Effect<void, unknown>; | |
| 461 | + readonly refreshSource?: (input: SourceLifecycleInput<TStore>) => Effect.Effect<void, unknown>; | |
| 507 | 462 | |
| 508 | 463 | /** URL autodetection hook. When the user pastes a URL in the |
| 509 | 464 | * onboarding UI, `executor.sources.detect(url)` fans out to every |
| @@ -526,9 +481,7 @@ export interface PluginSpec< · 16 unmodified lines | ||
| 526 | 481 | readonly secretProviders?: |
| 527 | 482 | | readonly SecretProvider[] |
| 528 | 483 | | ((ctx: PluginCtx<TStore>) => readonly SecretProvider[]) |
| 529 | − | (( | |
| 530 | − ctx: PluginCtx<TStore>, | |
| 531 | − ) => Effect.Effect<readonly SecretProvider[]>); | |
| 484 | + | ((ctx: PluginCtx<TStore>) => Effect.Effect<readonly SecretProvider[]>); | |
| 532 | 485 | |
| 533 | 486 | /** Connection providers contributed by this plugin. Same registration |
| 534 | 487 | * shape as `secretProviders`. Each provider's `key` is what |
| @@ -538,9 +491,7 @@ export interface PluginSpec< · 3 unmodified lines | ||
| 538 | 491 | readonly connectionProviders?: |
| 539 | 492 | | readonly ConnectionProvider[] |
| 540 | 493 | | ((ctx: PluginCtx<TStore>) => readonly ConnectionProvider[]) |
| 541 | − | (( | |
| 542 | − ctx: PluginCtx<TStore>, | |
| 543 | − ) => Effect.Effect<readonly ConnectionProvider[]>); | |
| 494 | + | ((ctx: PluginCtx<TStore>) => Effect.Effect<readonly ConnectionProvider[]>); | |
| 544 | 495 | |
| 545 | 496 | readonly close?: () => Effect.Effect<void, unknown>; |
| 546 | 497 | } |
| @@ -558,15 +509,7 @@ export interface Plugin< · 11 unmodified lines | ||
| 558 | 509 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 559 | 510 | THandlersLayer extends Layer.Layer<any, any, any> = any, |
| 560 | 511 | TGroup extends HttpApiGroup.Any = HttpApiGroup.Any, |
| 561 | −> extends PluginSpec< | |
| 562 | − TId, | |
| 563 | − TExtension, | |
| 564 | − TStore, | |
| 565 | − TSchema, | |
| 566 | − TExtensionService, | |
| 567 | − THandlersLayer, | |
| 568 | − TGroup | |
| 569 | − > {} | |
| 512 | +> extends PluginSpec<TId, TExtension, TStore, TSchema, TExtensionService, THandlersLayer, TGroup> {} | |
| 570 | 513 | |
| 571 | 514 | // --------------------------------------------------------------------------- |
| 572 | 515 | // definePlugin — factory-returning-spec. Options from the author factory |
| @@ -589,15 +532,7 @@ export type ConfiguredPlugin< · 16 unmodified lines | ||
| 589 | 532 | options?: TOptions & { |
| 590 | 533 | readonly storage?: (deps: StorageDeps<TSchema>) => TStore; |
| 591 | 534 | }, |
| 592 | −) => Plugin< | |
| 593 | − TId, | |
| 594 | − TExtension, | |
| 595 | − TStore, | |
| 596 | − TSchema, | |
| 597 | − TExtensionService, | |
| 598 | − THandlersLayer, | |
| 599 | − TGroup | |
| 600 | −>; | |
| 535 | +) => Plugin<TId, TExtension, TStore, TSchema, TExtensionService, THandlersLayer, TGroup>; | |
| 601 | 536 | |
| 602 | 537 | // eslint-disable-next-line @typescript-eslint/ban-types |
| 603 | 538 | export function definePlugin< |
| @@ -614,15 +549,7 @@ export function definePlugin< · 10 unmodified lines | ||
| 614 | 549 | >( |
| 615 | 550 | authorFactory: ( |
| 616 | 551 | options?: TOptions, |
| 617 | − ) => PluginSpec< | |
| 618 | − TId, | |
| 619 | − TExtension, | |
| 620 | − TStore, | |
| 621 | − TSchema, | |
| 622 | − TExtensionService, | |
| 623 | − THandlersLayer, | |
| 624 | − TGroup | |
| 625 | − >, | |
| 552 | + ) => PluginSpec<TId, TExtension, TStore, TSchema, TExtensionService, THandlersLayer, TGroup>, | |
| 626 | 553 | ): ConfiguredPlugin< |
| 627 | 554 | TId, |
| 628 | 555 | TExtension, |
| @@ -643,9 +570,7 @@ export function definePlugin< · 14 unmodified lines | ||
| 643 | 570 | } = options ?? {}; |
| 644 | 571 | |
| 645 | 572 | const hasAuthorOptions = Object.keys(rest).length > 0; |
| 646 | − const spec = authorFactory( | |
| 647 | − hasAuthorOptions ? (rest as TOptions) : undefined, | |
| 648 | − ); | |
| 573 | + const spec = authorFactory(hasAuthorOptions ? (rest as TOptions) : undefined); | |
| 649 | 574 | |
| 650 | 575 | return { |
| 651 | 576 | ...spec, |
| @@ -664,12 +589,7 @@ export function definePlugin< · 12 unmodified lines | ||
| 664 | 589 | export type AnyPlugin = Plugin<string>; |
| 665 | 590 | |
| 666 | 591 | export type PluginExtensions<TPlugins extends readonly AnyPlugin[]> = { |
| 667 | − readonly [P in TPlugins[number] as P["id"]]: P extends Plugin< | |
| 668 | − string, | |
| 669 | − infer TExt | |
| 670 | − > | |
| 671 | − ? TExt | |
| 672 | − : never; | |
| 592 | + readonly [P in TPlugins[number] as P["id"]]: P extends Plugin<string, infer TExt> ? TExt : never; | |
| 673 | 593 | }; |
| 674 | 594 | |
| 675 | 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 | 5 | import type { ToolPolicyRow } from "./core-schema"; |
| 6 | 6 | import { PolicyId } from "./ids"; |
| 7 | 7 | import { createExecutor } from "./executor"; |
| 8 | −import { | |
| 9 | − ElicitationResponse, | |
| 10 | − type ElicitationHandler, | |
| 11 | −} from "./elicitation"; | |
| 8 | +import { ElicitationResponse, type ElicitationHandler } from "./elicitation"; | |
| 12 | 9 | import { |
| 13 | 10 | effectivePolicyFromSorted, |
| 14 | 11 | isValidPattern, |
| @@ -149,13 +146,8 @@ describe("resolveToolPolicy", () => { · 134 unmodified lines | ||
| 149 | 146 | ROW("outer", "vercel.*", "block", "a0", "org"), |
| 150 | 147 | ROW("inner", "vercel.dns.create", "approve", "a0", "user"), |
| 151 | 148 | ]; |
| 152 | − const rank = (row: Pick<ToolPolicyRow, "scope_id">) => | |
| 153 | − row.scope_id === "user" ? 0 : 1; | |
| 154 | − const result = resolveToolPolicy( | |
| 155 | − "vercel.dns.create", | |
| 156 | − policies, | |
| 157 | − rank, | |
| 158 | − ); | |
| 149 | + const rank = (row: Pick<ToolPolicyRow, "scope_id">) => (row.scope_id === "user" ? 0 : 1); | |
| 150 | + const result = resolveToolPolicy("vercel.dns.create", policies, rank); | |
| 159 | 151 | expect(result?.action).toBe("approve"); |
| 160 | 152 | expect(result?.policyId).toBe("inner"); |
| 161 | 153 | }); |
| @@ -167,18 +159,12 @@ describe("resolveToolPolicy", () => { · 5 unmodified lines | ||
| 167 | 159 | // refetch and reorder math sees colliding neighbor keys. |
| 168 | 160 | const a = resolveToolPolicy( |
| 169 | 161 | "vercel.dns.create", |
| 170 | − [ | |
| 171 | − ROW("z", "vercel.dns.*", "block", "a0"), | |
| 172 | − ROW("a", "vercel.dns.*", "approve", "a0"), | |
| 173 | − ], | |
| 162 | + [ROW("z", "vercel.dns.*", "block", "a0"), ROW("a", "vercel.dns.*", "approve", "a0")], | |
| 174 | 163 | flatRank, |
| 175 | 164 | ); |
| 176 | 165 | const b = resolveToolPolicy( |
| 177 | 166 | "vercel.dns.create", |
| 178 | − [ | |
| 179 | − ROW("a", "vercel.dns.*", "approve", "a0"), | |
| 180 | − ROW("z", "vercel.dns.*", "block", "a0"), | |
| 181 | − ], | |
| 167 | + [ROW("a", "vercel.dns.*", "approve", "a0"), ROW("z", "vercel.dns.*", "block", "a0")], | |
| 182 | 168 | flatRank, |
| 183 | 169 | ); |
| 184 | 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 | 177 | describe("effectivePolicyFromSorted", () => { |
| 192 | − const POL = ( | |
| 193 | − id: string, | |
| 194 | − pattern: string, | |
| 195 | − action: "approve" | "require_approval" | "block", | |
| 196 | − ) => ({ id: PolicyId.make(id), pattern, action }); | |
| 178 | + const POL = (id: string, pattern: string, action: "approve" | "require_approval" | "block") => ({ | |
| 179 | + id: PolicyId.make(id), | |
| 180 | + pattern, | |
| 181 | + action, | |
| 182 | + }); | |
| 197 | 183 | |
| 198 | 184 | it("returns user policy when one matches", () => { |
| 199 | 185 | const result = effectivePolicyFromSorted( |
| @@ -218,9 +204,10 @@ describe("effectivePolicyFromSorted", () => { · 18 unmodified lines | ||
| 218 | 204 | }); |
| 219 | 205 | |
| 220 | 206 | it("falls back to plugin default approve when annotation is false/undefined", () => { |
| 221 | − expect( | |
| 222 | − effectivePolicyFromSorted("vercel.dns.create", [], false), | |
| 223 | − ).toEqual({ action: "approve", source: "plugin-default" }); | |
| 207 | + expect(effectivePolicyFromSorted("vercel.dns.create", [], false)).toEqual({ | |
| 208 | + action: "approve", | |
| 209 | + source: "plugin-default", | |
| 210 | + }); | |
| 224 | 211 | expect(effectivePolicyFromSorted("vercel.dns.create", [])).toEqual({ |
| 225 | 212 | action: "approve", |
| 226 | 213 | source: "plugin-default", |
| @@ -342,10 +329,7 @@ describe("executor.policies", () => { · 115 unmodified lines | ||
| 342 | 329 | expect(second.position < first.position).toBe(true); |
| 343 | 330 | |
| 344 | 331 | const rules = yield* executor.policies.list(); |
| 345 | − expect(rules.map((r) => r.pattern)).toEqual([ | |
| 346 | − "vercel.delete", | |
| 347 | − "vercel.*", | |
| 348 | − ]); | |
| 332 | + expect(rules.map((r) => r.pattern)).toEqual(["vercel.delete", "vercel.*"]); | |
| 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( | |
| 429 | − "consecutive creates produce strictly increasing-precedence keys", | |
| 430 | − () => | |
| 431 | − Effect.gen(function* () { | |
| 432 | − const executor = yield* setupExecutor(); | |
| 433 | − const created: string[] = []; | |
| 434 | − for (const pattern of ["a.*", "b.*", "c.*", "d.*"]) { | |
| 435 | − const row = yield* executor.policies.create({ | |
| 436 | − scope: "test-scope", | |
| 437 | − pattern, | |
| 438 | − action: "approve", | |
| 439 | − }); | |
| 440 | − created.push(row.position); | |
| 441 | − } | |
| 442 | − // Each new key sorts strictly above the previous (lower lex | |
| 443 | − // order = higher precedence). No collisions. | |
| 444 | − for (let i = 1; i < created.length; i++) { | |
| 445 | − expect(created[i]! < created[i - 1]!).toBe(true); | |
| 446 | − } | |
| 447 | − // List order matches insertion-reverse. | |
| 448 | − const rules = yield* executor.policies.list(); | |
| 449 | − expect(rules.map((r) => r.pattern)).toEqual([ | |
| 450 | − "d.*", | |
| 451 | − "c.*", | |
| 452 | − "b.*", | |
| 453 | − "a.*", | |
| 454 | − ]); | |
| 455 | − }), | |
| 412 | + it.effect("consecutive creates produce strictly increasing-precedence keys", () => | |
| 413 | + Effect.gen(function* () { | |
| 414 | + const executor = yield* setupExecutor(); | |
| 415 | + const created: string[] = []; | |
| 416 | + for (const pattern of ["a.*", "b.*", "c.*", "d.*"]) { | |
| 417 | + const row = yield* executor.policies.create({ | |
| 418 | + scope: "test-scope", | |
| 419 | + pattern, | |
| 420 | + action: "approve", | |
| 421 | + }); | |
| 422 | + created.push(row.position); | |
| 423 | + } | |
| 424 | + // Each new key sorts strictly above the previous (lower lex | |
| 425 | + // order = higher precedence). No collisions. | |
| 426 | + for (let i = 1; i < created.length; i++) { | |
| 427 | + expect(created[i]! < created[i - 1]!).toBe(true); | |
| 428 | + } | |
| 429 | + // List order matches insertion-reverse. | |
| 430 | + const rules = yield* executor.policies.list(); | |
| 431 | + expect(rules.map((r) => r.pattern)).toEqual(["d.*", "c.*", "b.*", "a.*"]); | |
| 432 | + }), | |
| 456 | 433 | ); |
| 457 | 434 | |
| 458 | 435 | it.effect("remove deletes the row", () => |
| @@ -521,14 +498,10 @@ describe("blocked tools", () => { · 62 unmodified lines | ||
| 521 | 498 | pattern: "vercel.*", |
| 522 | 499 | action: "block", |
| 523 | 500 | }); |
| 524 | − const result = yield* Effect.result( | |
| 525 | − executor.tools.invoke("vercel.delete", {}), | |
| 526 | − ); | |
| 501 | + const result = yield* Effect.result(executor.tools.invoke("vercel.delete", {})); | |
| 527 | 502 | expect(Result.isFailure(result)).toBe(true); |
| 528 | 503 | if (!Result.isFailure(result)) return; |
| 529 | − expect(Predicate.isTagged("ToolBlockedError")(result.failure)).toBe( | |
| 530 | − true, | |
| 531 | − ); | |
| 504 | + expect(Predicate.isTagged("ToolBlockedError")(result.failure)).toBe(true); | |
| 532 | 505 | if (!Predicate.isTagged("ToolBlockedError")(result.failure)) return; |
| 533 | 506 | expect(result.failure.pattern).toBe("vercel.*"); |
| 534 | 507 | }), |
| @@ -545,9 +518,13 @@ describe("approve / require_approval interaction with annotations", () => { · 10 unmodified lines | ||
| 545 | 518 | action: "approve", |
| 546 | 519 | }); |
| 547 | 520 | const calls = { count: 0 }; |
| 548 | − const result = yield* executor.tools.invoke("vercel.delete", {}, { | |
| 549 | − onElicitation: recordingHandler(calls), | |
| 550 | − }); | |
| 521 | + const result = yield* executor.tools.invoke( | |
| 522 | + "vercel.delete", | |
| 523 | + {}, | |
| 524 | + { | |
| 525 | + onElicitation: recordingHandler(calls), | |
| 526 | + }, | |
| 527 | + ); | |
| 551 | 528 | expect(calls.count).toBe(0); |
| 552 | 529 | expect(result).toEqual({ ran: "vercel.delete" }); |
| 553 | 530 | }), |
| @@ -562,9 +539,13 @@ describe("approve / require_approval interaction with annotations", () => { · 8 unmodified lines | ||
| 562 | 539 | action: "require_approval", |
| 563 | 540 | }); |
| 564 | 541 | const calls = { count: 0 }; |
| 565 | − yield* executor.tools.invoke("vercel.deploy", {}, { | |
| 566 | − onElicitation: recordingHandler(calls), | |
| 567 | − }); | |
| 542 | + yield* executor.tools.invoke( | |
| 543 | + "vercel.deploy", | |
| 544 | + {}, | |
| 545 | + { | |
| 546 | + onElicitation: recordingHandler(calls), | |
| 547 | + }, | |
| 548 | + ); | |
| 568 | 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 | 559 | action: "require_approval", |
| 579 | 560 | }); |
| 580 | 561 | const result = yield* Effect.result( |
| 581 | − executor.tools.invoke("vercel.deploy", {}, { | |
| 582 | − onElicitation: decliningHandler, | |
| 583 | − }), | |
| 562 | + executor.tools.invoke( | |
| 563 | + "vercel.deploy", | |
| 564 | + {}, | |
| 565 | + { | |
| 566 | + onElicitation: decliningHandler, | |
| 567 | + }, | |
| 568 | + ), | |
| 584 | 569 | ); |
| 585 | 570 | expect(Result.isFailure(result)).toBe(true); |
| 586 | 571 | if (!Result.isFailure(result)) return; |
| 587 | − expect( | |
| 588 | − Predicate.isTagged("ElicitationDeclinedError")(result.failure), | |
| 589 | − ).toBe(true); | |
| 572 | + expect(Predicate.isTagged("ElicitationDeclinedError")(result.failure)).toBe(true); | |
| 590 | 573 | }), |
| 591 | 574 | ); |
| 592 | 575 | |
| @@ -597,16 +580,24 @@ describe("approve / require_approval interaction with annotations", () => { · 4 unmodified lines | ||
| 597 | 580 | // marks any tool whose name contains "delete" as requiring |
| 598 | 581 | // approval, so the prompt should fire. |
| 599 | 582 | const calls = { count: 0 }; |
| 600 | − yield* executor.tools.invoke("vercel.delete", {}, { | |
| 601 | − onElicitation: recordingHandler(calls), | |
| 602 | − }); | |
| 583 | + yield* executor.tools.invoke( | |
| 584 | + "vercel.delete", | |
| 585 | + {}, | |
| 586 | + { | |
| 587 | + onElicitation: recordingHandler(calls), | |
| 588 | + }, | |
| 589 | + ); | |
| 603 | 590 | expect(calls.count).toBe(1); |
| 604 | 591 | |
| 605 | 592 | // vercel.deploy has no plugin-required approval and no policy, |
| 606 | 593 | // so no prompt. |
| 607 | − yield* executor.tools.invoke("vercel.deploy", {}, { | |
| 608 | − onElicitation: recordingHandler(calls), | |
| 609 | − }); | |
| 594 | + yield* executor.tools.invoke( | |
| 595 | + "vercel.deploy", | |
| 596 | + {}, | |
| 597 | + { | |
| 598 | + onElicitation: recordingHandler(calls), | |
| 599 | + }, | |
| 600 | + ); | |
| 610 | 601 | expect(calls.count).toBe(1); |
| 611 | 602 | }), |
| 612 | 603 | ); |
| @@ -184,9 +184,7 @@ export const resolveToolPolicy = ( · 183 unmodified lines | ||
| 184 | 184 | // policies in evaluation order from the API. |
| 185 | 185 | // --------------------------------------------------------------------------- |
| 186 | 186 | |
| 187 | −const liftPlugin = ( | |
| 188 | − defaultRequiresApproval: boolean | undefined, | |
| 189 | −): EffectivePolicy => | |
| 187 | +const liftPlugin = (defaultRequiresApproval: boolean | undefined): EffectivePolicy => | |
| 190 | 188 | defaultRequiresApproval |
| 191 | 189 | ? { action: "require_approval", source: "plugin-default" } |
| 192 | 190 | : { action: "approve", source: "plugin-default" }; |
| @@ -245,8 +243,4 @@ export const rowToToolPolicy = (row: ToolPolicyRow): ToolPolicy => ({ · 52 unmodified lines | ||
| 245 | 243 | // inputs with effect/Schema. |
| 246 | 244 | // --------------------------------------------------------------------------- |
| 247 | 245 | |
| 248 | −export const ToolPolicyActionSchema = Schema.Literals([ | |
| 249 | − "approve", | |
| 250 | − "require_approval", | |
| 251 | − "block", | |
| 252 | −]); | |
| 246 | +export const ToolPolicyActionSchema = Schema.Literals(["approve", "require_approval", "block"]); | |
| @@ -40,25 +40,24 @@ import { Scope } from "./scope"; · 39 unmodified lines | ||
| 40 | 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> | |
| 44 | − ? string | |
| 45 | − : T extends readonly (infer U)[] | |
| 46 | − ? readonly Unbrand<U>[] | |
| 47 | − : T extends ReadonlyMap<infer K, infer V> | |
| 48 | − ? ReadonlyMap<Unbrand<K>, Unbrand<V>> | |
| 49 | − : T extends ReadonlySet<infer U> | |
| 50 | − ? ReadonlySet<Unbrand<U>> | |
| 51 | − : T extends Date | |
| 52 | − ? T | |
| 53 | − : T extends (...args: infer A) => infer R | |
| 54 | − ? (...args: { [I in keyof A]: Unbrand<A[I]> }) => Unbrand<R> | |
| 55 | − : T extends object | |
| 56 | − ? { readonly [K in keyof T]: Unbrand<T[K]> } | |
| 57 | − : T; | |
| 58 | − | |
| 59 | −export type Promisified<T> = T extends ( | |
| 60 | − ...args: infer A | |
| 61 | −) => Effect.Effect<infer R, infer _E> | |
| 43 | +type Unbrand<T> = | |
| 44 | + T extends Brand.Brand<string> | |
| 45 | + ? string | |
| 46 | + : T extends readonly (infer U)[] | |
| 47 | + ? readonly Unbrand<U>[] | |
| 48 | + : T extends ReadonlyMap<infer K, infer V> | |
| 49 | + ? ReadonlyMap<Unbrand<K>, Unbrand<V>> | |
| 50 | + : T extends ReadonlySet<infer U> | |
| 51 | + ? ReadonlySet<Unbrand<U>> | |
| 52 | + : T extends Date | |
| 53 | + ? T | |
| 54 | + : T extends (...args: infer A) => infer R | |
| 55 | + ? (...args: { [I in keyof A]: Unbrand<A[I]> }) => Unbrand<R> | |
| 56 | + : T extends object | |
| 57 | + ? { readonly [K in keyof T]: Unbrand<T[K]> } | |
| 58 | + : T; | |
| 59 | + | |
| 60 | +export type Promisified<T> = T extends (...args: infer A) => Effect.Effect<infer R, infer _E> | |
| 62 | 61 | ? (...args: { [I in keyof A]: Unbrand<A[I]> }) => Promise<R> |
| 63 | 62 | : T extends readonly unknown[] |
| 64 | 63 | ? T |
| @@ -103,10 +102,7 @@ const isPlainObject = (v: unknown): v is Record<string | symbol, unknown> => · 38 unmodified lines | ||
| 103 | 102 | const promisifyDeep = <T>(value: T): Promisified<T> => { |
| 104 | 103 | if (typeof value === "function") { |
| 105 | 104 | return ((...args: unknown[]) => { |
| 106 | − const result = (value as (...a: unknown[]) => unknown).apply( | |
| 107 | − undefined, | |
| 108 | − args, | |
| 109 | − ); | |
| 105 | + const result = (value as (...a: unknown[]) => unknown).apply(undefined, args); | |
| 110 | 106 | if (Effect.isEffect(result)) { |
| 111 | 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 | 117 | const v = Reflect.get(target, prop, receiver); |
| 122 | 118 | if (typeof v === "function") { |
| 123 | 119 | return (...args: unknown[]) => { |
| 124 | − const result = (v as (...a: unknown[]) => unknown).apply( | |
| 125 | − target, | |
| 126 | − args, | |
| 127 | − ); | |
| 120 | + const result = (v as (...a: unknown[]) => unknown).apply(target, args); | |
| 128 | 121 | if (Effect.isEffect(result)) { |
| 129 | 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 | 136 | // construct an executor with just `{ plugins: [...] }`. |
| 144 | 137 | // --------------------------------------------------------------------------- |
| 145 | 138 | |
| 146 | −export const createExecutor = async < | |
| 147 | − const TPlugins extends readonly AnyPlugin[] = [], | |
| 148 | −>( | |
| 139 | +export const createExecutor = async <const TPlugins extends readonly AnyPlugin[] = []>( | |
| 149 | 140 | config: ExecutorConfig<TPlugins>, |
| 150 | 141 | ): Promise<Executor<TPlugins>> => { |
| 151 | 142 | const plugins = (config?.plugins ?? []) as TPlugins; |
| @@ -183,9 +174,7 @@ export const createExecutor = async < · 31 unmodified lines | ||
| 183 | 174 | // get the tagged error as the rejected value. See |
| 184 | 175 | // notes/promise-sdk-typed-errors.md for the planned `runPromiseExit` |
| 185 | 176 | // rewrite that exposes the full error union to consumers. |
| 186 | − const effectExecutor = await Effect.runPromise( | |
| 187 | − createEffectExecutor(effectConfig), | |
| 188 | − ); | |
| 177 | + const effectExecutor = await Effect.runPromise(createEffectExecutor(effectConfig)); | |
| 189 | 178 | |
| 190 | 179 | return promisifyDeep(effectExecutor) as Executor<TPlugins>; |
| 191 | 180 | }; |
| @@ -33,8 +33,7 @@ const echoPlugin = definePlugin(() => ({ · 32 unmodified lines | ||
| 33 | 33 | }, |
| 34 | 34 | ], |
| 35 | 35 | extension: () => ({ |
| 36 | − greet: (name: string) => | |
| 37 | − Effect.succeed(`hello, ${name}`) as Effect.Effect<string, never>, | |
| 36 | + greet: (name: string) => 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 | 109 | "ap.ctl.go", |
| 111 | 110 | {}, |
| 112 | 111 | { |
| 113 | − onElicitation: () => | |
| 114 | − Effect.succeed({ action: "decline" as const }) as any, | |
| 112 | + onElicitation: () => Effect.succeed({ action: "decline" as const }) as any, | |
| 115 | 113 | }, |
| 116 | 114 | ), |
| 117 | 115 | ).rejects.toMatchObject({ |
| @@ -51,11 +51,7 @@ export { · 50 unmodified lines | ||
| 51 | 51 | |
| 52 | 52 | // File-config helper for the CLI. Plain typed-object factory with no |
| 53 | 53 | // Effect in its signature, so it's safe to live on the Promise surface. |
| 54 | −export { | |
| 55 | − defineExecutorConfig, | |
| 56 | − type ExecutorCliConfig, | |
| 57 | − type ExecutorDialect, | |
| 58 | −} from "./config"; | |
| 54 | +export { defineExecutorConfig, type ExecutorCliConfig, type ExecutorDialect } from "./config"; | |
| 59 | 55 | |
| 60 | 56 | // Error tags — Promise callers handle these via .catch(). |
| 61 | 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 | 179 | it.effect("caller-supplied scope_id filter is stripped (can't bypass isolation)", () => |
| 180 | 180 | Effect.gen(function* () { |
| 181 | 181 | const inner = makeMemoryAdapter({ schema }); |
| 182 | − yield* typedAdapter<typeof schema>( | |
| 183 | − scopeAdapter(inner, { scopes: ["c"] }, schema), | |
| 184 | − ).create({ | |
| 182 | + yield* typedAdapter<typeof schema>(scopeAdapter(inner, { scopes: ["c"] }, schema)).create({ | |
| 185 | 183 | model: "thing", |
| 186 | 184 | data: { id: "t-hidden", scope_id: "c", value: "secret" }, |
| 187 | 185 | }); |
| 188 | 186 | |
| 189 | − const reader = typedAdapter<typeof schema>( | |
| 190 | − scopeAdapter(inner, { scopes: ["a"] }, schema), | |
| 191 | − ); | |
| 187 | + const reader = typedAdapter<typeof schema>(scopeAdapter(inner, { scopes: ["a"] }, schema)); | |
| 192 | 188 | // Attempt to bypass by explicitly filtering for scope_id "c". |
| 193 | 189 | const rows = yield* reader.findMany({ |
| 194 | 190 | model: "thing", |
| @@ -49,10 +49,7 @@ const collectScopedModels = (schema: DBSchema): Set<string> => { · 48 unmodified lines | ||
| 49 | 49 | return out; |
| 50 | 50 | }; |
| 51 | 51 | |
| 52 | −const withScopeRead = ( | |
| 53 | − where: readonly Where[] | undefined, | |
| 54 | − ctx: ScopeContext, | |
| 55 | −): Where[] => { | |
| 52 | +const withScopeRead = (where: readonly Where[] | undefined, ctx: ScopeContext): Where[] => { | |
| 56 | 53 | const base = (where ?? []).filter((w) => w.field !== SCOPE_FIELD); |
| 57 | 54 | const callerScope = (where ?? []).find((w) => w.field === SCOPE_FIELD); |
| 58 | 55 | |
| @@ -123,11 +120,7 @@ const wrapTxMethods = ( · 64 unmodified lines | ||
| 123 | 120 | create: (data) => |
| 124 | 121 | isScoped(data.model) |
| 125 | 122 | ? Effect.flatMap( |
| 126 | − assertScopedWrite( | |
| 127 | − data.model, | |
| 128 | − data.data as Record<string, unknown>, | |
| 129 | − ctx, | |
| 130 | − ), | |
| 123 | + assertScopedWrite(data.model, data.data as Record<string, unknown>, ctx), | |
| 131 | 124 | () => inner.create(data), |
| 132 | 125 | ) |
| 133 | 126 | : inner.create(data), |
| @@ -136,11 +129,7 @@ const wrapTxMethods = ( · 2 unmodified lines | ||
| 136 | 129 | ? Effect.flatMap( |
| 137 | 130 | Effect.all( |
| 138 | 131 | data.data.map((row) => |
| 139 | − assertScopedWrite( | |
| 140 | − data.model, | |
| 141 | − row as Record<string, unknown>, | |
| 142 | − ctx, | |
| 143 | − ), | |
| 132 | + assertScopedWrite(data.model, row as Record<string, unknown>, ctx), | |
| 144 | 133 | ), |
| 145 | 134 | ), |
| 146 | 135 | () => inner.createMany(data), |
| @@ -168,11 +157,7 @@ const wrapTxMethods = ( · 21 unmodified lines | ||
| 168 | 157 | // rows you can read. That's sufficient for isolation; we |
| 169 | 158 | // don't need to force-stamp on update. |
| 170 | 159 | (data.update as Record<string, unknown>)[SCOPE_FIELD] !== undefined |
| 171 | − ? assertScopedWrite( | |
| 172 | − data.model, | |
| 173 | − data.update as Record<string, unknown>, | |
| 174 | − ctx, | |
| 175 | − ) | |
| 160 | + ? assertScopedWrite(data.model, data.update as Record<string, unknown>, ctx) | |
| 176 | 161 | : Effect.void, |
| 177 | 162 | () => |
| 178 | 163 | inner.update({ |
| @@ -185,11 +170,7 @@ const wrapTxMethods = ( · 6 unmodified lines | ||
| 185 | 170 | isScoped(data.model) |
| 186 | 171 | ? Effect.flatMap( |
| 187 | 172 | (data.update as Record<string, unknown>)[SCOPE_FIELD] !== undefined |
| 188 | − ? assertScopedWrite( | |
| 189 | − data.model, | |
| 190 | − data.update as Record<string, unknown>, | |
| 191 | − ctx, | |
| 192 | − ) | |
| 173 | + ? assertScopedWrite(data.model, data.update as Record<string, unknown>, ctx) | |
| 193 | 174 | : Effect.void, |
| 194 | 175 | () => |
| 195 | 176 | inner.updateMany({ |
| @@ -209,22 +190,14 @@ const wrapTxMethods = ( · 13 unmodified lines | ||
| 209 | 190 | }; |
| 210 | 191 | }; |
| 211 | 192 | |
| 212 | −export const scopeAdapter = ( | |
| 213 | − inner: DBAdapter, | |
| 214 | − ctx: ScopeContext, | |
| 215 | − schema: DBSchema, | |
| 216 | −): DBAdapter => { | |
| 193 | +export const scopeAdapter = (inner: DBAdapter, ctx: ScopeContext, schema: DBSchema): DBAdapter => { | |
| 217 | 194 | const scopedModels = collectScopedModels(schema); |
| 218 | 195 | const tx = wrapTxMethods(inner, ctx, scopedModels); |
| 219 | 196 | return { |
| 220 | 197 | ...tx, |
| 221 | 198 | transaction: (callback) => |
| 222 | 199 | inner.transaction((rawTrx) => { |
| 223 | − const scopedTrx: DBTransactionAdapter = wrapTxMethods( | |
| 224 | − rawTrx, | |
| 225 | − ctx, | |
| 226 | − scopedModels, | |
| 227 | − ); | |
| 200 | + const scopedTrx: DBTransactionAdapter = wrapTxMethods(rawTrx, ctx, scopedModels); | |
| 228 | 201 | return callback(scopedTrx); |
| 229 | 202 | }), |
| 230 | 203 | createSchema: inner.createSchema, |
| @@ -30,31 +30,18 @@ export interface SecretProvider { · 29 unmodified lines | ||
| 30 | 30 | * failed, etc.) surface as `StorageFailure` — the executor treats |
| 31 | 31 | * a provider call the same as a DB call; `StorageError` is captured |
| 32 | 32 | * at the HTTP edge to `InternalError`, `UniqueViolationError` dies. */ |
| 33 | − readonly get: ( | |
| 34 | − id: string, | |
| 35 | − scope: string, | |
| 36 | − ) => Effect.Effect<string | null, StorageFailure>; | |
| 33 | + readonly get: (id: string, scope: string) => Effect.Effect<string | null, StorageFailure>; | |
| 37 | 34 | /** Check whether a provider has a backing value without returning it. |
| 38 | 35 | * Providers that can answer this cheaply should implement it so |
| 39 | 36 | * stale core routing rows don't appear as selectable secrets. */ |
| 40 | − readonly has?: ( | |
| 41 | − id: string, | |
| 42 | − scope: string, | |
| 43 | − ) => Effect.Effect<boolean, StorageFailure>; | |
| 37 | + readonly has?: (id: string, scope: string) => Effect.Effect<boolean, StorageFailure>; | |
| 44 | 38 | /** Set a secret value at a named scope. Only called on writable |
| 45 | 39 | * providers. Providers that partition by scope use this arg to |
| 46 | 40 | * decide where to write; flat providers ignore it. */ |
| 47 | − readonly set?: ( | |
| 48 | − id: string, | |
| 49 | − value: string, | |
| 50 | − scope: string, | |
| 51 | − ) => Effect.Effect<void, StorageFailure>; | |
| 41 | + readonly set?: (id: string, value: string, scope: string) => Effect.Effect<void, StorageFailure>; | |
| 52 | 42 | /** Delete a secret at a named scope. Only called on writable providers. |
| 53 | 43 | * Returns true if something was deleted. */ |
| 54 | − readonly delete?: ( | |
| 55 | − id: string, | |
| 56 | − scope: string, | |
| 57 | − ) => Effect.Effect<boolean, StorageFailure>; | |
| 44 | + readonly delete?: (id: string, scope: string) => Effect.Effect<boolean, StorageFailure>; | |
| 58 | 45 | /** Enumerate known secret entries. Optional — not all backends can |
| 59 | 46 | * enumerate (env-backed providers, for example). */ |
| 60 | 47 | readonly list?: () => Effect.Effect< |
| @@ -90,9 +77,7 @@ export class SecretRef extends Schema.Class<SecretRef>("SecretRef")({ · 29 unmodified lines | ||
| 90 | 77 | // OAuth token exchange writes to the innermost per-user scope. |
| 91 | 78 | // --------------------------------------------------------------------------- |
| 92 | 79 | |
| 93 | −export class SetSecretInput extends Schema.Class<SetSecretInput>( | |
| 94 | − "SetSecretInput", | |
| 95 | −)({ | |
| 80 | +export class SetSecretInput extends Schema.Class<SetSecretInput>("SetSecretInput")({ | |
| 96 | 81 | id: SecretId, |
| 97 | 82 | /** Scope id to own this secret. Must be one of the executor's |
| 98 | 83 | * configured scopes. */ |
| @@ -16,21 +16,18 @@ import { Scope } from "./scope"; · 15 unmodified lines | ||
| 16 | 16 | // need multi-scope behavior can pass `scopes` explicitly. |
| 17 | 17 | // --------------------------------------------------------------------------- |
| 18 | 18 | |
| 19 | −export const makeTestConfig = < | |
| 20 | − const TPlugins extends readonly AnyPlugin[] = [], | |
| 21 | −>(options?: { | |
| 19 | +export const makeTestConfig = <const TPlugins extends readonly AnyPlugin[] = []>(options?: { | |
| 22 | 20 | readonly scopeName?: string; |
| 23 | 21 | readonly scopes?: readonly Scope[]; |
| 24 | 22 | readonly plugins?: TPlugins; |
| 25 | 23 | }): ExecutorConfig<TPlugins> => { |
| 26 | − const scopes = | |
| 27 | − options?.scopes ?? [ | |
| 28 | − new Scope({ | |
| 29 | − id: ScopeId.make("test-scope"), | |
| 30 | − name: options?.scopeName ?? "test", | |
| 31 | − createdAt: new Date(), | |
| 32 | − }), | |
| 33 | − ]; | |
| 24 | + const scopes = options?.scopes ?? [ | |
| 25 | + new Scope({ | |
| 26 | + id: ScopeId.make("test-scope"), | |
| 27 | + name: options?.scopeName ?? "test", | |
| 28 | + createdAt: new Date(), | |
| 29 | + }), | |
| 30 | + ]; | |
| 34 | 31 | |
| 35 | 32 | const schema = collectSchemas(options?.plugins ?? []); |
| 36 | 33 | |
| @@ -65,9 +65,7 @@ export class ToolSchema extends Schema.Class<ToolSchema>("ToolSchema")({ · 64 unmodified lines | ||
| 65 | 65 | outputSchema: Schema.optional(Schema.Unknown), |
| 66 | 66 | inputTypeScript: Schema.optional(Schema.String), |
| 67 | 67 | outputTypeScript: Schema.optional(Schema.String), |
| 68 | − typeScriptDefinitions: Schema.optional( | |
| 69 | − Schema.Record(Schema.String, Schema.String), | |
| 70 | − ), | |
| 68 | + typeScriptDefinitions: Schema.optional(Schema.Record(Schema.String, Schema.String)), | |
| 71 | 69 | }) {} |
| 72 | 70 | |
| 73 | 71 | // --------------------------------------------------------------------------- |
| @@ -6,26 +6,27 @@ Modifications were made to remove authentication-specific types and to · 5 unmodified lines | ||
| 6 | 6 | adapt the interfaces to the executor project. |
| 7 | 7 | |
| 8 | 8 | Vendored files: |
| 9 | − - src/adapter.ts — ported from packages/core/src/db/adapter/index.ts | |
| 10 | − and types.ts (DBAdapter interface, Where DSL, CustomAdapter, | |
| 11 | − DBAdapterFactoryConfig). Promise → Effect conversion, auth-specific | |
| 12 | − model names stripped. | |
| 13 | − - src/schema.ts — ported from packages/core/src/db/type.ts | |
| 14 | − (DBSchema, DBFieldAttribute, InferDB*). | |
| 15 | − - src/factory.ts — ported from packages/core/src/db/adapter/factory.ts | |
| 16 | − (createAdapterFactory). Promise → Effect. Stripped auth-specific | |
| 17 | − concerns: BetterAuthOptions generic, numeric serial ids, joins, | |
| 18 | − telemetry spans, logger, plural model resolution. Matches our | |
| 19 | − simpler CustomAdapter + DBAdapterFactoryConfig shape. | |
| 20 | − - src/testing/memory.ts — ported from | |
| 21 | − packages/memory-adapter/src/memory-adapter.ts (memoryAdapter). | |
| 22 | − Promise → Effect. Piped through our createAdapter. | |
| 23 | − - storage-drizzle/src/adapter.ts — ported from | |
| 24 | − packages/drizzle-adapter/src/drizzle-adapter.ts (drizzleAdapter). | |
| 25 | − Promise → Effect. Takes an explicit tables map instead of reading | |
| 26 | − db._.fullSchema. Piped through our createAdapter. | |
| 27 | − | |
| 28 | −------------------------------------------------------------------------------- | |
| 9 | + | |
| 10 | +- src/adapter.ts — ported from packages/core/src/db/adapter/index.ts | |
| 11 | + and types.ts (DBAdapter interface, Where DSL, CustomAdapter, | |
| 12 | + DBAdapterFactoryConfig). Promise → Effect conversion, auth-specific | |
| 13 | + model names stripped. | |
| 14 | +- src/schema.ts — ported from packages/core/src/db/type.ts | |
| 15 | + (DBSchema, DBFieldAttribute, InferDB\*). | |
| 16 | +- src/factory.ts — ported from packages/core/src/db/adapter/factory.ts | |
| 17 | + (createAdapterFactory). Promise → Effect. Stripped auth-specific | |
| 18 | + concerns: BetterAuthOptions generic, numeric serial ids, joins, | |
| 19 | + telemetry spans, logger, plural model resolution. Matches our | |
| 20 | + simpler CustomAdapter + DBAdapterFactoryConfig shape. | |
| 21 | +- src/testing/memory.ts — ported from | |
| 22 | + packages/memory-adapter/src/memory-adapter.ts (memoryAdapter). | |
| 23 | + Promise → Effect. Piped through our createAdapter. | |
| 24 | +- storage-drizzle/src/adapter.ts — ported from | |
| 25 | + packages/drizzle-adapter/src/drizzle-adapter.ts (drizzleAdapter). | |
| 26 | + Promise → Effect. Takes an explicit tables map instead of reading | |
| 27 | + db.\_.fullSchema. Piped through our createAdapter. | |
| 28 | + | |
| 29 | +--- | |
| 29 | 30 | |
| 30 | 31 | The MIT License (MIT) |
| 31 | 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 | 42 | view, no runtime cost: |
| 43 | 43 | |
| 44 | 44 | ```ts |
| 45 | −import { | |
| 46 | − typedAdapter, | |
| 47 | − type DBAdapter, | |
| 48 | − type DBSchema, | |
| 49 | −} from "@executor-js/storage-core"; | |
| 45 | +import { typedAdapter, type DBAdapter, type DBSchema } from "@executor-js/storage-core"; | |
| 50 | 46 | |
| 51 | 47 | const schema = { |
| 52 | 48 | secrets: { |
| @@ -53,6 +53,12 @@ · 52 unmodified lines | ||
| 53 | 53 | "@standard-schema/spec": "^1.0.0", |
| 54 | 54 | "effect": "catalog:" |
| 55 | 55 | }, |
| 56 | + "devDependencies": { | |
| 57 | + "@effect/vitest": "catalog:", | |
| 58 | + "tsup": "catalog:", | |
| 59 | + "typescript": "catalog:", | |
| 60 | + "vitest": "catalog:" | |
| 61 | + }, | |
| 56 | 62 | "peerDependencies": { |
| 57 | 63 | "@effect/vitest": "catalog:", |
| 58 | 64 | "vitest": "catalog:" |
| @@ -64,11 +70,5 @@ · 5 unmodified lines | ||
| 64 | 70 | "vitest": { |
| 65 | 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 | 192 | * "does my adapter support JSON?") without having to thread the config |
| 193 | 193 | * manually. Populated by `createAdapter`. |
| 194 | 194 | */ |
| 195 | − options?: | |
| 196 | − | ({ adapterConfig: DBAdapterFactoryConfig } & CustomAdapter["options"]) | |
| 197 | − | undefined; | |
| 195 | + options?: ({ adapterConfig: DBAdapterFactoryConfig } & CustomAdapter["options"]) | undefined; | |
| 198 | 196 | }; |
| 199 | 197 | |
| 200 | 198 | // --------------------------------------------------------------------------- |
| @@ -252,10 +250,7 @@ export interface CustomAdapter { · 51 unmodified lines | ||
| 252 | 250 | join?: JoinConfig | undefined; |
| 253 | 251 | }) => Effect.Effect<T[], StorageFailure>; |
| 254 | 252 | |
| 255 | − delete: (data: { | |
| 256 | − model: string; | |
| 257 | − where: CleanedWhere[]; | |
| 258 | − }) => Effect.Effect<void, StorageFailure>; | |
| 253 | + delete: (data: { model: string; where: CleanedWhere[] }) => Effect.Effect<void, StorageFailure>; | |
| 259 | 254 | |
| 260 | 255 | deleteMany: (data: { |
| 261 | 256 | model: string; |
| @@ -35,8 +35,6 @@ export class StorageError extends Data.TaggedError("StorageError")<{ · 34 unmodified lines | ||
| 35 | 35 | * driver message) stays internal because plugin code rarely needs it |
| 36 | 36 | * and surfacing it leaks backend specifics. |
| 37 | 37 | */ |
| 38 | −export class UniqueViolationError extends Data.TaggedError( | |
| 39 | − "UniqueViolationError", | |
| 40 | −)<{ | |
| 38 | +export class UniqueViolationError extends Data.TaggedError("UniqueViolationError")<{ | |
| 41 | 39 | readonly model?: string; |
| 42 | 40 | }> {} |
| @@ -11,10 +11,7 @@ import { Effect } from "effect"; · 10 unmodified lines | ||
| 11 | 11 | |
| 12 | 12 | import type { DBAdapter } from "./adapter"; |
| 13 | 13 | import { makeMemoryAdapter } from "./testing/memory"; |
| 14 | −import { | |
| 15 | − conformanceSchema, | |
| 16 | − runAdapterConformance, | |
| 17 | −} from "./testing/conformance"; | |
| 14 | +import { conformanceSchema, runAdapterConformance } from "./testing/conformance"; | |
| 18 | 15 | |
| 19 | 16 | const withAdapter = <A, E>( |
| 20 | 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 | 41 | ? T[number] |
| 42 | 42 | : never; |
| 43 | 43 | |
| 44 | −export type InferDBFieldOutput<T extends DBFieldAttribute> = | |
| 45 | − T["returned"] extends false | |
| 46 | − ? never | |
| 47 | − : T["required"] extends false | |
| 48 | − ? InferDBValueType<T["type"]> | undefined | null | |
| 49 | − : InferDBValueType<T["type"]>; | |
| 44 | +export type InferDBFieldOutput<T extends DBFieldAttribute> = T["returned"] extends false | |
| 45 | + ? never | |
| 46 | + : T["required"] extends false | |
| 47 | + ? InferDBValueType<T["type"]> | undefined | null | |
| 48 | + : InferDBValueType<T["type"]>; | |
| 50 | 49 | |
| 51 | −export type InferDBFieldInput<T extends DBFieldAttribute> = InferDBValueType< | |
| 52 | − T["type"] | |
| 53 | −>; | |
| 50 | +export type InferDBFieldInput<T extends DBFieldAttribute> = InferDBValueType<T["type"]>; | |
| 54 | 51 | |
| 55 | 52 | export type InferDBFieldsInput<Field> = |
| 56 | 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< | |
| 74 | − Fields extends Record<string, DBFieldAttribute>, | |
| 75 | −> = | |
| 70 | +export type InferDBFieldsOutput<Fields extends Record<string, DBFieldAttribute>> = | |
| 76 | 71 | Fields extends Record<infer Key, DBFieldAttribute> |
| 77 | 72 | ? { |
| 78 | 73 | [K in Key as Fields[K]["returned"] extends false |
| 79 | 74 | ? never |
| 80 | 75 | : Fields[K]["required"] extends false |
| 81 | − ? Fields[K]["defaultValue"] extends | |
| 82 | − | boolean | |
| 83 | − | string | |
| 84 | − | number | |
| 85 | − | Date | |
| 76 | + ? Fields[K]["defaultValue"] extends boolean | string | number | Date | |
| 86 | 77 | ? K |
| 87 | 78 | : never |
| 88 | 79 | : K]: InferDBFieldOutput<Fields[K]>; |
| @@ -90,11 +81,7 @@ export type InferDBFieldsOutput< · 1 unmodified line | ||
| 90 | 81 | [K in Key as Fields[K]["returned"] extends false |
| 91 | 82 | ? never |
| 92 | 83 | : Fields[K]["required"] extends false |
| 93 | − ? Fields[K]["defaultValue"] extends | |
| 94 | − | boolean | |
| 95 | − | string | |
| 96 | − | number | |
| 97 | − | Date | |
| 84 | + ? Fields[K]["defaultValue"] extends boolean | string | number | Date | |
| 98 | 85 | ? never |
| 99 | 86 | : K |
| 100 | 87 | : never]?: InferDBFieldOutput<Fields[K]> | null; |
| @@ -131,12 +118,7 @@ export type DBFieldAttributeConfig = { · 30 unmodified lines | ||
| 131 | 118 | model: string; |
| 132 | 119 | field: string; |
| 133 | 120 | /** @default "cascade" */ |
| 134 | − onDelete?: | |
| 135 | − | "no action" | |
| 136 | − | "restrict" | |
| 137 | − | "cascade" | |
| 138 | − | "set null" | |
| 139 | − | "set default"; | |
| 121 | + onDelete?: "no action" | "restrict" | "cascade" | "set null" | "set default"; | |
| 140 | 122 | } |
| 141 | 123 | | undefined; |
| 142 | 124 | unique?: boolean | undefined; |
| @@ -18,20 +18,12 @@ · 17 unmodified lines | ||
| 18 | 18 | import type { Effect } from "effect"; |
| 19 | 19 | |
| 20 | 20 | import type { DBAdapter, StorageFailure, Where } from "./adapter"; |
| 21 | −import type { | |
| 22 | − DBSchema, | |
| 23 | − InferDBFieldsInput, | |
| 24 | − InferDBFieldsOutput, | |
| 25 | −} from "./schema"; | |
| 21 | +import type { DBSchema, InferDBFieldsInput, InferDBFieldsOutput } from "./schema"; | |
| 26 | 22 | |
| 27 | −type RowInput<S extends DBSchema, M extends keyof S> = InferDBFieldsInput< | |
| 28 | − S[M]["fields"] | |
| 29 | −> & | |
| 23 | +type RowInput<S extends DBSchema, M extends keyof S> = InferDBFieldsInput<S[M]["fields"]> & | |
| 30 | 24 | Record<string, unknown>; |
| 31 | 25 | |
| 32 | −type RowOutput<S extends DBSchema, M extends keyof S> = InferDBFieldsOutput< | |
| 33 | − S[M]["fields"] | |
| 34 | −> & | |
| 26 | +type RowOutput<S extends DBSchema, M extends keyof S> = InferDBFieldsOutput<S[M]["fields"]> & | |
| 35 | 27 | Record<string, unknown>; |
| 36 | 28 | |
| 37 | 29 | /** |
| @@ -103,9 +95,7 @@ export interface TypedAdapter<S extends DBSchema, E = StorageFailure> { · 65 unmodified lines | ||
| 103 | 95 | * this is a typed re-export of the adapter's methods. Pass the schema as |
| 104 | 96 | * a type parameter; the adapter argument is the normal untyped one. |
| 105 | 97 | */ |
| 106 | −export const typedAdapter = <S extends DBSchema>( | |
| 107 | − adapter: DBAdapter, | |
| 108 | −): TypedAdapter<S> => ({ | |
| 98 | +export const typedAdapter = <S extends DBSchema>(adapter: DBAdapter): TypedAdapter<S> => ({ | |
| 109 | 99 | raw: adapter, |
| 110 | 100 | create: adapter.create as TypedAdapter<S>["create"], |
| 111 | 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 | 11 | import { isTransientStorageError } from "./adapter"; |
| 12 | 12 | |
| 13 | −const transientMsg = | |
| 14 | − "[storage-drizzle] findOne select failed: Network connection lost."; | |
| 13 | +const transientMsg = "[storage-drizzle] findOne select failed: Network connection lost."; | |
| 15 | 14 | |
| 16 | 15 | describe("isTransientStorageError", () => { |
| 17 | 16 | it.for([ |
| @@ -20,9 +19,7 @@ describe("isTransientStorageError", () => { · 2 unmodified lines | ||
| 20 | 19 | "[storage-drizzle] insert returning failed: ECONNRESET", |
| 21 | 20 | "[storage-drizzle] findMany select failed: Connection terminated unexpectedly", |
| 22 | 21 | ])("returns true for %s", (message) => { |
| 23 | − expect( | |
| 24 | − isTransientStorageError(new StorageError({ message, cause: null })), | |
| 25 | − ).toBe(true); | |
| 22 | + expect(isTransientStorageError(new StorageError({ message, cause: null }))).toBe(true); | |
| 26 | 23 | }); |
| 27 | 24 | |
| 28 | 25 | it("returns false for unique violation", () => { |
| @@ -52,9 +49,7 @@ describe("transient retry policy", () => { · 23 unmodified lines | ||
| 52 | 49 | const result = yield* Effect.suspend(() => { |
| 53 | 50 | calls++; |
| 54 | 51 | if (calls < 3) { |
| 55 | − return Effect.fail( | |
| 56 | − new StorageError({ message: transientMsg, cause: null }), | |
| 57 | − ); | |
| 52 | + return Effect.fail(new StorageError({ message: transientMsg, cause: null })); | |
| 58 | 53 | } |
| 59 | 54 | return Effect.succeed("ok"); |
| 60 | 55 | }).pipe(Effect.retry(retryPolicy)); |
| @@ -70,9 +65,7 @@ describe("transient retry policy", () => { · 9 unmodified lines | ||
| 70 | 65 | const exit = yield* Effect.exit( |
| 71 | 66 | Effect.suspend(() => { |
| 72 | 67 | calls++; |
| 73 | − return Effect.fail( | |
| 74 | − new StorageError({ message: transientMsg, cause: null }), | |
| 75 | − ); | |
| 68 | + return Effect.fail(new StorageError({ message: transientMsg, cause: null })); | |
| 76 | 69 | }).pipe(Effect.retry(retryPolicy)), |
| 77 | 70 | ); |
| 78 | 71 | |
| @@ -28,16 +28,12 @@ export interface MakeSqliteAdapterOptions { · 27 unmodified lines | ||
| 28 | 28 | readonly generateId?: () => string; |
| 29 | 29 | } |
| 30 | 30 | |
| 31 | −export const makeSqliteAdapter = ( | |
| 32 | − options: MakeSqliteAdapterOptions, | |
| 33 | −): DBAdapter => | |
| 31 | +export const makeSqliteAdapter = (options: MakeSqliteAdapterOptions): DBAdapter => | |
| 34 | 32 | drizzleAdapter({ |
| 35 | 33 | db: options.db, |
| 36 | 34 | schema: options.schema, |
| 37 | 35 | provider: "sqlite", |
| 38 | 36 | adapterId: options.adapterId ?? "sqlite", |
| 39 | 37 | supportsTransaction: true, |
| 40 | − customIdGenerator: options.generateId | |
| 41 | − ? () => options.generateId!() | |
| 42 | − : undefined, | |
| 38 | + customIdGenerator: options.generateId ? () => options.generateId!() : undefined, | |
| 43 | 39 | }); |
| @@ -41,21 +41,14 @@ const wrapErr = · 40 unmodified lines | ||
| 41 | 41 | cause, |
| 42 | 42 | }); |
| 43 | 43 | |
| 44 | −export const makeSqliteBlobStore = ( | |
| 45 | − options: MakeSqliteBlobStoreOptions, | |
| 46 | −): BlobStore => ({ | |
| 44 | +export const makeSqliteBlobStore = (options: MakeSqliteBlobStoreOptions): BlobStore => ({ | |
| 47 | 45 | get: (namespace, key) => |
| 48 | 46 | Effect.try({ |
| 49 | 47 | try: () => |
| 50 | 48 | options.db |
| 51 | 49 | .select({ value: blobTable.value }) |
| 52 | 50 | .from(blobTable) |
| 53 | − .where( | |
| 54 | − and( | |
| 55 | − eq(blobTable.namespace, namespace), | |
| 56 | − eq(blobTable.key, key), | |
| 57 | − ), | |
| 58 | − ) | |
| 51 | + .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key))) | |
| 59 | 52 | .limit(1) |
| 60 | 53 | .all() as ReadonlyArray<{ value: string }>, |
| 61 | 54 | catch: wrapErr("get"), |
| @@ -72,12 +65,7 @@ export const makeSqliteBlobStore = ( · 10 unmodified lines | ||
| 72 | 65 | value: blobTable.value, |
| 73 | 66 | }) |
| 74 | 67 | .from(blobTable) |
| 75 | − .where( | |
| 76 | − and( | |
| 77 | − inArray(blobTable.namespace, [...namespaces]), | |
| 78 | − eq(blobTable.key, key), | |
| 79 | − ), | |
| 80 | − ) | |
| 68 | + .where(and(inArray(blobTable.namespace, [...namespaces]), eq(blobTable.key, key))) | |
| 81 | 69 | .all() as ReadonlyArray<{ namespace: string; value: string }>, |
| 82 | 70 | catch: wrapErr("getMany"), |
| 83 | 71 | }).pipe( |
| @@ -107,12 +95,7 @@ export const makeSqliteBlobStore = ( · 23 unmodified lines | ||
| 107 | 95 | try: () => |
| 108 | 96 | options.db |
| 109 | 97 | .delete(blobTable) |
| 110 | − .where( | |
| 111 | − and( | |
| 112 | − eq(blobTable.namespace, namespace), | |
| 113 | − eq(blobTable.key, key), | |
| 114 | − ), | |
| 115 | − ) | |
| 98 | + .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key))) | |
| 116 | 99 | .run(), |
| 117 | 100 | catch: wrapErr("delete"), |
| 118 | 101 | }).pipe(Effect.asVoid), |
| @@ -123,12 +106,7 @@ export const makeSqliteBlobStore = ( · 4 unmodified lines | ||
| 123 | 106 | options.db |
| 124 | 107 | .select({ one: drizzleSql<number>`1`.as("one") }) |
| 125 | 108 | .from(blobTable) |
| 126 | − .where( | |
| 127 | − and( | |
| 128 | − eq(blobTable.namespace, namespace), | |
| 129 | − eq(blobTable.key, key), | |
| 130 | − ), | |
| 131 | − ) | |
| 109 | + .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key))) | |
| 132 | 110 | .limit(1) |
| 133 | 111 | .all() as ReadonlyArray<{ one: number }>, |
| 134 | 112 | catch: wrapErr("has"), |
| @@ -2,19 +2,10 @@ import { Effect } from "effect"; · 1 unmodified line | ||
| 2 | 2 | import Database from "better-sqlite3"; |
| 3 | 3 | import { drizzle } from "drizzle-orm/better-sqlite3"; |
| 4 | 4 | import { relations, sql } from "drizzle-orm"; |
| 5 | −import { | |
| 6 | − sqliteTable, | |
| 7 | − text, | |
| 8 | − real, | |
| 9 | − integer, | |
| 10 | − getTableConfig, | |
| 11 | −} from "drizzle-orm/sqlite-core"; | |
| 5 | +import { sqliteTable, text, real, integer, getTableConfig } from "drizzle-orm/sqlite-core"; | |
| 12 | 6 | |
| 13 | 7 | import type { DBAdapter } from "@executor-js/storage-core"; |
| 14 | −import { | |
| 15 | − conformanceSchema, | |
| 16 | − runAdapterConformance, | |
| 17 | −} from "@executor-js/storage-core/testing"; | |
| 8 | +import { conformanceSchema, runAdapterConformance } from "@executor-js/storage-core/testing"; | |
| 18 | 9 | |
| 19 | 10 | import { makeSqliteAdapter } from "./index"; |
| 20 | 11 | |
| @@ -91,11 +82,7 @@ const bootstrapTables = ( · 70 unmodified lines | ||
| 91 | 82 | if (col.isUnique) parts.push("UNIQUE"); |
| 92 | 83 | return parts.join(" "); |
| 93 | 84 | }); |
| 94 | − db.run( | |
| 95 | − sql.raw( | |
| 96 | − `CREATE TABLE IF NOT EXISTS "${config.name}" (${cols.join(", ")})`, | |
| 97 | − ), | |
| 98 | − ); | |
| 85 | + db.run(sql.raw(`CREATE TABLE IF NOT EXISTS "${config.name}" (${cols.join(", ")})`)); | |
| 99 | 86 | } catch { |
| 100 | 87 | // Not a table (e.g. relations) — skip |
| 101 | 88 | } |
| @@ -25,16 +25,12 @@ export interface MakePostgresAdapterOptions { · 24 unmodified lines | ||
| 25 | 25 | readonly generateId?: () => string; |
| 26 | 26 | } |
| 27 | 27 | |
| 28 | −export const makePostgresAdapter = ( | |
| 29 | − options: MakePostgresAdapterOptions, | |
| 30 | −): DBAdapter => | |
| 28 | +export const makePostgresAdapter = (options: MakePostgresAdapterOptions): DBAdapter => | |
| 31 | 29 | drizzleAdapter({ |
| 32 | 30 | db: options.db, |
| 33 | 31 | schema: options.schema, |
| 34 | 32 | provider: "pg", |
| 35 | 33 | adapterId: options.adapterId ?? "postgres", |
| 36 | 34 | supportsTransaction: true, |
| 37 | − customIdGenerator: options.generateId | |
| 38 | − ? () => options.generateId!() | |
| 39 | − : undefined, | |
| 35 | + customIdGenerator: options.generateId ? () => options.generateId!() : undefined, | |
| 40 | 36 | }); |
| @@ -42,21 +42,14 @@ const wrapErr = · 41 unmodified lines | ||
| 42 | 42 | cause, |
| 43 | 43 | }); |
| 44 | 44 | |
| 45 | −export const makePostgresBlobStore = ( | |
| 46 | − options: MakePostgresBlobStoreOptions, | |
| 47 | −): BlobStore => ({ | |
| 45 | +export const makePostgresBlobStore = (options: MakePostgresBlobStoreOptions): BlobStore => ({ | |
| 48 | 46 | get: (namespace, key) => |
| 49 | 47 | Effect.tryPromise({ |
| 50 | 48 | try: async () => { |
| 51 | 49 | const rows = await options.db |
| 52 | 50 | .select({ value: blobTable.value }) |
| 53 | 51 | .from(blobTable) |
| 54 | − .where( | |
| 55 | − and( | |
| 56 | − eq(blobTable.namespace, namespace), | |
| 57 | − eq(blobTable.key, key), | |
| 58 | − ), | |
| 59 | − ) | |
| 52 | + .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key))) | |
| 60 | 53 | .limit(1); |
| 61 | 54 | return rows[0]?.value ?? null; |
| 62 | 55 | }, |
| @@ -73,12 +66,7 @@ export const makePostgresBlobStore = ( · 10 unmodified lines | ||
| 73 | 66 | value: blobTable.value, |
| 74 | 67 | }) |
| 75 | 68 | .from(blobTable) |
| 76 | − .where( | |
| 77 | − and( | |
| 78 | − inArray(blobTable.namespace, [...namespaces]), | |
| 79 | − eq(blobTable.key, key), | |
| 80 | − ), | |
| 81 | − ); | |
| 69 | + .where(and(inArray(blobTable.namespace, [...namespaces]), eq(blobTable.key, key))); | |
| 82 | 70 | const out = new Map<string, string>(); |
| 83 | 71 | for (const row of rows) out.set(row.namespace, row.value); |
| 84 | 72 | return out as ReadonlyMap<string, string>; |
| @@ -103,12 +91,7 @@ export const makePostgresBlobStore = ( · 18 unmodified lines | ||
| 103 | 91 | try: async () => { |
| 104 | 92 | await options.db |
| 105 | 93 | .delete(blobTable) |
| 106 | − .where( | |
| 107 | − and( | |
| 108 | − eq(blobTable.namespace, namespace), | |
| 109 | − eq(blobTable.key, key), | |
| 110 | − ), | |
| 111 | − ); | |
| 94 | + .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key))); | |
| 112 | 95 | }, |
| 113 | 96 | catch: wrapErr("delete"), |
| 114 | 97 | }), |
| @@ -118,12 +101,7 @@ export const makePostgresBlobStore = ( · 3 unmodified lines | ||
| 118 | 101 | const rows = await options.db |
| 119 | 102 | .select({ key: blobTable.key }) |
| 120 | 103 | .from(blobTable) |
| 121 | − .where( | |
| 122 | − and( | |
| 123 | − eq(blobTable.namespace, namespace), | |
| 124 | − eq(blobTable.key, key), | |
| 125 | − ), | |
| 126 | − ) | |
| 104 | + .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key))) | |
| 127 | 105 | .limit(1); |
| 128 | 106 | return rows.length > 0; |
| 129 | 107 | }, |
| @@ -22,18 +22,13 @@ import { · 21 unmodified lines | ||
| 22 | 22 | } from "drizzle-orm/pg-core"; |
| 23 | 23 | |
| 24 | 24 | import type { DBAdapter, StorageFailure } from "@executor-js/storage-core"; |
| 25 | −import { | |
| 26 | − conformanceSchema, | |
| 27 | − runAdapterConformance, | |
| 28 | −} from "@executor-js/storage-core/testing"; | |
| 25 | +import { conformanceSchema, runAdapterConformance } from "@executor-js/storage-core/testing"; | |
| 29 | 26 | |
| 30 | 27 | import { makePostgresAdapter } from "./index"; |
| 31 | 28 | |
| 32 | 29 | const url = "postgresql://postgres:postgres@127.0.0.1:5435/postgres"; |
| 33 | 30 | |
| 34 | −class PostgresTestDatabaseError extends Data.TaggedError( | |
| 35 | − "PostgresTestDatabaseError", | |
| 36 | −)<{ | |
| 31 | +class PostgresTestDatabaseError extends Data.TaggedError("PostgresTestDatabaseError")<{ | |
| 37 | 32 | readonly message: string; |
| 38 | 33 | readonly cause: unknown; |
| 39 | 34 | }> {} |
| @@ -153,7 +148,7 @@ const resetTables = Effect.gen(function* () { · 113 unmodified lines | ||
| 153 | 148 | try: () => |
| 154 | 149 | sql`DROP TABLE IF EXISTS "source", "tag", "source_tag", "with_defaults", "blob" CASCADE`.then( |
| 155 | 150 | () => undefined, |
| 156 | − ), | |
| 151 | + ), | |
| 157 | 152 | catch: (cause) => |
| 158 | 153 | new PostgresTestDatabaseError({ |
| 159 | 154 | message: "failed to reset postgres conformance tables", |
| @@ -5,9 +5,6 @@ · 4 unmodified lines | ||
| 5 | 5 | // wrapper around @executor-js/storage-drizzle. |
| 6 | 6 | // --------------------------------------------------------------------------- |
| 7 | 7 | |
| 8 | −export { | |
| 9 | − makePostgresAdapter, | |
| 10 | − type MakePostgresAdapterOptions, | |
| 11 | −} from "./adapter"; | |
| 8 | +export { makePostgresAdapter, type MakePostgresAdapterOptions } from "./adapter"; | |
| 12 | 9 | |
| 13 | 10 | export { makePostgresBlobStore, blobTable } from "./blob-store"; |
| @@ -44,9 +44,6 @@ · 43 unmodified lines | ||
| 44 | 44 | "jiti": "^2.6.1", |
| 45 | 45 | "jsonc-parser": "^3.3.1" |
| 46 | 46 | }, |
| 47 | − "peerDependencies": { | |
| 48 | − "vite": "catalog:" | |
| 49 | − }, | |
| 50 | 47 | "devDependencies": { |
| 51 | 48 | "@effect/vitest": "catalog:", |
| 52 | 49 | "@types/node": "catalog:", |
| @@ -54,5 +51,8 @@ · 1 unmodified line | ||
| 54 | 51 | "typescript": "catalog:", |
| 55 | 52 | "vite": "catalog:", |
| 56 | 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 = ( | |
| 60 | − packageName: string, | |
| 61 | − fromDir: string, | |
| 62 | −): string | null => { | |
| 59 | +const tryResolveClient = (packageName: string, fromDir: string): string | null => { | |
| 63 | 60 | const require = createRequire(resolvePath(fromDir, "_anchor.js")); |
| 64 | 61 | try { |
| 65 | 62 | return require.resolve(`${packageName}/client`); |
| @@ -83,9 +80,7 @@ interface ExecutorVitePluginOptions { · 17 unmodified lines | ||
| 83 | 80 | readonly jsoncPath?: string; |
| 84 | 81 | } |
| 85 | 82 | |
| 86 | −export default function executorVitePlugin( | |
| 87 | − options: ExecutorVitePluginOptions = {}, | |
| 88 | −): Plugin { | |
| 83 | +export default function executorVitePlugin(options: ExecutorVitePluginOptions = {}): Plugin { | |
| 89 | 84 | let projectRoot: string = process.cwd(); |
| 90 | 85 | let resolvedConfigPath: string | null = null; |
| 91 | 86 | let resolvedJsoncPath: string | null = null; |
| @@ -93,13 +88,9 @@ export default function executorVitePlugin( · 1 unmodified line | ||
| 93 | 88 | |
| 94 | 89 | const resolveConfigPath = (): string | null => { |
| 95 | 90 | if (resolvedConfigPath) return resolvedConfigPath; |
| 96 | − const candidates = options.configPath | |
| 97 | − ? [options.configPath] | |
| 98 | − : DEFAULT_CONFIG_CANDIDATES; | |
| 91 | + const candidates = options.configPath ? [options.configPath] : DEFAULT_CONFIG_CANDIDATES; | |
| 99 | 92 | for (const candidate of candidates) { |
| 100 | − const abs = isAbsolute(candidate) | |
| 101 | − ? candidate | |
| 102 | − : resolvePath(projectRoot, candidate); | |
| 93 | + const abs = isAbsolute(candidate) ? candidate : resolvePath(projectRoot, candidate); | |
| 103 | 94 | if (existsSync(abs)) { |
| 104 | 95 | resolvedConfigPath = abs; |
| 105 | 96 | return abs; |
| @@ -110,13 +101,9 @@ export default function executorVitePlugin( · 4 unmodified lines | ||
| 110 | 101 | |
| 111 | 102 | const resolveJsoncPath = (): string | null => { |
| 112 | 103 | if (resolvedJsoncPath) return resolvedJsoncPath; |
| 113 | − const candidates = options.jsoncPath | |
| 114 | − ? [options.jsoncPath] | |
| 115 | − : DEFAULT_JSONC_CANDIDATES; | |
| 104 | + const candidates = options.jsoncPath ? [options.jsoncPath] : DEFAULT_JSONC_CANDIDATES; | |
| 116 | 105 | for (const candidate of candidates) { |
| 117 | − const abs = isAbsolute(candidate) | |
| 118 | − ? candidate | |
| 119 | − : resolvePath(projectRoot, candidate); | |
| 106 | + const abs = isAbsolute(candidate) ? candidate : resolvePath(projectRoot, candidate); | |
| 120 | 107 | if (existsSync(abs)) { |
| 121 | 108 | resolvedJsoncPath = abs; |
| 122 | 109 | return abs; |
| @@ -212,8 +199,7 @@ export default function executorVitePlugin( · 89 unmodified lines | ||
| 212 | 199 | } |
| 213 | 200 | |
| 214 | 201 | cachedSource = |
| 215 | − `${lines.join("\n")}\n` + | |
| 216 | − `export const plugins = [${exportExpressions.join(", ")}];\n`; | |
| 202 | + `${lines.join("\n")}\n` + `export const plugins = [${exportExpressions.join(", ")}];\n`; | |
| 217 | 203 | return cachedSource; |
| 218 | 204 | }; |
| 219 | 205 | |
| @@ -235,8 +221,7 @@ export default function executorVitePlugin( · 15 unmodified lines | ||
| 235 | 221 | const configPath = resolveConfigPath(); |
| 236 | 222 | const jsoncPath = resolveJsoncPath(); |
| 237 | 223 | const isWatched = |
| 238 | − (configPath && ctx.file === configPath) || | |
| 239 | − (jsoncPath && ctx.file === jsoncPath); | |
| 224 | + (configPath && ctx.file === configPath) || (jsoncPath && ctx.file === jsoncPath); | |
| 240 | 225 | if (!isWatched) return undefined; |
| 241 | 226 | cachedSource = null; |
| 242 | 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 | 11 | const testScope = resolve(repoRoot, "apps/local"); |
| 12 | 12 | |
| 13 | 13 | describe("MCP stdio integration", () => { |
| 14 | − it.effect("execute tool returns result over stdio transport", () => | |
| 15 | − Effect.gen(function* () { | |
| 16 | − // Fresh temp dir so the test doesn't migrate against the developer's | |
| 17 | − // real ~/.executor/data.db. | |
| 18 | − const dataDir = mkdtempSync(join(tmpdir(), "executor-mcp-test-")); | |
| 19 | − | |
| 20 | − const transport = new StdioClientTransport({ | |
| 21 | − command: "bun", | |
| 22 | − args: ["run", cliEntry, "mcp", "--scope", testScope], | |
| 23 | − env: { ...process.env, EXECUTOR_DATA_DIR: dataDir }, | |
| 24 | − }); | |
| 25 | − | |
| 26 | − const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} }); | |
| 27 | − | |
| 28 | − yield* Effect.acquireRelease( | |
| 29 | − Effect.promise(() => client.connect(transport)), | |
| 30 | − () => Effect.promise(() => transport.close()), | |
| 31 | − ); | |
| 32 | − | |
| 33 | − const { tools } = yield* Effect.promise(() => client.listTools()); | |
| 34 | − expect(tools.map((t) => t.name)).toContain("execute"); | |
| 35 | − | |
| 36 | − const result = yield* Effect.promise(() => | |
| 37 | − client.callTool({ | |
| 38 | − name: "execute", | |
| 39 | − arguments: { code: "return 2+2" }, | |
| 40 | − }), | |
| 41 | − ); | |
| 42 | − | |
| 43 | − const text = (result.content as Array<{ type: string; text: string }>)[0]?.text; | |
| 44 | − expect(text).toContain("4"); | |
| 45 | − expect(result.isError).toBeFalsy(); | |
| 46 | − }).pipe(Effect.scoped), | |
| 14 | + it.effect( | |
| 15 | + "execute tool returns result over stdio transport", | |
| 16 | + () => | |
| 17 | + Effect.gen(function* () { | |
| 18 | + // Fresh temp dir so the test doesn't migrate against the developer's | |
| 19 | + // real ~/.executor/data.db. | |
| 20 | + const dataDir = mkdtempSync(join(tmpdir(), "executor-mcp-test-")); | |
| 21 | + | |
| 22 | + const transport = new StdioClientTransport({ | |
| 23 | + command: "bun", | |
| 24 | + args: ["run", cliEntry, "mcp", "--scope", testScope], | |
| 25 | + env: { ...process.env, EXECUTOR_DATA_DIR: dataDir }, | |
| 26 | + }); | |
| 27 | + | |
| 28 | + const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} }); | |
| 29 | + | |
| 30 | + yield* Effect.acquireRelease( | |
| 31 | + Effect.promise(() => client.connect(transport)), | |
| 32 | + () => Effect.promise(() => transport.close()), | |
| 33 | + ); | |
| 34 | + | |
| 35 | + const { tools } = yield* Effect.promise(() => client.listTools()); | |
| 36 | + expect(tools.map((t) => t.name)).toContain("execute"); | |
| 37 | + | |
| 38 | + const result = yield* Effect.promise(() => | |
| 39 | + client.callTool({ | |
| 40 | + name: "execute", | |
| 41 | + arguments: { code: "return 2+2" }, | |
| 42 | + }), | |
| 43 | + ); | |
| 44 | + | |
| 45 | + const text = (result.content as Array<{ type: string; text: string }>)[0]?.text; | |
| 46 | + expect(text).toContain("4"); | |
| 47 | + expect(result.isError).toBeFalsy(); | |
| 48 | + }).pipe(Effect.scoped), | |
| 47 | 49 | { timeout: 30_000 }, |
| 48 | 50 | ); |
| 49 | 51 | }); |
| @@ -17,11 +17,7 @@ npm install @executor-js/codemode-core · 16 unmodified lines | ||
| 17 | 17 | Implement a runtime that satisfies `CodeExecutor`: |
| 18 | 18 | |
| 19 | 19 | ```ts |
| 20 | −import type { | |
| 21 | − CodeExecutor, | |
| 22 | − ExecuteResult, | |
| 23 | − SandboxToolInvoker, | |
| 24 | −} from "@executor-js/codemode-core"; | |
| 20 | +import type { CodeExecutor, ExecuteResult, SandboxToolInvoker } from "@executor-js/codemode-core"; | |
| 25 | 21 | import { Effect } from "effect"; |
| 26 | 22 | |
| 27 | 23 | export const makeMyRuntime = (): CodeExecutor => ({ |
| @@ -57,7 +57,9 @@ describe("recoverExecutionBody", () => { · 56 unmodified lines | ||
| 57 | 57 | }); |
| 58 | 58 | |
| 59 | 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 | 65 | it("strips typescript fences", () => { |
| @@ -121,13 +123,7 @@ describe("recoverExecutionBody", () => { · 57 unmodified lines | ||
| 121 | 123 | }); |
| 122 | 124 | |
| 123 | 125 | it("extracts code from prose wrapped fences", () => { |
| 124 | − const code = [ | |
| 125 | − "Sure, here's the code:", | |
| 126 | − "", | |
| 127 | − "```ts", | |
| 128 | − "async () => 42", | |
| 129 | − "```", | |
| 130 | − ].join("\n"); | |
| 126 | + const code = ["Sure, here's the code:", "", "```ts", "async () => 42", "```"].join("\n"); | |
| 131 | 127 | |
| 132 | 128 | expect(recoverExecutionBody(code)).toBe(wrapCallableBody("async () => 42")); |
| 133 | 129 | }); |
| @@ -142,10 +138,7 @@ describe("recoverExecutionBody", () => { · 8 unmodified lines | ||
| 142 | 138 | }); |
| 143 | 139 | |
| 144 | 140 | it("handles block comments before export default", () => { |
| 145 | − const code = [ | |
| 146 | − "/* generated by the model */", | |
| 147 | − "export default async () => 42", | |
| 148 | − ].join("\n"); | |
| 141 | + const code = ["/* generated by the model */", "export default async () => 42"].join("\n"); | |
| 149 | 142 | |
| 150 | 143 | expect(recoverExecutionBody(code)).toBe(wrapCallableBody("async () => 42")); |
| 151 | 144 | }); |
| @@ -199,7 +192,7 @@ describe("recoverExecutionBody", () => { · 47 unmodified lines | ||
| 199 | 192 | " return { count: matches.length, top };", |
| 200 | 193 | "} catch (error) {", |
| 201 | 194 | " return {", |
| 202 | − ' error: error instanceof Error ? error.message : String(error),', | |
| 195 | + " error: error instanceof Error ? error.message : String(error),", | |
| 203 | 196 | " query,", |
| 204 | 197 | " };", |
| 205 | 198 | "}", |
| @@ -1,8 +1,7 @@ | ||
| 1 | 1 | import { parse } from "@babel/parser"; |
| 2 | 2 | |
| 3 | 3 | const FENCED_CODE_BLOCK = /```(?:[^\n`]*)?\s*\n([\s\S]*?)```/i; |
| 4 | −const FUNCTION_DECLARATION = | |
| 5 | − /^(?:async\s+)?function(?:\s+([a-zA-Z_$][a-zA-Z0-9_$]*))?\s*\(/; | |
| 4 | +const FUNCTION_DECLARATION = /^(?:async\s+)?function(?:\s+([a-zA-Z_$][a-zA-Z0-9_$]*))?\s*\(/; | |
| 6 | 5 | const CALLABLE_ERROR = "Code must evaluate to a function"; |
| 7 | 6 | |
| 8 | 7 | const extractCandidateSource = (code: string): string => { |
| @@ -47,7 +46,9 @@ const unwrapExpression = (expression: { type: string; expression?: unknown }): u · 38 unmodified lines | ||
| 47 | 46 | case "TSTypeAssertion": |
| 48 | 47 | case "TSNonNullExpression": |
| 49 | 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 | 52 | default: |
| 52 | 53 | return expression; |
| 53 | 54 | } |
| @@ -76,11 +77,11 @@ const renderExportDefaultBody = ( · 22 unmodified lines | ||
| 76 | 77 | }; |
| 77 | 78 | |
| 78 | 79 | type ExportDefaultDeclarationNode = { |
| 79 | − type: string; | |
| 80 | − start?: number | null; | |
| 81 | − end?: number | null; | |
| 82 | − id?: { name?: string | null } | null; | |
| 83 | − expression?: unknown; | |
| 80 | + type: string; | |
| 81 | + start?: number | null; | |
| 82 | + end?: number | null; | |
| 83 | + id?: { name?: string | null } | null; | |
| 84 | + expression?: unknown; | |
| 84 | 85 | }; |
| 85 | 86 | |
| 86 | 87 | const renderParsedBody = (source: string): string => { |
| @@ -99,10 +100,13 @@ const renderParsedBody = (source: string): string => { · 12 unmodified lines | ||
| 99 | 100 | |
| 100 | 101 | switch (statement.type) { |
| 101 | 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 | 106 | type?: string; |
| 104 | 107 | }; |
| 105 | − return expression?.type === "ArrowFunctionExpression" || expression?.type === "FunctionExpression" | |
| 108 | + return expression?.type === "ArrowFunctionExpression" || | |
| 109 | + expression?.type === "FunctionExpression" | |
| 106 | 110 | ? wrapCallableBody(source) |
| 107 | 111 | : source; |
| 108 | 112 | } |
| @@ -6,7 +6,7 @@ describe("stripTypeScript", () => { · 5 unmodified lines | ||
| 6 | 6 | it("removes variable type annotations", () => { |
| 7 | 7 | const out = stripTypeScript('const x: string = "hello"; return x;'); |
| 8 | 8 | expect(out).not.toContain(": string"); |
| 9 | − expect(out).toContain('const x'); | |
| 9 | + expect(out).toContain("const x"); | |
| 10 | 10 | expect(out).toContain('"hello"'); |
| 11 | 11 | }); |
| 12 | 12 | |
| @@ -30,7 +30,9 @@ describe("stripTypeScript", () => { · 17 unmodified lines | ||
| 30 | 30 | }); |
| 31 | 31 | |
| 32 | 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 | 36 | expect(out).not.toContain("interface User"); |
| 35 | 37 | expect(out).not.toContain(": string"); |
| 36 | 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 | 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", () => | |
| 171 | − Effect.gen(function* () { | |
| 172 | − const executor = makeDenoSubprocessExecutor({ | |
| 173 | − permissions: { | |
| 174 | − allowNet: true, | |
| 175 | − }, | |
| 176 | − }); | |
| 177 | − const toolInvoker = makeTestInvoker({}); | |
| 178 | − | |
| 179 | − const output = yield* executor.execute( | |
| 180 | − ['const res = await fetch("https://example.com");', "return res.status;"].join("\n"), | |
| 181 | − toolInvoker, | |
| 182 | − ); | |
| 183 | − | |
| 184 | − expect(output.result).toBe(200); | |
| 185 | − expect(output.error).toBeUndefined(); | |
| 186 | − }), | |
| 170 | + it.effect.skipIf(process.env["CI"] === "true" || process.platform === "win32")( | |
| 171 | + "network access can be allowed via permissions", | |
| 172 | + () => | |
| 173 | + Effect.gen(function* () { | |
| 174 | + const executor = makeDenoSubprocessExecutor({ | |
| 175 | + permissions: { | |
| 176 | + allowNet: true, | |
| 177 | + }, | |
| 178 | + }); | |
| 179 | + const toolInvoker = makeTestInvoker({}); | |
| 180 | + | |
| 181 | + const output = yield* executor.execute( | |
| 182 | + ['const res = await fetch("https://example.com");', "return res.status;"].join("\n"), | |
| 183 | + toolInvoker, | |
| 184 | + ); | |
| 185 | + | |
| 186 | + expect(output.result).toBe(200); | |
| 187 | + expect(output.error).toBeUndefined(); | |
| 188 | + }), | |
| 187 | 189 | ); |
| 188 | 190 | |
| 189 | 191 | it.effect("multiple sequential tool calls work correctly", () => |
| @@ -92,9 +92,7 @@ const defaultDenoExecutable = (): string => { · 91 unmodified lines | ||
| 92 | 92 | const isWindows = process.platform === "win32"; |
| 93 | 93 | const home = (process.env.HOME || process.env.USERPROFILE)?.trim(); |
| 94 | 94 | if (home) { |
| 95 | − const installedPath = isWindows | |
| 96 | − ? `${home}\\.deno\\bin\\deno.exe` | |
| 97 | − : `${home}/.deno/bin/deno`; | |
| 95 | + const installedPath = isWindows ? `${home}\\.deno\\bin\\deno.exe` : `${home}/.deno/bin/deno`; | |
| 98 | 96 | const result = spawnSync(installedPath, ["--version"], { |
| 99 | 97 | stdio: "ignore", |
| 100 | 98 | timeout: 5000, |
| @@ -33,11 +33,11 @@ const engine = createExecutionEngine({ · 32 unmodified lines | ||
| 33 | 33 | |
| 34 | 34 | ### Options |
| 35 | 35 | |
| 36 | −| Option | Default | Description | | |
| 37 | −| ------------------- | ---------- | -------------------------------------------- | | |
| 38 | −| `timeoutMs` | `5_000` | Max wall-clock time per execution | | |
| 39 | −| `memoryLimitBytes` | `64 * 1MB` | Max memory the VM can allocate | | |
| 40 | −| `maxStackSizeBytes` | `1 * 1MB` | Max call-stack depth | | |
| 36 | +| Option | Default | Description | | |
| 37 | +| ------------------- | ---------- | --------------------------------- | | |
| 38 | +| `timeoutMs` | `5_000` | Max wall-clock time per execution | | |
| 39 | +| `memoryLimitBytes` | `64 * 1MB` | Max memory the VM can allocate | | |
| 40 | +| `maxStackSizeBytes` | `1 * 1MB` | Max call-stack depth | | |
| 41 | 41 | |
| 42 | 42 | ### Swapping the QuickJS build |
| 43 | 43 | |
| @@ -52,14 +52,6 @@ · 51 unmodified lines | ||
| 52 | 52 | "dependencies": { |
| 53 | 53 | "@executor-js/sdk": "workspace:*" |
| 54 | 54 | }, |
| 55 | − "peerDependencies": { | |
| 56 | − "react": "catalog:" | |
| 57 | − }, | |
| 58 | − "peerDependenciesMeta": { | |
| 59 | − "react": { | |
| 60 | − "optional": true | |
| 61 | − } | |
| 62 | − }, | |
| 63 | 55 | "devDependencies": { |
| 64 | 56 | "@effect/vitest": "catalog:", |
| 65 | 57 | "@types/node": "catalog:", |
| @@ -69,5 +61,13 @@ · 3 unmodified lines | ||
| 69 | 61 | "tsup": "catalog:", |
| 70 | 62 | "typescript": "catalog:", |
| 71 | 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 | 13 | import { useState } from "react"; |
| 14 | −import { | |
| 15 | − defineClientPlugin, | |
| 16 | − createPluginAtomClient, | |
| 17 | − useAtomSet, | |
| 18 | −} from "@executor-js/sdk/client"; | |
| 14 | +import { defineClientPlugin, createPluginAtomClient, useAtomSet } from "@executor-js/sdk/client"; | |
| 19 | 15 | |
| 20 | 16 | import { ExampleApi } from "./shared"; |
| 21 | 17 | |
| @@ -68,9 +64,7 @@ function ExamplePage() { · 46 unmodified lines | ||
| 68 | 64 | </button> |
| 69 | 65 | </div> |
| 70 | 66 | {result && ( |
| 71 | − <pre style={{ marginTop: "1rem", padding: "0.5rem", background: "#f5f5f5" }}> | |
| 72 | − {result} | |
| 73 | − </pre> | |
| 67 | + <pre style={{ marginTop: "1rem", padding: "0.5rem", background: "#f5f5f5" }}>{result}</pre> | |
| 74 | 68 | )} |
| 75 | 69 | </div> |
| 76 | 70 | ); |
| @@ -11,13 +11,7 @@ · 10 unmodified lines | ||
| 11 | 11 | // React and other browser-only deps live in `./client` — never here. |
| 12 | 12 | // --------------------------------------------------------------------------- |
| 13 | 13 | |
| 14 | −import { | |
| 15 | − Context, | |
| 16 | − definePlugin, | |
| 17 | − Effect, | |
| 18 | − HttpApi, | |
| 19 | − HttpApiBuilder, | |
| 20 | −} from "@executor-js/sdk"; | |
| 14 | +import { Context, definePlugin, Effect, HttpApi, HttpApiBuilder } from "@executor-js/sdk"; | |
| 21 | 15 | |
| 22 | 16 | import { ExampleApi } from "./shared"; |
| 23 | 17 | |
| @@ -53,9 +53,7 @@ const WebHandler = Effect.acquireRelease( · 52 unmodified lines | ||
| 53 | 53 | Layer.provide(observabilityMiddleware(Api)), |
| 54 | 54 | Layer.provide(UnusedExecutor), |
| 55 | 55 | Layer.provide(UnusedExecutionEngine), |
| 56 | − Layer.provide( | |
| 57 | − Layer.succeed(GoogleDiscoveryExtensionService, failingExtension), | |
| 58 | − ), | |
| 56 | + Layer.provide(Layer.succeed(GoogleDiscoveryExtensionService, failingExtension)), | |
| 59 | 57 | Layer.provideMerge(HttpServer.layerServices), |
| 60 | 58 | Layer.provideMerge(Layer.succeed(HttpRouter.RouterConfig)({ maxParamLength: 1000 })), |
| 61 | 59 | ), |
| @@ -98,9 +96,7 @@ describe("GoogleDiscoveryHandlers", () => { · 36 unmodified lines | ||
| 98 | 96 | Layer.provide(observabilityMiddleware(Api)), |
| 99 | 97 | Layer.provide(UnusedExecutor), |
| 100 | 98 | Layer.provide(UnusedExecutionEngine), |
| 101 | − Layer.provide( | |
| 102 | − Layer.succeed(GoogleDiscoveryExtensionService, extension), | |
| 103 | − ), | |
| 99 | + Layer.provide(Layer.succeed(GoogleDiscoveryExtensionService, extension)), | |
| 104 | 100 | Layer.provideMerge(HttpServer.layerServices), |
| 105 | 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( | |
| 134 | − "defect-returning methods produce an opaque InternalError, no leakage", | |
| 135 | − () => | |
| 136 | − Effect.gen(function* () { | |
| 137 | − const web = yield* WebHandler; | |
| 138 | − const response = yield* Effect.promise(() => | |
| 139 | − web.handler( | |
| 140 | − new Request("http://localhost/scopes/scope_1/google-discovery/probe", { | |
| 141 | − method: "POST", | |
| 142 | − headers: { "content-type": "application/json" }, | |
| 143 | − body: JSON.stringify({ | |
| 144 | − discoveryUrl: "https://example.googleapis.com/$discovery/rest?version=v1", | |
| 145 | − }), | |
| 129 | + it.effect("defect-returning methods produce an opaque InternalError, no leakage", () => | |
| 130 | + Effect.gen(function* () { | |
| 131 | + const web = yield* WebHandler; | |
| 132 | + const response = yield* Effect.promise(() => | |
| 133 | + web.handler( | |
| 134 | + new Request("http://localhost/scopes/scope_1/google-discovery/probe", { | |
| 135 | + method: "POST", | |
| 136 | + headers: { "content-type": "application/json" }, | |
| 137 | + body: JSON.stringify({ | |
| 138 | + discoveryUrl: "https://example.googleapis.com/$discovery/rest?version=v1", | |
| 146 | 139 | }), |
| 147 | − HandlerContext, | |
| 148 | − ), | |
| 149 | − ); | |
| 140 | + }), | |
| 141 | + HandlerContext, | |
| 142 | + ), | |
| 143 | + ); | |
| 150 | 144 | |
| 151 | − expect(response.status).toBe(500); | |
| 152 | − const body = yield* Effect.promise(() => response.text()); | |
| 153 | − expect(body).not.toContain("Not implemented"); | |
| 154 | − }), | |
| 145 | + expect(response.status).toBe(500); | |
| 146 | + const body = yield* Effect.promise(() => response.text()); | |
| 147 | + expect(body).not.toContain("Not implemented"); | |
| 148 | + }), | |
| 155 | 149 | ); |
| 156 | 150 | }); |
| @@ -2,10 +2,7 @@ import { HttpApiBuilder } from "effect/unstable/httpapi"; · 1 unmodified line | ||
| 2 | 2 | import { Context, Effect } from "effect"; |
| 3 | 3 | |
| 4 | 4 | import { addGroup, capture } from "@executor-js/api"; |
| 5 | −import type { | |
| 6 | − GoogleDiscoveryAddSourceInput, | |
| 7 | − GoogleDiscoveryPluginExtension, | |
| 8 | −} from "../sdk/plugin"; | |
| 5 | +import type { GoogleDiscoveryAddSourceInput, GoogleDiscoveryPluginExtension } from "../sdk/plugin"; | |
| 9 | 6 | import { GoogleDiscoveryStoredSourceSchema } from "../sdk/stored-source"; |
| 10 | 7 | import { GoogleDiscoveryGroup } from "./group"; |
| 11 | 8 | |
| @@ -20,7 +17,9 @@ import { GoogleDiscoveryGroup } from "./group"; · 8 unmodified lines | ||
| 20 | 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 | 23 | >()("GoogleDiscoveryExtensionService") {} |
| 25 | 24 | |
| 26 | 25 | // --------------------------------------------------------------------------- |
| @@ -2,7 +2,6 @@ import { createPluginAtomClient } from "@executor-js/sdk/client"; · 1 unmodified line | ||
| 2 | 2 | import { getBaseUrl } from "@executor-js/react/api/base-url"; |
| 3 | 3 | import { GoogleDiscoveryGroup } from "../api/group"; |
| 4 | 4 | |
| 5 | −export const GoogleDiscoveryClient = createPluginAtomClient( | |
| 6 | − GoogleDiscoveryGroup, | |
| 7 | − { baseUrl: getBaseUrl() }, | |
| 8 | −); | |
| 5 | +export const GoogleDiscoveryClient = createPluginAtomClient(GoogleDiscoveryGroup, { | |
| 6 | + baseUrl: getBaseUrl(), | |
| 7 | +}); | |
| @@ -1,6 +1,3 @@ | ||
| 1 | 1 | export { googleDiscoverySourcePlugin } from "./source-plugin"; |
| 2 | 2 | export { GoogleDiscoveryClient } from "./client"; |
| 3 | −export { | |
| 4 | − addGoogleDiscoverySource, | |
| 5 | − probeGoogleDiscovery, | |
| 6 | −} from "./atoms"; | |
| 3 | +export { addGoogleDiscoverySource, probeGoogleDiscovery } from "./atoms"; | |
| @@ -22,9 +22,7 @@ const LowercaseText = Schema.String.pipe( · 21 unmodified lines | ||
| 22 | 22 | ); |
| 23 | 23 | const TextOption = Schema.OptionFromOptional(TrimmedString).pipe( |
| 24 | 24 | Schema.decode({ |
| 25 | − decode: SchemaGetter.transform((value) => | |
| 26 | − Option.filter(value, (text) => text.length > 0), | |
| 27 | − ), | |
| 25 | + decode: SchemaGetter.transform((value) => Option.filter(value, (text) => text.length > 0)), | |
| 28 | 26 | encode: SchemaGetter.transform((value) => value), |
| 29 | 27 | }), |
| 30 | 28 | Schema.withDecodingDefaultType(Effect.succeed(Option.none())), |
| @@ -48,9 +46,7 @@ const DiscoveryParameterLocationInput = Schema.optional( · 17 unmodified lines | ||
| 48 | 46 | const DiscoveryDefaultValue = Schema.Union([Schema.String, Schema.Number, Schema.Boolean]); |
| 49 | 47 | |
| 50 | 48 | const DiscoverySchemaModel = Schema.Struct({ |
| 51 | − type: Schema.optional( | |
| 52 | − LowercaseText, | |
| 53 | − ), | |
| 49 | + type: Schema.optional(LowercaseText), | |
| 54 | 50 | description: TextOption, |
| 55 | 51 | properties: UnknownRecordWithDefault, |
| 56 | 52 | items: Schema.optional(Schema.Unknown), |
| @@ -65,9 +61,7 @@ const DiscoverySchemaModel = Schema.Struct({ · 8 unmodified lines | ||
| 65 | 61 | type DiscoverySchema = typeof DiscoverySchemaModel.Type; |
| 66 | 62 | |
| 67 | 63 | const DiscoveryParameterModel = Schema.Struct({ |
| 68 | − type: Schema.optional( | |
| 69 | − LowercaseText, | |
| 70 | − ), | |
| 64 | + type: Schema.optional(LowercaseText), | |
| 71 | 65 | description: TextOption, |
| 72 | 66 | properties: UnknownRecordWithDefault, |
| 73 | 67 | items: Schema.optional(Schema.Unknown), |
| @@ -446,7 +440,9 @@ const manifestMethodFromMethod = (input: { · 372 unmodified lines | ||
| 446 | 440 | hasBody: requestRef !== undefined, |
| 447 | 441 | }), |
| 448 | 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 | 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) => | |
| 501 | − manifestMethodFromMethod({ | |
| 502 | − service, | |
| 503 | − rawMethod, | |
| 504 | − globalParameters: document.parameters ?? {}, | |
| 505 | − }), | |
| 496 | + const topLevelMethods = yield* Effect.forEach( | |
| 497 | + Object.values(document.methods ?? {}), | |
| 498 | + (rawMethod) => | |
| 499 | + manifestMethodFromMethod({ | |
| 500 | + service, | |
| 501 | + rawMethod, | |
| 502 | + globalParameters: document.parameters ?? {}, | |
| 503 | + }), | |
| 506 | 504 | ); |
| 507 | 505 | |
| 508 | − const nestedMethods = yield* Effect.forEach(Object.values(document.resources ?? {}), (rawResource) => | |
| 509 | − collectMethods({ | |
| 510 | − service, | |
| 511 | − rawResource, | |
| 512 | − globalParameters: document.parameters ?? {}, | |
| 513 | − }), | |
| 506 | + const nestedMethods = yield* Effect.forEach( | |
| 507 | + Object.values(document.resources ?? {}), | |
| 508 | + (rawResource) => | |
| 509 | + collectMethods({ | |
| 510 | + service, | |
| 511 | + rawResource, | |
| 512 | + globalParameters: document.parameters ?? {}, | |
| 513 | + }), | |
| 514 | 514 | ); |
| 515 | 515 | |
| 516 | 516 | return new GoogleDiscoveryManifest({ |
| @@ -3,10 +3,7 @@ import { Schema } from "effect"; · 2 unmodified lines | ||
| 3 | 3 | import { ScopeId } from "@executor-js/sdk/core"; |
| 4 | 4 | import { InternalError } from "@executor-js/api"; |
| 5 | 5 | |
| 6 | −import { | |
| 7 | − GraphqlIntrospectionError, | |
| 8 | − GraphqlExtractionError, | |
| 9 | −} from "../sdk/errors"; | |
| 6 | +import { GraphqlIntrospectionError, GraphqlExtractionError } from "../sdk/errors"; | |
| 10 | 7 | import { GraphqlSourceAuth, HeaderValue } from "../sdk/types"; |
| 11 | 8 | |
| 12 | 9 | // StoredGraphqlSource shape as an HTTP response schema. Kept local to the |
| @@ -72,12 +69,8 @@ const AddSourceResponse = Schema.Struct({ · 59 unmodified lines | ||
| 72 | 69 | // Errors with HTTP status |
| 73 | 70 | // --------------------------------------------------------------------------- |
| 74 | 71 | |
| 75 | −const IntrospectionError = GraphqlIntrospectionError.annotate( | |
| 76 | − { httpApiStatus: 400 }, | |
| 77 | −); | |
| 78 | −const ExtractionError = GraphqlExtractionError.annotate( | |
| 79 | − { httpApiStatus: 400 }, | |
| 80 | −); | |
| 72 | +const IntrospectionError = GraphqlIntrospectionError.annotate({ httpApiStatus: 400 }); | |
| 73 | +const ExtractionError = GraphqlExtractionError.annotate({ httpApiStatus: 400 }); | |
| 81 | 74 | |
| 82 | 75 | // --------------------------------------------------------------------------- |
| 83 | 76 | // Group |
| @@ -119,6 +112,6 @@ export const GraphqlGroup = HttpApiGroup.make("graphql") · 35 unmodified lines | ||
| 119 | 112 | success: UpdateSourceResponse, |
| 120 | 113 | error: GraphqlErrors, |
| 121 | 114 | }), |
| 122 | − ) | |
| 123 | − // Plugin domain errors carry their own HTTP status (4xx); | |
| 124 | − // `InternalError` is the shared opaque 500 translated at the HTTP edge. | |
| 115 | + ); | |
| 116 | +// Plugin domain errors carry their own HTTP status (4xx); | |
| 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 | 2 | import { Context, Effect } from "effect"; |
| 3 | 3 | |
| 4 | 4 | import { addGroup, capture } from "@executor-js/api"; |
| 5 | −import type { | |
| 6 | − GraphqlPluginExtension, | |
| 7 | − HeaderValue, | |
| 8 | − GraphqlUpdateSourceInput, | |
| 9 | −} from "../sdk/plugin"; | |
| 5 | +import type { GraphqlPluginExtension, HeaderValue, GraphqlUpdateSourceInput } from "../sdk/plugin"; | |
| 10 | 6 | import { GraphqlGroup } from "./group"; |
| 11 | 7 | |
| 12 | 8 | // --------------------------------------------------------------------------- |
| @@ -20,7 +16,10 @@ import { GraphqlGroup } from "./group"; · 7 unmodified lines | ||
| 20 | 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 | 25 | // Composed API — core + graphql group |
| @@ -38,61 +37,50 @@ const ExecutorApiWithGraphql = addGroup(GraphqlGroup); · 11 unmodified lines | ||
| 38 | 37 | // by the API-level observability middleware. |
| 39 | 38 | // --------------------------------------------------------------------------- |
| 40 | 39 | |
| 41 | −export const GraphqlHandlers = HttpApiBuilder.group( | |
| 42 | − ExecutorApiWithGraphql, | |
| 43 | − "graphql", | |
| 44 | − (handlers) => | |
| 45 | − handlers | |
| 46 | − .handle("addSource", ({ params: path, payload }) => | |
| 47 | − capture( | |
| 48 | − Effect.gen(function* () { | |
| 49 | − const ext = yield* GraphqlExtensionService; | |
| 50 | − const result = yield* ext.addSource({ | |
| 51 | − endpoint: payload.endpoint, | |
| 52 | − scope: path.scopeId, | |
| 53 | − name: payload.name, | |
| 54 | − introspectionJson: payload.introspectionJson, | |
| 55 | − namespace: payload.namespace, | |
| 56 | − headers: payload.headers as | |
| 57 | − | Record<string, HeaderValue> | |
| 58 | − | undefined, | |
| 59 | − queryParams: payload.queryParams as | |
| 60 | − | Record<string, HeaderValue> | |
| 61 | − | undefined, | |
| 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 | − ), | |
| 40 | +export const GraphqlHandlers = HttpApiBuilder.group(ExecutorApiWithGraphql, "graphql", (handlers) => | |
| 41 | + handlers | |
| 42 | + .handle("addSource", ({ params: path, payload }) => | |
| 43 | + capture( | |
| 44 | + Effect.gen(function* () { | |
| 45 | + const ext = yield* GraphqlExtensionService; | |
| 46 | + const result = yield* ext.addSource({ | |
| 47 | + endpoint: payload.endpoint, | |
| 48 | + scope: path.scopeId, | |
| 49 | + name: payload.name, | |
| 50 | + introspectionJson: payload.introspectionJson, | |
| 51 | + namespace: payload.namespace, | |
| 52 | + headers: payload.headers as Record<string, HeaderValue> | undefined, | |
| 53 | + queryParams: payload.queryParams as Record<string, HeaderValue> | undefined, | |
| 54 | + auth: payload.auth, | |
| 55 | + }); | |
| 56 | + return { | |
| 57 | + toolCount: result.toolCount, | |
| 58 | + namespace: result.namespace, | |
| 59 | + }; | |
| 60 | + }), | |
| 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 | 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 | 30 | const oauth2 = source?.auth.kind === "oauth2" ? source.auth : null; |
| 30 | 31 | const connections = AsyncResult.isSuccess(connectionsResult) ? connectionsResult.value : null; |
| 31 | 32 | const isConnected = |
| @@ -3,5 +3,4 @@ import { · 2 unmodified lines | ||
| 3 | 3 | type HttpCredentialsState, |
| 4 | 4 | } from "@executor-js/react/plugins/http-credentials"; |
| 5 | 5 | |
| 6 | −export const initialGraphqlCredentials = (): HttpCredentialsState => | |
| 7 | − emptyHttpCredentials(); | |
| 6 | +export const initialGraphqlCredentials = (): HttpCredentialsState => emptyHttpCredentials(); | |
| @@ -226,9 +226,7 @@ export const introspect = Effect.fn("GraphQL.introspect")(function* ( · 225 unmodified lines | ||
| 226 | 226 | } |
| 227 | 227 | |
| 228 | 228 | const raw = yield* response.json.pipe( |
| 229 | − Effect.tapCause((cause) => | |
| 230 | − Effect.logError("graphql introspection JSON parse failed", cause), | |
| 231 | − ), | |
| 229 | + Effect.tapCause((cause) => Effect.logError("graphql introspection JSON parse failed", cause)), | |
| 232 | 230 | Effect.mapError( |
| 233 | 231 | () => |
| 234 | 232 | new GraphqlIntrospectionError({ |
| @@ -20,8 +20,7 @@ export const resolveHeaders = ( · 19 unmodified lines | ||
| 20 | 20 | ); |
| 21 | 21 | return resolveSecretBackedMap({ |
| 22 | 22 | values: headers, |
| 23 | − getSecret: (secretId) => | |
| 24 | − secrets.get(secretId).pipe(Effect.catch(() => Effect.succeed(null))), | |
| 23 | + getSecret: (secretId) => secrets.get(secretId).pipe(Effect.catch(() => Effect.succeed(null))), | |
| 25 | 24 | missing: "drop", |
| 26 | 25 | onMissing: (name) => |
| 27 | 26 | new GraphqlInvocationError({ |
| @@ -64,15 +64,13 @@ export type QueryParamValue = typeof QueryParamValue.Type; · 63 unmodified lines | ||
| 64 | 64 | // Source auth |
| 65 | 65 | // --------------------------------------------------------------------------- |
| 66 | 66 | |
| 67 | −export const GraphqlSourceAuth = Schema.Union( | |
| 68 | − [ | |
| 69 | − Schema.Struct({ kind: Schema.Literal("none") }), | |
| 70 | − Schema.Struct({ | |
| 71 | − kind: Schema.Literal("oauth2"), | |
| 72 | − connectionId: Schema.String, | |
| 73 | − }), | |
| 74 | − ], | |
| 75 | −); | |
| 67 | +export const GraphqlSourceAuth = Schema.Union([ | |
| 68 | + Schema.Struct({ kind: Schema.Literal("none") }), | |
| 69 | + Schema.Struct({ | |
| 70 | + kind: Schema.Literal("oauth2"), | |
| 71 | + connectionId: Schema.String, | |
| 72 | + }), | |
| 73 | +]); | |
| 76 | 74 | export type GraphqlSourceAuth = typeof GraphqlSourceAuth.Type; |
| 77 | 75 | |
| 78 | 76 | export class InvocationConfig extends Schema.Class<InvocationConfig>("InvocationConfig")({ |
| @@ -62,7 +62,9 @@ describe("keychain plugin", () => { · 61 unmodified lines | ||
| 62 | 62 | const resolved = yield* executor.secrets.get(testId); |
| 63 | 63 | expect(resolved).toBe("keychain-test-value"); |
| 64 | 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 | 1 | import { Effect } from "effect"; |
| 2 | 2 | |
| 3 | −import { | |
| 4 | − definePlugin, | |
| 5 | − type PluginCtx, | |
| 6 | − type SecretProvider, | |
| 7 | −} from "@executor-js/sdk/core"; | |
| 3 | +import { definePlugin, type PluginCtx, type SecretProvider } from "@executor-js/sdk/core"; | |
| 8 | 4 | |
| 9 | 5 | import { |
| 10 | 6 | deletePassword, |
| @@ -59,8 +55,7 @@ export type KeychainExtension = ReturnType<typeof makeKeychainExtension>; · 48 unmodified lines | ||
| 59 | 55 | const scopedServiceName = ( |
| 60 | 56 | ctx: PluginCtx<unknown>, |
| 61 | 57 | options: KeychainPluginConfig | undefined, |
| 62 | −): string => | |
| 63 | − `${resolveServiceName(options?.serviceName)}/${ctx.scopes[0]!.id}`; | |
| 58 | +): string => `${resolveServiceName(options?.serviceName)}/${ctx.scopes[0]!.id}`; | |
| 64 | 59 | |
| 65 | 60 | const makeKeychainExtension = ( |
| 66 | 61 | ctx: PluginCtx<unknown>, |
| @@ -83,34 +78,26 @@ const makeKeychainExtension = ( · 16 unmodified lines | ||
| 83 | 78 | }; |
| 84 | 79 | }; |
| 85 | 80 | |
| 86 | −export const keychainPlugin = definePlugin( | |
| 87 | − (options?: KeychainPluginConfig) => ({ | |
| 88 | − id: "keychain" as const, | |
| 89 | − storage: () => ({}), | |
| 90 | − | |
| 91 | − extension: (ctx): KeychainExtension => makeKeychainExtension(ctx, options), | |
| 92 | − | |
| 93 | − secretProviders: (ctx): Effect.Effect<readonly SecretProvider[]> => | |
| 94 | − Effect.gen(function* () { | |
| 95 | − const serviceName = scopedServiceName(ctx, options); | |
| 96 | − const reachable = yield* setPassword( | |
| 97 | − serviceName, | |
| 98 | − PROBE_ACCOUNT, | |
| 99 | − PROBE_VALUE, | |
| 100 | − ).pipe( | |
| 101 | − Effect.andThen( | |
| 102 | − deletePassword(serviceName, PROBE_ACCOUNT).pipe( | |
| 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)), | |
| 81 | +export const keychainPlugin = definePlugin((options?: KeychainPluginConfig) => ({ | |
| 82 | + id: "keychain" as const, | |
| 83 | + storage: () => ({}), | |
| 84 | + | |
| 85 | + extension: (ctx): KeychainExtension => makeKeychainExtension(ctx, options), | |
| 86 | + | |
| 87 | + secretProviders: (ctx): Effect.Effect<readonly SecretProvider[]> => | |
| 88 | + Effect.gen(function* () { | |
| 89 | + const serviceName = scopedServiceName(ctx, options); | |
| 90 | + const reachable = yield* setPassword(serviceName, PROBE_ACCOUNT, PROBE_VALUE).pipe( | |
| 91 | + Effect.andThen( | |
| 92 | + deletePassword(serviceName, PROBE_ACCOUNT).pipe(Effect.catch(() => Effect.void)), | |
| 93 | + ), | |
| 94 | + Effect.as(true), | |
| 95 | + Effect.catch(() => | |
| 96 | + Effect.logWarning("keychain unavailable, skipping provider registration").pipe( | |
| 97 | + Effect.as(false), | |
| 111 | 98 | ), |
| 112 | − ); | |
| 113 | − return reachable ? [makeKeychainProvider(serviceName)] : []; | |
| 114 | − }), | |
| 115 | − }), | |
| 116 | −); | |
| 99 | + ), | |
| 100 | + ); | |
| 101 | + return reachable ? [makeKeychainProvider(serviceName)] : []; | |
| 102 | + }), | |
| 103 | +})); | |
| @@ -179,14 +179,13 @@ export const McpGroup = HttpApiGroup.make("mcp") · 178 unmodified lines | ||
| 179 | 179 | success: UpdateSourceResponse, |
| 180 | 180 | error: [InternalError, McpConnectionError, McpToolDiscoveryError], |
| 181 | 181 | }), |
| 182 | − ) | |
| 183 | − // Errors declared once at the group level — every endpoint inherits. | |
| 184 | − // Plugin domain errors carry their own HttpApiSchema status (4xx); | |
| 185 | − // `InternalError` is the shared opaque 500 translated at the HTTP | |
| 186 | − // edge by `withCapture`. We only list errors an MCP *group* | |
| 187 | − // endpoint can surface: `McpInvocationError` is thrown inside | |
| 188 | − // `invokeTool` which is reached via the core `tools.invoke` | |
| 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` | |
| 191 | − // now — the MCP group only declares its own plugin-domain errors. | |
| 192 | −; | |
| 182 | + ); | |
| 183 | +// Errors declared once at the group level — every endpoint inherits. | |
| 184 | +// Plugin domain errors carry their own HttpApiSchema status (4xx); | |
| 185 | +// `InternalError` is the shared opaque 500 translated at the HTTP | |
| 186 | +// edge by `withCapture`. We only list errors an MCP *group* | |
| 187 | +// endpoint can surface: `McpInvocationError` is thrown inside | |
| 188 | +// `invokeTool` which is reached via the core `tools.invoke` | |
| 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` | |
| 191 | +// now — the MCP group only declares its own plugin-domain errors. | |
| @@ -69,25 +69,23 @@ const McpConnectionErrorResponse = Schema.Struct({ · 68 unmodified lines | ||
| 69 | 69 | }); |
| 70 | 70 | |
| 71 | 71 | describe("McpHandlers", () => { |
| 72 | − it.effect( | |
| 73 | − "defect-returning methods produce an opaque InternalError, no leakage", | |
| 74 | − () => | |
| 75 | − Effect.gen(function* () { | |
| 76 | − const web = yield* WebHandler; | |
| 77 | − const response = yield* Effect.promise(() => | |
| 78 | − (web.handler as (request: Request) => Promise<Response>)( | |
| 79 | − new Request("http://localhost/scopes/scope_1/mcp/probe", { | |
| 80 | − method: "POST", | |
| 81 | − headers: { "content-type": "application/json" }, | |
| 82 | − body: JSON.stringify({ endpoint: "https://example.com/mcp" }), | |
| 83 | − }), | |
| 84 | − ), | |
| 85 | − ); | |
| 72 | + it.effect("defect-returning methods produce an opaque InternalError, no leakage", () => | |
| 73 | + Effect.gen(function* () { | |
| 74 | + const web = yield* WebHandler; | |
| 75 | + const response = yield* Effect.promise(() => | |
| 76 | + (web.handler as (request: Request) => Promise<Response>)( | |
| 77 | + new Request("http://localhost/scopes/scope_1/mcp/probe", { | |
| 78 | + method: "POST", | |
| 79 | + headers: { "content-type": "application/json" }, | |
| 80 | + body: JSON.stringify({ endpoint: "https://example.com/mcp" }), | |
| 81 | + }), | |
| 82 | + ), | |
| 83 | + ); | |
| 86 | 84 | |
| 87 | − expect(response.status).toBe(500); | |
| 88 | − const body = yield* Effect.promise(() => response.text()); | |
| 89 | − expect(body).not.toContain("Not implemented"); | |
| 90 | − }), | |
| 85 | + expect(response.status).toBe(500); | |
| 86 | + const body = yield* Effect.promise(() => response.text()); | |
| 87 | + expect(body).not.toContain("Not implemented"); | |
| 88 | + }), | |
| 91 | 89 | ); |
| 92 | 90 | |
| 93 | 91 | it.effect("domain MCP connection errors are encoded as 400 responses", () => |
| @@ -20,8 +20,9 @@ import { McpGroup } from "./group"; · 19 unmodified lines | ||
| 20 | 20 | // (no `Captured<>` inversion). |
| 21 | 21 | // --------------------------------------------------------------------------- |
| 22 | 22 | |
| 23 | −export class McpExtensionService extends Context.Service<McpExtensionService, McpPluginExtension | |
| 24 | −>()("McpExtensionService") {} | |
| 23 | +export class McpExtensionService extends Context.Service<McpExtensionService, McpPluginExtension>()( | |
| 24 | + "McpExtensionService", | |
| 25 | +) {} | |
| 25 | 26 | |
| 26 | 27 | // --------------------------------------------------------------------------- |
| 27 | 28 | // Composed API |
| @@ -1,9 +1,4 @@ | ||
| 1 | 1 | export { mcpSourcePlugin, createMcpSourcePlugin } from "./source-plugin"; |
| 2 | 2 | export type { McpSourcePluginOptions } from "./source-plugin"; |
| 3 | 3 | export { McpClient } from "./client"; |
| 4 | −export { | |
| 5 | − probeMcpEndpoint, | |
| 6 | − addMcpSource, | |
| 7 | − removeMcpSource, | |
| 8 | − refreshMcpSource, | |
| 9 | −} from "./atoms"; | |
| 4 | +export { probeMcpEndpoint, addMcpSource, removeMcpSource, refreshMcpSource } from "./atoms"; | |
| @@ -23,9 +23,7 @@ export interface McpSourcePluginOptions { · 22 unmodified lines | ||
| 23 | 23 | readonly allowStdio?: boolean; |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | −export const createMcpSourcePlugin = ( | |
| 27 | − options?: McpSourcePluginOptions, | |
| 28 | −): SourcePlugin => { | |
| 26 | +export const createMcpSourcePlugin = (options?: McpSourcePluginOptions): SourcePlugin => { | |
| 29 | 27 | const allowStdio = options?.allowStdio ?? false; |
| 30 | 28 | |
| 31 | 29 | const AddWithFlag: ComponentType<AddProps> = (props) => ( |
| @@ -163,11 +163,7 @@ describe("MCP connection pooling (regression)", () => { · 162 unmodified lines | ||
| 163 | 163 | // First invoke is necessarily a cache miss — it cold-handshakes the |
| 164 | 164 | // pooled connection. Every subsequent invoke must hit the cache and |
| 165 | 165 | // produce zero new MCP server sessions. |
| 166 | − yield* executor.tools.invoke( | |
| 167 | − echo.id, | |
| 168 | − { value: "1" }, | |
| 169 | − { onElicitation: "accept-all" }, | |
| 170 | − ); | |
| 166 | + yield* executor.tools.invoke(echo.id, { value: "1" }, { onElicitation: "accept-all" }); | |
| 171 | 167 | const sessionsAfterFirstInvoke = server.sessionCount(); |
| 172 | 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( | |
| 189 | − "different tools on the same source share the cached connection", | |
| 190 | − () => | |
| 191 | − Effect.gen(function* () { | |
| 192 | − const server = yield* serveMcpServer; | |
| 193 | − const executor = yield* makeTestExecutor(server.url); | |
| 194 | − const tools = yield* executor.tools.list(); | |
| 195 | − const echo = tools.find((t) => t.name === "echo")!; | |
| 196 | − const echo2 = tools.find((t) => t.name === "echo2")!; | |
| 197 | − expect(echo).toBeDefined(); | |
| 198 | − expect(echo2).toBeDefined(); | |
| 199 | − | |
| 200 | − const sessionsAfterAddSource = server.sessionCount(); | |
| 201 | − | |
| 202 | − // Cold handshake on first call. | |
| 203 | − yield* executor.tools.invoke( | |
| 204 | − echo.id, | |
| 205 | − { value: "a" }, | |
| 206 | − { onElicitation: "accept-all" }, | |
| 207 | − ); | |
| 208 | − const baseline = server.sessionCount(); | |
| 209 | − expect(baseline).toBe(sessionsAfterAddSource + 1); | |
| 210 | − | |
| 211 | − // Different tool on the SAME source — same cache key, same conn. | |
| 212 | − yield* executor.tools.invoke( | |
| 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 | − }), | |
| 184 | + it.effect("different tools on the same source share the cached connection", () => | |
| 185 | + Effect.gen(function* () { | |
| 186 | + const server = yield* serveMcpServer; | |
| 187 | + const executor = yield* makeTestExecutor(server.url); | |
| 188 | + const tools = yield* executor.tools.list(); | |
| 189 | + const echo = tools.find((t) => t.name === "echo")!; | |
| 190 | + const echo2 = tools.find((t) => t.name === "echo2")!; | |
| 191 | + expect(echo).toBeDefined(); | |
| 192 | + expect(echo2).toBeDefined(); | |
| 193 | + | |
| 194 | + const sessionsAfterAddSource = server.sessionCount(); | |
| 195 | + | |
| 196 | + // Cold handshake on first call. | |
| 197 | + yield* executor.tools.invoke(echo.id, { value: "a" }, { onElicitation: "accept-all" }); | |
| 198 | + const baseline = server.sessionCount(); | |
| 199 | + expect(baseline).toBe(sessionsAfterAddSource + 1); | |
| 200 | + | |
| 201 | + // Different tool on the SAME source — same cache key, same conn. | |
| 202 | + yield* executor.tools.invoke(echo2.id, { value: "b" }, { onElicitation: "accept-all" }); | |
| 203 | + expect(server.sessionCount()).toBe(baseline); | |
| 204 | + | |
| 205 | + // Back to the first tool — still pooled. | |
| 206 | + yield* executor.tools.invoke(echo.id, { value: "c" }, { onElicitation: "accept-all" }); | |
| 207 | + expect(server.sessionCount()).toBe(baseline); | |
| 208 | + }), | |
| 227 | 209 | ); |
| 228 | 210 | }); |
| @@ -53,10 +53,7 @@ const makeSharedOrgExecutors = () => · 52 unmodified lines | ||
| 53 | 53 | |
| 54 | 54 | const makeFor = (innerId: ScopeId) => |
| 55 | 55 | createExecutor({ |
| 56 | − scopes: [ | |
| 57 | − new Scope({ id: innerId, name: "Personal", createdAt: new Date() }), | |
| 58 | − orgScope, | |
| 59 | − ], | |
| 56 | + scopes: [new Scope({ id: innerId, name: "Personal", createdAt: new Date() }), orgScope], | |
| 60 | 57 | adapter, |
| 61 | 58 | blobs, |
| 62 | 59 | plugins, |
| @@ -142,8 +139,7 @@ describe("MCP cross-user isolation within the same org", () => { · 79 unmodified lines | ||
| 142 | 139 | |
| 143 | 140 | it.effect("A sees A's source, B sees org's, neither sees the other's inner", () => |
| 144 | 141 | Effect.gen(function* () { |
| 145 | − const { execA, execB, aInnerId, bInnerId, orgScopeId } = | |
| 146 | − yield* makeSharedOrgExecutors(); | |
| 142 | + const { execA, execB, aInnerId, bInnerId, orgScopeId } = yield* makeSharedOrgExecutors(); | |
| 147 | 143 | |
| 148 | 144 | yield* seedSource(execA.mcp.addSource, { |
| 149 | 145 | scope: aInnerId, |
| @@ -145,8 +145,7 @@ describe("MCP elicitation (end-to-end)", () => { · 144 unmodified lines | ||
| 145 | 145 | gatedEcho.id, |
| 146 | 146 | { value: "nope" }, |
| 147 | 147 | { |
| 148 | − onElicitation: () => | |
| 149 | − Effect.succeed(new ElicitationResponse({ action: "decline" })), | |
| 148 | + onElicitation: () => 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 | 103 | const isToolInvocationError = (error: unknown): error is ToolInvocationError => |
| 104 | 104 | Predicate.isTagged(error, "ToolInvocationError"); |
| 105 | 105 | |
| 106 | −const createAuthRecordingServer: Effect.Effect<TestServer, Error, never> = | |
| 107 | − Effect.callback<TestServer, Error>((resume) => { | |
| 108 | − const transports = new Map<string, StreamableHTTPServerTransport>(); | |
| 109 | − const recorded: RecordedRequest[] = []; | |
| 110 | − | |
| 111 | − const httpServer = http.createServer(async (req, res) => { | |
| 112 | − recorded.push({ | |
| 113 | − authorization: req.headers["authorization"] as string | undefined, | |
| 114 | − }); | |
| 106 | +const createAuthRecordingServer: Effect.Effect<TestServer, Error, never> = Effect.callback< | |
| 107 | + TestServer, | |
| 108 | + Error | |
| 109 | +>((resume) => { | |
| 110 | + const transports = new Map<string, StreamableHTTPServerTransport>(); | |
| 111 | + const recorded: RecordedRequest[] = []; | |
| 112 | + | |
| 113 | + const httpServer = http.createServer(async (req, res) => { | |
| 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; | |
| 117 | − if (sessionId) { | |
| 118 | − const transport = transports.get(sessionId); | |
| 119 | − if (!transport) { | |
| 120 | − res.writeHead(404); | |
| 121 | − res.end("Session not found"); | |
| 122 | − return; | |
| 123 | − } | |
| 124 | − await transport.handleRequest(req, res); | |
| 118 | + const sessionId = req.headers["mcp-session-id"] as string | undefined; | |
| 119 | + if (sessionId) { | |
| 120 | + const transport = transports.get(sessionId); | |
| 121 | + if (!transport) { | |
| 122 | + res.writeHead(404); | |
| 123 | + res.end("Session not found"); | |
| 125 | 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 | 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, () => { | |
| 154 | − const addr = httpServer.address(); | |
| 155 | − const port = typeof addr === "object" && addr ? addr.port : 0; | |
| 156 | − resume( | |
| 157 | − Effect.succeed({ | |
| 158 | − url: `http://127.0.0.1:${port}`, | |
| 159 | − httpServer, | |
| 160 | − recorded: () => recorded, | |
| 161 | − }), | |
| 162 | − ); | |
| 142 | + const transport = new StreamableHTTPServerTransport({ | |
| 143 | + sessionIdGenerator: () => crypto.randomUUID(), | |
| 144 | + onsessioninitialized: (sid) => { | |
| 145 | + transports.set(sid, transport); | |
| 146 | + }, | |
| 163 | 147 | }); |
| 148 | + await mcpServer.connect(transport); | |
| 149 | + await transport.handleRequest(req, res); | |
| 164 | 150 | }); |
| 165 | 151 | |
| 166 | −const serveMcpServer = Effect.acquireRelease( | |
| 167 | − createAuthRecordingServer, | |
| 168 | − ({ httpServer }) => | |
| 169 | − Effect.sync(() => { | |
| 170 | − httpServer.close(); | |
| 171 | − }), | |
| 152 | + httpServer.listen(0, () => { | |
| 153 | + const addr = httpServer.address(); | |
| 154 | + const port = typeof addr === "object" && addr ? addr.port : 0; | |
| 155 | + resume( | |
| 156 | + Effect.succeed({ | |
| 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 | 178 | const USER_B = ScopeId.make("user-b"); |
| 182 | 179 | const ORG = ScopeId.make("org"); |
| 183 | 180 | |
| 184 | −const scope = (id: ScopeId, name: string): Scope => | |
| 185 | − new Scope({ id, name, createdAt: new Date() }); | |
| 181 | +const scope = (id: ScopeId, name: string): Scope => new Scope({ id, name, createdAt: new Date() }); | |
| 186 | 182 | |
| 187 | 183 | const makeLayeredMcpExecutors = () => |
| 188 | 184 | Effect.gen(function* () { |
| @@ -290,9 +286,7 @@ describe("per-user MCP auth isolation", () => { · 101 unmodified lines | ||
| 290 | 286 | content: [{ type: "text", text: "ok:from-user-a" }], |
| 291 | 287 | }); |
| 292 | 288 | const userARequests = server.recorded().slice(recordedBeforeUserA); |
| 293 | − expect( | |
| 294 | − userARequests.some((r) => r.authorization === "Bearer token-user-a"), | |
| 295 | − ).toBe(true); | |
| 289 | + expect(userARequests.some((r) => r.authorization === "Bearer token-user-a")).toBe(true); | |
| 296 | 290 | |
| 297 | 291 | // Snapshot the recorded-auth slice we'll check for the user-B |
| 298 | 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 | 338 | // must fail rather than silently fall through to any other scope's |
| 345 | 339 | // value. This also locks the contract that the MCP plugin doesn't |
| 346 | 340 | // quietly downgrade a missing secret to "no auth" on the transport. |
| 347 | − it.effect( | |
| 348 | − "header source: unauthenticated user B cannot invoke via a per-user secret", | |
| 349 | − () => | |
| 350 | − Effect.gen(function* () { | |
| 351 | − const server = yield* serveMcpServer; | |
| 352 | − const { execUserA, execUserB } = yield* makeLayeredMcpExecutors(); | |
| 341 | + it.effect("header source: unauthenticated user B cannot invoke via a per-user secret", () => | |
| 342 | + Effect.gen(function* () { | |
| 343 | + const server = yield* serveMcpServer; | |
| 344 | + const { execUserA, execUserB } = yield* makeLayeredMcpExecutors(); | |
| 345 | + | |
| 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 | |
| 357 | − // FIRST — so the subsequent addSource's discovery call can | |
| 358 | − // resolve it through user A's stack. | |
| 359 | − yield* execUserA.secrets.set( | |
| 360 | − new SetSecretInput({ | |
| 361 | − id: SECRET, | |
| 362 | − scope: USER_A, | |
| 363 | − name: "User A MCP token", | |
| 364 | − value: "token-user-a-header", | |
| 365 | − }), | |
| 366 | − ); | |
| 374 | + // User A sanity invoke — server sees A's token. Asserting on | |
| 375 | + // the recorded header here guards against false-positive for | |
| 376 | + // the negative check below (e.g. if the plugin silently dropped | |
| 377 | + // auth, both invocations would fail for unrelated reasons). | |
| 378 | + const userATools = yield* execUserA.tools.list(); | |
| 379 | + const whoamiForA = userATools.find((t) => t.name === "whoami")!; | |
| 380 | + | |
| 381 | + const recordedBeforeUserA = server.recorded().length; | |
| 382 | + const userAResult = yield* execUserA.tools.invoke( | |
| 383 | + whoamiForA.id, | |
| 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({ | |
| 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 | − }); | |
| 395 | + const recordedBeforeUserB = server.recorded().length; | |
| 381 | 396 | |
| 382 | − // User A sanity invoke — server sees A's token. Asserting on | |
| 383 | − // the recorded header here guards against false-positive for | |
| 384 | − // the negative check below (e.g. if the plugin silently dropped | |
| 385 | − // auth, both invocations would fail for unrelated reasons). | |
| 386 | − const userATools = yield* execUserA.tools.list(); | |
| 387 | − const whoamiForA = userATools.find((t) => t.name === "whoami")!; | |
| 397 | + // User B has no personal token. The fall-through lookup walks | |
| 398 | + // [userB, org] — neither scope has the secret row, so the | |
| 399 | + // resolver must return null and the invoke must fail. | |
| 400 | + const userBTools = yield* execUserB.tools.list(); | |
| 401 | + const whoamiForB = userBTools.find((t) => t.name === "whoami")!; | |
| 388 | 402 | |
| 389 | − const recordedBeforeUserA = server.recorded().length; | |
| 390 | − const userAResult = yield* execUserA.tools.invoke( | |
| 391 | − whoamiForA.id, | |
| 392 | − { marker: "user-a-header" }, | |
| 403 | + const userBResult = yield* Effect.exit( | |
| 404 | + execUserB.tools.invoke( | |
| 405 | + whoamiForB.id, | |
| 406 | + { marker: "user-b-header" }, | |
| 393 | 407 | { onElicitation: "accept-all" }, |
| 394 | − ); | |
| 395 | − expect(userAResult).toMatchObject({ | |
| 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); | |
| 408 | + ), | |
| 409 | + ); | |
| 431 | 410 | |
| 432 | − const afterUserB = server.recorded().slice(recordedBeforeUserB); | |
| 433 | − for (const req of afterUserB) { | |
| 434 | − expect(req.authorization).not.toBe("Bearer token-user-a-header"); | |
| 435 | − } | |
| 436 | − }), | |
| 411 | + expect(Exit.isFailure(userBResult)).toBe(true); | |
| 412 | + // tools.invoke wraps plugin failures in ToolInvocationError | |
| 413 | + // with the original error carried on `cause`. Pin the exact | |
| 414 | + // inner tag — a regression that swapped the "no connection | |
| 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 | 130 | { status: 400, headers: { "content-type": "application/json" } }, |
| 131 | 131 | ); |
| 132 | − const result = yield* probeMcpEndpointShape( | |
| 133 | − "https://backboard.railway.com/graphql/v2", | |
| 134 | − { fetch: stubFetch(response) }, | |
| 135 | − ); | |
| 132 | + const result = yield* probeMcpEndpointShape("https://backboard.railway.com/graphql/v2", { | |
| 133 | + fetch: stubFetch(response), | |
| 134 | + }); | |
| 136 | 135 | expect(result.kind).toBe("not-mcp"); |
| 137 | 136 | }), |
| 138 | 137 | ); |
| @@ -133,8 +133,7 @@ export const probeMcpEndpointShape = ( · 132 unmodified lines | ||
| 133 | 133 | } |
| 134 | 134 | return { |
| 135 | 135 | kind: "not-mcp", |
| 136 | − reason: | |
| 137 | − "401 without Bearer WWW-Authenticate — not an MCP auth challenge", | |
| 136 | + reason: "401 without Bearer WWW-Authenticate — not an MCP auth challenge", | |
| 138 | 137 | } as const; |
| 139 | 138 | } |
| 140 | 139 | |
| @@ -26,8 +26,6 @@ export const createStdioTransport = (config: StdioTransportConfig) => · 25 unmodified lines | ||
| 26 | 26 | new StdioClientTransport({ |
| 27 | 27 | command: config.command, |
| 28 | 28 | args: config.args ? [...config.args] : undefined, |
| 29 | − env: config.env | |
| 30 | − ? ({ ...process.env, ...config.env } as Record<string, string>) | |
| 31 | − : undefined, | |
| 29 | + env: config.env ? ({ ...process.env, ...config.env } as Record<string, string>) : undefined, | |
| 32 | 30 | cwd: config.cwd, |
| 33 | 31 | }); |
| @@ -16,7 +16,9 @@ import { OnePasswordGroup } from "./group"; · 15 unmodified lines | ||
| 16 | 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 | 22 | >()("OnePasswordExtensionService") {} |
| 21 | 23 | |
| 22 | 24 | // --------------------------------------------------------------------------- |
| @@ -41,38 +43,48 @@ export const OnePasswordHandlers = HttpApiBuilder.group( · 18 unmodified lines | ||
| 41 | 43 | (handlers) => |
| 42 | 44 | handlers |
| 43 | 45 | .handle("getConfig", () => |
| 44 | − capture(Effect.gen(function* () { | |
| 45 | − const ext = yield* OnePasswordExtensionService; | |
| 46 | − return yield* ext.getConfig(); | |
| 47 | − })), | |
| 46 | + capture( | |
| 47 | + Effect.gen(function* () { | |
| 48 | + const ext = yield* OnePasswordExtensionService; | |
| 49 | + return yield* ext.getConfig(); | |
| 50 | + }), | |
| 51 | + ), | |
| 48 | 52 | ) |
| 49 | 53 | .handle("configure", ({ payload }) => |
| 50 | − capture(Effect.gen(function* () { | |
| 51 | − const ext = yield* OnePasswordExtensionService; | |
| 52 | − yield* ext.configure(payload); | |
| 53 | − })), | |
| 54 | + capture( | |
| 55 | + Effect.gen(function* () { | |
| 56 | + const ext = yield* OnePasswordExtensionService; | |
| 57 | + yield* ext.configure(payload); | |
| 58 | + }), | |
| 59 | + ), | |
| 54 | 60 | ) |
| 55 | 61 | .handle("removeConfig", () => |
| 56 | − capture(Effect.gen(function* () { | |
| 57 | − const ext = yield* OnePasswordExtensionService; | |
| 58 | − yield* ext.removeConfig(); | |
| 59 | − })), | |
| 62 | + capture( | |
| 63 | + Effect.gen(function* () { | |
| 64 | + const ext = yield* OnePasswordExtensionService; | |
| 65 | + yield* ext.removeConfig(); | |
| 66 | + }), | |
| 67 | + ), | |
| 60 | 68 | ) |
| 61 | 69 | .handle("status", () => |
| 62 | − capture(Effect.gen(function* () { | |
| 63 | − const ext = yield* OnePasswordExtensionService; | |
| 64 | − return yield* ext.status(); | |
| 65 | − })), | |
| 70 | + capture( | |
| 71 | + Effect.gen(function* () { | |
| 72 | + const ext = yield* OnePasswordExtensionService; | |
| 73 | + return yield* ext.status(); | |
| 74 | + }), | |
| 75 | + ), | |
| 66 | 76 | ) |
| 67 | 77 | .handle("listVaults", ({ query: urlParams }) => |
| 68 | − capture(Effect.gen(function* () { | |
| 69 | − const ext = yield* OnePasswordExtensionService; | |
| 70 | − const auth = | |
| 71 | − urlParams.authKind === "desktop-app" | |
| 72 | − ? { kind: "desktop-app" as const, accountName: urlParams.account } | |
| 73 | − : { kind: "service-account" as const, tokenSecretId: urlParams.account }; | |
| 74 | − const vaults = yield* ext.listVaults(auth); | |
| 75 | − return { vaults: [...vaults] }; | |
| 76 | − })), | |
| 78 | + capture( | |
| 79 | + Effect.gen(function* () { | |
| 80 | + const ext = yield* OnePasswordExtensionService; | |
| 81 | + const auth = | |
| 82 | + urlParams.authKind === "desktop-app" | |
| 83 | + ? { kind: "desktop-app" as const, accountName: urlParams.account } | |
| 84 | + : { kind: "service-account" as const, tokenSecretId: urlParams.account }; | |
| 85 | + const vaults = yield* ext.listVaults(auth); | |
| 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 | 168 | .add( |
| 169 | − HttpApiEndpoint.post( | |
| 170 | − "removeSourceBinding", | |
| 171 | − "/scopes/:scopeId/openapi/source-bindings/remove", | |
| 172 | − { | |
| 173 | − params: ScopeIdParam, | |
| 174 | − payload: RemoveBindingPayload, | |
| 175 | − success: Schema.Struct({ removed: Schema.Boolean }), | |
| 176 | − error: DomainErrors, | |
| 177 | − }, | |
| 178 | − ), | |
| 179 | − ) | |
| 180 | −; | |
| 169 | + HttpApiEndpoint.post("removeSourceBinding", "/scopes/:scopeId/openapi/source-bindings/remove", { | |
| 170 | + params: ScopeIdParam, | |
| 171 | + payload: RemoveBindingPayload, | |
| 172 | + success: Schema.Struct({ removed: Schema.Boolean }), | |
| 173 | + error: DomainErrors, | |
| 174 | + }), | |
| 175 | + ); | |
| @@ -24,7 +24,9 @@ import { OpenApiGroup } from "./group"; · 23 unmodified lines | ||
| 24 | 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 | 30 | >()("OpenApiExtensionService") {} |
| 29 | 31 | |
| 30 | 32 | // --------------------------------------------------------------------------- |
| @@ -401,29 +401,29 @@ export default function AddOpenApiSource(props: { · 400 unmodified lines | ||
| 401 | 401 | const result = exit.value; |
| 402 | 402 | setPreview(result); |
| 403 | 403 | |
| 404 | − const firstServer = result.servers[0]; | |
| 405 | − if (firstServer) { | |
| 406 | − setSelectedServerIndex(0); | |
| 407 | − setVariableSelections(defaultSelectionsFor(firstServer)); | |
| 408 | − setCustomBaseUrl(""); | |
| 409 | − } else { | |
| 410 | − setSelectedServerIndex(-1); | |
| 411 | − setVariableSelections({}); | |
| 412 | − setCustomBaseUrl(""); | |
| 413 | − } | |
| 404 | + const firstServer = result.servers[0]; | |
| 405 | + if (firstServer) { | |
| 406 | + setSelectedServerIndex(0); | |
| 407 | + setVariableSelections(defaultSelectionsFor(firstServer)); | |
| 408 | + setCustomBaseUrl(""); | |
| 409 | + } else { | |
| 410 | + setSelectedServerIndex(-1); | |
| 411 | + setVariableSelections({}); | |
| 412 | + setCustomBaseUrl(""); | |
| 413 | + } | |
| 414 | 414 | |
| 415 | − const firstPreset = result.headerPresets[0]; | |
| 416 | − if (firstPreset) { | |
| 417 | − setStrategy({ kind: "header", presetIndex: 0 }); | |
| 418 | − setCustomHeaders(entriesFromSpecPreset(firstPreset)); | |
| 419 | − } else { | |
| 420 | − // No header presets — default to "custom" so the headers editor is | |
| 421 | − // visible immediately. Specs with no `security` block (e.g. Microsoft | |
| 422 | − // Graph) would otherwise leave the user staring at just the | |
| 423 | − // Authentication heading with no way to add headers. | |
| 424 | − setStrategy({ kind: "custom" }); | |
| 425 | − setCustomHeaders([]); | |
| 426 | − } | |
| 415 | + const firstPreset = result.headerPresets[0]; | |
| 416 | + if (firstPreset) { | |
| 417 | + setStrategy({ kind: "header", presetIndex: 0 }); | |
| 418 | + setCustomHeaders(entriesFromSpecPreset(firstPreset)); | |
| 419 | + } else { | |
| 420 | + // No header presets — default to "custom" so the headers editor is | |
| 421 | + // visible immediately. Specs with no `security` block (e.g. Microsoft | |
| 422 | + // Graph) would otherwise leave the user staring at just the | |
| 423 | + // Authentication heading with no way to add headers. | |
| 424 | + setStrategy({ kind: "custom" }); | |
| 425 | + setCustomHeaders([]); | |
| 426 | + } | |
| 427 | 427 | setAnalyzing(false); |
| 428 | 428 | }; |
| 429 | 429 | |
| @@ -490,126 +490,126 @@ export default function AddOpenApiSource(props: { · 60 unmodified lines | ||
| 490 | 490 | const tokenUrl = resolveOAuthUrl(selectedOAuth2Preset.tokenUrl, resolvedBaseUrl); |
| 491 | 491 | |
| 492 | 492 | if (selectedOAuth2Preset.flow === "clientCredentials") { |
| 493 | − // RFC 6749 §4.4: no user-interactive consent step. The client_secret | |
| 494 | − // is mandatory; the backend exchanges tokens inline and returns a | |
| 495 | − // completed OAuth2Auth we can attach to the source directly. | |
| 496 | − if (!oauth2ClientSecretSecretId) { | |
| 497 | − setOauth2Error("client_credentials requires a client secret"); | |
| 498 | − return; | |
| 499 | − } | |
| 500 | − setStartingOAuth(true); | |
| 501 | − const connectionId = openApiOAuthConnectionId(resolvedSourceId, selectedOAuth2Preset.flow); | |
| 493 | + // RFC 6749 §4.4: no user-interactive consent step. The client_secret | |
| 494 | + // is mandatory; the backend exchanges tokens inline and returns a | |
| 495 | + // completed OAuth2Auth we can attach to the source directly. | |
| 496 | + if (!oauth2ClientSecretSecretId) { | |
| 497 | + setOauth2Error("client_credentials requires a client secret"); | |
| 498 | + return; | |
| 499 | + } | |
| 500 | + setStartingOAuth(true); | |
| 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 | 556 | const exit = await doStartOAuth({ |
| 503 | 557 | params: { scopeId }, |
| 504 | 558 | payload: { |
| 505 | − endpoint: tokenUrl, | |
| 506 | − redirectUrl: tokenUrl, | |
| 507 | − connectionId, | |
| 559 | + endpoint: authorizationUrl, | |
| 560 | + connectionId: openApiOAuthConnectionId(resolvedSourceId, selectedOAuth2Preset.flow), | |
| 508 | 561 | tokenScope: scopeId, |
| 562 | + redirectUrl: oauth2RedirectUrl, | |
| 509 | 563 | strategy: { |
| 510 | − kind: "client-credentials", | |
| 564 | + kind: "authorization-code", | |
| 565 | + authorizationEndpoint: authorizationUrl, | |
| 511 | 566 | tokenEndpoint: tokenUrl, |
| 567 | + issuerUrl, | |
| 512 | 568 | clientIdSecretId: oauth2ClientIdSecretId, |
| 513 | − clientSecretSecretId: oauth2ClientSecretSecretId, | |
| 569 | + clientSecretSecretId: oauth2ClientSecretSecretId ?? null, | |
| 514 | 570 | scopes: [...oauth2SelectedScopes], |
| 515 | 571 | }, |
| 516 | 572 | pluginId: "openapi", |
| 517 | 573 | identityLabel: `${displayName} OAuth`, |
| 518 | 574 | }, |
| 519 | 575 | }); |
| 520 | − setStartingOAuth(false); | |
| 521 | 576 | if (Exit.isFailure(exit)) { |
| 522 | − setOauth2Error(errorMessageFromExit(exit, "Failed to start OAuth")); | |
| 523 | − return; | |
| 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")); | |
| 524 | 579 | } |
| 525 | 580 | const response = exit.value; |
| 526 | − if (!response.completedConnection) { | |
| 527 | − setOauth2Error("client_credentials flow did not mint a connection"); | |
| 528 | − return; | |
| 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"); | |
| 529 | 584 | } |
| 585 | + return { | |
| 586 | + sessionId: response.sessionId, | |
| 587 | + authorizationUrl: response.authorizationUrl, | |
| 588 | + }; | |
| 589 | + }, | |
| 590 | + onSuccess: (result) => { | |
| 530 | 591 | setOauth2AuthState({ |
| 531 | 592 | fingerprint: selectedOAuth2Fingerprint, |
| 532 | 593 | auth: new OAuth2Auth({ |
| 533 | 594 | kind: "oauth2", |
| 534 | − connectionId: response.completedConnection.connectionId, | |
| 595 | + connectionId: result.connectionId, | |
| 535 | 596 | securitySchemeName: selectedOAuth2Preset.securitySchemeName, |
| 536 | − flow: "clientCredentials", | |
| 597 | + flow: "authorizationCode", | |
| 537 | 598 | tokenUrl, |
| 538 | − authorizationUrl: null, | |
| 599 | + authorizationUrl, | |
| 600 | + issuerUrl, | |
| 539 | 601 | clientIdSecretId: oauth2ClientIdSecretId, |
| 540 | 602 | clientSecretSecretId: oauth2ClientSecretSecretId, |
| 541 | 603 | scopes: [...oauth2SelectedScopes], |
| 542 | 604 | }), |
| 543 | 605 | }); |
| 544 | 606 | 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 () => { | |
| 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 | − }); | |
| 607 | + }, | |
| 608 | + onError: (message) => { | |
| 609 | + setStartingOAuth(false); | |
| 610 | + setOauth2Error(message); | |
| 611 | + }, | |
| 612 | + }); | |
| 613 | 613 | }, [ |
| 614 | 614 | selectedOAuth2Preset, |
| 615 | 615 | oauth2ClientIdSecretId, |
| @@ -87,9 +87,7 @@ export default function OpenApiSourceSummary(props: { · 86 unmodified lines | ||
| 87 | 87 | } |
| 88 | 88 | const connections = AsyncResult.isSuccess(connectionsResult) ? connectionsResult.value : []; |
| 89 | 89 | const liveConnectionIds = new Set(connections.map((connection) => connection.id)); |
| 90 | − const scopeRanks = new Map( | |
| 91 | − scopeStack.map((scope, index) => [scope.id, index] as const), | |
| 92 | − ); | |
| 90 | + const scopeRanks = new Map(scopeStack.map((scope, index) => [scope.id, index] as const)); | |
| 93 | 91 | const credentialTargetScope = userScope; |
| 94 | 92 | const missing = missingCredentialLabels(source, bindings, credentialTargetScope, scopeRanks, { |
| 95 | 93 | liveConnectionIds, |
| @@ -8,7 +8,13 @@ · 7 unmodified lines | ||
| 8 | 8 | |
| 9 | 9 | import { afterEach, expect, layer } from "@effect/vitest"; |
| 10 | 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 | 18 | import { FetchHttpClient, HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http"; |
| 13 | 19 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; |
| 14 | 20 | |
| @@ -91,36 +97,40 @@ const mockClientCredentialsFetch = (args: { · 76 unmodified lines | ||
| 91 | 97 | readonly expiresIn?: number; |
| 92 | 98 | }) => { |
| 93 | 99 | let callIndex = 0; |
| 94 | − globalThis.fetch = Object.assign(async (_input: RequestInfo | URL, init?: RequestInit) => { | |
| 95 | − const url = typeof _input === "string" ? _input : _input.toString(); | |
| 96 | − if (!url.includes("token.example.com")) { | |
| 97 | − return originalFetch(_input, init); | |
| 98 | − } | |
| 99 | − const bodyText = | |
| 100 | − init?.body instanceof URLSearchParams | |
| 101 | − ? init.body.toString() | |
| 102 | − : typeof init?.body === "string" | |
| 103 | − ? init.body | |
| 104 | − : ""; | |
| 105 | − const params = new URLSearchParams(bodyText); | |
| 106 | − args.calls.push({ | |
| 107 | − grantType: params.get("grant_type"), | |
| 108 | − clientId: params.get("client_id"), | |
| 109 | − clientSecret: params.get("client_secret"), | |
| 110 | − scope: params.get("scope"), | |
| 111 | − }); | |
| 112 | − const token = args.accessTokens[Math.min(callIndex, args.accessTokens.length - 1)] ?? "unknown"; | |
| 113 | − callIndex += 1; | |
| 114 | − const body: Record<string, unknown> = { | |
| 115 | − access_token: token, | |
| 116 | − token_type: "Bearer", | |
| 117 | − }; | |
| 118 | − if (typeof args.expiresIn === "number") body.expires_in = args.expiresIn; | |
| 119 | − return new Response(JSON.stringify(body), { | |
| 120 | − status: 200, | |
| 121 | − headers: { "content-type": "application/json" }, | |
| 122 | − }); | |
| 123 | − }, { preconnect: originalFetch.preconnect }); | |
| 100 | + globalThis.fetch = Object.assign( | |
| 101 | + async (_input: RequestInfo | URL, init?: RequestInit) => { | |
| 102 | + const url = typeof _input === "string" ? _input : _input.toString(); | |
| 103 | + if (!url.includes("token.example.com")) { | |
| 104 | + return originalFetch(_input, init); | |
| 105 | + } | |
| 106 | + const bodyText = | |
| 107 | + init?.body instanceof URLSearchParams | |
| 108 | + ? init.body.toString() | |
| 109 | + : typeof init?.body === "string" | |
| 110 | + ? init.body | |
| 111 | + : ""; | |
| 112 | + const params = new URLSearchParams(bodyText); | |
| 113 | + args.calls.push({ | |
| 114 | + grantType: params.get("grant_type"), | |
| 115 | + clientId: params.get("client_id"), | |
| 116 | + clientSecret: params.get("client_secret"), | |
| 117 | + scope: params.get("scope"), | |
| 118 | + }); | |
| 119 | + const token = | |
| 120 | + args.accessTokens[Math.min(callIndex, args.accessTokens.length - 1)] ?? "unknown"; | |
| 121 | + callIndex += 1; | |
| 122 | + const body: Record<string, unknown> = { | |
| 123 | + access_token: token, | |
| 124 | + token_type: "Bearer", | |
| 125 | + }; | |
| 126 | + if (typeof args.expiresIn === "number") body.expires_in = args.expiresIn; | |
| 127 | + return new Response(JSON.stringify(body), { | |
| 128 | + status: 200, | |
| 129 | + headers: { "content-type": "application/json" }, | |
| 130 | + }); | |
| 131 | + }, | |
| 132 | + { preconnect: originalFetch.preconnect }, | |
| 133 | + ); | |
| 124 | 134 | }; |
| 125 | 135 | |
| 126 | 136 | afterEach(() => { |
| @@ -306,17 +316,13 @@ layer(TestLayer)("OpenAPI client_credentials OAuth", (it) => { · 179 unmodified lines | ||
| 306 | 316 | |
| 307 | 317 | // Access-token secret is owned by the connection and filtered |
| 308 | 318 | // out of the user-facing secret list. |
| 309 | − const userSecretIds = new Set( | |
| 310 | − (yield* userExec.secrets.list()).map((s) => String(s.id)), | |
| 311 | − ); | |
| 319 | + const userSecretIds = new Set((yield* userExec.secrets.list()).map((s) => String(s.id))); | |
| 312 | 320 | expect(userSecretIds).toContain("petstore_client_id"); |
| 313 | 321 | expect(userSecretIds).toContain("petstore_client_secret"); |
| 314 | 322 | expect(userSecretIds).not.toContain(`${auth.connectionId}.access_token`); |
| 315 | 323 | |
| 316 | 324 | // Admin scope sees neither alice's connection nor her token. |
| 317 | − const adminSecretIds = new Set( | |
| 318 | − (yield* adminExec.secrets.list()).map((s) => String(s.id)), | |
| 319 | − ); | |
| 325 | + const adminSecretIds = new Set((yield* adminExec.secrets.list()).map((s) => String(s.id))); | |
| 320 | 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 | 84 | it("does not treat personal bindings as satisfying org-level credential status", () => { |
| 85 | 85 | expect( |
| 86 | 86 | missingCredentialLabels(source, bindings(userScope, allSlots), orgScope, scopeRanks), |
| 87 | − ).toEqual([ | |
| 88 | − "Authorization", | |
| 89 | − "Client ID", | |
| 90 | − "Client Secret", | |
| 91 | − "OAuth client connection", | |
| 92 | − ]); | |
| 87 | + ).toEqual(["Authorization", "Client ID", "Client Secret", "OAuth client connection"]); | |
| 93 | 88 | }); |
| 94 | 89 | |
| 95 | 90 | it("prefers the personal binding over a shared org binding", () => { |
| @@ -33,15 +33,9 @@ export const effectiveBindingForScope = ( · 32 unmodified lines | ||
| 33 | 33 | ): BindingRowForCredentialStatus | null => |
| 34 | 34 | rows |
| 35 | 35 | .filter( |
| 36 | − (row) => | |
| 37 | − row.slot === slot && | |
| 38 | − scopeRank(ranks, row.scopeId) >= scopeRank(ranks, targetScope), | |
| 36 | + (row) => row.slot === slot && scopeRank(ranks, row.scopeId) >= scopeRank(ranks, targetScope), | |
| 39 | 37 | ) |
| 40 | − .sort( | |
| 41 | − (a, b) => | |
| 42 | − scopeRank(ranks, a.scopeId) - | |
| 43 | − scopeRank(ranks, b.scopeId), | |
| 44 | − )[0] ?? null; | |
| 38 | + .sort((a, b) => scopeRank(ranks, a.scopeId) - scopeRank(ranks, b.scopeId))[0] ?? null; | |
| 45 | 39 | |
| 46 | 40 | const hasSecretBinding = ( |
| 47 | 41 | rows: readonly BindingRowForCredentialStatus[], |
| @@ -59,9 +53,7 @@ const hasConnectionBinding = ( · 11 unmodified lines | ||
| 59 | 53 | ) => { |
| 60 | 54 | const binding = effectiveBindingForScope(rows, slot, targetScope, ranks); |
| 61 | 55 | if (binding?.value.kind !== "connection") return false; |
| 62 | − return liveConnectionIds | |
| 63 | − ? liveConnectionIds.has(binding.value.connectionId) | |
| 64 | − : true; | |
| 56 | + return liveConnectionIds ? liveConnectionIds.has(binding.value.connectionId) : true; | |
| 65 | 57 | }; |
| 66 | 58 | |
| 67 | 59 | const effectiveClientSecretSlot = (oauth2: { |
| @@ -106,17 +98,9 @@ export function missingCredentialLabels( · 38 unmodified lines | ||
| 106 | 98 | } |
| 107 | 99 | |
| 108 | 100 | if ( |
| 109 | − !hasConnectionBinding( | |
| 110 | − bindings, | |
| 111 | − oauth2.connectionSlot, | |
| 112 | − targetScope, | |
| 113 | − ranks, | |
| 114 | − liveConnectionIds, | |
| 115 | − ) | |
| 101 | + !hasConnectionBinding(bindings, oauth2.connectionSlot, targetScope, ranks, liveConnectionIds) | |
| 116 | 102 | ) { |
| 117 | − missing.push( | |
| 118 | − oauth2.flow === "clientCredentials" ? "OAuth client connection" : "OAuth sign-in", | |
| 119 | − ); | |
| 103 | + missing.push(oauth2.flow === "clientCredentials" ? "OAuth client connection" : "OAuth sign-in"); | |
| 120 | 104 | } |
| 121 | 105 | |
| 122 | 106 | return missing; |
| @@ -124,9 +124,7 @@ const extractRequestBody = ( · 123 unmodified lines | ||
| 124 | 124 | contentType: mediaType, |
| 125 | 125 | schema: Option.fromNullishOr(media.schema), |
| 126 | 126 | encoding: Option.fromNullishOr( |
| 127 | − buildEncodingRecord( | |
| 128 | − (media as { encoding?: Record<string, unknown> }).encoding, | |
| 129 | − ), | |
| 127 | + buildEncodingRecord((media as { encoding?: Record<string, unknown> }).encoding), | |
| 130 | 128 | ), |
| 131 | 129 | }), |
| 132 | 130 | ); |
| @@ -246,9 +244,7 @@ const extractServers = (doc: ParsedDocument): ServerInfo[] => · 113 unmodified lines | ||
| 246 | 244 | new ServerVariable({ |
| 247 | 245 | default: String(v.default), |
| 248 | 246 | enum: |
| 249 | − enumValues && enumValues.length > 0 | |
| 250 | − ? Option.some(enumValues) | |
| 251 | − : Option.none(), | |
| 247 | + enumValues && enumValues.length > 0 ? Option.some(enumValues) : Option.none(), | |
| 252 | 248 | description: Option.fromNullishOr(v.description), |
| 253 | 249 | }), |
| 254 | 250 | ], |
| @@ -36,14 +36,12 @@ const memoryProvider: SecretProvider = (() => { · 35 unmodified lines | ||
| 36 | 36 | return { |
| 37 | 37 | key: "memory", |
| 38 | 38 | writable: true, |
| 39 | − get: (id, scope) => | |
| 40 | − Effect.sync(() => store.get(`${scope}\u0000${id}`) ?? null), | |
| 39 | + get: (id, scope) => Effect.sync(() => store.get(`${scope}\u0000${id}`) ?? null), | |
| 41 | 40 | set: (id, value, scope) => |
| 42 | 41 | Effect.sync(() => { |
| 43 | 42 | store.set(`${scope}\u0000${id}`, value); |
| 44 | 43 | }), |
| 45 | − delete: (id, scope) => | |
| 46 | − Effect.sync(() => store.delete(`${scope}\u0000${id}`)), | |
| 44 | + delete: (id, scope) => Effect.sync(() => store.delete(`${scope}\u0000${id}`)), | |
| 47 | 45 | list: () => Effect.sync(() => []), |
| 48 | 46 | }; |
| 49 | 47 | })(); |
| @@ -61,31 +59,29 @@ type Captured = { · 11 unmodified lines | ||
| 61 | 59 | |
| 62 | 60 | const startEchoServer = () => |
| 63 | 61 | Effect.acquireRelease( |
| 64 | − Effect.callback<{ baseUrl: string; captured: Captured; close: () => void }>( | |
| 65 | − (resume) => { | |
| 66 | − const captured: Captured = { contentType: "", body: "" }; | |
| 67 | − const server = createServer((req, res) => { | |
| 68 | − const chunks: Buffer[] = []; | |
| 69 | − req.on("data", (c: Buffer) => chunks.push(c)); | |
| 70 | − req.on("end", () => { | |
| 71 | − captured.contentType = req.headers["content-type"] ?? ""; | |
| 72 | − captured.body = Buffer.concat(chunks).toString("utf8"); | |
| 73 | − res.writeHead(200, { "content-type": "application/json" }); | |
| 74 | − res.end(JSON.stringify({ ok: true })); | |
| 75 | − }); | |
| 62 | + Effect.callback<{ baseUrl: string; captured: Captured; close: () => void }>((resume) => { | |
| 63 | + const captured: Captured = { contentType: "", body: "" }; | |
| 64 | + const server = createServer((req, res) => { | |
| 65 | + const chunks: Buffer[] = []; | |
| 66 | + req.on("data", (c: Buffer) => chunks.push(c)); | |
| 67 | + req.on("end", () => { | |
| 68 | + captured.contentType = req.headers["content-type"] ?? ""; | |
| 69 | + captured.body = Buffer.concat(chunks).toString("utf8"); | |
| 70 | + res.writeHead(200, { "content-type": "application/json" }); | |
| 71 | + res.end(JSON.stringify({ ok: true })); | |
| 76 | 72 | }); |
| 77 | − server.listen(0, "127.0.0.1", () => { | |
| 78 | − const port = (server.address() as AddressInfo).port; | |
| 79 | − resume( | |
| 80 | − Effect.succeed({ | |
| 81 | − baseUrl: `http://127.0.0.1:${port}`, | |
| 82 | − captured, | |
| 83 | − close: () => server.close(), | |
| 84 | − }), | |
| 85 | − ); | |
| 86 | − }); | |
| 87 | − }, | |
| 88 | − ), | |
| 73 | + }); | |
| 74 | + server.listen(0, "127.0.0.1", () => { | |
| 75 | + const port = (server.address() as AddressInfo).port; | |
| 76 | + resume( | |
| 77 | + Effect.succeed({ | |
| 78 | + baseUrl: `http://127.0.0.1:${port}`, | |
| 79 | + captured, | |
| 80 | + close: () => server.close(), | |
| 81 | + }), | |
| 82 | + ); | |
| 83 | + }); | |
| 84 | + }), | |
| 89 | 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 | 128 | describe("OpenAPI non-JSON request body serialization", () => { |
| 133 | − it.effect( | |
| 134 | − "form-urlencoded object body is properly encoded (no '[object Object]')", | |
| 135 | − () => | |
| 136 | − Effect.gen(function* () { | |
| 137 | − const { baseUrl, captured } = yield* startEchoServer(); | |
| 129 | + it.effect("form-urlencoded object body is properly encoded (no '[object Object]')", () => | |
| 130 | + Effect.gen(function* () { | |
| 131 | + const { baseUrl, captured } = yield* startEchoServer(); | |
| 138 | 132 | |
| 139 | − const executor = yield* createExecutor( | |
| 140 | − makeTestConfig({ | |
| 141 | − plugins: [ | |
| 142 | − openApiPlugin({ httpClientLayer: FetchHttpClient.layer }), | |
| 143 | − memorySecretsPlugin(), | |
| 144 | − ] as const, | |
| 145 | − }), | |
| 146 | − ); | |
| 133 | + const executor = yield* createExecutor( | |
| 134 | + makeTestConfig({ | |
| 135 | + plugins: [ | |
| 136 | + openApiPlugin({ httpClientLayer: FetchHttpClient.layer }), | |
| 137 | + memorySecretsPlugin(), | |
| 138 | + ] as const, | |
| 139 | + }), | |
| 140 | + ); | |
| 147 | 141 | |
| 148 | − yield* executor.openapi.addSpec({ | |
| 149 | − spec: formSpec, | |
| 150 | − scope: TEST_SCOPE, | |
| 151 | − namespace: "form", | |
| 152 | − baseUrl, | |
| 153 | − }); | |
| 142 | + yield* executor.openapi.addSpec({ | |
| 143 | + spec: formSpec, | |
| 144 | + scope: TEST_SCOPE, | |
| 145 | + namespace: "form", | |
| 146 | + baseUrl, | |
| 147 | + }); | |
| 154 | 148 | |
| 155 | − yield* executor.tools.invoke( | |
| 156 | − "form.forms.submit", | |
| 157 | − { body: { name: "Acme", email: "a@b.com" } }, | |
| 158 | − autoApprove, | |
| 159 | − ); | |
| 149 | + yield* executor.tools.invoke( | |
| 150 | + "form.forms.submit", | |
| 151 | + { body: { name: "Acme", email: "a@b.com" } }, | |
| 152 | + autoApprove, | |
| 153 | + ); | |
| 160 | 154 | |
| 161 | − expect(captured.contentType).toBe("application/x-www-form-urlencoded"); | |
| 162 | − expect(captured.body).not.toBe("[object Object]"); | |
| 155 | + expect(captured.contentType).toBe("application/x-www-form-urlencoded"); | |
| 156 | + expect(captured.body).not.toBe("[object Object]"); | |
| 163 | 157 | |
| 164 | − const parsed = new URLSearchParams(captured.body); | |
| 165 | − expect(parsed.get("name")).toBe("Acme"); | |
| 166 | − expect(parsed.get("email")).toBe("a@b.com"); | |
| 167 | − }), | |
| 158 | + const parsed = new URLSearchParams(captured.body); | |
| 159 | + expect(parsed.get("name")).toBe("Acme"); | |
| 160 | + expect(parsed.get("email")).toBe("a@b.com"); | |
| 161 | + }), | |
| 168 | 162 | ); |
| 169 | 163 | }); |
| @@ -195,9 +195,10 @@ describe("OpenAPI plugin", () => { · 194 unmodified lines | ||
| 195 | 195 | |
| 196 | 196 | const vars = Option.getOrThrow(server.variables); |
| 197 | 197 | expect(vars.region!.default).toBe("us"); |
| 198 | − expect( | |
| 199 | − Option.getOrElse(vars.region!.enum, () => [] as readonly string[]), | |
| 200 | − ).toEqual(["us", "de"]); | |
| 198 | + expect(Option.getOrElse(vars.region!.enum, () => [] as readonly string[])).toEqual([ | |
| 199 | + "us", | |
| 200 | + "de", | |
| 201 | + ]); | |
| 201 | 202 | expect(Option.getOrElse(vars.region!.description, () => "")).toBe( |
| 202 | 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 | 191 | const normalized = normalizeContentType(ct); |
| 192 | 192 | if (!normalized) return false; |
| 193 | 193 | return ( |
| 194 | − normalized === "application/xml" || | |
| 195 | − normalized === "text/xml" || | |
| 196 | − normalized.endsWith("+xml") | |
| 194 | + normalized === "application/xml" || normalized === "text/xml" || normalized.endsWith("+xml") | |
| 197 | 195 | ); |
| 198 | 196 | }; |
| 199 | 197 | |
| @@ -291,8 +289,7 @@ const serializeFormUrlEncoded = ( · 91 unmodified lines | ||
| 291 | 289 | parts.push(`${encKey}=${encodeFormValue(v, allowReserved)}`); |
| 292 | 290 | } |
| 293 | 291 | } else { |
| 294 | − const sep = | |
| 295 | − style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ","; | |
| 292 | + const sep = style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ","; | |
| 296 | 293 | parts.push( |
| 297 | 294 | `${encKey}=${encodeFormValue( |
| 298 | 295 | raw.map((v) => (typeof v === "object" ? JSON.stringify(v) : String(v))).join(sep), |
| @@ -313,18 +310,13 @@ const serializeFormUrlEncoded = ( · 14 unmodified lines | ||
| 313 | 310 | // `%5B` / `%5D`. Matches swagger-client's behaviour and remains |
| 314 | 311 | // accepted by common server-side parsers (qs, Rails, etc.). |
| 315 | 312 | parts.push( |
| 316 | − `${encodeURIComponent(`${key}[${subkey}]`)}=${encodeFormValue( | |
| 317 | − subval, | |
| 318 | − allowReserved, | |
| 319 | − )}`, | |
| 313 | + `${encodeURIComponent(`${key}[${subkey}]`)}=${encodeFormValue(subval, allowReserved)}`, | |
| 320 | 314 | ); |
| 321 | 315 | } |
| 322 | 316 | } else if (explode) { |
| 323 | 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.