| @@ -2,8 +2,15 @@ import { Effect, Inspectable, Layer, Option, Predicate, Schema } from "effect"; · 1 unmodified line |
| 2 | 2 | import { FetchHttpClient, type HttpClient } from "effect/unstable/http"; |
| 3 | 3 | import { fumadb } from "@executor-js/fumadb"; |
| 4 | 4 | import { memoryAdapter } from "@executor-js/fumadb/adapters/memory"; |
| 5 | | −import { withQueryContext, type Condition, type ConditionBuilder } from "@executor-js/fumadb/query"; |
| 6 | | −import { schema as fumaSchema, type RelationsMap } from "@executor-js/fumadb/schema"; |
| 5 | +import { |
| 6 | + withQueryContext, |
| 7 | + type Condition, |
| 8 | + type ConditionBuilder, |
| 9 | +} from "@executor-js/fumadb/query"; |
| 10 | +import { |
| 11 | + schema as fumaSchema, |
| 12 | + type RelationsMap, |
| 13 | +} from "@executor-js/fumadb/schema"; |
| 7 | 14 | import type { AnyColumn } from "@executor-js/fumadb/schema"; |
| 8 | 15 | import { generateKeyBetween } from "fractional-indexing"; |
| 9 | 16 | |
| @@ -16,7 +23,12 @@ import { · 6 unmodified lines |
| 16 | 23 | type FumaTables, |
| 17 | 24 | type StorageFailure, |
| 18 | 25 | } from "./fuma-runtime"; |
| 19 | | −import { makeFumaBlobStore, pluginBlobStore, type BlobStore, type OwnerPartitions } from "./blob"; |
| 26 | +import { |
| 27 | + makeFumaBlobStore, |
| 28 | + pluginBlobStore, |
| 29 | + type BlobStore, |
| 30 | + type OwnerPartitions, |
| 31 | +} from "./blob"; |
| 20 | 32 | import { coreToolsPlugin } from "./core-tools"; |
| 21 | 33 | import type { |
| 22 | 34 | Connection, |
| @@ -85,7 +97,10 @@ import type { · 62 unmodified lines |
| 85 | 97 | IntegrationConfig, |
| 86 | 98 | RegisterIntegrationInput, |
| 87 | 99 | } from "./integration"; |
| 88 | | −import { makeOAuthService, type MintOAuthConnectionInput } from "./oauth-service"; |
| 100 | +import { |
| 101 | + makeOAuthService, |
| 102 | + type MintOAuthConnectionInput, |
| 103 | +} from "./oauth-service"; |
| 89 | 104 | import type { OAuthService } from "./oauth-client"; |
| 90 | 105 | import { |
| 91 | 106 | comparePolicyRow, |
| @@ -130,7 +145,12 @@ import { · 38 unmodified lines |
| 130 | 145 | type ExecutorOwnerPolicyContext, |
| 131 | 146 | } from "./owner-policy"; |
| 132 | 147 | import { ToolSchemaView, type IntegrationDetectionResult } from "./types"; |
| 133 | | −import { type Tool, type ToolAnnotations, type ToolDef, type ToolListFilter } from "./tool"; |
| 148 | +import { |
| 149 | + type Tool, |
| 150 | + type ToolAnnotations, |
| 151 | + type ToolDef, |
| 152 | + type ToolListFilter, |
| 153 | +} from "./tool"; |
| 134 | 154 | import { buildToolTypeScriptPreview } from "./schema-types"; |
| 135 | 155 | import { collectReferencedDefinitions } from "./schema-refs"; |
| 136 | 156 | import { |
| @@ -155,7 +175,9 @@ const MAX_APPROVAL_ARGUMENT_PREVIEW_CHARS = 4_000; · 18 unmodified lines |
| 155 | 175 | const acceptAllHandler: ElicitationHandler = () => |
| 156 | 176 | Effect.succeed(ElicitationResponse.make({ action: "accept" })); |
| 157 | 177 | |
| 158 | | −const resolveElicitationHandler = (onElicitation: OnElicitation): ElicitationHandler => |
| 178 | +const resolveElicitationHandler = ( |
| 179 | + onElicitation: OnElicitation, |
| 180 | +): ElicitationHandler => |
| 159 | 181 | onElicitation === "accept-all" ? acceptAllHandler : onElicitation; |
| 160 | 182 | |
| 161 | 183 | // --------------------------------------------------------------------------- |
| @@ -171,7 +193,8 @@ export interface ParsedToolAddress { · 9 unmodified lines |
| 171 | 193 | readonly tool: ToolName; |
| 172 | 194 | } |
| 173 | 195 | |
| 174 | | −const isOwner = (value: string): value is Owner => value === "org" || value === "user"; |
| 196 | +const isOwner = (value: string): value is Owner => |
| 197 | + value === "org" || value === "user"; |
| 175 | 198 | |
| 176 | 199 | /** Parse a callable address; null when it's not a well-formed |
| 177 | 200 | * `tools.<integration>.<owner>.<connection>.<tool>`. |
| @@ -190,16 +213,17 @@ export const parseToolAddress = (address: string): ParsedToolAddress | null => { · 12 unmodified lines |
| 190 | 213 | cut = address.indexOf(".", cut + 1); |
| 191 | 214 | if (cut === -1) return null; |
| 192 | 215 | } |
| 193 | | − const [prefix, integration, owner, connection] = address.slice(0, cut).split(".") as [ |
| 194 | | − string, |
| 195 | | − string, |
| 196 | | − string, |
| 197 | | − string, |
| 198 | | − ]; |
| 216 | + const [prefix, integration, owner, connection] = address |
| 217 | + .slice(0, cut) |
| 218 | + .split(".") as [string, string, string, string]; |
| 199 | 219 | const tool = address.slice(cut + 1); |
| 200 | 220 | if (prefix !== ADDRESS_PREFIX) return null; |
| 201 | 221 | if (!isOwner(owner)) return null; |
| 202 | | − if (integration.length === 0 || connection.length === 0 || tool.length === 0) { |
| 222 | + if ( |
| 223 | + integration.length === 0 || |
| 224 | + connection.length === 0 || |
| 225 | + tool.length === 0 |
| 226 | + ) { |
| 203 | 227 | return null; |
| 204 | 228 | } |
| 205 | 229 | return { |
| @@ -215,7 +239,9 @@ export const connectionAddress = ( · 9 unmodified lines |
| 215 | 239 | integration: IntegrationSlug, |
| 216 | 240 | connection: ConnectionName, |
| 217 | 241 | ): ConnectionAddress => |
| 218 | | − ConnectionAddress.make(`${ADDRESS_PREFIX}.${integration}.${owner}.${connection}`); |
| 242 | + ConnectionAddress.make( |
| 243 | + `${ADDRESS_PREFIX}.${integration}.${owner}.${connection}`, |
| 244 | + ); |
| 219 | 245 | |
| 220 | 246 | export const toolAddress = ( |
| 221 | 247 | owner: Owner, |
| @@ -223,7 +249,9 @@ export const toolAddress = ( · 1 unmodified line |
| 223 | 249 | connection: ConnectionName, |
| 224 | 250 | tool: ToolName, |
| 225 | 251 | ): ToolAddress => |
| 226 | | − ToolAddress.make(`${ADDRESS_PREFIX}.${integration}.${owner}.${connection}.${tool}`); |
| 252 | + ToolAddress.make( |
| 253 | + `${ADDRESS_PREFIX}.${integration}.${owner}.${connection}.${tool}`, |
| 254 | + ); |
| 227 | 255 | |
| 228 | 256 | // --------------------------------------------------------------------------- |
| 229 | 257 | // Owner key helpers — every owned-row write stamps `tenant`, `owner`, |
| @@ -241,17 +269,34 @@ interface OwnedKeys { · 11 unmodified lines |
| 241 | 269 | // core-table query unioned with the in-memory static pool. |
| 242 | 270 | // --------------------------------------------------------------------------- |
| 243 | 271 | |
| 272 | +// ExecutorWrapper — a plugin-contributed executor middleware. Given the |
| 273 | +// assembled executor and the per-request selector string (e.g. an MCP |
| 274 | +// `?toolkit=` value), it returns a possibly-narrowed executor. A plugin |
| 275 | +// contributes one by exposing a `wrapExecutor` method on its extension; the |
| 276 | +// assembler collects them and the execution stack runs each before the engine |
| 277 | +// is built. Core knows nothing about what a wrapper narrows to — that domain |
| 278 | +// (toolkits, etc.) lives entirely in the contributing plugin. |
| 279 | +export type ExecutorWrapper = <TPlugins extends readonly AnyPlugin[]>( |
| 280 | + base: Executor<TPlugins>, |
| 281 | + selector: string, |
| 282 | +) => Effect.Effect<Executor<TPlugins>, StorageFailure>; |
| 283 | + |
| 244 | 284 | export type Executor<TPlugins extends readonly AnyPlugin[] = readonly []> = { |
| 245 | 285 | readonly integrations: { |
| 246 | 286 | readonly list: () => Effect.Effect<readonly Integration[], StorageFailure>; |
| 247 | | − readonly get: (slug: IntegrationSlug) => Effect.Effect<Integration | null, StorageFailure>; |
| 287 | + readonly get: ( |
| 288 | + slug: IntegrationSlug, |
| 289 | + ) => Effect.Effect<Integration | null, StorageFailure>; |
| 248 | 290 | readonly update: ( |
| 249 | 291 | slug: IntegrationSlug, |
| 250 | 292 | patch: { readonly name?: string; readonly description?: string }, |
| 251 | 293 | ) => Effect.Effect<void, IntegrationNotFoundError | StorageFailure>; |
| 252 | 294 | readonly remove: ( |
| 253 | 295 | slug: IntegrationSlug, |
| 254 | | − ) => Effect.Effect<void, IntegrationRemovalNotAllowedError | StorageFailure>; |
| 296 | + ) => Effect.Effect< |
| 297 | + void, |
| 298 | + IntegrationRemovalNotAllowedError | StorageFailure |
| 299 | + >; |
| 255 | 300 | readonly detect: ( |
| 256 | 301 | url: string, |
| 257 | 302 | ) => Effect.Effect<readonly IntegrationDetectionResult[], StorageFailure>; |
| @@ -271,7 +316,9 @@ export type Executor<TPlugins extends readonly AnyPlugin[] = readonly []> = { · 13 unmodified lines |
| 271 | 316 | readonly integration?: IntegrationSlug; |
| 272 | 317 | readonly owner?: Owner; |
| 273 | 318 | }) => Effect.Effect<readonly Connection[], StorageFailure>; |
| 274 | | − readonly get: (ref: ConnectionRef) => Effect.Effect<Connection | null, StorageFailure>; |
| 319 | + readonly get: ( |
| 320 | + ref: ConnectionRef, |
| 321 | + ) => Effect.Effect<Connection | null, StorageFailure>; |
| 275 | 322 | /** Edit user-curated metadata (description, identityLabel). Credentials and |
| 276 | 323 | * OAuth lifecycle fields are not editable here. */ |
| 277 | 324 | readonly update: ( |
| @@ -294,21 +341,35 @@ export type Executor<TPlugins extends readonly AnyPlugin[] = readonly []> = { · 16 unmodified lines |
| 294 | 341 | readonly oauth: OAuthService; |
| 295 | 342 | |
| 296 | 343 | readonly tools: { |
| 297 | | − readonly list: (filter?: ToolListFilter) => Effect.Effect<readonly Tool[], StorageFailure>; |
| 298 | | − readonly schema: (address: ToolAddress) => Effect.Effect<ToolSchemaView | null, StorageFailure>; |
| 344 | + readonly list: ( |
| 345 | + filter?: ToolListFilter, |
| 346 | + ) => Effect.Effect<readonly Tool[], StorageFailure>; |
| 347 | + readonly schema: ( |
| 348 | + address: ToolAddress, |
| 349 | + ) => Effect.Effect<ToolSchemaView | null, StorageFailure>; |
| 299 | 350 | }; |
| 300 | 351 | |
| 301 | 352 | readonly providers: { |
| 302 | 353 | readonly list: () => Effect.Effect<readonly ProviderKey[]>; |
| 303 | | − readonly items: (key: ProviderKey) => Effect.Effect<readonly ProviderEntry[], StorageFailure>; |
| 354 | + readonly items: ( |
| 355 | + key: ProviderKey, |
| 356 | + ) => Effect.Effect<readonly ProviderEntry[], StorageFailure>; |
| 304 | 357 | }; |
| 305 | 358 | |
| 306 | 359 | readonly policies: { |
| 307 | 360 | readonly list: () => Effect.Effect<readonly ToolPolicy[], StorageFailure>; |
| 308 | | − readonly create: (input: CreateToolPolicyInput) => Effect.Effect<ToolPolicy, StorageFailure>; |
| 309 | | − readonly update: (input: UpdateToolPolicyInput) => Effect.Effect<ToolPolicy, StorageFailure>; |
| 310 | | − readonly remove: (input: RemoveToolPolicyInput) => Effect.Effect<void, StorageFailure>; |
| 311 | | − readonly resolve: (address: ToolAddress) => Effect.Effect<EffectivePolicy, StorageFailure>; |
| 361 | + readonly create: ( |
| 362 | + input: CreateToolPolicyInput, |
| 363 | + ) => Effect.Effect<ToolPolicy, StorageFailure>; |
| 364 | + readonly update: ( |
| 365 | + input: UpdateToolPolicyInput, |
| 366 | + ) => Effect.Effect<ToolPolicy, StorageFailure>; |
| 367 | + readonly remove: ( |
| 368 | + input: RemoveToolPolicyInput, |
| 369 | + ) => Effect.Effect<void, StorageFailure>; |
| 370 | + readonly resolve: ( |
| 371 | + address: ToolAddress, |
| 372 | + ) => Effect.Effect<EffectivePolicy, StorageFailure>; |
| 312 | 373 | }; |
| 313 | 374 | |
| 314 | 375 | readonly execute: ( |
| @@ -322,7 +383,10 @@ export type Executor<TPlugins extends readonly AnyPlugin[] = readonly []> = { · 7 unmodified lines |
| 322 | 383 | |
| 323 | 384 | export interface ExecutorDb { |
| 324 | 385 | readonly db: FumaDb<any>; |
| 325 | | − readonly close?: () => Effect.Effect<void, StorageFailure> | Promise<void> | void; |
| 386 | + readonly close?: () => |
| 387 | + | Effect.Effect<void, StorageFailure> |
| 388 | + | Promise<void> |
| 389 | + | void; |
| 326 | 390 | } |
| 327 | 391 | |
| 328 | 392 | export type ExecutorDbInput = FumaDb<any> | ExecutorDb; |
| @@ -331,7 +395,9 @@ export type ExecutorDbFactory = (config: { · 2 unmodified lines |
| 331 | 395 | readonly tables: FumaTables; |
| 332 | 396 | }) => ExecutorDbInput | Effect.Effect<ExecutorDbInput, StorageFailure>; |
| 333 | 397 | |
| 334 | | −export interface ExecutorConfig<TPlugins extends readonly AnyPlugin[] = readonly []> { |
| 398 | +export interface ExecutorConfig< |
| 399 | + TPlugins extends readonly AnyPlugin[] = readonly [], |
| 400 | +> { |
| 335 | 401 | /** The org / workspace this executor is bound to. `owner: "org"` rows file |
| 336 | 402 | * here. */ |
| 337 | 403 | readonly tenant: Tenant; |
| @@ -392,7 +458,10 @@ const validateExecutorOwnerPolicyTables = (tables: FumaTables): void => { · 54 unmodified lines |
| 392 | 458 | } |
| 393 | 459 | }; |
| 394 | 460 | |
| 395 | | −const validateExecutorDbTables = (required: FumaTables, actual: FumaTables): void => { |
| 461 | +const validateExecutorDbTables = ( |
| 462 | + required: FumaTables, |
| 463 | + actual: FumaTables, |
| 464 | +): void => { |
| 396 | 465 | const missing = Object.keys(required) |
| 397 | 466 | .filter((tableName) => !actual[tableName]) |
| 398 | 467 | .sort(); |
| @@ -408,18 +477,27 @@ const validateExecutorDbTables = (required: FumaTables, actual: FumaTables): voi · 9 unmodified lines |
| 408 | 477 | }); |
| 409 | 478 | }; |
| 410 | 479 | |
| 411 | | −const storageFailureFromUnknown = (message: string, cause: unknown): StorageFailure => |
| 480 | +const storageFailureFromUnknown = ( |
| 481 | + message: string, |
| 482 | + cause: unknown, |
| 483 | +): StorageFailure => |
| 412 | 484 | isStorageFailure(cause) ? cause : new StorageError({ message, cause }); |
| 413 | 485 | |
| 414 | | −const pluginStorageFailure = (pluginId: string, hook: string, cause: unknown): StorageFailure => |
| 486 | +const pluginStorageFailure = ( |
| 487 | + pluginId: string, |
| 488 | + hook: string, |
| 489 | + cause: unknown, |
| 490 | +): StorageFailure => |
| 415 | 491 | storageFailureFromUnknown(`${hook} failed for plugin ${pluginId}`, cause); |
| 416 | 492 | |
| 417 | 493 | const createDefaultMemoryDb = (tables: FumaTables): ExecutorDb => { |
| 418 | 494 | const version = "1.0.0"; |
| 419 | | − const latestSchema = fumaSchema<string, FumaTables, RelationsMap<FumaTables>>({ |
| 420 | | − version, |
| 421 | | − tables, |
| 422 | | − }); |
| 495 | + const latestSchema = fumaSchema<string, FumaTables, RelationsMap<FumaTables>>( |
| 496 | + { |
| 497 | + version, |
| 498 | + tables, |
| 499 | + }, |
| 500 | + ); |
| 423 | 501 | const factory = fumadb({ |
| 424 | 502 | namespace: "executor_memory", |
| 425 | 503 | schemas: [latestSchema], |
| @@ -434,7 +512,9 @@ const createDefaultMemoryDb = (tables: FumaTables): ExecutorDb => { · 8 unmodified lines |
| 434 | 512 | // JSON helpers + row → public projection conversions |
| 435 | 513 | // --------------------------------------------------------------------------- |
| 436 | 514 | |
| 437 | | −const decodeJsonFromString = Schema.decodeUnknownOption(Schema.UnknownFromJsonString); |
| 515 | +const decodeJsonFromString = Schema.decodeUnknownOption( |
| 516 | + Schema.UnknownFromJsonString, |
| 517 | +); |
| 438 | 518 | |
| 439 | 519 | const decodeJsonColumn = (value: unknown): unknown => { |
| 440 | 520 | if (value === null || value === undefined) return undefined; |
| @@ -482,9 +562,14 @@ const rowToConnection = (row: ConnectionRow): Connection => { · 41 unmodified lines |
| 482 | 562 | identityLabel: row.identity_label ?? null, |
| 483 | 563 | description: row.description ?? null, |
| 484 | 564 | expiresAt: row.expires_at == null ? null : Number(row.expires_at), |
| 485 | | − oauthClient: row.oauth_client == null ? null : OAuthClientSlug.make(String(row.oauth_client)), |
| 565 | + oauthClient: |
| 566 | + row.oauth_client == null |
| 567 | + ? null |
| 568 | + : OAuthClientSlug.make(String(row.oauth_client)), |
| 486 | 569 | oauthClientOwner: |
| 487 | | − row.oauth_client_owner == null ? null : (String(row.oauth_client_owner) as Owner), |
| 570 | + row.oauth_client_owner == null |
| 571 | + ? null |
| 572 | + : (String(row.oauth_client_owner) as Owner), |
| 488 | 573 | oauthScope: row.oauth_scope == null ? null : String(row.oauth_scope), |
| 489 | 574 | }; |
| 490 | 575 | }; |
| @@ -505,7 +590,10 @@ const normalizeConnectionInputs = ( · 14 unmodified lines |
| 505 | 590 | input: ConnectionValueInput, |
| 506 | 591 | ): readonly NormalizedConnectionInput[] => { |
| 507 | 592 | if ("inputs" in input) { |
| 508 | | − return Object.entries(input.inputs).map(([variable, origin]) => ({ variable, origin })); |
| 593 | + return Object.entries(input.inputs).map(([variable, origin]) => ({ |
| 594 | + variable, |
| 595 | + origin, |
| 596 | + })); |
| 509 | 597 | } |
| 510 | 598 | if ("values" in input) { |
| 511 | 599 | return Object.entries(input.values).map(([variable, value]) => ({ |
| @@ -532,7 +620,8 @@ const connectionItemIds = (row: ConnectionRow): Record<string, string> => { · 20 unmodified lines |
| 532 | 620 | // schema columns); `Tool.inputSchema`/`outputSchema` are optional and stay |
| 533 | 621 | // absent for those callers — `tools.schema` is the schema-bearing surface. |
| 534 | 622 | const rowToTool = ( |
| 535 | | − row: ToolInvocationRow & Partial<Pick<ToolRow, "input_schema" | "output_schema">>, |
| 623 | + row: ToolInvocationRow & |
| 624 | + Partial<Pick<ToolRow, "input_schema" | "output_schema">>, |
| 536 | 625 | annotations?: ToolAnnotations, |
| 537 | 626 | ): Tool => { |
| 538 | 627 | const owner = row.owner as Owner; |
| @@ -549,7 +638,9 @@ const rowToTool = ( · 10 unmodified lines |
| 549 | 638 | description: row.description, |
| 550 | 639 | inputSchema: decodeJsonColumn(row.input_schema), |
| 551 | 640 | outputSchema: decodeJsonColumn(row.output_schema), |
| 552 | | − annotations: annotations ?? (decodeJsonColumn(row.annotations) as ToolAnnotations | undefined), |
| 641 | + annotations: |
| 642 | + annotations ?? |
| 643 | + (decodeJsonColumn(row.annotations) as ToolAnnotations | undefined), |
| 553 | 644 | }; |
| 554 | 645 | }; |
| 555 | 646 | |
| @@ -580,7 +671,10 @@ type CoreFindFirstOptions<TName extends CoreTableName = CoreTableName> = Omit< · 24 unmodified lines |
| 580 | 671 | >; |
| 581 | 672 | /** The narrowed row a projected query returns: the selected columns keep |
| 582 | 673 | * their types, everything else is absent. */ |
| 583 | | −type CoreProjectedRow<TName extends CoreTableName, TSelect> = TSelect extends readonly (infer K)[] |
| 674 | +type CoreProjectedRow< |
| 675 | + TName extends CoreTableName, |
| 676 | + TSelect, |
| 677 | +> = TSelect extends readonly (infer K)[] |
| 584 | 678 | ? Pick<CoreRow<TName>, Extract<K, keyof CoreRow<TName>>> |
| 585 | 679 | : CoreRow<TName>; |
| 586 | 680 | |
| @@ -613,7 +707,9 @@ const makeCoreDb = (fuma: ReturnType<typeof makeFumaClient>) => ({ · 26 unmodified lines |
| 613 | 707 | tableName: TName, |
| 614 | 708 | options?: { readonly where?: CoreWhere }, |
| 615 | 709 | ): Effect.Effect<number, StorageFailure> => |
| 616 | | − fuma.use(`${tableName}.count`, (db) => asLooseStorageDb(db).count(tableName, options)), |
| 710 | + fuma.use(`${tableName}.count`, (db) => |
| 711 | + asLooseStorageDb(db).count(tableName, options), |
| 712 | + ), |
| 617 | 713 | create: <TName extends CoreTableName>( |
| 618 | 714 | tableName: TName, |
| 619 | 715 | row: Record<string, unknown>, |
| @@ -628,7 +724,9 @@ const makeCoreDb = (fuma: ReturnType<typeof makeFumaClient>) => ({ · 8 unmodified lines |
| 628 | 724 | rows.length === 0 |
| 629 | 725 | ? Effect.void |
| 630 | 726 | : fuma |
| 631 | | − .use(`${tableName}.createMany`, (db) => asLooseStorageDb(db).createMany(tableName, rows)) |
| 727 | + .use(`${tableName}.createMany`, (db) => |
| 728 | + asLooseStorageDb(db).createMany(tableName, rows), |
| 729 | + ) |
| 632 | 730 | .pipe(Effect.asVoid), |
| 633 | 731 | deleteMany: <TName extends CoreTableName>( |
| 634 | 732 | tableName: TName, |
| @@ -637,20 +735,38 @@ const makeCoreDb = (fuma: ReturnType<typeof makeFumaClient>) => ({ · 2 unmodified lines |
| 637 | 735 | fuma.use(`${tableName}.deleteMany`, (db) => |
| 638 | 736 | asLooseStorageDb(db).deleteMany(tableName, options), |
| 639 | 737 | ), |
| 640 | | − findFirst: <TName extends CoreTableName, const TOptions extends CoreFindFirstOptions<TName>>( |
| 738 | + findFirst: < |
| 739 | + TName extends CoreTableName, |
| 740 | + const TOptions extends CoreFindFirstOptions<TName>, |
| 741 | + >( |
| 641 | 742 | tableName: TName, |
| 642 | 743 | options: TOptions, |
| 643 | | − ): Effect.Effect<CoreProjectedRow<TName, TOptions["select"]> | null, StorageFailure> => |
| 744 | + ): Effect.Effect< |
| 745 | + CoreProjectedRow<TName, TOptions["select"]> | null, |
| 746 | + StorageFailure |
| 747 | + > => |
| 644 | 748 | fuma.use(`${tableName}.findFirst`, (db) => |
| 645 | 749 | asLooseStorageDb(db).findFirst(tableName, options), |
| 646 | | − ) as Effect.Effect<CoreProjectedRow<TName, TOptions["select"]> | null, StorageFailure>, |
| 647 | | − findMany: <TName extends CoreTableName, const TOptions extends CoreFindManyOptions<TName>>( |
| 750 | + ) as Effect.Effect< |
| 751 | + CoreProjectedRow<TName, TOptions["select"]> | null, |
| 752 | + StorageFailure |
| 753 | + >, |
| 754 | + findMany: < |
| 755 | + TName extends CoreTableName, |
| 756 | + const TOptions extends CoreFindManyOptions<TName>, |
| 757 | + >( |
| 648 | 758 | tableName: TName, |
| 649 | 759 | options: TOptions = {} as TOptions, |
| 650 | | − ): Effect.Effect<readonly CoreProjectedRow<TName, TOptions["select"]>[], StorageFailure> => |
| 760 | + ): Effect.Effect< |
| 761 | + readonly CoreProjectedRow<TName, TOptions["select"]>[], |
| 762 | + StorageFailure |
| 763 | + > => |
| 651 | 764 | fuma.use(`${tableName}.findMany`, (db) => |
| 652 | 765 | asLooseStorageDb(db).findMany(tableName, options), |
| 653 | | − ) as Effect.Effect<readonly CoreProjectedRow<TName, TOptions["select"]>[], StorageFailure>, |
| 766 | + ) as Effect.Effect< |
| 767 | + readonly CoreProjectedRow<TName, TOptions["select"]>[], |
| 768 | + StorageFailure |
| 769 | + >, |
| 654 | 770 | updateMany: <TName extends CoreTableName>( |
| 655 | 771 | tableName: TName, |
| 656 | 772 | options: { |
| @@ -670,7 +786,9 @@ type CoreDb = ReturnType<typeof makeCoreDb>; · 13 unmodified lines |
| 670 | 786 | // [user, org]; writes/deletes name an explicit owner. |
| 671 | 787 | // --------------------------------------------------------------------------- |
| 672 | 788 | |
| 673 | | −const pluginStorageEntryFromRow = <T>(row: CoreRow<"plugin_storage">): PluginStorageEntry<T> => ({ |
| 789 | +const pluginStorageEntryFromRow = <T>( |
| 790 | + row: CoreRow<"plugin_storage">, |
| 791 | +): PluginStorageEntry<T> => ({ |
| 674 | 792 | id: pluginStorageId({ |
| 675 | 793 | pluginId: row.plugin_id, |
| 676 | 794 | collection: row.collection, |
| @@ -681,21 +799,28 @@ const pluginStorageEntryFromRow = <T>(row: CoreRow<"plugin_storage">): PluginSto · 4 unmodified lines |
| 681 | 799 | collection: row.collection, |
| 682 | 800 | key: row.key, |
| 683 | 801 | data: row.data as T, |
| 684 | | − createdAt: row.created_at instanceof Date ? row.created_at : new Date(row.created_at), |
| 685 | | − updatedAt: row.updated_at instanceof Date ? row.updated_at : new Date(row.updated_at), |
| 802 | + createdAt: |
| 803 | + row.created_at instanceof Date ? row.created_at : new Date(row.created_at), |
| 804 | + updatedAt: |
| 805 | + row.updated_at instanceof Date ? row.updated_at : new Date(row.updated_at), |
| 686 | 806 | }); |
| 687 | 807 | |
| 688 | | −const pluginStorageIndexSpecFields = (spec: PluginStorageRuntimeIndexSpec): readonly string[] => |
| 689 | | − typeof spec === "string" ? [spec] : spec; |
| 808 | +const pluginStorageIndexSpecFields = ( |
| 809 | + spec: PluginStorageRuntimeIndexSpec, |
| 810 | +): readonly string[] => (typeof spec === "string" ? [spec] : spec); |
| 690 | 811 | |
| 691 | 812 | const pluginStorageCollectionIndexedFields = ( |
| 692 | 813 | definition: PluginStorageRuntimeCollectionDefinition, |
| 693 | 814 | ): ReadonlySet<string> => |
| 694 | | − new Set(definition.indexes.flatMap((spec) => pluginStorageIndexSpecFields(spec))); |
| 815 | + new Set( |
| 816 | + definition.indexes.flatMap((spec) => pluginStorageIndexSpecFields(spec)), |
| 817 | + ); |
| 695 | 818 | |
| 696 | 819 | const pluginStorageQueryValidationError = ( |
| 697 | 820 | definition: PluginStorageRuntimeCollectionDefinition, |
| 698 | | − query: PluginStorageCollectionQueryInput<PluginStorageCollectionDefinition> | undefined, |
| 821 | + query: |
| 822 | + | PluginStorageCollectionQueryInput<PluginStorageCollectionDefinition> |
| 823 | + | undefined, |
| 699 | 824 | ): StorageError | null => { |
| 700 | 825 | if (!query) return null; |
| 701 | 826 | const indexedFields = pluginStorageCollectionIndexedFields(definition); |
| @@ -711,13 +836,19 @@ const pluginStorageQueryValidationError = ( · 9 unmodified lines |
| 711 | 836 | }); |
| 712 | 837 | } |
| 713 | 838 | } |
| 714 | | − if (query.limit !== undefined && (!Number.isInteger(query.limit) || query.limit < 0)) { |
| 839 | + if ( |
| 840 | + query.limit !== undefined && |
| 841 | + (!Number.isInteger(query.limit) || query.limit < 0) |
| 842 | + ) { |
| 715 | 843 | return new StorageError({ |
| 716 | 844 | message: `Plugin storage collection "${definition.name}" received an invalid query limit`, |
| 717 | 845 | cause: undefined, |
| 718 | 846 | }); |
| 719 | 847 | } |
| 720 | | − if (query.offset !== undefined && (!Number.isInteger(query.offset) || query.offset < 0)) { |
| 848 | + if ( |
| 849 | + query.offset !== undefined && |
| 850 | + (!Number.isInteger(query.offset) || query.offset < 0) |
| 851 | + ) { |
| 721 | 852 | return new StorageError({ |
| 722 | 853 | message: `Plugin storage collection "${definition.name}" received an invalid query offset`, |
| 723 | 854 | cause: undefined, |
| @@ -726,17 +857,35 @@ const pluginStorageQueryValidationError = ( · 2 unmodified lines |
| 726 | 857 | return null; |
| 727 | 858 | }; |
| 728 | 859 | |
| 729 | | −const isPluginStorageRecord = (value: unknown): value is Readonly<Record<string, unknown>> => |
| 860 | +const isPluginStorageRecord = ( |
| 861 | + value: unknown, |
| 862 | +): value is Readonly<Record<string, unknown>> => |
| 730 | 863 | value !== null && typeof value === "object" && !Array.isArray(value); |
| 731 | 864 | |
| 732 | | −const pluginStorageWhereOperators = ["eq", "in", "gt", "gte", "lt", "lte"] as const; |
| 865 | +const pluginStorageWhereOperators = [ |
| 866 | + "eq", |
| 867 | + "in", |
| 868 | + "gt", |
| 869 | + "gte", |
| 870 | + "lt", |
| 871 | + "lte", |
| 872 | +] as const; |
| 733 | 873 | |
| 734 | | −const isPluginStorageWhereFilter = (value: unknown): value is Readonly<Record<string, unknown>> => |
| 735 | | − isPluginStorageRecord(value) && pluginStorageWhereOperators.some((operator) => operator in value); |
| 874 | +const isPluginStorageWhereFilter = ( |
| 875 | + value: unknown, |
| 876 | +): value is Readonly<Record<string, unknown>> => |
| 877 | + isPluginStorageRecord(value) && |
| 878 | + pluginStorageWhereOperators.some((operator) => operator in value); |
| 736 | 879 | |
| 737 | | −const pluginStorageComparableValue = (value: unknown): string | number | boolean | null => { |
| 880 | +const pluginStorageComparableValue = ( |
| 881 | + value: unknown, |
| 882 | +): string | number | boolean | null => { |
| 738 | 883 | if (value instanceof Date) return value.getTime(); |
| 739 | | − if (typeof value === "number" || typeof value === "string" || typeof value === "boolean") { |
| 884 | + if ( |
| 885 | + typeof value === "number" || |
| 886 | + typeof value === "string" || |
| 887 | + typeof value === "boolean" |
| 888 | + ) { |
| 740 | 889 | return value; |
| 741 | 890 | } |
| 742 | 891 | if (value == null) return null; |
| @@ -755,8 +904,13 @@ const comparePluginStorageValues = (left: unknown, right: unknown): number => { · 12 unmodified lines |
| 755 | 904 | const pluginStorageDataField = (data: unknown, field: string): unknown => |
| 756 | 905 | isPluginStorageRecord(data) ? data[field] : undefined; |
| 757 | 906 | |
| 758 | | −const matchesWhereOperator = (operator: string, value: unknown, operand: unknown): boolean => { |
| 759 | | − if (operator === "eq") return comparePluginStorageValues(value, operand) === 0; |
| 907 | +const matchesWhereOperator = ( |
| 908 | + operator: string, |
| 909 | + value: unknown, |
| 910 | + operand: unknown, |
| 911 | +): boolean => { |
| 912 | + if (operator === "eq") |
| 913 | + return comparePluginStorageValues(value, operand) === 0; |
| 760 | 914 | if (operator === "in") { |
| 761 | 915 | return ( |
| 762 | 916 | Array.isArray(operand) && |
| @@ -764,9 +918,11 @@ const matchesWhereOperator = (operator: string, value: unknown, operand: unknown · 1 unmodified line |
| 764 | 918 | ); |
| 765 | 919 | } |
| 766 | 920 | if (operator === "gt") return comparePluginStorageValues(value, operand) > 0; |
| 767 | | − if (operator === "gte") return comparePluginStorageValues(value, operand) >= 0; |
| 921 | + if (operator === "gte") |
| 922 | + return comparePluginStorageValues(value, operand) >= 0; |
| 768 | 923 | if (operator === "lt") return comparePluginStorageValues(value, operand) < 0; |
| 769 | | − if (operator === "lte") return comparePluginStorageValues(value, operand) <= 0; |
| 924 | + if (operator === "lte") |
| 925 | + return comparePluginStorageValues(value, operand) <= 0; |
| 770 | 926 | return false; |
| 771 | 927 | }; |
| 772 | 928 | |
| @@ -802,9 +958,12 @@ const makePluginStorageFacade = (input: { · 29 unmodified lines |
| 802 | 958 | readonly owner: OwnerBinding; |
| 803 | 959 | }): PluginStorageFacade => { |
| 804 | 960 | // Owner partitions: org always, plus this subject's user partition. |
| 805 | | − const readOwners: readonly Owner[] = input.owner.subject == null ? ["org"] : ["user", "org"]; |
| 961 | + const readOwners: readonly Owner[] = |
| 962 | + input.owner.subject == null ? ["org"] : ["user", "org"]; |
| 806 | 963 | |
| 807 | | − const ownerSubject = (owner: Owner): { owner: Owner; subject: string } | null => { |
| 964 | + const ownerSubject = ( |
| 965 | + owner: Owner, |
| 966 | + ): { owner: Owner; subject: string } | null => { |
| 808 | 967 | if (owner === "org") return { owner: "org", subject: ORG_SUBJECT }; |
| 809 | 968 | if (input.owner.subject == null) return null; |
| 810 | 969 | return { owner: "user", subject: String(input.owner.subject) }; |
| @@ -821,7 +980,11 @@ const makePluginStorageFacade = (input: { · 10 unmodified lines |
| 821 | 980 | key === undefined ? true : b("key", "=", key), |
| 822 | 981 | ); |
| 823 | 982 | |
| 824 | | − const whereOwner = (owner: Owner, collection: string, key: string): CoreWhere => { |
| 983 | + const whereOwner = ( |
| 984 | + owner: Owner, |
| 985 | + collection: string, |
| 986 | + key: string, |
| 987 | + ): CoreWhere => { |
| 825 | 988 | const os = ownerSubject(owner); |
| 826 | 989 | return (b: AnyCb) => |
| 827 | 990 | b.and( |
| @@ -843,19 +1006,28 @@ const makePluginStorageFacade = (input: { · 15 unmodified lines |
| 843 | 1006 | }); |
| 844 | 1007 | |
| 845 | 1008 | const getVisible = <T>(collection: string, key: string) => |
| 846 | | − input.core.findMany("plugin_storage", { where: whereFor(collection, key) }).pipe( |
| 847 | | − Effect.map((rows) => sortByOwnerPrecedence(rows)[0] ?? null), |
| 848 | | − Effect.map((row) => (row ? pluginStorageEntryFromRow<T>(row) : null)), |
| 849 | | − ); |
| 1009 | + input.core |
| 1010 | + .findMany("plugin_storage", { where: whereFor(collection, key) }) |
| 1011 | + .pipe( |
| 1012 | + Effect.map((rows) => sortByOwnerPrecedence(rows)[0] ?? null), |
| 1013 | + Effect.map((row) => (row ? pluginStorageEntryFromRow<T>(row) : null)), |
| 1014 | + ); |
| 850 | 1015 | |
| 851 | 1016 | const getForOwnerImpl = <T>(owner: Owner, collection: string, key: string) => |
| 852 | 1017 | input.core |
| 853 | 1018 | .findFirst("plugin_storage", { |
| 854 | 1019 | where: whereOwner(owner, collection, key), |
| 855 | 1020 | }) |
| 856 | | − .pipe(Effect.map((row) => (row ? pluginStorageEntryFromRow<T>(row) : null))); |
| 1021 | + .pipe( |
| 1022 | + Effect.map((row) => (row ? pluginStorageEntryFromRow<T>(row) : null)), |
| 1023 | + ); |
| 857 | 1024 | |
| 858 | | − const putImpl = <T>(owner: Owner, collection: string, key: string, data: unknown) => |
| 1025 | + const putImpl = <T>( |
| 1026 | + owner: Owner, |
| 1027 | + collection: string, |
| 1028 | + key: string, |
| 1029 | + data: unknown, |
| 1030 | + ) => |
| 859 | 1031 | Effect.gen(function* () { |
| 860 | 1032 | const os = ownerSubject(owner); |
| 861 | 1033 | if (!os) { |
| @@ -935,7 +1107,10 @@ const makePluginStorageFacade = (input: { · 73 unmodified lines |
| 935 | 1107 | offset < uniqueKeys.length; |
| 936 | 1108 | offset += PLUGIN_STORAGE_DELETE_KEY_BATCH_SIZE |
| 937 | 1109 | ) { |
| 938 | | − const batchKeys = uniqueKeys.slice(offset, offset + PLUGIN_STORAGE_DELETE_KEY_BATCH_SIZE); |
| 1110 | + const batchKeys = uniqueKeys.slice( |
| 1111 | + offset, |
| 1112 | + offset + PLUGIN_STORAGE_DELETE_KEY_BATCH_SIZE, |
| 1113 | + ); |
| 939 | 1114 | yield* input.core.deleteMany("plugin_storage", { |
| 940 | 1115 | where: (b) => |
| 941 | 1116 | b.and( |
| @@ -1013,7 +1188,9 @@ const makePluginStorageFacade = (input: { · 71 unmodified lines |
| 1013 | 1188 | yield* deleteManyImpl(owner, os.subject, entries); |
| 1014 | 1189 | }); |
| 1015 | 1190 | |
| 1016 | | − const queryCollection = <TDefinition extends PluginStorageCollectionDefinition>( |
| 1191 | + const queryCollection = < |
| 1192 | + TDefinition extends PluginStorageCollectionDefinition, |
| 1193 | + >( |
| 1017 | 1194 | definition: TDefinition, |
| 1018 | 1195 | queryInput?: PluginStorageCollectionQueryInput<TDefinition>, |
| 1019 | 1196 | ) => |
| @@ -1031,7 +1208,9 @@ const makePluginStorageFacade = (input: { · 11 unmodified lines |
| 1031 | 1208 | }); |
| 1032 | 1209 | const filtered = sortByOwnerPrecedence(rows) |
| 1033 | 1210 | .filter((row) => |
| 1034 | | − queryInput?.keyPrefix === undefined ? true : row.key.startsWith(queryInput.keyPrefix), |
| 1211 | + queryInput?.keyPrefix === undefined |
| 1212 | + ? true |
| 1213 | + : row.key.startsWith(queryInput.keyPrefix), |
| 1035 | 1214 | ) |
| 1036 | 1215 | .filter((row) => |
| 1037 | 1216 | rowMatchesPluginStorageWhere( |
| @@ -1053,7 +1232,8 @@ const makePluginStorageFacade = (input: { · 15 unmodified lines |
| 1053 | 1232 | if (compared !== 0) return compared; |
| 1054 | 1233 | } |
| 1055 | 1234 | return ( |
| 1056 | | − ownerRank(left.owner as Owner) - ownerRank(right.owner as Owner) || |
| 1235 | + ownerRank(left.owner as Owner) - |
| 1236 | + ownerRank(right.owner as Owner) || |
| 1057 | 1237 | left.key.localeCompare(right.key) |
| 1058 | 1238 | ); |
| 1059 | 1239 | }) |
| @@ -1065,7 +1245,9 @@ const makePluginStorageFacade = (input: { · 5 unmodified lines |
| 1065 | 1245 | ? sorted.slice(offset) |
| 1066 | 1246 | : sorted.slice(offset, offset + queryInput.limit); |
| 1067 | 1247 | return limited.map((row) => |
| 1068 | | − pluginStorageEntryFromRow<PluginStorageCollectionData<TDefinition>>(row), |
| 1248 | + pluginStorageEntryFromRow<PluginStorageCollectionData<TDefinition>>( |
| 1249 | + row, |
| 1250 | + ), |
| 1069 | 1251 | ); |
| 1070 | 1252 | }); |
| 1071 | 1253 | |
| @@ -1073,15 +1255,24 @@ const makePluginStorageFacade = (input: { · 1 unmodified line |
| 1073 | 1255 | collection: (definition) => ({ |
| 1074 | 1256 | get: (storageInput) => |
| 1075 | 1257 | getVisible(definition.name, storageInput.key) as Effect.Effect< |
| 1076 | | − PluginStorageEntry<PluginStorageCollectionData<typeof definition>> | null, |
| 1258 | + PluginStorageEntry< |
| 1259 | + PluginStorageCollectionData<typeof definition> |
| 1260 | + > | null, |
| 1077 | 1261 | StorageFailure |
| 1078 | 1262 | >, |
| 1079 | 1263 | getForOwner: (storageInput) => |
| 1080 | | − getForOwnerImpl(storageInput.owner, definition.name, storageInput.key) as Effect.Effect< |
| 1081 | | − PluginStorageEntry<PluginStorageCollectionData<typeof definition>> | null, |
| 1264 | + getForOwnerImpl( |
| 1265 | + storageInput.owner, |
| 1266 | + definition.name, |
| 1267 | + storageInput.key, |
| 1268 | + ) as Effect.Effect< |
| 1269 | + PluginStorageEntry< |
| 1270 | + PluginStorageCollectionData<typeof definition> |
| 1271 | + > | null, |
| 1082 | 1272 | StorageFailure |
| 1083 | 1273 | >, |
| 1084 | | − list: (storageInput) => queryCollection(definition, { keyPrefix: storageInput?.keyPrefix }), |
| 1274 | + list: (storageInput) => |
| 1275 | + queryCollection(definition, { keyPrefix: storageInput?.keyPrefix }), |
| 1085 | 1276 | put: (storageInput) => |
| 1086 | 1277 | putImpl( |
| 1087 | 1278 | storageInput.owner, |
| @@ -1094,12 +1285,20 @@ const makePluginStorageFacade = (input: { · 6 unmodified lines |
| 1094 | 1285 | >, |
| 1095 | 1286 | query: (storageInput) => queryCollection(definition, storageInput), |
| 1096 | 1287 | count: (storageInput) => |
| 1097 | | − queryCollection(definition, storageInput).pipe(Effect.map((rows) => rows.length)), |
| 1098 | | − remove: (storageInput) => removeImpl(storageInput.owner, definition.name, storageInput.key), |
| 1288 | + queryCollection(definition, storageInput).pipe( |
| 1289 | + Effect.map((rows) => rows.length), |
| 1290 | + ), |
| 1291 | + remove: (storageInput) => |
| 1292 | + removeImpl(storageInput.owner, definition.name, storageInput.key), |
| 1099 | 1293 | }), |
| 1100 | | − get: (storageInput) => getVisible(storageInput.collection, storageInput.key), |
| 1294 | + get: (storageInput) => |
| 1295 | + getVisible(storageInput.collection, storageInput.key), |
| 1101 | 1296 | getForOwner: (storageInput) => |
| 1102 | | − getForOwnerImpl(storageInput.owner, storageInput.collection, storageInput.key), |
| 1297 | + getForOwnerImpl( |
| 1298 | + storageInput.owner, |
| 1299 | + storageInput.collection, |
| 1300 | + storageInput.key, |
| 1301 | + ), |
| 1103 | 1302 | list: (storageInput) => |
| 1104 | 1303 | Effect.gen(function* () { |
| 1105 | 1304 | const rows = yield* input.core.findMany("plugin_storage", { |
| @@ -1114,11 +1313,18 @@ const makePluginStorageFacade = (input: { · 8 unmodified lines |
| 1114 | 1313 | .map((row) => pluginStorageEntryFromRow(row)); |
| 1115 | 1314 | }), |
| 1116 | 1315 | put: (storageInput) => |
| 1117 | | − putImpl(storageInput.owner, storageInput.collection, storageInput.key, storageInput.data), |
| 1118 | | − putMany: (storageInput) => putManyImpl(storageInput.owner, storageInput.entries), |
| 1316 | + putImpl( |
| 1317 | + storageInput.owner, |
| 1318 | + storageInput.collection, |
| 1319 | + storageInput.key, |
| 1320 | + storageInput.data, |
| 1321 | + ), |
| 1322 | + putMany: (storageInput) => |
| 1323 | + putManyImpl(storageInput.owner, storageInput.entries), |
| 1119 | 1324 | remove: (storageInput) => |
| 1120 | 1325 | removeImpl(storageInput.owner, storageInput.collection, storageInput.key), |
| 1121 | | − removeMany: (storageInput) => removeManyImpl(storageInput.owner, storageInput.entries), |
| 1326 | + removeMany: (storageInput) => |
| 1327 | + removeManyImpl(storageInput.owner, storageInput.entries), |
| 1122 | 1328 | }; |
| 1123 | 1329 | }; |
| 1124 | 1330 | |
| @@ -1161,7 +1367,9 @@ const EXECUTOR_SOURCE: StaticSourceDecl = { · 36 unmodified lines |
| 1161 | 1367 | tools: [], |
| 1162 | 1368 | }; |
| 1163 | 1369 | |
| 1164 | | −const isReadonlyRecord = (value: unknown): value is Readonly<Record<PropertyKey, unknown>> => |
| 1370 | +const isReadonlyRecord = ( |
| 1371 | + value: unknown, |
| 1372 | +): value is Readonly<Record<PropertyKey, unknown>> => |
| 1165 | 1373 | typeof value === "object" && value !== null; |
| 1166 | 1374 | |
| 1167 | 1375 | type StandardJsonSchemaSide = "input" | "output"; |
| @@ -1180,10 +1388,14 @@ const staticToolSchemaRoot = ( · 12 unmodified lines |
| 1180 | 1388 | const jsonSchema = standard["jsonSchema"]; |
| 1181 | 1389 | if (!isReadonlyRecord(jsonSchema)) return schema; |
| 1182 | 1390 | const materialize = (jsonSchema as StandardJsonSchemaFns)[side]; |
| 1183 | | − return typeof materialize === "function" ? materialize({ target: "draft-07" }) : jsonSchema; |
| 1391 | + return typeof materialize === "function" |
| 1392 | + ? materialize({ target: "draft-07" }) |
| 1393 | + : jsonSchema; |
| 1184 | 1394 | }; |
| 1185 | 1395 | |
| 1186 | | −export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = readonly []>( |
| 1396 | +export const createExecutor = < |
| 1397 | + const TPlugins extends readonly AnyPlugin[] = readonly [], |
| 1398 | +>( |
| 1187 | 1399 | config: ExecutorConfig<TPlugins>, |
| 1188 | 1400 | ): Effect.Effect<Executor<TPlugins>, StorageFailure> => |
| 1189 | 1401 | Effect.gen(function* () { |
| @@ -1213,7 +1425,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 23 unmodified lines |
| 1213 | 1425 | return { tenant, owner, subject }; |
| 1214 | 1426 | }; |
| 1215 | 1427 | |
| 1216 | | − const requireUserSubject = (owner: Owner): Effect.Effect<void, StorageFailure> => |
| 1428 | + const requireUserSubject = ( |
| 1429 | + owner: Owner, |
| 1430 | + ): Effect.Effect<void, StorageFailure> => |
| 1217 | 1431 | owner === "user" && subject == null |
| 1218 | 1432 | ? Effect.fail( |
| 1219 | 1433 | new StorageError({ |
| @@ -1236,7 +1450,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 16 unmodified lines |
| 1236 | 1450 | |
| 1237 | 1451 | const tables = yield* Effect.try({ |
| 1238 | 1452 | try: () => collectTables(), |
| 1239 | | − catch: (cause) => storageFailureFromUnknown("Failed to collect executor tables", cause), |
| 1453 | + catch: (cause) => |
| 1454 | + storageFailureFromUnknown("Failed to collect executor tables", cause), |
| 1240 | 1455 | }); |
| 1241 | 1456 | const dbInput = yield* Effect.suspend(() => { |
| 1242 | 1457 | if (!config.db) return Effect.succeed(createDefaultMemoryDb(tables)); |
| @@ -1251,7 +1466,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 8 unmodified lines |
| 1251 | 1466 | validateExecutorDbTables(tables, rootDbUntyped.internal.tables); |
| 1252 | 1467 | validateExecutorOwnerPolicyTables(rootDbUntyped.internal.tables); |
| 1253 | 1468 | }, |
| 1254 | | − catch: (cause) => storageFailureFromUnknown("Failed to validate executor tables", cause), |
| 1469 | + catch: (cause) => |
| 1470 | + storageFailureFromUnknown("Failed to validate executor tables", cause), |
| 1255 | 1471 | }); |
| 1256 | 1472 | |
| 1257 | 1473 | const ownerContext: ExecutorOwnerPolicyContext = { tenant, subject }; |
| @@ -1259,7 +1475,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 1 unmodified line |
| 1259 | 1475 | const fuma = makeFumaClient(rootDb); |
| 1260 | 1476 | const core = makeCoreDb(fuma); |
| 1261 | 1477 | const blobs = config.blobs ?? makeFumaBlobStore(fuma); |
| 1262 | | − const transaction = <A, E>(effect: Effect.Effect<A, E>) => fuma.transaction(effect); |
| 1478 | + const transaction = <A, E>(effect: Effect.Effect<A, E>) => |
| 1479 | + fuma.transaction(effect); |
| 1263 | 1480 | |
| 1264 | 1481 | // Populated once, never mutated after startup. |
| 1265 | 1482 | const staticTools = new Map<string, StaticTools>(); |
| @@ -1270,7 +1487,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 4 unmodified lines |
| 1270 | 1487 | |
| 1271 | 1488 | const staticToolOwner = (): Owner => (subject == null ? "org" : "user"); |
| 1272 | 1489 | const staticToolConnection = (source: StaticSourceDecl): ConnectionName => |
| 1273 | | − ConnectionName.make(source.id === EXECUTOR_SOURCE_ID ? "coreTools" : "static"); |
| 1490 | + ConnectionName.make( |
| 1491 | + source.id === EXECUTOR_SOURCE_ID ? "coreTools" : "static", |
| 1492 | + ); |
| 1274 | 1493 | |
| 1275 | 1494 | const staticSources = (): readonly StaticSourceDecl[] => { |
| 1276 | 1495 | const byId = new Map<string, StaticSourceDecl>(); |
| @@ -1280,7 +1499,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 3 unmodified lines |
| 1280 | 1499 | return [...byId.values()]; |
| 1281 | 1500 | }; |
| 1282 | 1501 | |
| 1283 | | − const staticSourceToIntegration = (source: StaticSourceDecl): Integration => ({ |
| 1502 | + const staticSourceToIntegration = ( |
| 1503 | + source: StaticSourceDecl, |
| 1504 | + ): Integration => ({ |
| 1284 | 1505 | slug: IntegrationSlug.make(source.id), |
| 1285 | 1506 | name: source.name, |
| 1286 | 1507 | description: source.name, |
| @@ -1336,6 +1557,10 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 49 unmodified lines |
| 1336 | 1557 | }; |
| 1337 | 1558 | |
| 1338 | 1559 | const extensions: Record<string, object> = {}; |
| 1560 | + // Generic executor-wrapper hook: a plugin may expose `wrapExecutor` on its |
| 1561 | + // extension to narrow the executor per request (the toolkits plugin does). |
| 1562 | + // Collected here, run by the execution stack — core stays domain-agnostic. |
| 1563 | + const executorWrappers: ExecutorWrapper[] = []; |
| 1339 | 1564 | |
| 1340 | 1565 | // ------------------------------------------------------------------ |
| 1341 | 1566 | // Owner condition builders. The owner policy already restricts reads to |
| @@ -1389,7 +1614,10 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 47 unmodified lines |
| 1389 | 1614 | const performTokenRefresh = ( |
| 1390 | 1615 | row: ConnectionRow, |
| 1391 | 1616 | provider: CredentialProvider, |
| 1392 | | − ): Effect.Effect<string | null, StorageFailure | CredentialResolutionError> => |
| 1617 | + ): Effect.Effect< |
| 1618 | + string | null, |
| 1619 | + StorageFailure | CredentialResolutionError |
| 1620 | + > => |
| 1393 | 1621 | Effect.gen(function* () { |
| 1394 | 1622 | const owner = row.owner as Owner; |
| 1395 | 1623 | const reauth = (message: string): CredentialResolutionError => |
| @@ -1404,15 +1632,21 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 8 unmodified lines |
| 1404 | 1632 | // Load the backing app by the owner STORED on the connection (a Personal |
| 1405 | 1633 | // connection may be backed by a shared Workspace app) — no derivation. |
| 1406 | 1634 | const clientOwner = (row.oauth_client_owner ?? row.owner) as Owner; |
| 1407 | | − const clientRow = yield* loadOAuthClientRow(clientOwner, String(row.oauth_client)); |
| 1635 | + const clientRow = yield* loadOAuthClientRow( |
| 1636 | + clientOwner, |
| 1637 | + String(row.oauth_client), |
| 1638 | + ); |
| 1408 | 1639 | if (!clientRow) { |
| 1409 | | − return yield* reauth(`OAuth client "${row.oauth_client}" is no longer registered.`); |
| 1640 | + return yield* reauth( |
| 1641 | + `OAuth client "${row.oauth_client}" is no longer registered.`, |
| 1642 | + ); |
| 1410 | 1643 | } |
| 1411 | 1644 | |
| 1412 | 1645 | // The secret is stored in the provider (a vault item id), not inline. |
| 1413 | 1646 | const clientSecret = clientRow.client_secret_item_id |
| 1414 | | − ? ((yield* provider.get(ProviderItemId.make(String(clientRow.client_secret_item_id)))) ?? |
| 1415 | | − "") |
| 1647 | + ? ((yield* provider.get( |
| 1648 | + ProviderItemId.make(String(clientRow.client_secret_item_id)), |
| 1649 | + )) ?? "") |
| 1416 | 1650 | : ""; |
| 1417 | 1651 | // Re-request the scopes this connection was GRANTED (RFC 6749 §6: a |
| 1418 | 1652 | // refresh must not exceed the originally-granted scope). Empty → omit |
| @@ -1433,7 +1667,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 14 unmodified lines |
| 1433 | 1667 | clientId: String(clientRow.client_id), |
| 1434 | 1668 | clientSecret, |
| 1435 | 1669 | scopes: grantedScopes, |
| 1436 | | − resource: clientRow.resource ? String(clientRow.resource) : undefined, |
| 1670 | + resource: clientRow.resource |
| 1671 | + ? String(clientRow.resource) |
| 1672 | + : undefined, |
| 1437 | 1673 | endpointUrlPolicy: config.oauthEndpointUrlPolicy, |
| 1438 | 1674 | }).pipe( |
| 1439 | 1675 | // A client_credentials failure is never a rotated-refresh-token |
| @@ -1451,11 +1687,17 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 11 unmodified lines |
| 1451 | 1687 | ) |
| 1452 | 1688 | : yield* Effect.gen(function* () { |
| 1453 | 1689 | if (!row.refresh_item_id) { |
| 1454 | | − return yield* reauth("No refresh token is stored for this connection."); |
| 1690 | + return yield* reauth( |
| 1691 | + "No refresh token is stored for this connection.", |
| 1692 | + ); |
| 1455 | 1693 | } |
| 1456 | | − const refreshToken = yield* provider.get(ProviderItemId.make(row.refresh_item_id)); |
| 1694 | + const refreshToken = yield* provider.get( |
| 1695 | + ProviderItemId.make(row.refresh_item_id), |
| 1696 | + ); |
| 1457 | 1697 | if (!refreshToken) { |
| 1458 | | − return yield* reauth("Stored refresh token could not be resolved."); |
| 1698 | + return yield* reauth( |
| 1699 | + "Stored refresh token could not be resolved.", |
| 1700 | + ); |
| 1459 | 1701 | } |
| 1460 | 1702 | return yield* refreshAccessToken({ |
| 1461 | 1703 | tokenUrl: String(clientRow.token_url), |
| @@ -1465,7 +1707,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 3 unmodified lines |
| 1465 | 1707 | scopes: grantedScopes, |
| 1466 | 1708 | // RFC 8707: keep the re-minted token bound to the same resource |
| 1467 | 1709 | // (MCP servers require this on refresh). |
| 1468 | | − resource: clientRow.resource ? String(clientRow.resource) : undefined, |
| 1710 | + resource: clientRow.resource |
| 1711 | + ? String(clientRow.resource) |
| 1712 | + : undefined, |
| 1469 | 1713 | endpointUrlPolicy: config.oauthEndpointUrlPolicy, |
| 1470 | 1714 | }).pipe( |
| 1471 | 1715 | Effect.mapError((cause) => |
| @@ -1489,14 +1733,22 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 17 unmodified lines |
| 1489 | 1733 | const tokenItemId = |
| 1490 | 1734 | connectionItemIds(row)[PRIMARY_INPUT_VARIABLE] ?? |
| 1491 | 1735 | `connection:${row.owner}:${row.integration}:${row.name}:${PRIMARY_INPUT_VARIABLE}`; |
| 1492 | | − yield* provider.set(ProviderItemId.make(tokenItemId), token.access_token); |
| 1736 | + yield* provider.set( |
| 1737 | + ProviderItemId.make(tokenItemId), |
| 1738 | + token.access_token, |
| 1739 | + ); |
| 1493 | 1740 | if (token.refresh_token && row.refresh_item_id) { |
| 1494 | | − yield* provider.set(ProviderItemId.make(row.refresh_item_id), token.refresh_token); |
| 1741 | + yield* provider.set( |
| 1742 | + ProviderItemId.make(row.refresh_item_id), |
| 1743 | + token.refresh_token, |
| 1744 | + ); |
| 1495 | 1745 | } |
| 1496 | 1746 | } |
| 1497 | 1747 | |
| 1498 | 1748 | const nextExpiresAt = |
| 1499 | | − typeof token.expires_in === "number" ? Date.now() + token.expires_in * 1000 : null; |
| 1749 | + typeof token.expires_in === "number" |
| 1750 | + ? Date.now() + token.expires_in * 1000 |
| 1751 | + : null; |
| 1500 | 1752 | const set: Record<string, unknown> = { |
| 1501 | 1753 | expires_at: nextExpiresAt, |
| 1502 | 1754 | updated_at: new Date(), |
| @@ -1518,7 +1770,10 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 15 unmodified lines |
| 1518 | 1770 | const refreshConnectionToken = ( |
| 1519 | 1771 | row: ConnectionRow, |
| 1520 | 1772 | provider: CredentialProvider, |
| 1521 | | − ): Effect.Effect<string | null, StorageFailure | CredentialResolutionError> => |
| 1773 | + ): Effect.Effect< |
| 1774 | + string | null, |
| 1775 | + StorageFailure | CredentialResolutionError |
| 1776 | + > => |
| 1522 | 1777 | // Share a single refresh per connection so concurrent resolves of the same |
| 1523 | 1778 | // connection all await one refresh-token grant (the AS rotates the refresh |
| 1524 | 1779 | // token; parallel grants would race on a consumed token — v1's refresh |
| @@ -1530,7 +1785,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 5 unmodified lines |
| 1530 | 1785 | if (existing) return yield* existing; |
| 1531 | 1786 | // `Effect.cached` memoizes the grant onto a deferred: it runs once and |
| 1532 | 1787 | // replays to every awaiter sharing this entry. |
| 1533 | | − const memoized = yield* Effect.cached(performTokenRefresh(row, provider)); |
| 1788 | + const memoized = yield* Effect.cached( |
| 1789 | + performTokenRefresh(row, provider), |
| 1790 | + ); |
| 1534 | 1791 | const gated = memoized.pipe( |
| 1535 | 1792 | Effect.ensuring(Effect.sync(() => refreshInFlight.delete(key))), |
| 1536 | 1793 | ); |
| @@ -1547,7 +1804,10 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 10 unmodified lines |
| 1547 | 1804 | // first (always single-input → `{ token: <access> }`). |
| 1548 | 1805 | const resolveConnectionValues = ( |
| 1549 | 1806 | row: ConnectionRow, |
| 1550 | | − ): Effect.Effect<Record<string, string | null>, StorageFailure | CredentialResolutionError> => |
| 1807 | + ): Effect.Effect< |
| 1808 | + Record<string, string | null>, |
| 1809 | + StorageFailure | CredentialResolutionError |
| 1810 | + > => |
| 1551 | 1811 | Effect.gen(function* () { |
| 1552 | 1812 | const provider = credentialProviders.get(row.provider); |
| 1553 | 1813 | if (!provider) { |
| @@ -1557,13 +1817,16 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 3 unmodified lines |
| 1557 | 1817 | } |
| 1558 | 1818 | // OAuth connections refresh their access token before resolving when |
| 1559 | 1819 | // it has expired (or is within the skew window). |
| 1560 | | − const expiresAt = row.expires_at == null ? null : Number(row.expires_at); |
| 1820 | + const expiresAt = |
| 1821 | + row.expires_at == null ? null : Number(row.expires_at); |
| 1561 | 1822 | if (row.oauth_client != null && shouldRefreshToken({ expiresAt })) { |
| 1562 | 1823 | const access = yield* refreshConnectionToken(row, provider); |
| 1563 | 1824 | return { [PRIMARY_INPUT_VARIABLE]: access }; |
| 1564 | 1825 | } |
| 1565 | 1826 | const out: Record<string, string | null> = {}; |
| 1566 | | − for (const [variable, itemId] of Object.entries(connectionItemIds(row))) { |
| 1827 | + for (const [variable, itemId] of Object.entries( |
| 1828 | + connectionItemIds(row), |
| 1829 | + )) { |
| 1567 | 1830 | out[variable] = yield* provider.get(ProviderItemId.make(itemId)); |
| 1568 | 1831 | } |
| 1569 | 1832 | return out; |
| @@ -1584,7 +1847,10 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 14 unmodified lines |
| 1584 | 1847 | * callers that only ever need one value. */ |
| 1585 | 1848 | const resolveConnectionValue = ( |
| 1586 | 1849 | row: ConnectionRow, |
| 1587 | | − ): Effect.Effect<string | null, StorageFailure | CredentialResolutionError> => |
| 1850 | + ): Effect.Effect< |
| 1851 | + string | null, |
| 1852 | + StorageFailure | CredentialResolutionError |
| 1853 | + > => |
| 1588 | 1854 | resolveConnectionValues(row).pipe( |
| 1589 | 1855 | Effect.map((values) => values[PRIMARY_INPUT_VARIABLE] ?? null), |
| 1590 | 1856 | ); |
| @@ -1644,7 +1910,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 53 unmodified lines |
| 1644 | 1910 | // plugin's `describeAuthMethods` hook. The hook is plugin-authored, so a |
| 1645 | 1911 | // throw (malformed config it didn't guard) degrades to `[]` rather than |
| 1646 | 1912 | // failing the catalog read. |
| 1647 | | − const describeAuthMethodsForRow = (row: IntegrationRow): readonly AuthMethodDescriptor[] => { |
| 1913 | + const describeAuthMethodsForRow = ( |
| 1914 | + row: IntegrationRow, |
| 1915 | + ): readonly AuthMethodDescriptor[] => { |
| 1648 | 1916 | const runtime = runtimes.get(row.plugin_id); |
| 1649 | 1917 | const describe = runtime?.plugin.describeAuthMethods; |
| 1650 | 1918 | if (!describe) return []; |
| @@ -1657,7 +1925,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 6 unmodified lines |
| 1657 | 1925 | } |
| 1658 | 1926 | }; |
| 1659 | 1927 | |
| 1660 | | − const describeDisplayUrlForRow = (row: IntegrationRow): string | undefined => { |
| 1928 | + const describeDisplayUrlForRow = ( |
| 1929 | + row: IntegrationRow, |
| 1930 | + ): string | undefined => { |
| 1661 | 1931 | const runtime = runtimes.get(row.plugin_id); |
| 1662 | 1932 | const describe = runtime?.plugin.describeIntegrationDisplay; |
| 1663 | 1933 | if (!describe) return undefined; |
| @@ -1671,14 +1941,21 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 7 unmodified lines |
| 1671 | 1941 | } |
| 1672 | 1942 | }; |
| 1673 | 1943 | |
| 1674 | | − const integrationsList = (): Effect.Effect<readonly Integration[], StorageFailure> => |
| 1944 | + const integrationsList = (): Effect.Effect< |
| 1945 | + readonly Integration[], |
| 1946 | + StorageFailure |
| 1947 | + > => |
| 1675 | 1948 | core |
| 1676 | 1949 | .findMany("integration", {}) |
| 1677 | 1950 | .pipe( |
| 1678 | 1951 | Effect.map((rows) => [ |
| 1679 | 1952 | ...staticSources().map(staticSourceToIntegration), |
| 1680 | 1953 | ...rows.map((row) => |
| 1681 | | − rowToIntegration(row, describeAuthMethodsForRow(row), describeDisplayUrlForRow(row)), |
| 1954 | + rowToIntegration( |
| 1955 | + row, |
| 1956 | + describeAuthMethodsForRow(row), |
| 1957 | + describeDisplayUrlForRow(row), |
| 1958 | + ), |
| 1682 | 1959 | ), |
| 1683 | 1960 | ]), |
| 1684 | 1961 | ); |
| @@ -1687,11 +1964,17 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 2 unmodified lines |
| 1687 | 1964 | slug: IntegrationSlug, |
| 1688 | 1965 | ): Effect.Effect<Integration | null, StorageFailure> => |
| 1689 | 1966 | Effect.gen(function* () { |
| 1690 | | − const staticSource = staticSources().find((source) => source.id === String(slug)); |
| 1967 | + const staticSource = staticSources().find( |
| 1968 | + (source) => source.id === String(slug), |
| 1969 | + ); |
| 1691 | 1970 | if (staticSource) return staticSourceToIntegration(staticSource); |
| 1692 | 1971 | const row = yield* findIntegrationRow(slug); |
| 1693 | 1972 | return row |
| 1694 | | − ? rowToIntegration(row, describeAuthMethodsForRow(row), describeDisplayUrlForRow(row)) |
| 1973 | + ? rowToIntegration( |
| 1974 | + row, |
| 1975 | + describeAuthMethodsForRow(row), |
| 1976 | + describeDisplayUrlForRow(row), |
| 1977 | + ) |
| 1695 | 1978 | : null; |
| 1696 | 1979 | }); |
| 1697 | 1980 | |
| @@ -1700,7 +1983,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 2 unmodified lines |
| 1700 | 1983 | ): Effect.Effect<IntegrationRecord | null, StorageFailure> => |
| 1701 | 1984 | findIntegrationRow(slug).pipe( |
| 1702 | 1985 | Effect.map((row) => |
| 1703 | | − row ? rowToIntegrationRecord(row, describeAuthMethodsForRow(row)) : null, |
| 1986 | + row |
| 1987 | + ? rowToIntegrationRecord(row, describeAuthMethodsForRow(row)) |
| 1988 | + : null, |
| 1704 | 1989 | ), |
| 1705 | 1990 | ); |
| 1706 | 1991 | |
| @@ -1755,7 +2040,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 48 unmodified lines |
| 1755 | 2040 | const now = new Date(); |
| 1756 | 2041 | const set: Record<string, unknown> = { updated_at: now }; |
| 1757 | 2042 | if (patch.name !== undefined) set.name = patch.name; |
| 1758 | | − if (patch.description !== undefined) set.description = patch.description; |
| 2043 | + if (patch.description !== undefined) |
| 2044 | + set.description = patch.description; |
| 1759 | 2045 | if (patch.config !== undefined) { |
| 1760 | 2046 | set.config = patch.config; |
| 1761 | 2047 | // A config change can change the derived tools. The writer can only |
| @@ -1782,7 +2068,10 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 20 unmodified lines |
| 1782 | 2068 | |
| 1783 | 2069 | const integrationsRemove = ( |
| 1784 | 2070 | slug: IntegrationSlug, |
| 1785 | | − ): Effect.Effect<void, IntegrationRemovalNotAllowedError | StorageFailure> => |
| 2071 | + ): Effect.Effect< |
| 2072 | + void, |
| 2073 | + IntegrationRemovalNotAllowedError | StorageFailure |
| 2074 | + > => |
| 1786 | 2075 | transaction( |
| 1787 | 2076 | Effect.gen(function* () { |
| 1788 | 2077 | const existing = yield* findIntegrationRow(slug); |
| @@ -1811,7 +2100,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 22 unmodified lines |
| 1811 | 2100 | const result = yield* runtime.plugin |
| 1812 | 2101 | .detect({ ctx: runtime.ctx, url }) |
| 1813 | 2102 | .pipe( |
| 1814 | | − Effect.mapError((cause) => pluginStorageFailure(runtime.plugin.id, "detect", cause)), |
| 2103 | + Effect.mapError((cause) => |
| 2104 | + pluginStorageFailure(runtime.plugin.id, "detect", cause), |
| 2105 | + ), |
| 1815 | 2106 | ); |
| 1816 | 2107 | if (result) results.push(result); |
| 1817 | 2108 | } |
| @@ -1825,7 +2116,10 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 7 unmodified lines |
| 1825 | 2116 | const produceConnectionTools = ( |
| 1826 | 2117 | integrationRow: IntegrationRow, |
| 1827 | 2118 | ref: ConnectionRef, |
| 1828 | | − ): Effect.Effect<readonly Tool[], IntegrationNotFoundError | StorageFailure> => |
| 2119 | + ): Effect.Effect< |
| 2120 | + readonly Tool[], |
| 2121 | + IntegrationNotFoundError | StorageFailure |
| 2122 | + > => |
| 1829 | 2123 | Effect.gen(function* () { |
| 1830 | 2124 | const runtime = runtimes.get(integrationRow.plugin_id); |
| 1831 | 2125 | const keys = yield* Effect.try({ |
| @@ -1894,14 +2188,20 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 62 unmodified lines |
| 1894 | 2188 | integration: rowToIntegration(integrationRow), |
| 1895 | 2189 | config: decodeJsonColumn(integrationRow.config), |
| 1896 | 2190 | connection: ref, |
| 1897 | | − template: existingRow ? AuthTemplateSlug.make(existingRow.template) : null, |
| 2191 | + template: existingRow |
| 2192 | + ? AuthTemplateSlug.make(existingRow.template) |
| 2193 | + : null, |
| 1898 | 2194 | storage: runtime.storage, |
| 1899 | 2195 | getValue: () => resolveConnectionValueByRef(ref), |
| 1900 | 2196 | getValues: () => resolveConnectionValuesByRef(ref), |
| 1901 | 2197 | }) |
| 1902 | 2198 | .pipe( |
| 1903 | 2199 | Effect.mapError((cause) => |
| 1904 | | − pluginStorageFailure(integrationRow.plugin_id, "resolveTools", cause), |
| 2200 | + pluginStorageFailure( |
| 2201 | + integrationRow.plugin_id, |
| 2202 | + "resolveTools", |
| 2203 | + cause, |
| 2204 | + ), |
| 1905 | 2205 | ), |
| 1906 | 2206 | ); |
| 1907 | 2207 | |
| @@ -1922,17 +2222,19 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 14 unmodified lines |
| 1922 | 2222 | updated_at: now, |
| 1923 | 2223 | })); |
| 1924 | 2224 | |
| 1925 | | − const definitionRows = Object.entries(result.definitions ?? {}).map(([name, schema]) => ({ |
| 1926 | | − tenant: keys.tenant, |
| 1927 | | − owner: keys.owner, |
| 1928 | | − subject: keys.subject, |
| 1929 | | − integration: String(ref.integration), |
| 1930 | | − connection: String(ref.name), |
| 1931 | | − plugin_id: integrationRow.plugin_id, |
| 1932 | | − name, |
| 1933 | | − schema, |
| 1934 | | − created_at: now, |
| 1935 | | − })); |
| 2225 | + const definitionRows = Object.entries(result.definitions ?? {}).map( |
| 2226 | + ([name, schema]) => ({ |
| 2227 | + tenant: keys.tenant, |
| 2228 | + owner: keys.owner, |
| 2229 | + subject: keys.subject, |
| 2230 | + integration: String(ref.integration), |
| 2231 | + connection: String(ref.name), |
| 2232 | + plugin_id: integrationRow.plugin_id, |
| 2233 | + name, |
| 2234 | + schema, |
| 2235 | + created_at: now, |
| 2236 | + }), |
| 2237 | + ); |
| 1936 | 2238 | |
| 1937 | 2239 | yield* transaction( |
| 1938 | 2240 | Effect.gen(function* () { |
| @@ -2024,16 +2326,20 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 85 unmodified lines |
| 2024 | 2326 | const itemIds: Record<string, string> = {}; |
| 2025 | 2327 | if (external.length > 0 && pasted.length > 0) { |
| 2026 | 2328 | return yield* new InvalidConnectionInputError({ |
| 2027 | | − message: "A connection cannot mix pasted and external-provider inputs.", |
| 2329 | + message: |
| 2330 | + "A connection cannot mix pasted and external-provider inputs.", |
| 2028 | 2331 | }); |
| 2029 | 2332 | } |
| 2030 | 2333 | if (external.length > 0) { |
| 2031 | 2334 | const providers = new Set( |
| 2032 | | − external.map((i) => ("from" in i.origin ? String(i.origin.from.provider) : "")), |
| 2335 | + external.map((i) => |
| 2336 | + "from" in i.origin ? String(i.origin.from.provider) : "", |
| 2337 | + ), |
| 2033 | 2338 | ); |
| 2034 | 2339 | if (providers.size > 1) { |
| 2035 | 2340 | return yield* new InvalidConnectionInputError({ |
| 2036 | | − message: "A connection's inputs must all use the same external provider.", |
| 2341 | + message: |
| 2342 | + "A connection's inputs must all use the same external provider.", |
| 2037 | 2343 | }); |
| 2038 | 2344 | } |
| 2039 | 2345 | const [only] = [...providers]; |
| @@ -2045,7 +2351,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 5 unmodified lines |
| 2045 | 2351 | } |
| 2046 | 2352 | providerKey = only ?? ""; |
| 2047 | 2353 | for (const i of external) { |
| 2048 | | − if ("from" in i.origin) itemIds[i.variable] = String(i.origin.from.id); |
| 2354 | + if ("from" in i.origin) |
| 2355 | + itemIds[i.variable] = String(i.origin.from.id); |
| 2049 | 2356 | } |
| 2050 | 2357 | } else { |
| 2051 | 2358 | const provider = defaultWritableProvider(); |
| @@ -2083,7 +2390,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 31 unmodified lines |
| 2083 | 2390 | identity_label: input.identityLabel ?? null, |
| 2084 | 2391 | // Re-saving a credential keeps an existing curated description |
| 2085 | 2392 | // unless the caller explicitly provides one. |
| 2086 | | − ...(input.description !== undefined ? { description: input.description } : {}), |
| 2393 | + ...(input.description !== undefined |
| 2394 | + ? { description: input.description } |
| 2395 | + : {}), |
| 2087 | 2396 | updated_at: now, |
| 2088 | 2397 | }; |
| 2089 | 2398 | if (existing) { |
| @@ -2127,7 +2436,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 37 unmodified lines |
| 2127 | 2436 | }; |
| 2128 | 2437 | // Produce + persist tools for the new connection. |
| 2129 | 2438 | yield* produceConnectionTools(integrationRow, ref).pipe( |
| 2130 | | − Effect.catchTag("IntegrationNotFoundError", () => Effect.succeed([] as readonly Tool[])), |
| 2439 | + Effect.catchTag("IntegrationNotFoundError", () => |
| 2440 | + Effect.succeed([] as readonly Tool[]), |
| 2441 | + ), |
| 2131 | 2442 | ); |
| 2132 | 2443 | |
| 2133 | 2444 | const row = yield* findConnectionRow(ref); |
| @@ -2236,7 +2547,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 102 unmodified lines |
| 2236 | 2547 | // Produce + persist tools for the minted connection (same path |
| 2237 | 2548 | // connections.create uses). |
| 2238 | 2549 | yield* produceConnectionTools(integrationRow, ref).pipe( |
| 2239 | | − Effect.catchTag("IntegrationNotFoundError", () => Effect.succeed([] as readonly Tool[])), |
| 2550 | + Effect.catchTag("IntegrationNotFoundError", () => |
| 2551 | + Effect.succeed([] as readonly Tool[]), |
| 2552 | + ), |
| 2240 | 2553 | ); |
| 2241 | 2554 | |
| 2242 | 2555 | const row = yield* findConnectionRow(ref); |
| @@ -2275,13 +2588,19 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 32 unmodified lines |
| 2275 | 2588 | filter?.integration === undefined |
| 2276 | 2589 | ? true |
| 2277 | 2590 | : b("integration", "=", String(filter.integration)), |
| 2278 | | − filter?.owner === undefined ? true : b("owner", "=", filter.owner), |
| 2591 | + filter?.owner === undefined |
| 2592 | + ? true |
| 2593 | + : b("owner", "=", filter.owner), |
| 2279 | 2594 | ), |
| 2280 | 2595 | }) |
| 2281 | 2596 | .pipe(Effect.map((rows) => rows.map(rowToConnection))); |
| 2282 | 2597 | |
| 2283 | | − const connectionsGet = (ref: ConnectionRef): Effect.Effect<Connection | null, StorageFailure> => |
| 2284 | | − findConnectionRow(ref).pipe(Effect.map((row) => (row ? rowToConnection(row) : null))); |
| 2598 | + const connectionsGet = ( |
| 2599 | + ref: ConnectionRef, |
| 2600 | + ): Effect.Effect<Connection | null, StorageFailure> => |
| 2601 | + findConnectionRow(ref).pipe( |
| 2602 | + Effect.map((row) => (row ? rowToConnection(row) : null)), |
| 2603 | + ); |
| 2285 | 2604 | |
| 2286 | 2605 | const connectionsUpdate = ( |
| 2287 | 2606 | ref: ConnectionRef, |
| @@ -2297,8 +2616,10 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 9 unmodified lines |
| 2297 | 2616 | }); |
| 2298 | 2617 | } |
| 2299 | 2618 | const set: Record<string, unknown> = { updated_at: new Date() }; |
| 2300 | | − if (input.description !== undefined) set.description = input.description; |
| 2301 | | − if (input.identityLabel !== undefined) set.identity_label = input.identityLabel; |
| 2619 | + if (input.description !== undefined) |
| 2620 | + set.description = input.description; |
| 2621 | + if (input.identityLabel !== undefined) |
| 2622 | + set.identity_label = input.identityLabel; |
| 2302 | 2623 | yield* core.updateMany("connection", { |
| 2303 | 2624 | where: (b: AnyCb) => |
| 2304 | 2625 | b.and( |
| @@ -2326,7 +2647,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 21 unmodified lines |
| 2326 | 2647 | }); |
| 2327 | 2648 | } |
| 2328 | 2649 | const integrationRow = yield* findIntegrationRow(ref.integration); |
| 2329 | | − const runtime = integrationRow ? runtimes.get(integrationRow.plugin_id) : undefined; |
| 2650 | + const runtime = integrationRow |
| 2651 | + ? runtimes.get(integrationRow.plugin_id) |
| 2652 | + : undefined; |
| 2330 | 2653 | if (integrationRow && runtime?.plugin.removeConnection) { |
| 2331 | 2654 | yield* runtime.plugin |
| 2332 | 2655 | .removeConnection({ |
| @@ -2336,7 +2659,11 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 3 unmodified lines |
| 2336 | 2659 | }) |
| 2337 | 2660 | .pipe( |
| 2338 | 2661 | Effect.mapError((cause) => |
| 2339 | | − pluginStorageFailure(integrationRow.plugin_id, "removeConnection", cause), |
| 2662 | + pluginStorageFailure( |
| 2663 | + integrationRow.plugin_id, |
| 2664 | + "removeConnection", |
| 2665 | + cause, |
| 2666 | + ), |
| 2340 | 2667 | ), |
| 2341 | 2668 | ); |
| 2342 | 2669 | } |
| @@ -2385,11 +2712,23 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 42 unmodified lines |
| 2385 | 2712 | // Tools (read surface) |
| 2386 | 2713 | // ------------------------------------------------------------------ |
| 2387 | 2714 | |
| 2388 | | − const matchesToolFilter = (tool: Tool, filter: ToolListFilter | undefined): boolean => { |
| 2715 | + const matchesToolFilter = ( |
| 2716 | + tool: Tool, |
| 2717 | + filter: ToolListFilter | undefined, |
| 2718 | + ): boolean => { |
| 2389 | 2719 | if (!filter) return true; |
| 2390 | | − if (filter.integration !== undefined && tool.integration !== filter.integration) return false; |
| 2391 | | − if (filter.owner !== undefined && tool.owner !== filter.owner) return false; |
| 2392 | | − if (filter.connection !== undefined && tool.connection !== filter.connection) return false; |
| 2720 | + if ( |
| 2721 | + filter.integration !== undefined && |
| 2722 | + tool.integration !== filter.integration |
| 2723 | + ) |
| 2724 | + return false; |
| 2725 | + if (filter.owner !== undefined && tool.owner !== filter.owner) |
| 2726 | + return false; |
| 2727 | + if ( |
| 2728 | + filter.connection !== undefined && |
| 2729 | + tool.connection !== filter.connection |
| 2730 | + ) |
| 2731 | + return false; |
| 2393 | 2732 | if (filter.query !== undefined) { |
| 2394 | 2733 | const q = filter.query.toLowerCase(); |
| 2395 | 2734 | const hay = `${tool.name} ${tool.description}`.toLowerCase(); |
| @@ -2410,18 +2749,25 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 14 unmodified lines |
| 2410 | 2749 | }); |
| 2411 | 2750 | if (revised.length === 0) return; |
| 2412 | 2751 | const revisedAt = new Map( |
| 2413 | | − revised.map((row) => [row.slug, Number(row.config_revised_at)] as const), |
| 2752 | + revised.map( |
| 2753 | + (row) => [row.slug, Number(row.config_revised_at)] as const, |
| 2754 | + ), |
| 2414 | 2755 | ); |
| 2415 | 2756 | const connections = yield* core.findMany("connection", { |
| 2416 | | − where: (b: AnyCb) => b.or(...revised.map((row) => b("integration", "=", row.slug))), |
| 2757 | + where: (b: AnyCb) => |
| 2758 | + b.or(...revised.map((row) => b("integration", "=", row.slug))), |
| 2417 | 2759 | }); |
| 2418 | 2760 | for (const connection of connections) { |
| 2419 | 2761 | const revisedTime = revisedAt.get(connection.integration); |
| 2420 | 2762 | if (revisedTime === undefined) continue; |
| 2421 | 2763 | const syncedAt = |
| 2422 | | − connection.tools_synced_at == null ? 0 : Number(connection.tools_synced_at); |
| 2764 | + connection.tools_synced_at == null |
| 2765 | + ? 0 |
| 2766 | + : Number(connection.tools_synced_at); |
| 2423 | 2767 | if (syncedAt >= revisedTime) continue; |
| 2424 | | − const integrationRow = revised.find((row) => row.slug === connection.integration); |
| 2768 | + const integrationRow = revised.find( |
| 2769 | + (row) => row.slug === connection.integration, |
| 2770 | + ); |
| 2425 | 2771 | if (!integrationRow) continue; |
| 2426 | 2772 | yield* produceConnectionTools(integrationRow, { |
| 2427 | 2773 | owner: connection.owner as Owner, |
| @@ -2439,7 +2785,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 11 unmodified lines |
| 2439 | 2785 | } |
| 2440 | 2786 | }); |
| 2441 | 2787 | |
| 2442 | | − const toolsList = (filter?: ToolListFilter): Effect.Effect<readonly Tool[], StorageFailure> => |
| 2788 | + const toolsList = ( |
| 2789 | + filter?: ToolListFilter, |
| 2790 | + ): Effect.Effect<readonly Tool[], StorageFailure> => |
| 2443 | 2791 | Effect.gen(function* () { |
| 2444 | 2792 | yield* syncStaleConnectionTools; |
| 2445 | 2793 | // Projected: the list surface is metadata (address, description, |
| @@ -2451,7 +2799,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 5 unmodified lines |
| 2451 | 2799 | filter?.integration === undefined |
| 2452 | 2800 | ? true |
| 2453 | 2801 | : b("integration", "=", String(filter.integration)), |
| 2454 | | − filter?.owner === undefined ? true : b("owner", "=", filter.owner), |
| 2802 | + filter?.owner === undefined |
| 2803 | + ? true |
| 2804 | + : b("owner", "=", filter.owner), |
| 2455 | 2805 | filter?.connection === undefined |
| 2456 | 2806 | ? true |
| 2457 | 2807 | : b("connection", "=", String(filter.connection)), |
| @@ -2507,7 +2857,10 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 49 unmodified lines |
| 2507 | 2857 | defs: new Map(), |
| 2508 | 2858 | }), |
| 2509 | 2859 | catch: (cause) => |
| 2510 | | − storageFailureFromUnknown("Failed to build static tool TypeScript preview", cause), |
| 2860 | + storageFailureFromUnknown( |
| 2861 | + "Failed to build static tool TypeScript preview", |
| 2862 | + cause, |
| 2863 | + ), |
| 2511 | 2864 | }).pipe(Effect.option); |
| 2512 | 2865 | return ToolSchemaView.make({ |
| 2513 | 2866 | address, |
| @@ -2517,7 +2870,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 3 unmodified lines |
| 2517 | 2870 | outputSchema: tool.outputSchema, |
| 2518 | 2871 | inputTypeScript: Option.getOrUndefined(preview)?.inputTypeScript, |
| 2519 | 2872 | outputTypeScript: Option.getOrUndefined(preview)?.outputTypeScript, |
| 2520 | | − typeScriptDefinitions: Option.getOrUndefined(preview)?.typeScriptDefinitions, |
| 2873 | + typeScriptDefinitions: |
| 2874 | + Option.getOrUndefined(preview)?.typeScriptDefinitions, |
| 2521 | 2875 | }); |
| 2522 | 2876 | } |
| 2523 | 2877 | |
| @@ -2544,7 +2898,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 20 unmodified lines |
| 2544 | 2898 | ), |
| 2545 | 2899 | }); |
| 2546 | 2900 | const defs = new Map<string, unknown>(); |
| 2547 | | − for (const def of definitionRows) defs.set(def.name, decodeJsonColumn(def.schema)); |
| 2901 | + for (const def of definitionRows) |
| 2902 | + defs.set(def.name, decodeJsonColumn(def.schema)); |
| 2548 | 2903 | |
| 2549 | 2904 | const referenced = collectReferencedDefinitions( |
| 2550 | 2905 | [tool.inputSchema, tool.outputSchema], |
| @@ -2558,7 +2913,10 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 7 unmodified lines |
| 2558 | 2913 | defs, |
| 2559 | 2914 | }), |
| 2560 | 2915 | catch: (cause) => |
| 2561 | | − storageFailureFromUnknown("Failed to build tool TypeScript preview", cause), |
| 2916 | + storageFailureFromUnknown( |
| 2917 | + "Failed to build tool TypeScript preview", |
| 2918 | + cause, |
| 2919 | + ), |
| 2562 | 2920 | }).pipe(Effect.option); |
| 2563 | 2921 | |
| 2564 | 2922 | const view = preview; |
| @@ -2574,7 +2932,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 9 unmodified lines |
| 2574 | 2932 | : undefined, |
| 2575 | 2933 | inputTypeScript: Option.getOrUndefined(view)?.inputTypeScript, |
| 2576 | 2934 | outputTypeScript: Option.getOrUndefined(view)?.outputTypeScript, |
| 2577 | | − typeScriptDefinitions: Option.getOrUndefined(view)?.typeScriptDefinitions, |
| 2935 | + typeScriptDefinitions: |
| 2936 | + Option.getOrUndefined(view)?.typeScriptDefinitions, |
| 2578 | 2937 | }); |
| 2579 | 2938 | }); |
| 2580 | 2939 | |
| @@ -2583,7 +2942,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 2 unmodified lines |
| 2583 | 2942 | // ------------------------------------------------------------------ |
| 2584 | 2943 | |
| 2585 | 2944 | const providersList = (): Effect.Effect<readonly ProviderKey[]> => |
| 2586 | | − Effect.sync(() => credentialProviderOrder.map((key) => ProviderKey.make(key))); |
| 2945 | + Effect.sync(() => |
| 2946 | + credentialProviderOrder.map((key) => ProviderKey.make(key)), |
| 2947 | + ); |
| 2587 | 2948 | |
| 2588 | 2949 | const providersItems = ( |
| 2589 | 2950 | key: ProviderKey, |
| @@ -2609,13 +2970,20 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 19 unmodified lines |
| 2609 | 2970 | ? String(tool.address) |
| 2610 | 2971 | : `${tool.integration}.${tool.owner}.${tool.connection}.${tool.name}`; |
| 2611 | 2972 | |
| 2612 | | − const policiesList = (): Effect.Effect<readonly ToolPolicy[], StorageFailure> => |
| 2973 | + const policiesList = (): Effect.Effect< |
| 2974 | + readonly ToolPolicy[], |
| 2975 | + StorageFailure |
| 2976 | + > => |
| 2613 | 2977 | core |
| 2614 | 2978 | .findMany("tool_policy", {}) |
| 2615 | 2979 | .pipe( |
| 2616 | 2980 | Effect.map((rows) => |
| 2617 | 2981 | [...rows] |
| 2618 | | − .sort((a, b) => ownerRankForRow(a) - ownerRankForRow(b) || comparePolicyRow(a, b)) |
| 2982 | + .sort( |
| 2983 | + (a, b) => |
| 2984 | + ownerRankForRow(a) - ownerRankForRow(b) || |
| 2985 | + comparePolicyRow(a, b), |
| 2986 | + ) |
| 2619 | 2987 | .map(rowToToolPolicy), |
| 2620 | 2988 | ), |
| 2621 | 2989 | ); |
| @@ -2648,7 +3016,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 26 unmodified lines |
| 2648 | 3016 | .map((row) => row.position) |
| 2649 | 3017 | .sort() |
| 2650 | 3018 | .at(0); |
| 2651 | | − const position = input.position ?? generateKeyBetween(null, minPosition ?? null); |
| 3019 | + const position = |
| 3020 | + input.position ?? generateKeyBetween(null, minPosition ?? null); |
| 2652 | 3021 | const id = PolicyId.make( |
| 2653 | 3022 | `pol_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`, |
| 2654 | 3023 | ); |
| @@ -2677,7 +3046,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 22 unmodified lines |
| 2677 | 3046 | cause: undefined, |
| 2678 | 3047 | }); |
| 2679 | 3048 | } |
| 2680 | | − const where = (b: AnyCb) => b.and(byOwner(input.owner)(b), b("id", "=", input.id)); |
| 3049 | + const where = (b: AnyCb) => |
| 3050 | + b.and(byOwner(input.owner)(b), b("id", "=", input.id)); |
| 2681 | 3051 | const existing = yield* core.findFirst("tool_policy", { where }); |
| 2682 | 3052 | if (!existing) { |
| 2683 | 3053 | return yield* new StorageError({ |
| @@ -2691,12 +3061,17 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 7 unmodified lines |
| 2691 | 3061 | if (input.position !== undefined) set.position = input.position; |
| 2692 | 3062 | yield* core.updateMany("tool_policy", { where, set }); |
| 2693 | 3063 | const updated = yield* core.findFirst("tool_policy", { where }); |
| 2694 | | − return rowToToolPolicy(updated ?? ({ ...existing, ...set } as ToolPolicyRow)); |
| 3064 | + return rowToToolPolicy( |
| 3065 | + updated ?? ({ ...existing, ...set } as ToolPolicyRow), |
| 3066 | + ); |
| 2695 | 3067 | }); |
| 2696 | 3068 | |
| 2697 | | − const policiesRemove = (input: RemoveToolPolicyInput): Effect.Effect<void, StorageFailure> => |
| 3069 | + const policiesRemove = ( |
| 3070 | + input: RemoveToolPolicyInput, |
| 3071 | + ): Effect.Effect<void, StorageFailure> => |
| 2698 | 3072 | core.deleteMany("tool_policy", { |
| 2699 | | − where: (b: AnyCb) => b.and(byOwner(input.owner)(b), b("id", "=", input.id)), |
| 3073 | + where: (b: AnyCb) => |
| 3074 | + b.and(byOwner(input.owner)(b), b("id", "=", input.id)), |
| 2700 | 3075 | }); |
| 2701 | 3076 | |
| 2702 | 3077 | const policiesResolve = ( |
| @@ -2721,20 +3096,31 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 18 unmodified lines |
| 2721 | 3096 | ), |
| 2722 | 3097 | }); |
| 2723 | 3098 | if (row) { |
| 2724 | | − const annotations = decodeJsonColumn(row.annotations) as ToolAnnotations | undefined; |
| 3099 | + const annotations = decodeJsonColumn(row.annotations) as |
| 3100 | + | ToolAnnotations |
| 3101 | + | undefined; |
| 2725 | 3102 | requiresApproval = annotations?.requiresApproval; |
| 2726 | 3103 | } |
| 2727 | 3104 | } |
| 2728 | | − return resolveEffectivePolicy(toolId, policyRows, ownerRankForRow, requiresApproval); |
| 3105 | + return resolveEffectivePolicy( |
| 3106 | + toolId, |
| 3107 | + policyRows, |
| 3108 | + ownerRankForRow, |
| 3109 | + requiresApproval, |
| 3110 | + ); |
| 2729 | 3111 | }); |
| 2730 | 3112 | |
| 2731 | 3113 | // ------------------------------------------------------------------ |
| 2732 | 3114 | // Elicitation |
| 2733 | 3115 | // ------------------------------------------------------------------ |
| 2734 | 3116 | |
| 2735 | | − const defaultElicitationHandler = resolveElicitationHandler(config.onElicitation); |
| 3117 | + const defaultElicitationHandler = resolveElicitationHandler( |
| 3118 | + config.onElicitation, |
| 3119 | + ); |
| 2736 | 3120 | |
| 2737 | | − const pickHandler = (options: InvokeOptions | undefined): ElicitationHandler => |
| 3121 | + const pickHandler = ( |
| 3122 | + options: InvokeOptions | undefined, |
| 3123 | + ): ElicitationHandler => |
| 2738 | 3124 | options?.onElicitation |
| 2739 | 3125 | ? resolveElicitationHandler(options.onElicitation) |
| 2740 | 3126 | : defaultElicitationHandler; |
| @@ -2796,15 +3182,17 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 55 unmodified lines |
| 2796 | 3182 | |
| 2797 | 3183 | const TOOL_SUGGESTION_LIMIT = 5; |
| 2798 | 3184 | |
| 2799 | | − const toolSuggestions = (rows: readonly ToolInvocationRow[]): readonly ToolAddress[] => |
| 2800 | | − rows.map((row) => rowToTool(row).address); |
| 3185 | + const toolSuggestions = ( |
| 3186 | + rows: readonly ToolInvocationRow[], |
| 3187 | + ): readonly ToolAddress[] => rows.map((row) => rowToTool(row).address); |
| 2801 | 3188 | |
| 2802 | | − const toolRowsForConnectionWhere = (parsed: ParsedToolAddress) => (b: AnyCb) => |
| 2803 | | − b.and( |
| 2804 | | − byOwner(parsed.owner)(b), |
| 2805 | | − b("integration", "=", String(parsed.integration)), |
| 2806 | | − b("connection", "=", String(parsed.connection)), |
| 2807 | | − ); |
| 3189 | + const toolRowsForConnectionWhere = |
| 3190 | + (parsed: ParsedToolAddress) => (b: AnyCb) => |
| 3191 | + b.and( |
| 3192 | + byOwner(parsed.owner)(b), |
| 3193 | + b("integration", "=", String(parsed.integration)), |
| 3194 | + b("connection", "=", String(parsed.connection)), |
| 3195 | + ); |
| 2808 | 3196 | |
| 2809 | 3197 | const searchToolRowsForConnection = ( |
| 2810 | 3198 | parsed: ParsedToolAddress, |
| @@ -2842,7 +3230,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 31 unmodified lines |
| 2842 | 3230 | return Effect.gen(function* () { |
| 2843 | 3231 | // oxlint-disable executor/no-instanceof-error, executor/no-unknown-error-message, executor/no-manual-tag-check -- boundary: normalize arbitrary unknown plugin failures into a human-readable message for ToolInvocationError/telemetry |
| 2844 | 3232 | const formatInvocationCauseMessage = (cause: unknown): string => { |
| 2845 | | − if (cause instanceof Error && cause.message.length > 0) return cause.message; |
| 3233 | + if (cause instanceof Error && cause.message.length > 0) |
| 3234 | + return cause.message; |
| 2846 | 3235 | // Non-Error / empty-message causes: `String(plainObject)` renders |
| 2847 | 3236 | // "[object Object]", which is what telemetry then shows as the only |
| 2848 | 3237 | // label for the failure. Prefer the tag, else stringify structurally. |
| @@ -2886,7 +3275,13 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 37 unmodified lines |
| 2886 | 3275 | pattern: policy.pattern ?? "*", |
| 2887 | 3276 | }); |
| 2888 | 3277 | } |
| 2889 | | − yield* enforceApproval(staticEntry.tool.annotations, address, args, policy, handler); |
| 3278 | + yield* enforceApproval( |
| 3279 | + staticEntry.tool.annotations, |
| 3280 | + address, |
| 3281 | + args, |
| 3282 | + policy, |
| 3283 | + handler, |
| 3284 | + ); |
| 2890 | 3285 | return yield* wrapInvocationError( |
| 2891 | 3286 | staticEntry.tool.handler({ |
| 2892 | 3287 | ctx: staticEntry.ctx, |
| @@ -2917,7 +3312,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 24 unmodified lines |
| 2917 | 3312 | if (!row) { |
| 2918 | 3313 | const searchMatches = yield* searchToolRowsForConnection(parsed); |
| 2919 | 3314 | const connectionTools = |
| 2920 | | − searchMatches.length > 0 ? searchMatches : yield* findToolRowsForConnection(parsed); |
| 3315 | + searchMatches.length > 0 |
| 3316 | + ? searchMatches |
| 3317 | + : yield* findToolRowsForConnection(parsed); |
| 2921 | 3318 | return yield* new ToolNotFoundError({ |
| 2922 | 3319 | address, |
| 2923 | 3320 | suggestions: toolSuggestions(connectionTools), |
| @@ -2927,7 +3324,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 3 unmodified lines |
| 2927 | 3324 | // Resolve policy (owner-ranked). |
| 2928 | 3325 | const toolForPolicy = rowToTool(row); |
| 2929 | 3326 | const policyRows = yield* core.findMany("tool_policy", {}); |
| 2930 | | − const annotations = decodeJsonColumn(row.annotations) as ToolAnnotations | undefined; |
| 3327 | + const annotations = decodeJsonColumn(row.annotations) as |
| 3328 | + | ToolAnnotations |
| 3329 | + | undefined; |
| 2931 | 3330 | const policy = resolveEffectivePolicy( |
| 2932 | 3331 | normalizedPolicyId(toolForPolicy), |
| 2933 | 3332 | policyRows, |
| @@ -2982,7 +3381,13 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 48 unmodified lines |
| 2982 | 3381 | .pipe(wrapInvocationError); |
| 2983 | 3382 | resolvedAnnotations = map[String(parsed.tool)] ?? annotations; |
| 2984 | 3383 | } |
| 2985 | | − yield* enforceApproval(resolvedAnnotations, address, args, policy, handler); |
| 3384 | + yield* enforceApproval( |
| 3385 | + resolvedAnnotations, |
| 3386 | + address, |
| 3387 | + args, |
| 3388 | + policy, |
| 3389 | + handler, |
| 3390 | + ); |
| 2986 | 3391 | |
| 2987 | 3392 | // Resolve every named credential input (`variable → value`); `value` is |
| 2988 | 3393 | // the primary `token` for single-input + OAuth callers. |
| @@ -2995,7 +3400,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 6 unmodified lines |
| 2995 | 3400 | template: AuthTemplateSlug.make(connectionRow.template), |
| 2996 | 3401 | value: values[PRIMARY_INPUT_VARIABLE] ?? null, |
| 2997 | 3402 | values, |
| 2998 | | − config: integrationRow ? decodeJsonColumn(integrationRow.config) : undefined, |
| 3403 | + config: integrationRow |
| 3404 | + ? decodeJsonColumn(integrationRow.config) |
| 3405 | + : undefined, |
| 2999 | 3406 | }; |
| 3000 | 3407 | |
| 3001 | 3408 | return yield* wrapInvocationError( |
| @@ -3035,7 +3442,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 33 unmodified lines |
| 3035 | 3442 | subject, |
| 3036 | 3443 | ownedKeys: (owner: Owner) => ownedKeys(owner), |
| 3037 | 3444 | defaultWritableProvider, |
| 3038 | | − mintOAuthConnection: (input: MintOAuthConnectionInput) => mintOAuthConnection(input), |
| 3445 | + mintOAuthConnection: (input: MintOAuthConnectionInput) => |
| 3446 | + mintOAuthConnection(input), |
| 3039 | 3447 | // Resolve the integration's DECLARED oauth scopes for a (integration, |
| 3040 | 3448 | // template): load the row, run the owning plugin's auth-method projector, |
| 3041 | 3449 | // and return the matching oauth method's declared `oauth.scopes`. Drives |
| @@ -3043,15 +3451,20 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 1 unmodified line |
| 3043 | 3451 | // client on a broad integration still requests the integration's full |
| 3044 | 3452 | // scope set. Empty (no row / no oauth method / no declared scopes) ⇒ the |
| 3045 | 3453 | // union collapses to the client's scopes (current behavior). |
| 3046 | | − resolveDeclaredOAuthScopes: (integration: IntegrationSlug, template: AuthTemplateSlug) => |
| 3454 | + resolveDeclaredOAuthScopes: ( |
| 3455 | + integration: IntegrationSlug, |
| 3456 | + template: AuthTemplateSlug, |
| 3457 | + ) => |
| 3047 | 3458 | findIntegrationRow(integration).pipe( |
| 3048 | 3459 | Effect.map((row): readonly string[] => { |
| 3049 | 3460 | if (!row) return []; |
| 3050 | 3461 | const methods = describeAuthMethodsForRow(row); |
| 3051 | 3462 | const match = |
| 3052 | 3463 | methods.find( |
| 3053 | | − (m: AuthMethodDescriptor) => m.kind === "oauth" && m.template === String(template), |
| 3054 | | − ) ?? methods.find((m: AuthMethodDescriptor) => m.kind === "oauth"); |
| 3464 | + (m: AuthMethodDescriptor) => |
| 3465 | + m.kind === "oauth" && m.template === String(template), |
| 3466 | + ) ?? |
| 3467 | + methods.find((m: AuthMethodDescriptor) => m.kind === "oauth"); |
| 3055 | 3468 | return match?.oauth?.scopes ?? []; |
| 3056 | 3469 | }), |
| 3057 | 3470 | ), |
| @@ -3101,7 +3514,8 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 43 unmodified lines |
| 3101 | 3514 | httpClientLayer: config.httpClientLayer ?? FetchHttpClient.layer, |
| 3102 | 3515 | core: { |
| 3103 | 3516 | integrations: { |
| 3104 | | − register: (input: RegisterIntegrationInput) => integrationsRegister(plugin.id, input), |
| 3517 | + register: (input: RegisterIntegrationInput) => |
| 3518 | + integrationsRegister(plugin.id, input), |
| 3105 | 3519 | update: (slug, patch) => integrationsUpdate(slug, patch), |
| 3106 | 3520 | list: () => integrationsList(), |
| 3107 | 3521 | get: (slug) => integrationsGetRecord(slug), |
| @@ -3156,6 +3570,12 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 48 unmodified lines |
| 3156 | 3570 | if (plugin.extension) { |
| 3157 | 3571 | extensions[plugin.id] = extension; |
| 3158 | 3572 | } |
| 3573 | + // A plugin that narrows the executor exposes `wrapExecutor` on its |
| 3574 | + // extension; collect it into the generic hook the execution stack runs. |
| 3575 | + const wrapExecutor = (extension as { wrapExecutor?: ExecutorWrapper }) |
| 3576 | + .wrapExecutor; |
| 3577 | + if (typeof wrapExecutor === "function") |
| 3578 | + executorWrappers.push(wrapExecutor); |
| 3159 | 3579 | |
| 3160 | 3580 | const decls = plugin.staticSources ? plugin.staticSources(extension) : []; |
| 3161 | 3581 | for (const source of decls) { |
| @@ -3212,7 +3632,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 50 unmodified lines |
| 3212 | 3632 | yield* runtime.plugin |
| 3213 | 3633 | .close() |
| 3214 | 3634 | .pipe( |
| 3215 | | − Effect.mapError((cause) => pluginStorageFailure(runtime.plugin.id, "close", cause)), |
| 3635 | + Effect.mapError((cause) => |
| 3636 | + pluginStorageFailure(runtime.plugin.id, "close", cause), |
| 3637 | + ), |
| 3216 | 3638 | ); |
| 3217 | 3639 | } |
| 3218 | 3640 | } |
| @@ -3269,8 +3691,9 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea · 50 unmodified lines |
| 3269 | 3691 | close, |
| 3270 | 3692 | }; |
| 3271 | 3693 | |
| 3272 | | − const toExecutor = (value: unknown): Executor<TPlugins> => value as Executor<TPlugins>; |
| 3273 | | − return toExecutor(Object.assign(base, extensions)); |
| 3694 | + const toExecutor = (value: unknown): Executor<TPlugins> => |
| 3695 | + value as Executor<TPlugins>; |
| 3696 | + return toExecutor(Object.assign(base, extensions, { executorWrappers })); |
| 3274 | 3697 | }); |
| 3275 | 3698 | |
| 3276 | 3699 | // Helper alias so the inline literal used for the optimistic projection in |