Unified · Split
| @@ -171,7 +171,7 @@ const writeConfiguredLocalMcpSource = (input: { · 170 unmodified lines | ||
| 171 | 171 | materialization, |
| 172 | 172 | }), |
| 173 | 173 | }); |
| 174 | − }); | |
| 174 | + }).pipe(Effect.provide(NodeFileSystem.layer)); | |
| 175 | 175 | |
| 176 | 176 | const makeServer = createIsolatedLocalExecutorServer({ |
| 177 | 177 | port: 0, |
| @@ -9,12 +9,14 @@ import { SecretMaterialIdSchema } from "#schema"; · 8 unmodified lines | ||
| 9 | 9 | import { |
| 10 | 10 | getLocalInstallation, |
| 11 | 11 | } from "../../runtime/local-operations"; |
| 12 | +import { requireRuntimeLocalWorkspace } from "../../runtime/local-runtime-context"; | |
| 12 | 13 | import { |
| 13 | 14 | ENV_SECRET_PROVIDER_ID, |
| 14 | 15 | KEYCHAIN_SECRET_PROVIDER_ID, |
| 15 | 16 | LOCAL_SECRET_PROVIDER_ID, |
| 16 | 17 | parseSecretStoreProviderId, |
| 17 | 18 | } from "../../runtime/secret-material-providers"; |
| 19 | +import { RuntimeSourceStoreService } from "../../runtime/source-store"; | |
| 18 | 20 | import { ControlPlaneStore } from "../../runtime/store"; |
| 19 | 21 | import type { |
| 20 | 22 | CreateSecretResult, |
| @@ -84,16 +86,25 @@ export const ControlPlaneLocalLive = HttpApiBuilder.group( · 63 unmodified lines | ||
| 84 | 86 | .handle("config", () => |
| 85 | 87 | getInstanceConfig(), |
| 86 | 88 | ) |
| 87 | − .handle("listSecrets", () => | |
| 88 | − Effect.gen(function* () { | |
| 89 | − const store = yield* ControlPlaneStore; | |
| 90 | − const rows = yield* store.secretMaterials.listAll().pipe( | |
| 91 | − Effect.mapError(() => storageError("Failed listing secrets.")), | |
| 92 | − ); | |
| 93 | − const linkedSourcesMap = yield* store.secretMaterials.listLinkedSources().pipe( | |
| 94 | − Effect.mapError(() => storageError("Failed loading linked sources.")), | |
| 95 | − ); | |
| 96 | − return rows.map((row) => ({ | |
| 89 | + .handle("listSecrets", () => | |
| 90 | + Effect.gen(function* () { | |
| 91 | + const store = yield* ControlPlaneStore; | |
| 92 | + const sourceStore = yield* RuntimeSourceStoreService; | |
| 93 | + const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace().pipe( | |
| 94 | + Effect.mapError(() => storageError("Failed resolving local workspace.")), | |
| 95 | + ); | |
| 96 | + const rows = yield* store.secretMaterials.listAll().pipe( | |
| 97 | + Effect.mapError(() => storageError("Failed listing secrets.")), | |
| 98 | + ); | |
| 99 | + const linkedSourcesMap = yield* sourceStore.listLinkedSecretSourcesInWorkspace( | |
| 100 | + runtimeLocalWorkspace.installation.workspaceId, | |
| 101 | + { | |
| 102 | + actorAccountId: runtimeLocalWorkspace.installation.accountId, | |
| 103 | + }, | |
| 104 | + ).pipe( | |
| 105 | + Effect.mapError(() => storageError("Failed loading linked sources.")), | |
| 106 | + ); | |
| 107 | + return rows.map((row) => ({ | |
| 97 | 108 | ...row, |
| 98 | 109 | linkedSources: linkedSourcesMap.get(row.id) ?? [], |
| 99 | 110 | })); |
| @@ -1,8 +1,9 @@ | ||
| 1 | 1 | import { createServer } from "node:http"; |
| 2 | −import { mkdtempSync, readFileSync } from "node:fs"; | |
| 3 | −import { tmpdir } from "node:os"; | |
| 2 | +import { readFileSync } from "node:fs"; | |
| 4 | 3 | import { join } from "node:path"; |
| 5 | 4 | |
| 5 | +import { FileSystem } from "@effect/platform"; | |
| 6 | +import { NodeFileSystem } from "@effect/platform-node"; | |
| 6 | 7 | import { describe, expect, it } from "@effect/vitest"; |
| 7 | 8 | import { assertTrue } from "@effect/vitest/utils"; |
| 8 | 9 | import * as Effect from "effect/Effect"; |
| @@ -13,6 +14,7 @@ import type { AccountId } from "#schema"; · 4 unmodified lines | ||
| 13 | 14 | import { |
| 14 | 15 | ExecutionIdSchema, |
| 15 | 16 | ExecutionInteractionIdSchema, |
| 17 | + type ExecutionInteraction, | |
| 16 | 18 | SecretMaterialIdSchema, |
| 17 | 19 | SourceIdSchema, |
| 18 | 20 | } from "#schema"; |
| @@ -21,19 +23,24 @@ import type { ToolPath } from "@executor/codemode-core"; · 2 unmodified lines | ||
| 21 | 23 | import { |
| 22 | 24 | createControlPlaneRuntime, |
| 23 | 25 | LiveExecutionManagerService, |
| 26 | + provideControlPlaneRuntime, | |
| 24 | 27 | } from "./index"; |
| 25 | 28 | import { createSourceFromPayload } from "./source-definitions"; |
| 26 | 29 | import { decodeSourceCredentialSelectionContent } from "./source-credential-interactions"; |
| 27 | 30 | import { persistSource } from "./source-store"; |
| 28 | 31 | import { withControlPlaneClient } from "./test-http-client"; |
| 29 | 32 | |
| 30 | −const makeRuntime = Effect.acquireRelease( | |
| 31 | − createControlPlaneRuntime({ | |
| 32 | − localDataDir: ":memory:", | |
| 33 | − workspaceRoot: mkdtempSync(join(tmpdir(), "executor-control-plane-runtime-")), | |
| 34 | − }), | |
| 35 | − (runtime) => Effect.promise(() => runtime.close()).pipe(Effect.orDie), | |
| 36 | −); | |
| 33 | +const makeRuntime = Effect.gen(function* () { | |
| 34 | + const fs = yield* FileSystem.FileSystem; | |
| 35 | + const workspaceRoot = yield* fs.makeTempDirectoryScoped({ | |
| 36 | + prefix: "executor-control-plane-runtime-", | |
| 37 | + }); | |
| 38 | + | |
| 39 | + return yield* Effect.acquireRelease( | |
| 40 | + createControlPlaneRuntime({ workspaceRoot }), | |
| 41 | + (runtime) => Effect.promise(() => runtime.close()).pipe(Effect.orDie), | |
| 42 | + ); | |
| 43 | +}).pipe(Effect.provide(NodeFileSystem.layer)); | |
| 37 | 44 | |
| 38 | 45 | type OpenApiSpecServer = { |
| 39 | 46 | baseUrl: string; |
| @@ -121,10 +128,12 @@ const expectLeft = <A, E>(effect: Effect.Effect<A, E, never>) => · 81 unmodified lines | ||
| 121 | 128 | describe("control-plane-runtime", () => { |
| 122 | 129 | it.scoped("writes local source changes through executor.jsonc", () => |
| 123 | 130 | Effect.gen(function* () { |
| 124 | − const workspaceRoot = mkdtempSync(join(tmpdir(), "executor-local-config-runtime-")); | |
| 131 | + const fs = yield* FileSystem.FileSystem; | |
| 132 | + const workspaceRoot = yield* fs.makeTempDirectoryScoped({ | |
| 133 | + prefix: "executor-local-config-runtime-", | |
| 134 | + }); | |
| 125 | 135 | const runtime = yield* Effect.acquireRelease( |
| 126 | 136 | createControlPlaneRuntime({ |
| 127 | − localDataDir: ":memory:", | |
| 128 | 137 | workspaceRoot, |
| 129 | 138 | }), |
| 130 | 139 | (createdRuntime) => Effect.promise(() => createdRuntime.close()).pipe(Effect.orDie), |
| @@ -174,7 +183,7 @@ describe("control-plane-runtime", () => { · 43 unmodified lines | ||
| 174 | 183 | sources?: Record<string, unknown>; |
| 175 | 184 | }; |
| 176 | 185 | expect(removedConfig.sources?.github).toBeUndefined(); |
| 177 | − }), | |
| 186 | + }).pipe(Effect.provide(NodeFileSystem.layer)), | |
| 178 | 187 | 60_000, |
| 179 | 188 | ); |
| 180 | 189 | |
| @@ -228,10 +237,7 @@ describe("control-plane-runtime", () => { · 47 unmodified lines | ||
| 228 | 237 | }).pipe(Effect.orDie); |
| 229 | 238 | yield* persistSource(runtime.persistence.rows, localSource, { |
| 230 | 239 | actorAccountId: installation.accountId, |
| 231 | − }).pipe( | |
| 232 | − Effect.provide(runtime.runtimeLayer), | |
| 233 | − Effect.orDie, | |
| 234 | − ); | |
| 240 | + }).pipe((effect) => provideControlPlaneRuntime(effect, runtime), Effect.orDie); | |
| 235 | 241 | |
| 236 | 242 | const interactionFiber = yield* Effect.gen(function* () { |
| 237 | 243 | const liveExecutionManager = yield* LiveExecutionManagerService; |
| @@ -260,14 +266,24 @@ describe("control-plane-runtime", () => { · 22 unmodified lines | ||
| 260 | 266 | }, |
| 261 | 267 | }); |
| 262 | 268 | }).pipe( |
| 263 | − Effect.provide(runtime.runtimeLayer), | |
| 269 | + (effect) => provideControlPlaneRuntime(effect, runtime), | |
| 264 | 270 | Effect.fork, |
| 265 | 271 | ); |
| 266 | 272 | |
| 267 | − yield* Effect.yieldNow(); | |
| 268 | − | |
| 269 | − const pendingInteraction = yield* runtime.persistence.rows.executionInteractions | |
| 270 | − .getPendingByExecutionId(executionId); | |
| 273 | + const waitForPendingInteraction = ( | |
| 274 | + remaining: number, | |
| 275 | + ): Effect.Effect<Option.Option<ExecutionInteraction>, Error> => | |
| 276 | + runtime.persistence.rows.executionInteractions.getPendingByExecutionId(executionId).pipe( | |
| 277 | + Effect.flatMap((pendingInteraction) => | |
| 278 | + Option.isSome(pendingInteraction) || remaining <= 0 | |
| 279 | + ? Effect.succeed(pendingInteraction) | |
| 280 | + : Effect.yieldNow().pipe( | |
| 281 | + Effect.zipRight(waitForPendingInteraction(remaining - 1)), | |
| 282 | + ), | |
| 283 | + ), | |
| 284 | + ); | |
| 285 | + | |
| 286 | + const pendingInteraction = yield* waitForPendingInteraction(20); | |
| 271 | 287 | assertTrue(Option.isSome(pendingInteraction)); |
| 272 | 288 | expect(pendingInteraction.value.id).toBe(interactionId); |
| 273 | 289 | |
| @@ -391,10 +407,7 @@ describe("control-plane-runtime", () => { · 117 unmodified lines | ||
| 391 | 407 | }).pipe(Effect.orDie); |
| 392 | 408 | yield* persistSource(runtime.persistence.rows, localSource, { |
| 393 | 409 | actorAccountId: installation.accountId, |
| 394 | − }).pipe( | |
| 395 | − Effect.provide(runtime.runtimeLayer), | |
| 396 | − Effect.orDie, | |
| 397 | − ); | |
| 410 | + }).pipe((effect) => provideControlPlaneRuntime(effect, runtime), Effect.orDie); | |
| 398 | 411 | |
| 399 | 412 | const interactionFiber = yield* Effect.gen(function* () { |
| 400 | 413 | const liveExecutionManager = yield* LiveExecutionManagerService; |
| @@ -423,14 +436,24 @@ describe("control-plane-runtime", () => { · 22 unmodified lines | ||
| 423 | 436 | }, |
| 424 | 437 | }); |
| 425 | 438 | }).pipe( |
| 426 | − Effect.provide(runtime.runtimeLayer), | |
| 439 | + (effect) => provideControlPlaneRuntime(effect, runtime), | |
| 427 | 440 | Effect.fork, |
| 428 | 441 | ); |
| 429 | 442 | |
| 430 | − yield* Effect.yieldNow(); | |
| 431 | − | |
| 432 | − const pendingInteraction = yield* runtime.persistence.rows.executionInteractions | |
| 433 | − .getPendingByExecutionId(executionId); | |
| 443 | + const waitForPendingInteraction = ( | |
| 444 | + remaining: number, | |
| 445 | + ): Effect.Effect<Option.Option<ExecutionInteraction>, Error> => | |
| 446 | + runtime.persistence.rows.executionInteractions.getPendingByExecutionId(executionId).pipe( | |
| 447 | + Effect.flatMap((pendingInteraction) => | |
| 448 | + Option.isSome(pendingInteraction) || remaining <= 0 | |
| 449 | + ? Effect.succeed(pendingInteraction) | |
| 450 | + : Effect.yieldNow().pipe( | |
| 451 | + Effect.zipRight(waitForPendingInteraction(remaining - 1)), | |
| 452 | + ), | |
| 453 | + ), | |
| 454 | + ); | |
| 455 | + | |
| 456 | + const pendingInteraction = yield* waitForPendingInteraction(20); | |
| 434 | 457 | assertTrue(Option.isSome(pendingInteraction)); |
| 435 | 458 | |
| 436 | 459 | const submittedPage = yield* withControlPlaneClient( |
| @@ -34,6 +34,10 @@ import { · 33 unmodified lines | ||
| 34 | 34 | sanitizePersistedElicitationResponse, |
| 35 | 35 | type LiveExecutionManager, |
| 36 | 36 | } from "./live-execution"; |
| 37 | +import { | |
| 38 | + getRuntimeLocalWorkspaceOption, | |
| 39 | + provideOptionalRuntimeLocalWorkspace, | |
| 40 | +} from "./local-runtime-context"; | |
| 37 | 41 | import { |
| 38 | 42 | asOperationErrors, |
| 39 | 43 | operationErrors, |
| @@ -658,16 +662,22 @@ const forkExecutionAttemptWithDependencies = ( · 618 unmodified lines | ||
| 658 | 662 | execution: Execution, |
| 659 | 663 | interactionMode: InteractionMode, |
| 660 | 664 | ) => |
| 661 | − Effect.sync(() => { | |
| 662 | − Effect.runFork( | |
| 663 | − runExecutionAttemptWithDependencies( | |
| 665 | + Effect.gen(function* () { | |
| 666 | + const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption(); | |
| 667 | + | |
| 668 | + yield* Effect.sync(() => { | |
| 669 | + const effect = runExecutionAttemptWithDependencies( | |
| 664 | 670 | store, |
| 665 | 671 | executionResolver, |
| 666 | 672 | liveExecutionManager, |
| 667 | 673 | execution, |
| 668 | 674 | interactionMode, |
| 669 | − ), | |
| 670 | − ); | |
| 675 | + ); | |
| 676 | + | |
| 677 | + Effect.runFork( | |
| 678 | + provideOptionalRuntimeLocalWorkspace(effect, runtimeLocalWorkspace), | |
| 679 | + ); | |
| 680 | + }); | |
| 671 | 681 | }); |
| 672 | 682 | |
| 673 | 683 | const submitExecutionInteractionResponseWithDependencies = ( |
| @@ -18,33 +18,35 @@ import { · 17 unmodified lines | ||
| 18 | 18 | import * as Effect from "effect/Effect"; |
| 19 | 19 | import * as Cause from "effect/Cause"; |
| 20 | 20 | import * as Exit from "effect/Exit"; |
| 21 | −import * as Layer from "effect/Layer"; | |
| 22 | 21 | import * as Schema from "effect/Schema"; |
| 23 | 22 | import type { RuntimeLocalWorkspaceState } from "./local-runtime-context"; |
| 24 | 23 | import { |
| 25 | − InstallationStore, | |
| 26 | 24 | type LocalStorageServices, |
| 27 | 25 | LocalInstallationStore, |
| 28 | 26 | LocalSourceArtifactStore, |
| 29 | 27 | LocalWorkspaceConfigStore, |
| 30 | 28 | LocalWorkspaceStateStore, |
| 31 | − SourceArtifactStore, | |
| 32 | − WorkspaceConfigStore, | |
| 33 | − WorkspaceStateStore, | |
| 29 | + makeLocalStorageLayer, | |
| 34 | 30 | } from "./local-storage"; |
| 31 | +import { provideOptionalRuntimeLocalWorkspace } from "./local-runtime-context"; | |
| 35 | 32 | |
| 36 | 33 | /** Run an Effect as a Promise, preserving the original error (not FiberFailure). */ |
| 37 | 34 | const runEffect = async <A>( |
| 38 | 35 | effect: Effect.Effect<A, unknown, LocalStorageServices>, |
| 39 | − _runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null = null, | |
| 36 | + runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null = null, | |
| 40 | 37 | ): Promise<A> => { |
| 41 | − const baseLayer = Layer.mergeAll( | |
| 42 | − Layer.succeed(InstallationStore, LocalInstallationStore), | |
| 43 | − Layer.succeed(WorkspaceConfigStore, LocalWorkspaceConfigStore), | |
| 44 | − Layer.succeed(WorkspaceStateStore, LocalWorkspaceStateStore), | |
| 45 | − Layer.succeed(SourceArtifactStore, LocalSourceArtifactStore), | |
| 38 | + const baseLayer = makeLocalStorageLayer({ | |
| 39 | + installationStore: LocalInstallationStore, | |
| 40 | + workspaceConfigStore: LocalWorkspaceConfigStore, | |
| 41 | + workspaceStateStore: LocalWorkspaceStateStore, | |
| 42 | + sourceArtifactStore: LocalSourceArtifactStore, | |
| 43 | + }); | |
| 44 | + const exit = await Effect.runPromiseExit( | |
| 45 | + provideOptionalRuntimeLocalWorkspace( | |
| 46 | + effect.pipe(Effect.provide(baseLayer)), | |
| 47 | + runtimeLocalWorkspace, | |
| 48 | + ), | |
| 46 | 49 | ); |
| 47 | − const exit = await Effect.runPromiseExit(effect.pipe(Effect.provide(baseLayer))); | |
| 48 | 50 | if (Exit.isSuccess(exit)) return exit.value; |
| 49 | 51 | throw Cause.squash(exit.cause); |
| 50 | 52 | }; |
| @@ -1,7 +1,9 @@ | ||
| 1 | 1 | import * as Effect from "effect/Effect"; |
| 2 | 2 | import * as Exit from "effect/Exit"; |
| 3 | 3 | import * as Layer from "effect/Layer"; |
| 4 | +import * as ManagedRuntime from "effect/ManagedRuntime"; | |
| 4 | 5 | import * as Scope from "effect/Scope"; |
| 6 | +import { NodeFileSystem } from "@effect/platform-node"; | |
| 5 | 7 | |
| 6 | 8 | import { type ControlPlaneApiRuntimeContext } from "#api"; |
| 7 | 9 | import type { LocalInstallation } from "#schema"; |
| @@ -9,7 +11,6 @@ import type { LocalInstallation } from "#schema"; · 1 unmodified line | ||
| 9 | 11 | import { type ResolveExecutionEnvironment } from "./execution-state"; |
| 10 | 12 | import { |
| 11 | 13 | createLiveExecutionManager, |
| 12 | − LiveExecutionManagerLive, | |
| 13 | 14 | LiveExecutionManagerService, |
| 14 | 15 | } from "./live-execution"; |
| 15 | 16 | import { |
| @@ -20,36 +21,28 @@ import { · 4 unmodified lines | ||
| 20 | 21 | resolveLocalWorkspaceContext, |
| 21 | 22 | } from "./local-config"; |
| 22 | 23 | import { |
| 23 | − InstallationStore, | |
| 24 | + LocalStorageLive, | |
| 24 | 25 | LocalInstallationStore, |
| 25 | − LocalInstallationStoreLive, | |
| 26 | − LocalSourceArtifactStore, | |
| 27 | − LocalSourceArtifactStoreLive, | |
| 28 | − SourceArtifactStore, | |
| 29 | 26 | LocalWorkspaceConfigStore, |
| 30 | − LocalWorkspaceConfigStoreLive, | |
| 31 | − LocalWorkspaceStateStore, | |
| 32 | − LocalWorkspaceStateStoreLive, | |
| 33 | − WorkspaceConfigStore, | |
| 34 | − WorkspaceStateStore, | |
| 35 | 27 | } from "./local-storage"; |
| 36 | 28 | import { |
| 37 | 29 | type RuntimeLocalWorkspaceState, |
| 38 | − RuntimeLocalWorkspaceService, | |
| 30 | + RuntimeLocalWorkspaceLive, | |
| 39 | 31 | } from "./local-runtime-context"; |
| 32 | +import { LocalToolRuntimeLoaderLive } from "./local-tools"; | |
| 40 | 33 | import { synchronizeLocalWorkspaceState } from "./local-workspace-sync"; |
| 41 | 34 | import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store"; |
| 35 | +import { RuntimeSourceStoreLive } from "./source-store"; | |
| 36 | +import { RuntimeSourceRecipeStoreLive } from "./source-recipes-runtime"; | |
| 37 | +import { RuntimeSourceAuthMaterialLive } from "./source-auth-material"; | |
| 38 | +import { RuntimeSourceMaterializationLive } from "./source-materialization"; | |
| 42 | 39 | import { |
| 43 | − createRuntimeSourceAuthService, | |
| 44 | 40 | RuntimeSourceAuthServiceLive, |
| 45 | − RuntimeSourceAuthServiceTag, | |
| 46 | 41 | } from "./source-auth-service"; |
| 47 | 42 | import type { ResolveSecretMaterial } from "./secret-material-providers"; |
| 48 | −import { createDefaultSecretMaterialResolver } from "./secret-material-providers"; | |
| 43 | +import { SecretMaterialLive } from "./secret-material-providers"; | |
| 49 | 44 | import { |
| 50 | − createWorkspaceExecutionEnvironmentResolver, | |
| 51 | 45 | RuntimeExecutionResolverLive, |
| 52 | − RuntimeExecutionResolverService, | |
| 53 | 46 | } from "./workspace-execution-environment"; |
| 54 | 47 | |
| 55 | 48 | export * from "./execution-state"; |
| @@ -101,34 +94,84 @@ export const createRuntimeControlPlaneLayer = ( · 45 unmodified lines | ||
| 101 | 94 | input: RuntimeControlPlaneOptions & { |
| 102 | 95 | store: ControlPlaneStoreShape; |
| 103 | 96 | localWorkspaceState: RuntimeLocalWorkspaceState; |
| 97 | + liveExecutionManager: ReturnType<typeof createLiveExecutionManager>; | |
| 104 | 98 | }, |
| 105 | 99 | ) => { |
| 100 | + const platformLayer = NodeFileSystem.layer; | |
| 101 | + const storageLayer = LocalStorageLive.pipe( | |
| 102 | + Layer.provide(platformLayer), | |
| 103 | + ); | |
| 104 | + const localToolRuntimeLayer = LocalToolRuntimeLoaderLive.pipe( | |
| 105 | + Layer.provide(platformLayer), | |
| 106 | + ); | |
| 107 | + | |
| 106 | 108 | const baseLayer = Layer.mergeAll( |
| 109 | + platformLayer, | |
| 107 | 110 | Layer.succeed(ControlPlaneStore, input.store), |
| 108 | − Layer.succeed(RuntimeLocalWorkspaceService, input.localWorkspaceState), | |
| 109 | − LocalInstallationStoreLive, | |
| 110 | − LocalWorkspaceConfigStoreLive, | |
| 111 | − LocalWorkspaceStateStoreLive, | |
| 112 | − LocalSourceArtifactStoreLive, | |
| 113 | − LiveExecutionManagerLive, | |
| 111 | + RuntimeLocalWorkspaceLive(input.localWorkspaceState), | |
| 112 | + storageLayer, | |
| 113 | + Layer.succeed(LiveExecutionManagerService, input.liveExecutionManager), | |
| 114 | + ); | |
| 115 | + | |
| 116 | + const secretMaterialLayer = SecretMaterialLive({ | |
| 117 | + resolveSecretMaterial: input.resolveSecretMaterial, | |
| 118 | + }).pipe(Layer.provide(baseLayer)); | |
| 119 | + | |
| 120 | + const sourceStoreLayer = RuntimeSourceStoreLive.pipe( | |
| 121 | + Layer.provide(baseLayer), | |
| 122 | + ); | |
| 123 | + | |
| 124 | + const sourceRecipeStoreLayer = RuntimeSourceRecipeStoreLive.pipe( | |
| 125 | + Layer.provide(Layer.mergeAll(baseLayer, sourceStoreLayer)), | |
| 126 | + ); | |
| 127 | + | |
| 128 | + const sourceAuthMaterialLayer = RuntimeSourceAuthMaterialLive.pipe( | |
| 129 | + Layer.provide(Layer.mergeAll(baseLayer, secretMaterialLayer)), | |
| 130 | + ); | |
| 131 | + | |
| 132 | + const sourceMaterializationLayer = RuntimeSourceMaterializationLive.pipe( | |
| 133 | + Layer.provide( | |
| 134 | + Layer.mergeAll(baseLayer, secretMaterialLayer, sourceAuthMaterialLayer), | |
| 135 | + ), | |
| 114 | 136 | ); |
| 115 | 137 | |
| 116 | 138 | const sourceAuthLayer = RuntimeSourceAuthServiceLive({ |
| 117 | 139 | getLocalServerBaseUrl: input.getLocalServerBaseUrl, |
| 118 | − localConfig: input.localWorkspaceState.loadedConfig.config, | |
| 119 | − workspaceRoot: input.localWorkspaceState.context.workspaceRoot, | |
| 120 | − localWorkspaceState: input.localWorkspaceState, | |
| 121 | − }).pipe(Layer.provide(baseLayer)); | |
| 140 | + }).pipe( | |
| 141 | + Layer.provide( | |
| 142 | + Layer.mergeAll( | |
| 143 | + baseLayer, | |
| 144 | + secretMaterialLayer, | |
| 145 | + sourceStoreLayer, | |
| 146 | + sourceMaterializationLayer, | |
| 147 | + ), | |
| 148 | + ), | |
| 149 | + ); | |
| 122 | 150 | |
| 123 | 151 | const executionResolverLayer = RuntimeExecutionResolverLive({ |
| 124 | 152 | executionResolver: input.executionResolver, |
| 125 | − resolveSecretMaterial: input.resolveSecretMaterial, | |
| 126 | 153 | }).pipe( |
| 127 | − Layer.provide(Layer.mergeAll(baseLayer, sourceAuthLayer)), | |
| 154 | + Layer.provide( | |
| 155 | + Layer.mergeAll( | |
| 156 | + baseLayer, | |
| 157 | + secretMaterialLayer, | |
| 158 | + sourceAuthMaterialLayer, | |
| 159 | + sourceMaterializationLayer, | |
| 160 | + sourceAuthLayer, | |
| 161 | + sourceRecipeStoreLayer, | |
| 162 | + localToolRuntimeLayer, | |
| 163 | + ), | |
| 164 | + ), | |
| 128 | 165 | ); |
| 129 | 166 | |
| 130 | 167 | return Layer.mergeAll( |
| 131 | 168 | baseLayer, |
| 169 | + secretMaterialLayer, | |
| 170 | + sourceStoreLayer, | |
| 171 | + sourceAuthMaterialLayer, | |
| 172 | + sourceMaterializationLayer, | |
| 173 | + sourceRecipeStoreLayer, | |
| 174 | + localToolRuntimeLayer, | |
| 132 | 175 | sourceAuthLayer, |
| 133 | 176 | executionResolverLayer, |
| 134 | 177 | ) as RuntimeControlPlaneLayer; |
| @@ -137,12 +180,19 @@ export const createRuntimeControlPlaneLayer = ( · 2 unmodified lines | ||
| 137 | 180 | export type ControlPlaneRuntime = { |
| 138 | 181 | persistence: LocalControlPlanePersistence; |
| 139 | 182 | localInstallation: LocalInstallation; |
| 183 | + managedRuntime: ManagedRuntime.ManagedRuntime<ControlPlaneApiRuntimeContext, never>; | |
| 140 | 184 | runtimeLayer: RuntimeControlPlaneLayer; |
| 141 | 185 | close: () => Promise<void>; |
| 142 | 186 | }; |
| 143 | 187 | |
| 144 | 188 | export type CreateControlPlaneRuntimeOptions = RuntimeControlPlaneOptions; |
| 145 | 189 | |
| 190 | +export const provideControlPlaneRuntime = <A, E, R>( | |
| 191 | + effect: Effect.Effect<A, E, R>, | |
| 192 | + runtime: ControlPlaneRuntime, | |
| 193 | +): Effect.Effect<A, E | never, Exclude<R, ControlPlaneApiRuntimeContext>> => | |
| 194 | + effect.pipe(Effect.provide(runtime.managedRuntime)); | |
| 195 | + | |
| 146 | 196 | export const createControlPlaneRuntime = ( |
| 147 | 197 | options: CreateControlPlaneRuntimeOptions, |
| 148 | 198 | ): Effect.Effect<ControlPlaneRuntime, Error> => |
| @@ -160,8 +210,6 @@ export const createControlPlaneRuntime = ( · 11 unmodified lines | ||
| 160 | 210 | |
| 161 | 211 | const installationStore = LocalInstallationStore; |
| 162 | 212 | const workspaceConfigStore = LocalWorkspaceConfigStore; |
| 163 | − const workspaceStateStore = LocalWorkspaceStateStore; | |
| 164 | − const sourceArtifactStore = LocalSourceArtifactStore; | |
| 165 | 213 | |
| 166 | 214 | const localInstallation = yield* installationStore.getOrProvision({ |
| 167 | 215 | context: localWorkspaceContext, |
| @@ -194,34 +242,6 @@ export const createControlPlaneRuntime = ( · 26 unmodified lines | ||
| 194 | 242 | ), |
| 195 | 243 | ); |
| 196 | 244 | |
| 197 | − const resolveSecretMaterial = | |
| 198 | − options.resolveSecretMaterial ?? | |
| 199 | − createDefaultSecretMaterialResolver({ | |
| 200 | − rows, | |
| 201 | − localConfig: effectiveLocalConfig, | |
| 202 | − workspaceRoot: localWorkspaceContext.workspaceRoot, | |
| 203 | − }); | |
| 204 | − | |
| 205 | − const liveExecutionManager = createLiveExecutionManager(); | |
| 206 | − const sourceAuthService = createRuntimeSourceAuthService({ | |
| 207 | − rows, | |
| 208 | − liveExecutionManager, | |
| 209 | − getLocalServerBaseUrl: options.getLocalServerBaseUrl, | |
| 210 | − localConfig: effectiveLocalConfig, | |
| 211 | − workspaceRoot: localWorkspaceContext.workspaceRoot, | |
| 212 | − localWorkspaceState: { | |
| 213 | − context: localWorkspaceContext, | |
| 214 | − installation: { | |
| 215 | − workspaceId: localInstallation.workspaceId, | |
| 216 | − accountId: localInstallation.accountId, | |
| 217 | − }, | |
| 218 | − loadedConfig: { | |
| 219 | − ...loadedLocalConfig, | |
| 220 | − config: effectiveLocalConfig, | |
| 221 | − }, | |
| 222 | − }, | |
| 223 | − }); | |
| 224 | − | |
| 225 | 245 | const runtimeLocalWorkspaceState: RuntimeLocalWorkspaceState = { |
| 226 | 246 | context: localWorkspaceContext, |
| 227 | 247 | installation: { |
| @@ -233,30 +253,25 @@ export const createControlPlaneRuntime = ( · 5 unmodified lines | ||
| 233 | 253 | config: effectiveLocalConfig, |
| 234 | 254 | }, |
| 235 | 255 | }; |
| 236 | − | |
| 237 | − const executionResolver = | |
| 238 | − options.executionResolver ?? | |
| 239 | − createWorkspaceExecutionEnvironmentResolver({ | |
| 240 | − rows, | |
| 241 | − sourceAuthService, | |
| 242 | − resolveSecretMaterial, | |
| 243 | − workspaceConfigStore, | |
| 244 | − workspaceStateStore, | |
| 245 | − sourceArtifactStore, | |
| 246 | − }); | |
| 256 | + const liveExecutionManager = createLiveExecutionManager(); | |
| 247 | 257 | |
| 248 | 258 | const concreteRuntimeLayer = createRuntimeControlPlaneLayer({ |
| 249 | 259 | ...options, |
| 250 | 260 | store: rows, |
| 251 | 261 | localWorkspaceState: runtimeLocalWorkspaceState, |
| 252 | − resolveSecretMaterial, | |
| 253 | − executionResolver, | |
| 262 | + liveExecutionManager, | |
| 254 | 263 | }); |
| 264 | + const managedRuntime = ManagedRuntime.make(concreteRuntimeLayer); | |
| 265 | + const runtime = yield* managedRuntime.runtimeEffect; | |
| 266 | + const runtimeLayer = Layer.succeedContext( | |
| 267 | + runtime.context, | |
| 268 | + ) as RuntimeControlPlaneLayer; | |
| 255 | 269 | |
| 256 | 270 | return { |
| 257 | 271 | persistence, |
| 258 | 272 | localInstallation, |
| 259 | − runtimeLayer: concreteRuntimeLayer, | |
| 260 | − close: () => Effect.runPromise(Scope.close(scope, Exit.void)).catch(() => {}), | |
| 273 | + managedRuntime, | |
| 274 | + runtimeLayer: runtimeLayer as RuntimeControlPlaneLayer, | |
| 275 | + close: () => managedRuntime.dispose().catch(() => {}), | |
| 261 | 276 | }; |
| 262 | − }); | |
| 277 | + }).pipe(Effect.provide(NodeFileSystem.layer)); | |
| @@ -15,6 +15,7 @@ import * as Layer from "effect/Layer"; · 14 unmodified lines | ||
| 15 | 15 | import type { ControlPlaneStoreShape } from "./store"; |
| 16 | 16 | |
| 17 | 17 | type VisibleExecutionState = |
| 18 | + | "running" | |
| 18 | 19 | | "waiting_for_interaction" |
| 19 | 20 | | "completed" |
| 20 | 21 | | "failed"; |
| @@ -209,6 +210,10 @@ export const createLiveExecutionManager = () => { · 188 unmodified lines | ||
| 209 | 210 | status: "running", |
| 210 | 211 | updatedAt: resolvedAt, |
| 211 | 212 | }); |
| 213 | + yield* publishState({ | |
| 214 | + executionId, | |
| 215 | + state: "running", | |
| 216 | + }); | |
| 212 | 217 | |
| 213 | 218 | run.currentInteraction = null; |
| 214 | 219 | return resolved; |
| @@ -2,6 +2,7 @@ import { mkdtempSync } from "node:fs"; · 1 unmodified line | ||
| 2 | 2 | import { mkdir, writeFile } from "node:fs/promises"; |
| 3 | 3 | import { tmpdir } from "node:os"; |
| 4 | 4 | import { join } from "node:path"; |
| 5 | +import { NodeFileSystem } from "@effect/platform-node"; | |
| 5 | 6 | import { describe, expect, it } from "@effect/vitest"; |
| 6 | 7 | import * as Effect from "effect/Effect"; |
| 7 | 8 | |
| @@ -50,7 +51,7 @@ describe("local-config", () => { · 42 unmodified lines | ||
| 50 | 51 | expect(loaded.config?.sources?.github?.connection.endpoint).toBe( |
| 51 | 52 | "https://api.github.com", |
| 52 | 53 | ); |
| 53 | − }), | |
| 54 | + }).pipe(Effect.provide(NodeFileSystem.layer)), | |
| 54 | 55 | ); |
| 55 | 56 | |
| 56 | 57 | it.effect("reports jsonc syntax errors with line and column details", () => |
| @@ -81,7 +82,7 @@ describe("local-config", () => { · 24 unmodified lines | ||
| 81 | 82 | |
| 82 | 83 | expect(failure.message).toContain("Invalid executor config"); |
| 83 | 84 | expect(failure.message).toContain("line 5, column 7"); |
| 84 | − }), | |
| 85 | + }).pipe(Effect.provide(NodeFileSystem.layer)), | |
| 85 | 86 | ); |
| 86 | 87 | |
| 87 | 88 | it("uses platform-standard home config candidates", () => { |
| @@ -123,6 +124,6 @@ describe("local-config", () => { · 35 unmodified lines | ||
| 123 | 124 | }); |
| 124 | 125 | |
| 125 | 126 | expect(resolvedPath).toBe(join(legacyConfigDirectory, "executor.jsonc")); |
| 126 | − }), | |
| 127 | + }).pipe(Effect.provide(NodeFileSystem.layer)), | |
| 127 | 128 | ); |
| 128 | 129 | }); |
| @@ -1,7 +1,6 @@ | ||
| 1 | 1 | import { homedir } from "node:os"; |
| 2 | 2 | import { basename, dirname, isAbsolute, join, resolve } from "node:path"; |
| 3 | 3 | import { FileSystem } from "@effect/platform"; |
| 4 | −import { NodeFileSystem } from "@effect/platform-node"; | |
| 5 | 4 | import { |
| 6 | 5 | type ParseError as JsoncParseError, |
| 7 | 6 | parse as parseJsoncDocument, |
| @@ -30,15 +29,6 @@ const PROJECT_CONFIG_FALLBACK_BASENAME = "executor.json"; · 22 unmodified lines | ||
| 30 | 29 | const PROJECT_CONFIG_DIRECTORY = ".executor"; |
| 31 | 30 | const EXECUTOR_CONFIG_DIR_ENV = "EXECUTOR_CONFIG_DIR"; |
| 32 | 31 | |
| 33 | −const provideNodeFileSystem = <A, E, R>( | |
| 34 | − effect: Effect.Effect<A, E, R | FileSystem.FileSystem>, | |
| 35 | −): Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem>> => | |
| 36 | − effect.pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect< | |
| 37 | − A, | |
| 38 | − E, | |
| 39 | − Exclude<R, FileSystem.FileSystem> | |
| 40 | − >; | |
| 41 | − | |
| 42 | 32 | const mapFileSystemError = (path: string, action: string) => (cause: unknown) => |
| 43 | 33 | new LocalFileSystemError({ |
| 44 | 34 | message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`, |
| @@ -114,8 +104,8 @@ export const resolveHomeConfigPath = (input: { · 69 unmodified lines | ||
| 114 | 104 | env?: NodeJS.ProcessEnv; |
| 115 | 105 | platform?: NodeJS.Platform; |
| 116 | 106 | homeDirectory?: string; |
| 117 | −} = {}): Effect.Effect<string, LocalFileSystemError> => | |
| 118 | − provideNodeFileSystem(Effect.gen(function* () { | |
| 107 | +} = {}): Effect.Effect<string, LocalFileSystemError, FileSystem.FileSystem> => | |
| 108 | + Effect.gen(function* () { | |
| 119 | 109 | const fs = yield* FileSystem.FileSystem; |
| 120 | 110 | const candidates = resolveDefaultHomeConfigCandidates(input); |
| 121 | 111 | |
| @@ -129,7 +119,7 @@ export const resolveHomeConfigPath = (input: { · 7 unmodified lines | ||
| 129 | 119 | } |
| 130 | 120 | |
| 131 | 121 | return candidates[0]!; |
| 132 | − })); | |
| 122 | + }); | |
| 133 | 123 | |
| 134 | 124 | const formatJsoncParseErrors = (content: string, errors: readonly JsoncParseError[]): string => { |
| 135 | 125 | const lines = content.split("\n"); |
| @@ -338,8 +328,12 @@ export type LoadedLocalExecutorConfig = { · 202 unmodified lines | ||
| 338 | 328 | export const resolveLocalWorkspaceContext = (input: { |
| 339 | 329 | cwd?: string; |
| 340 | 330 | workspaceRoot?: string; |
| 341 | −} = {}): Effect.Effect<ResolvedLocalWorkspaceContext, LocalFileSystemError> => | |
| 342 | − provideNodeFileSystem(Effect.gen(function* () { | |
| 331 | +} = {}): Effect.Effect< | |
| 332 | + ResolvedLocalWorkspaceContext, | |
| 333 | + LocalFileSystemError, | |
| 334 | + FileSystem.FileSystem | |
| 335 | +> => | |
| 336 | + Effect.gen(function* () { | |
| 343 | 337 | const cwd = resolve(input.cwd ?? process.cwd()); |
| 344 | 338 | const workspaceRoot = resolve( |
| 345 | 339 | input.workspaceRoot ?? (yield* resolveWorkspaceRootFromCwdEffect(cwd)), |
| @@ -358,12 +352,16 @@ export const resolveLocalWorkspaceContext = (input: { · 12 unmodified lines | ||
| 358 | 352 | artifactsDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, "artifacts"), |
| 359 | 353 | stateDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, "state"), |
| 360 | 354 | }; |
| 361 | − })); | |
| 355 | + }); | |
| 362 | 356 | |
| 363 | 357 | export const readOptionalLocalExecutorConfig = ( |
| 364 | 358 | path: string, |
| 365 | −): Effect.Effect<LocalExecutorConfig | null, LocalFileSystemError | LocalExecutorConfigDecodeError> => | |
| 366 | − provideNodeFileSystem(Effect.gen(function* () { | |
| 359 | +): Effect.Effect< | |
| 360 | + LocalExecutorConfig | null, | |
| 361 | + LocalFileSystemError | LocalExecutorConfigDecodeError, | |
| 362 | + FileSystem.FileSystem | |
| 363 | +> => | |
| 364 | + Effect.gen(function* () { | |
| 367 | 365 | const fs = yield* FileSystem.FileSystem; |
| 368 | 366 | const exists = yield* fs.exists(path).pipe( |
| 369 | 367 | Effect.mapError(mapFileSystemError(path, "check config path")), |
| @@ -386,13 +384,14 @@ export const readOptionalLocalExecutorConfig = ( · 16 unmodified lines | ||
| 386 | 384 | details: unknownLocalErrorDetails(cause), |
| 387 | 385 | }), |
| 388 | 386 | }); |
| 389 | − })); | |
| 387 | + }); | |
| 390 | 388 | |
| 391 | 389 | export const loadLocalExecutorConfig = ( |
| 392 | 390 | context: ResolvedLocalWorkspaceContext, |
| 393 | 391 | ): Effect.Effect< |
| 394 | 392 | LoadedLocalExecutorConfig, |
| 395 | − LocalFileSystemError | LocalExecutorConfigDecodeError | |
| 393 | + LocalFileSystemError | LocalExecutorConfigDecodeError, | |
| 394 | + FileSystem.FileSystem | |
| 396 | 395 | > => |
| 397 | 396 | Effect.gen(function* () { |
| 398 | 397 | const [homeConfig, projectConfig] = yield* Effect.all([ |
| @@ -415,8 +414,8 @@ export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string = · 16 unmodified lines | ||
| 415 | 414 | export const writeProjectLocalExecutorConfig = (input: { |
| 416 | 415 | context: ResolvedLocalWorkspaceContext; |
| 417 | 416 | config: LocalExecutorConfig; |
| 418 | −}): Effect.Effect<void, LocalFileSystemError> => | |
| 419 | − provideNodeFileSystem(Effect.gen(function* () { | |
| 417 | +}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> => | |
| 418 | + Effect.gen(function* () { | |
| 420 | 419 | const fs = yield* FileSystem.FileSystem; |
| 421 | 420 | yield* fs.makeDirectory(input.context.configDirectory, { recursive: true }).pipe( |
| 422 | 421 | Effect.mapError(mapFileSystemError(input.context.configDirectory, "create config directory")), |
| @@ -427,7 +426,7 @@ export const writeProjectLocalExecutorConfig = (input: { · 4 unmodified lines | ||
| 427 | 426 | ).pipe( |
| 428 | 427 | Effect.mapError(mapFileSystemError(input.context.projectConfigPath, "write config")), |
| 429 | 428 | ); |
| 430 | − })); | |
| 429 | + }); | |
| 431 | 430 | |
| 432 | 431 | export const resolveConfigRelativePath = (input: { |
| 433 | 432 | path: string; |
| @@ -25,9 +25,7 @@ import * as Effect from "effect/Effect"; · 24 unmodified lines | ||
| 25 | 25 | import * as Option from "effect/Option"; |
| 26 | 26 | import * as Schema from "effect/Schema"; |
| 27 | 27 | |
| 28 | −import { authArtifactSecretMaterialRefs } from "./auth-artifacts"; | |
| 29 | 28 | import type { ResolvedLocalWorkspaceContext } from "./local-config"; |
| 30 | −import { loadLocalExecutorConfig } from "./local-config"; | |
| 31 | 29 | import { |
| 32 | 30 | LocalFileSystemError, |
| 33 | 31 | unknownLocalErrorDetails, |
| @@ -303,7 +301,10 @@ const createStateManager = (context: ResolvedLocalWorkspaceContext) => { · 269 unmodified lines | ||
| 303 | 301 | operation: (state: LocalControlPlaneState) => A | Promise<A>, |
| 304 | 302 | ): Effect.Effect<A, LocalFileSystemError> => |
| 305 | 303 | Effect.tryPromise({ |
| 306 | − try: async () => operation(cloneValue(await ensureLoaded())), | |
| 304 | + try: async () => { | |
| 305 | + await mutationQueue; | |
| 306 | + return operation(cloneValue(await ensureLoaded())); | |
| 307 | + }, | |
| 307 | 308 | catch: mapFileSystemError( |
| 308 | 309 | localControlPlaneStatePath(context), |
| 309 | 310 | "read control plane state", |
| @@ -772,45 +773,6 @@ export const createLocalControlPlaneStore = ( · 462 unmodified lines | ||
| 772 | 773 | value: nextMaterials.length !== state.secretMaterials.length, |
| 773 | 774 | } satisfies StateMutationResult<boolean>; |
| 774 | 775 | }), |
| 775 | − | |
| 776 | − listLinkedSources: () => | |
| 777 | − Effect.gen(function* () { | |
| 778 | − const [state, loadedConfig] = yield* Effect.all([ | |
| 779 | − stateManager.read((current) => current), | |
| 780 | − loadLocalExecutorConfig(context), | |
| 781 | − ]); | |
| 782 | − | |
| 783 | − const sourceNames = new Map<string, string>(); | |
| 784 | − for (const [sourceId, source] of Object.entries( | |
| 785 | − loadedConfig.config?.sources ?? {}, | |
| 786 | − )) { | |
| 787 | − sourceNames.set(sourceId, source.name ?? sourceId); | |
| 788 | − } | |
| 789 | − | |
| 790 | − const materialIds = new Set( | |
| 791 | − state.secretMaterials.map((material) => String(material.id)), | |
| 792 | − ); | |
| 793 | − const linkedSources = new Map<string, Array<{ sourceId: string; sourceName: string }>>(); | |
| 794 | − | |
| 795 | − for (const artifact of state.authArtifacts) { | |
| 796 | − for (const ref of authArtifactSecretMaterialRefs(artifact)) { | |
| 797 | − if (!materialIds.has(ref.handle)) { | |
| 798 | − continue; | |
| 799 | − } | |
| 800 | − | |
| 801 | − const existing = linkedSources.get(ref.handle) ?? []; | |
| 802 | − if (!existing.some((link) => link.sourceId === artifact.sourceId)) { | |
| 803 | − existing.push({ | |
| 804 | − sourceId: artifact.sourceId, | |
| 805 | − sourceName: sourceNames.get(artifact.sourceId) ?? artifact.sourceId, | |
| 806 | − }); | |
| 807 | − linkedSources.set(ref.handle, existing); | |
| 808 | − } | |
| 809 | − } | |
| 810 | − } | |
| 811 | − | |
| 812 | − return linkedSources; | |
| 813 | − }), | |
| 814 | 776 | }, |
| 815 | 777 | |
| 816 | 778 | executions: { |
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | import { existsSync, mkdtempSync } from "node:fs"; |
| 2 | 2 | import { tmpdir } from "node:os"; |
| 3 | 3 | import { join } from "node:path"; |
| 4 | +import { NodeFileSystem } from "@effect/platform-node"; | |
| 4 | 5 | import { describe, expect, it } from "@effect/vitest"; |
| 5 | 6 | import * as Effect from "effect/Effect"; |
| 6 | 7 | |
| @@ -45,6 +46,6 @@ describe("local-installation", () => { · 38 unmodified lines | ||
| 45 | 46 | |
| 46 | 47 | expect(second.accountId).toBe(first.accountId); |
| 47 | 48 | expect(second.workspaceId).toBe(first.workspaceId); |
| 48 | − }), | |
| 49 | + }).pipe(Effect.provide(NodeFileSystem.layer)), | |
| 49 | 50 | ); |
| 50 | 51 | }); |
| @@ -1,5 +1,6 @@ | ||
| 1 | 1 | import * as Context from "effect/Context"; |
| 2 | 2 | import * as Effect from "effect/Effect"; |
| 3 | +import * as Layer from "effect/Layer"; | |
| 3 | 4 | import * as Option from "effect/Option"; |
| 4 | 5 | |
| 5 | 6 | import type { AccountId, WorkspaceId } from "#schema"; |
| @@ -25,6 +26,18 @@ export class RuntimeLocalWorkspaceService extends Context.Tag( · 19 unmodified lines | ||
| 25 | 26 | "#runtime/RuntimeLocalWorkspaceService", |
| 26 | 27 | )<RuntimeLocalWorkspaceService, RuntimeLocalWorkspaceState>() {} |
| 27 | 28 | |
| 29 | +export const RuntimeLocalWorkspaceLive = ( | |
| 30 | + runtimeLocalWorkspace: RuntimeLocalWorkspaceState, | |
| 31 | +) => Layer.succeed(RuntimeLocalWorkspaceService, runtimeLocalWorkspace); | |
| 32 | + | |
| 33 | +export const provideOptionalRuntimeLocalWorkspace = <A, E, R>( | |
| 34 | + effect: Effect.Effect<A, E, R>, | |
| 35 | + runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null | undefined, | |
| 36 | +): Effect.Effect<A, E, R> => | |
| 37 | + runtimeLocalWorkspace === null || runtimeLocalWorkspace === undefined | |
| 38 | + ? effect | |
| 39 | + : effect.pipe(Effect.provide(RuntimeLocalWorkspaceLive(runtimeLocalWorkspace))); | |
| 40 | + | |
| 28 | 41 | export const getRuntimeLocalWorkspaceOption = () => |
| 29 | 42 | Effect.contextWith((context) => |
| 30 | 43 | Context.getOption(context, RuntimeLocalWorkspaceService), |
| @@ -1,6 +1,5 @@ | ||
| 1 | 1 | import { join } from "node:path"; |
| 2 | 2 | import { FileSystem } from "@effect/platform"; |
| 3 | −import { NodeFileSystem } from "@effect/platform-node"; | |
| 4 | 3 | |
| 5 | 4 | import { |
| 6 | 5 | SourceIdSchema, |
| @@ -48,15 +47,6 @@ export type LocalSourceArtifact = typeof LocalSourceArtifactSchema.Type; · 41 unmodified lines | ||
| 48 | 47 | |
| 49 | 48 | const decodeLocalSourceArtifact = Schema.decodeUnknownSync(LocalSourceArtifactSchema); |
| 50 | 49 | |
| 51 | −const provideNodeFileSystem = <A, E, R>( | |
| 52 | − effect: Effect.Effect<A, E, R | FileSystem.FileSystem>, | |
| 53 | −): Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem>> => | |
| 54 | − effect.pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect< | |
| 55 | − A, | |
| 56 | − E, | |
| 57 | − Exclude<R, FileSystem.FileSystem> | |
| 58 | − >; | |
| 59 | − | |
| 60 | 50 | const mapFileSystemError = (path: string, action: string) => (cause: unknown) => |
| 61 | 51 | new LocalFileSystemError({ |
| 62 | 52 | message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`, |
| @@ -167,9 +157,10 @@ export const readLocalSourceArtifact = (input: { · 104 unmodified lines | ||
| 167 | 157 | sourceId: string; |
| 168 | 158 | }): Effect.Effect< |
| 169 | 159 | LocalSourceArtifact | null, |
| 170 | − LocalFileSystemError | LocalSourceArtifactDecodeError | |
| 160 | + LocalFileSystemError | LocalSourceArtifactDecodeError, | |
| 161 | + FileSystem.FileSystem | |
| 171 | 162 | > => |
| 172 | − provideNodeFileSystem(Effect.gen(function* () { | |
| 163 | + Effect.gen(function* () { | |
| 173 | 164 | const fs = yield* FileSystem.FileSystem; |
| 174 | 165 | const path = localSourceArtifactPath(input); |
| 175 | 166 | const exists = yield* fs.exists(path).pipe( |
| @@ -192,14 +183,14 @@ export const readLocalSourceArtifact = (input: { · 16 unmodified lines | ||
| 192 | 183 | }); |
| 193 | 184 | }, |
| 194 | 185 | }); |
| 195 | − })); | |
| 186 | + }); | |
| 196 | 187 | |
| 197 | 188 | export const writeLocalSourceArtifact = (input: { |
| 198 | 189 | context: ResolvedLocalWorkspaceContext; |
| 199 | 190 | sourceId: string; |
| 200 | 191 | artifact: LocalSourceArtifact; |
| 201 | −}): Effect.Effect<void, LocalFileSystemError> => | |
| 202 | − provideNodeFileSystem(Effect.gen(function* () { | |
| 192 | +}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> => | |
| 193 | + Effect.gen(function* () { | |
| 203 | 194 | const fs = yield* FileSystem.FileSystem; |
| 204 | 195 | const directory = join(input.context.artifactsDirectory, "sources"); |
| 205 | 196 | const path = localSourceArtifactPath(input); |
| @@ -209,13 +200,13 @@ export const writeLocalSourceArtifact = (input: { · 3 unmodified lines | ||
| 209 | 200 | yield* fs.writeFileString(path, `${JSON.stringify(input.artifact, null, 2)}\n`).pipe( |
| 210 | 201 | Effect.mapError(mapFileSystemError(path, "write source artifact")), |
| 211 | 202 | ); |
| 212 | − })); | |
| 203 | + }); | |
| 213 | 204 | |
| 214 | 205 | export const removeLocalSourceArtifact = (input: { |
| 215 | 206 | context: ResolvedLocalWorkspaceContext; |
| 216 | 207 | sourceId: string; |
| 217 | −}): Effect.Effect<void, LocalFileSystemError> => | |
| 218 | − provideNodeFileSystem(Effect.gen(function* () { | |
| 208 | +}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> => | |
| 209 | + Effect.gen(function* () { | |
| 219 | 210 | const fs = yield* FileSystem.FileSystem; |
| 220 | 211 | const path = localSourceArtifactPath(input); |
| 221 | 212 | const exists = yield* fs.exists(path).pipe( |
| @@ -227,4 +218,4 @@ export const removeLocalSourceArtifact = (input: { · 5 unmodified lines | ||
| 227 | 218 | yield* fs.remove(path).pipe( |
| 228 | 219 | Effect.mapError(mapFileSystemError(path, "remove source artifact")), |
| 229 | 220 | ); |
| 230 | − })); | |
| 221 | + }); | |
| @@ -1,6 +1,8 @@ | ||
| 1 | +import { FileSystem } from "@effect/platform"; | |
| 2 | +import { NodeFileSystem } from "@effect/platform-node"; | |
| 1 | 3 | import type { LocalInstallation, LocalExecutorConfig } from "#schema"; |
| 2 | 4 | import * as Context from "effect/Context"; |
| 3 | −import type * as Effect from "effect/Effect"; | |
| 5 | +import * as Effect from "effect/Effect"; | |
| 4 | 6 | import * as Layer from "effect/Layer"; |
| 5 | 7 | |
| 6 | 8 | import type { |
| @@ -120,35 +122,104 @@ export const LocalInstallationStoreLive = Layer.succeed( · 113 unmodified lines | ||
| 120 | 122 | LocalInstallationStore, |
| 121 | 123 | ); |
| 122 | 124 | |
| 125 | +const bindFileSystem = <A, E>( | |
| 126 | + fileSystem: FileSystem.FileSystem, | |
| 127 | + effect: Effect.Effect<A, E, FileSystem.FileSystem>, | |
| 128 | +): Effect.Effect<A, E, never> => | |
| 129 | + effect.pipe(Effect.provideService(FileSystem.FileSystem, fileSystem)); | |
| 130 | + | |
| 131 | +const bindNodeFileSystem = <A, E>( | |
| 132 | + effect: Effect.Effect<A, E, FileSystem.FileSystem>, | |
| 133 | +): Effect.Effect<A, E, never> => | |
| 134 | + effect.pipe(Effect.provide(NodeFileSystem.layer)); | |
| 135 | + | |
| 123 | 136 | export const LocalWorkspaceConfigStore: WorkspaceConfigStoreShape = { |
| 124 | − load: loadLocalExecutorConfig, | |
| 125 | − writeProject: writeProjectLocalExecutorConfig, | |
| 137 | + load: (context) => bindNodeFileSystem(loadLocalExecutorConfig(context)), | |
| 138 | + writeProject: (input) => bindNodeFileSystem(writeProjectLocalExecutorConfig(input)), | |
| 126 | 139 | resolveRelativePath: resolveConfigRelativePath, |
| 127 | 140 | }; |
| 128 | 141 | |
| 129 | −export const LocalWorkspaceConfigStoreLive = Layer.succeed( | |
| 142 | +export const LocalWorkspaceConfigStoreLive = Layer.effect( | |
| 130 | 143 | WorkspaceConfigStore, |
| 131 | − LocalWorkspaceConfigStore, | |
| 144 | + Effect.gen(function* () { | |
| 145 | + const fileSystem = yield* FileSystem.FileSystem; | |
| 146 | + | |
| 147 | + return WorkspaceConfigStore.of({ | |
| 148 | + load: (context) => bindFileSystem(fileSystem, loadLocalExecutorConfig(context)), | |
| 149 | + writeProject: (input) => | |
| 150 | + bindFileSystem(fileSystem, writeProjectLocalExecutorConfig(input)), | |
| 151 | + resolveRelativePath: resolveConfigRelativePath, | |
| 152 | + }); | |
| 153 | + }), | |
| 132 | 154 | ); |
| 133 | 155 | |
| 134 | 156 | export const LocalWorkspaceStateStore: WorkspaceStateStoreShape = { |
| 135 | − load: loadLocalWorkspaceState, | |
| 136 | − write: writeLocalWorkspaceState, | |
| 157 | + load: (context) => bindNodeFileSystem(loadLocalWorkspaceState(context)), | |
| 158 | + write: (input) => bindNodeFileSystem(writeLocalWorkspaceState(input)), | |
| 137 | 159 | }; |
| 138 | 160 | |
| 139 | −export const LocalWorkspaceStateStoreLive = Layer.succeed( | |
| 161 | +export const LocalWorkspaceStateStoreLive = Layer.effect( | |
| 140 | 162 | WorkspaceStateStore, |
| 141 | − LocalWorkspaceStateStore, | |
| 163 | + Effect.gen(function* () { | |
| 164 | + const fileSystem = yield* FileSystem.FileSystem; | |
| 165 | + | |
| 166 | + return WorkspaceStateStore.of({ | |
| 167 | + load: (context) => bindFileSystem(fileSystem, loadLocalWorkspaceState(context)), | |
| 168 | + write: (input) => bindFileSystem(fileSystem, writeLocalWorkspaceState(input)), | |
| 169 | + }); | |
| 170 | + }), | |
| 142 | 171 | ); |
| 143 | 172 | |
| 144 | 173 | export const LocalSourceArtifactStore: SourceArtifactStoreShape = { |
| 145 | 174 | build: buildLocalSourceArtifact, |
| 146 | − read: readLocalSourceArtifact, | |
| 147 | − write: writeLocalSourceArtifact, | |
| 148 | − remove: removeLocalSourceArtifact, | |
| 175 | + read: (input) => bindNodeFileSystem(readLocalSourceArtifact(input)), | |
| 176 | + write: (input) => bindNodeFileSystem(writeLocalSourceArtifact(input)), | |
| 177 | + remove: (input) => bindNodeFileSystem(removeLocalSourceArtifact(input)), | |
| 149 | 178 | }; |
| 150 | 179 | |
| 151 | −export const LocalSourceArtifactStoreLive = Layer.succeed( | |
| 180 | +export const LocalSourceArtifactStoreLive = Layer.effect( | |
| 152 | 181 | SourceArtifactStore, |
| 153 | − LocalSourceArtifactStore, | |
| 182 | + Effect.gen(function* () { | |
| 183 | + const fileSystem = yield* FileSystem.FileSystem; | |
| 184 | + | |
| 185 | + return SourceArtifactStore.of({ | |
| 186 | + build: buildLocalSourceArtifact, | |
| 187 | + read: (input) => bindFileSystem(fileSystem, readLocalSourceArtifact(input)), | |
| 188 | + write: (input) => bindFileSystem(fileSystem, writeLocalSourceArtifact(input)), | |
| 189 | + remove: (input) => bindFileSystem(fileSystem, removeLocalSourceArtifact(input)), | |
| 190 | + }); | |
| 191 | + }), | |
| 192 | +); | |
| 193 | + | |
| 194 | +export const makeWorkspaceStorageLayer = (input: { | |
| 195 | + workspaceConfigStore: WorkspaceConfigStoreShape; | |
| 196 | + workspaceStateStore: WorkspaceStateStoreShape; | |
| 197 | + sourceArtifactStore: SourceArtifactStoreShape; | |
| 198 | +}) => | |
| 199 | + Layer.mergeAll( | |
| 200 | + Layer.succeed(WorkspaceConfigStore, input.workspaceConfigStore), | |
| 201 | + Layer.succeed(WorkspaceStateStore, input.workspaceStateStore), | |
| 202 | + Layer.succeed(SourceArtifactStore, input.sourceArtifactStore), | |
| 203 | + ); | |
| 204 | + | |
| 205 | +export const makeLocalStorageLayer = (input: { | |
| 206 | + installationStore: InstallationStoreShape; | |
| 207 | + workspaceConfigStore: WorkspaceConfigStoreShape; | |
| 208 | + workspaceStateStore: WorkspaceStateStoreShape; | |
| 209 | + sourceArtifactStore: SourceArtifactStoreShape; | |
| 210 | +}) => | |
| 211 | + Layer.mergeAll( | |
| 212 | + Layer.succeed(InstallationStore, input.installationStore), | |
| 213 | + makeWorkspaceStorageLayer(input), | |
| 214 | + ); | |
| 215 | + | |
| 216 | +export const WorkspaceStorageLive = Layer.mergeAll( | |
| 217 | + LocalWorkspaceConfigStoreLive, | |
| 218 | + LocalWorkspaceStateStoreLive, | |
| 219 | + LocalSourceArtifactStoreLive, | |
| 220 | +); | |
| 221 | + | |
| 222 | +export const LocalStorageLive = Layer.mergeAll( | |
| 223 | + LocalInstallationStoreLive, | |
| 224 | + WorkspaceStorageLive, | |
| 154 | 225 | ); |
| @@ -3,7 +3,6 @@ import { existsSync } from "node:fs"; · 2 unmodified lines | ||
| 3 | 3 | import { dirname, extname, join, relative, resolve, sep } from "node:path"; |
| 4 | 4 | import { fileURLToPath, pathToFileURL } from "node:url"; |
| 5 | 5 | import { FileSystem } from "@effect/platform"; |
| 6 | −import { NodeFileSystem } from "@effect/platform-node"; | |
| 7 | 6 | import { |
| 8 | 7 | createToolCatalogFromTools, |
| 9 | 8 | makeToolInvokerFromTools, |
| @@ -17,7 +16,9 @@ import { · 7 unmodified lines | ||
| 17 | 16 | type ToolMetadata, |
| 18 | 17 | type ToolPath, |
| 19 | 18 | } from "@executor/codemode-core"; |
| 19 | +import * as Context from "effect/Context"; | |
| 20 | 20 | import * as Effect from "effect/Effect"; |
| 21 | +import * as Layer from "effect/Layer"; | |
| 21 | 22 | import * as ts from "typescript"; |
| 22 | 23 | |
| 23 | 24 | import type { ResolvedLocalWorkspaceContext } from "./local-config"; |
| @@ -96,15 +97,6 @@ const emptyLocalToolRuntime = (): LocalToolRuntime => { · 72 unmodified lines | ||
| 96 | 97 | }; |
| 97 | 98 | }; |
| 98 | 99 | |
| 99 | −const provideNodeFileSystem = <A, E, R>( | |
| 100 | − effect: Effect.Effect<A, E, R | FileSystem.FileSystem>, | |
| 101 | −): Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem>> => | |
| 102 | − effect.pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect< | |
| 103 | − A, | |
| 104 | − E, | |
| 105 | − Exclude<R, FileSystem.FileSystem> | |
| 106 | − >; | |
| 107 | − | |
| 108 | 100 | const mapFileSystemError = (path: string, action: string) => (cause: unknown) => |
| 109 | 101 | new LocalFileSystemError({ |
| 110 | 102 | message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`, |
| @@ -589,66 +581,97 @@ export const loadLocalToolRuntime = ( · 478 unmodified lines | ||
| 589 | 581 | | LocalToolDefinitionError |
| 590 | 582 | | LocalToolImportError |
| 591 | 583 | | LocalToolPathConflictError |
| 592 | − | LocalToolTranspileError | |
| 584 | + | LocalToolTranspileError, | |
| 585 | + FileSystem.FileSystem | |
| 593 | 586 | > => |
| 594 | − provideNodeFileSystem( | |
| 595 | − Effect.gen(function* () { | |
| 596 | − const sourceDirectory = localToolsDirectory(context); | |
| 597 | − const sourceFiles = yield* collectLocalToolSourceFiles(sourceDirectory); | |
| 587 | + Effect.gen(function* () { | |
| 588 | + const sourceDirectory = localToolsDirectory(context); | |
| 589 | + const sourceFiles = yield* collectLocalToolSourceFiles(sourceDirectory); | |
| 598 | 590 | |
| 599 | − if (sourceFiles.length === 0) { | |
| 600 | − return emptyLocalToolRuntime(); | |
| 591 | + if (sourceFiles.length === 0) { | |
| 592 | + return emptyLocalToolRuntime(); | |
| 593 | + } | |
| 594 | + | |
| 595 | + const artifactPaths = yield* buildLocalToolArtifacts({ | |
| 596 | + context, | |
| 597 | + sourceDirectory, | |
| 598 | + sourceFiles, | |
| 599 | + }); | |
| 600 | + const candidateFiles = toolCandidateFiles(sourceDirectory, sourceFiles); | |
| 601 | + const tools: ToolMap = {}; | |
| 602 | + const seenToolPaths = new Map<string, string>(); | |
| 603 | + | |
| 604 | + for (const sourcePath of candidateFiles) { | |
| 605 | + const relativePath = relative(sourceDirectory, sourcePath); | |
| 606 | + const toolPath = yield* toToolPath(relativePath); | |
| 607 | + const existing = seenToolPaths.get(toolPath); | |
| 608 | + if (existing) { | |
| 609 | + return yield* Effect.fail( | |
| 610 | + new LocalToolPathConflictError({ | |
| 611 | + message: `Local tool path conflict for ${toolPath}`, | |
| 612 | + path: sourcePath, | |
| 613 | + otherPath: existing, | |
| 614 | + toolPath, | |
| 615 | + }), | |
| 616 | + ); | |
| 617 | + } | |
| 618 | + | |
| 619 | + const artifactPath = artifactPaths.get(sourcePath); | |
| 620 | + if (!artifactPath) { | |
| 621 | + return yield* Effect.fail( | |
| 622 | + new LocalToolImportError({ | |
| 623 | + message: `Missing compiled artifact for local tool ${sourcePath}`, | |
| 624 | + path: sourcePath, | |
| 625 | + details: | |
| 626 | + "Expected a compiled local tool artifact, but none was produced.", | |
| 627 | + }), | |
| 628 | + ); | |
| 601 | 629 | } |
| 602 | 630 | |
| 603 | − const artifactPaths = yield* buildLocalToolArtifacts({ | |
| 604 | − context, | |
| 605 | − sourceDirectory, | |
| 606 | − sourceFiles, | |
| 631 | + tools[toolPath] = yield* importLocalToolModule({ | |
| 632 | + sourcePath, | |
| 633 | + artifactPath, | |
| 634 | + toolPath, | |
| 607 | 635 | }); |
| 608 | − const candidateFiles = toolCandidateFiles(sourceDirectory, sourceFiles); | |
| 609 | − const tools: ToolMap = {}; | |
| 610 | − const seenToolPaths = new Map<string, string>(); | |
| 611 | − | |
| 612 | − for (const sourcePath of candidateFiles) { | |
| 613 | − const relativePath = relative(sourceDirectory, sourcePath); | |
| 614 | − const toolPath = yield* toToolPath(relativePath); | |
| 615 | − const existing = seenToolPaths.get(toolPath); | |
| 616 | − if (existing) { | |
| 617 | − return yield* Effect.fail( | |
| 618 | − new LocalToolPathConflictError({ | |
| 619 | − message: `Local tool path conflict for ${toolPath}`, | |
| 620 | − path: sourcePath, | |
| 621 | − otherPath: existing, | |
| 622 | − toolPath, | |
| 623 | − }), | |
| 624 | − ); | |
| 625 | − } | |
| 636 | + seenToolPaths.set(toolPath, sourcePath); | |
| 637 | + } | |
| 626 | 638 | |
| 627 | − const artifactPath = artifactPaths.get(sourcePath); | |
| 628 | − if (!artifactPath) { | |
| 629 | − return yield* Effect.fail( | |
| 630 | − new LocalToolImportError({ | |
| 631 | − message: `Missing compiled artifact for local tool ${sourcePath}`, | |
| 632 | − path: sourcePath, | |
| 633 | − details: | |
| 634 | − "Expected a compiled local tool artifact, but none was produced.", | |
| 635 | − }), | |
| 636 | − ); | |
| 637 | − } | |
| 639 | + return { | |
| 640 | + tools, | |
| 641 | + catalog: createToolCatalogFromTools({ tools }), | |
| 642 | + toolInvoker: makeToolInvokerFromTools({ tools }), | |
| 643 | + toolPaths: new Set(Object.keys(tools)), | |
| 644 | + } satisfies LocalToolRuntime; | |
| 645 | + }); | |
| 638 | 646 | |
| 639 | − tools[toolPath] = yield* importLocalToolModule({ | |
| 640 | − sourcePath, | |
| 641 | − artifactPath, | |
| 642 | − toolPath, | |
| 643 | − }); | |
| 644 | − seenToolPaths.set(toolPath, sourcePath); | |
| 645 | − } | |
| 647 | +export type LocalToolRuntimeLoaderShape = { | |
| 648 | + load: ( | |
| 649 | + context: ResolvedLocalWorkspaceContext, | |
| 650 | + ) => Effect.Effect< | |
| 651 | + LocalToolRuntime, | |
| 652 | + | LocalFileSystemError | |
| 653 | + | LocalToolDefinitionError | |
| 654 | + | LocalToolImportError | |
| 655 | + | LocalToolPathConflictError | |
| 656 | + | LocalToolTranspileError, | |
| 657 | + never | |
| 658 | + >; | |
| 659 | +}; | |
| 646 | 660 | |
| 647 | − return { | |
| 648 | − tools, | |
| 649 | − catalog: createToolCatalogFromTools({ tools }), | |
| 650 | − toolInvoker: makeToolInvokerFromTools({ tools }), | |
| 651 | − toolPaths: new Set(Object.keys(tools)), | |
| 652 | − } satisfies LocalToolRuntime; | |
| 653 | − }), | |
| 654 | − ); | |
| 661 | +export class LocalToolRuntimeLoaderService extends Context.Tag( | |
| 662 | + "#runtime/LocalToolRuntimeLoaderService", | |
| 663 | +)<LocalToolRuntimeLoaderService, LocalToolRuntimeLoaderShape>() {} | |
| 664 | + | |
| 665 | +export const LocalToolRuntimeLoaderLive = Layer.effect( | |
| 666 | + LocalToolRuntimeLoaderService, | |
| 667 | + Effect.gen(function* () { | |
| 668 | + const fileSystem = yield* FileSystem.FileSystem; | |
| 669 | + | |
| 670 | + return LocalToolRuntimeLoaderService.of({ | |
| 671 | + load: (context) => | |
| 672 | + loadLocalToolRuntime(context).pipe( | |
| 673 | + Effect.provideService(FileSystem.FileSystem, fileSystem), | |
| 674 | + ), | |
| 675 | + }); | |
| 676 | + }), | |
| 677 | +); | |
| @@ -1,6 +1,5 @@ | ||
| 1 | 1 | import { join } from "node:path"; |
| 2 | 2 | import { FileSystem } from "@effect/platform"; |
| 3 | −import { NodeFileSystem } from "@effect/platform-node"; | |
| 4 | 3 | |
| 5 | 4 | import { |
| 6 | 5 | PolicyIdSchema, |
| @@ -51,15 +50,6 @@ export type LocalWorkspaceState = typeof LocalWorkspaceStateSchema.Type; · 44 unmodified lines | ||
| 51 | 50 | |
| 52 | 51 | const decodeLocalWorkspaceState = Schema.decodeUnknownSync(LocalWorkspaceStateSchema); |
| 53 | 52 | |
| 54 | −const provideNodeFileSystem = <A, E, R>( | |
| 55 | − effect: Effect.Effect<A, E, R | FileSystem.FileSystem>, | |
| 56 | −): Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem>> => | |
| 57 | − effect.pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect< | |
| 58 | − A, | |
| 59 | − E, | |
| 60 | − Exclude<R, FileSystem.FileSystem> | |
| 61 | − >; | |
| 62 | − | |
| 63 | 53 | const mapFileSystemError = (path: string, action: string) => (cause: unknown) => |
| 64 | 54 | new LocalFileSystemError({ |
| 65 | 55 | message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`, |
| @@ -80,8 +70,12 @@ export const localWorkspaceStatePath = ( · 14 unmodified lines | ||
| 80 | 70 | |
| 81 | 71 | export const loadLocalWorkspaceState = ( |
| 82 | 72 | context: ResolvedLocalWorkspaceContext, |
| 83 | −): Effect.Effect<LocalWorkspaceState, LocalFileSystemError | LocalWorkspaceStateDecodeError> => | |
| 84 | − provideNodeFileSystem(Effect.gen(function* () { | |
| 73 | +): Effect.Effect< | |
| 74 | + LocalWorkspaceState, | |
| 75 | + LocalFileSystemError | LocalWorkspaceStateDecodeError, | |
| 76 | + FileSystem.FileSystem | |
| 77 | +> => | |
| 78 | + Effect.gen(function* () { | |
| 85 | 79 | const fs = yield* FileSystem.FileSystem; |
| 86 | 80 | const path = localWorkspaceStatePath(context); |
| 87 | 81 | const exists = yield* fs.exists(path).pipe( |
| @@ -104,13 +98,13 @@ export const loadLocalWorkspaceState = ( · 16 unmodified lines | ||
| 104 | 98 | }); |
| 105 | 99 | }, |
| 106 | 100 | }); |
| 107 | − })); | |
| 101 | + }); | |
| 108 | 102 | |
| 109 | 103 | export const writeLocalWorkspaceState = (input: { |
| 110 | 104 | context: ResolvedLocalWorkspaceContext; |
| 111 | 105 | state: LocalWorkspaceState; |
| 112 | −}): Effect.Effect<void, LocalFileSystemError> => | |
| 113 | − provideNodeFileSystem(Effect.gen(function* () { | |
| 106 | +}): Effect.Effect<void, LocalFileSystemError, FileSystem.FileSystem> => | |
| 107 | + Effect.gen(function* () { | |
| 114 | 108 | const fs = yield* FileSystem.FileSystem; |
| 115 | 109 | yield* fs.makeDirectory(input.context.stateDirectory, { recursive: true }).pipe( |
| 116 | 110 | Effect.mapError(mapFileSystemError(input.context.stateDirectory, "create state directory")), |
| @@ -121,4 +115,4 @@ export const writeLocalWorkspaceState = (input: { · 4 unmodified lines | ||
| 121 | 115 | ).pipe( |
| 122 | 116 | Effect.mapError(mapFileSystemError(localWorkspaceStatePath(input.context), "write workspace state")), |
| 123 | 117 | ); |
| 124 | − })); | |
| 118 | + }); | |
| @@ -1,3 +1,4 @@ | ||
| 1 | +import { FileSystem } from "@effect/platform"; | |
| 1 | 2 | import * as Effect from "effect/Effect"; |
| 2 | 3 | |
| 3 | 4 | import { |
| @@ -42,7 +43,7 @@ export const derivePolicyConfigKey = ( · 38 unmodified lines | ||
| 42 | 43 | const pruneLocalWorkspaceState = (input: { |
| 43 | 44 | context: ResolvedLocalWorkspaceContext; |
| 44 | 45 | loadedConfig: LoadedLocalExecutorConfig; |
| 45 | −}): Effect.Effect<LocalWorkspaceState, Error, never> => | |
| 46 | +}): Effect.Effect<LocalWorkspaceState, Error, FileSystem.FileSystem> => | |
| 46 | 47 | Effect.gen(function* () { |
| 47 | 48 | const currentState = yield* loadLocalWorkspaceState(input.context); |
| 48 | 49 | |
| @@ -82,7 +83,7 @@ const pruneLocalWorkspaceState = (input: { · 33 unmodified lines | ||
| 82 | 83 | export const synchronizeLocalWorkspaceState = (input: { |
| 83 | 84 | context: ResolvedLocalWorkspaceContext; |
| 84 | 85 | loadedConfig: LoadedLocalExecutorConfig; |
| 85 | −}): Effect.Effect<LoadedLocalExecutorConfig["config"], Error, never> => | |
| 86 | +}): Effect.Effect<LoadedLocalExecutorConfig["config"], Error, FileSystem.FileSystem> => | |
| 86 | 87 | Effect.gen(function* () { |
| 87 | 88 | yield* pruneLocalWorkspaceState({ |
| 88 | 89 | context: input.context, |
| @@ -11,11 +11,15 @@ import { · 10 unmodified lines | ||
| 11 | 11 | SecretMaterialIdSchema, |
| 12 | 12 | type SecretRef, |
| 13 | 13 | } from "#schema"; |
| 14 | +import * as Context from "effect/Context"; | |
| 14 | 15 | import * as Effect from "effect/Effect"; |
| 16 | +import * as Layer from "effect/Layer"; | |
| 15 | 17 | import * as Option from "effect/Option"; |
| 16 | 18 | |
| 17 | 19 | import { resolveConfigRelativePath } from "./local-config"; |
| 18 | 20 | import { fromConfigSecretProviderId } from "./local-config-secrets"; |
| 21 | +import { getRuntimeLocalWorkspaceOption } from "./local-runtime-context"; | |
| 22 | +import { ControlPlaneStore } from "./store"; | |
| 19 | 23 | import type { ControlPlaneStoreShape } from "./store"; |
| 20 | 24 | |
| 21 | 25 | export const ENV_SECRET_PROVIDER_ID = "env"; |
| @@ -46,6 +50,18 @@ export type DeleteSecretMaterial = ( · 24 unmodified lines | ||
| 46 | 50 | ref: SecretRef, |
| 47 | 51 | ) => Effect.Effect<boolean, Error, never>; |
| 48 | 52 | |
| 53 | +export class SecretMaterialResolverService extends Context.Tag( | |
| 54 | + "#runtime/SecretMaterialResolverService", | |
| 55 | +)<SecretMaterialResolverService, ResolveSecretMaterial>() {} | |
| 56 | + | |
| 57 | +export class SecretMaterialStorerService extends Context.Tag( | |
| 58 | + "#runtime/SecretMaterialStorerService", | |
| 59 | +)<SecretMaterialStorerService, StoreSecretMaterial>() {} | |
| 60 | + | |
| 61 | +export class SecretMaterialDeleterService extends Context.Tag( | |
| 62 | + "#runtime/SecretMaterialDeleterService", | |
| 63 | +)<SecretMaterialDeleterService, DeleteSecretMaterial>() {} | |
| 64 | + | |
| 49 | 65 | type SecretMaterialProviderRuntime = { |
| 50 | 66 | rows: ControlPlaneStoreShape; |
| 51 | 67 | env: NodeJS.ProcessEnv; |
| @@ -740,3 +756,95 @@ export const createDefaultSecretMaterialDeleter = (input: { · 688 unmodified lines | ||
| 740 | 756 | }); |
| 741 | 757 | }); |
| 742 | 758 | }; |
| 759 | + | |
| 760 | +const resolveRuntimeSecretMaterialConfig = (input: { | |
| 761 | + localConfig?: LocalExecutorConfig | null; | |
| 762 | + workspaceRoot?: string | null; | |
| 763 | +}) => | |
| 764 | + Effect.gen(function* () { | |
| 765 | + const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption(); | |
| 766 | + | |
| 767 | + return { | |
| 768 | + localConfig: input.localConfig | |
| 769 | + ?? runtimeLocalWorkspace?.loadedConfig.config | |
| 770 | + ?? null, | |
| 771 | + workspaceRoot: input.workspaceRoot | |
| 772 | + ?? runtimeLocalWorkspace?.context.workspaceRoot | |
| 773 | + ?? null, | |
| 774 | + }; | |
| 775 | + }); | |
| 776 | + | |
| 777 | +export const SecretMaterialResolverLive = (input: { | |
| 778 | + resolveSecretMaterial?: ResolveSecretMaterial; | |
| 779 | + dangerouslyAllowEnvSecrets?: boolean; | |
| 780 | + keychainServiceName?: string; | |
| 781 | + localConfig?: LocalExecutorConfig | null; | |
| 782 | + workspaceRoot?: string | null; | |
| 783 | +} = {}) => | |
| 784 | + input.resolveSecretMaterial | |
| 785 | + ? Layer.succeed(SecretMaterialResolverService, input.resolveSecretMaterial) | |
| 786 | + : Layer.effect( | |
| 787 | + SecretMaterialResolverService, | |
| 788 | + Effect.gen(function* () { | |
| 789 | + const rows = yield* ControlPlaneStore; | |
| 790 | + const runtimeConfig = yield* resolveRuntimeSecretMaterialConfig(input); | |
| 791 | + | |
| 792 | + return createDefaultSecretMaterialResolver({ | |
| 793 | + rows, | |
| 794 | + dangerouslyAllowEnvSecrets: input.dangerouslyAllowEnvSecrets, | |
| 795 | + keychainServiceName: input.keychainServiceName, | |
| 796 | + localConfig: runtimeConfig.localConfig, | |
| 797 | + workspaceRoot: runtimeConfig.workspaceRoot, | |
| 798 | + }); | |
| 799 | + }), | |
| 800 | + ); | |
| 801 | + | |
| 802 | +export const SecretMaterialStorerLive = (input: { | |
| 803 | + storeProviderId?: SecretStoreProviderId; | |
| 804 | + dangerouslyAllowEnvSecrets?: boolean; | |
| 805 | + keychainServiceName?: string; | |
| 806 | +} = {}) => | |
| 807 | + Layer.effect( | |
| 808 | + SecretMaterialStorerService, | |
| 809 | + Effect.gen(function* () { | |
| 810 | + const rows = yield* ControlPlaneStore; | |
| 811 | + | |
| 812 | + return createDefaultSecretMaterialStorer({ | |
| 813 | + rows, | |
| 814 | + storeProviderId: input.storeProviderId, | |
| 815 | + dangerouslyAllowEnvSecrets: input.dangerouslyAllowEnvSecrets, | |
| 816 | + keychainServiceName: input.keychainServiceName, | |
| 817 | + }); | |
| 818 | + }), | |
| 819 | + ); | |
| 820 | + | |
| 821 | +export const SecretMaterialDeleterLive = (input: { | |
| 822 | + dangerouslyAllowEnvSecrets?: boolean; | |
| 823 | + keychainServiceName?: string; | |
| 824 | +} = {}) => | |
| 825 | + Layer.effect( | |
| 826 | + SecretMaterialDeleterService, | |
| 827 | + Effect.gen(function* () { | |
| 828 | + const rows = yield* ControlPlaneStore; | |
| 829 | + | |
| 830 | + return createDefaultSecretMaterialDeleter({ | |
| 831 | + rows, | |
| 832 | + dangerouslyAllowEnvSecrets: input.dangerouslyAllowEnvSecrets, | |
| 833 | + keychainServiceName: input.keychainServiceName, | |
| 834 | + }); | |
| 835 | + }), | |
| 836 | + ); | |
| 837 | + | |
| 838 | +export const SecretMaterialLive = (input: { | |
| 839 | + resolveSecretMaterial?: ResolveSecretMaterial; | |
| 840 | + storeProviderId?: SecretStoreProviderId; | |
| 841 | + dangerouslyAllowEnvSecrets?: boolean; | |
| 842 | + keychainServiceName?: string; | |
| 843 | + localConfig?: LocalExecutorConfig | null; | |
| 844 | + workspaceRoot?: string | null; | |
| 845 | +} = {}) => | |
| 846 | + Layer.mergeAll( | |
| 847 | + SecretMaterialResolverLive(input), | |
| 848 | + SecretMaterialStorerLive(input), | |
| 849 | + SecretMaterialDeleterLive(input), | |
| 850 | + ); | |
| @@ -4,6 +4,7 @@ import { · 3 unmodified lines | ||
| 4 | 4 | HttpClientRequest, |
| 5 | 5 | } from "@effect/platform"; |
| 6 | 6 | import { |
| 7 | + allowAllToolInteractions, | |
| 7 | 8 | makeToolInvokerFromTools, |
| 8 | 9 | typeSignatureFromSchemaJson, |
| 9 | 10 | } from "@executor/codemode-core"; |
| @@ -616,6 +617,13 @@ export const googleDiscoverySourceAdapter: SourceAdapter = { · 606 unmodified lines | ||
| 616 | 617 | tools: { |
| 617 | 618 | [path]: tool, |
| 618 | 619 | }, |
| 620 | + // Authorization was already handled by authorizePersistedToolInvocation | |
| 621 | + // at the workspace level, so skip the redundant tool-level interaction | |
| 622 | + // gate. Without this, the default "required" interaction on write tools | |
| 623 | + // would re-elicit approval and then merge the approval form content | |
| 624 | + // (e.g. { approve: true }) into the tool args, breaking schemas that | |
| 625 | + // use additionalProperties: false. | |
| 626 | + onToolInteraction: allowAllToolInteractions, | |
| 619 | 627 | onElicitation, |
| 620 | 628 | }).invoke({ |
| 621 | 629 | path, |
| @@ -1,4 +1,5 @@ | ||
| 1 | 1 | import { |
| 2 | + allowAllToolInteractions, | |
| 2 | 3 | applyCookiePlacementsToHeaders, |
| 3 | 4 | applyHttpQueryPlacementsToUrl, |
| 4 | 5 | applyJsonBodyPlacements, |
| @@ -524,6 +525,7 @@ export const graphqlSourceAdapter: SourceAdapter = { · 519 unmodified lines | ||
| 524 | 525 | tools: { |
| 525 | 526 | [path]: tool, |
| 526 | 527 | }, |
| 528 | + onToolInteraction: allowAllToolInteractions, | |
| 527 | 529 | onElicitation, |
| 528 | 530 | }).invoke({ |
| 529 | 531 | path, |
| @@ -1,4 +1,5 @@ | ||
| 1 | 1 | import { |
| 2 | + allowAllToolInteractions, | |
| 2 | 3 | applyCookiePlacementsToHeaders, |
| 3 | 4 | makeToolInvokerFromTools, |
| 4 | 5 | } from "@executor/codemode-core"; |
| @@ -409,6 +410,7 @@ export const mcpSourceAdapter: SourceAdapter = { · 404 unmodified lines | ||
| 409 | 410 | |
| 410 | 411 | return yield* makeToolInvokerFromTools({ |
| 411 | 412 | tools, |
| 413 | + onToolInteraction: allowAllToolInteractions, | |
| 412 | 414 | onElicitation, |
| 413 | 415 | }).invoke({ |
| 414 | 416 | path, |
| @@ -4,6 +4,7 @@ import { · 3 unmodified lines | ||
| 4 | 4 | HttpClientRequest, |
| 5 | 5 | } from "@effect/platform"; |
| 6 | 6 | import { |
| 7 | + allowAllToolInteractions, | |
| 7 | 8 | applyCookiePlacementsToHeaders, |
| 8 | 9 | applyHttpQueryPlacementsToUrl, |
| 9 | 10 | makeToolInvokerFromTools, |
| @@ -563,6 +564,7 @@ export const openApiSourceAdapter: SourceAdapter = { · 553 unmodified lines | ||
| 563 | 564 | tools: { |
| 564 | 565 | [path]: tool, |
| 565 | 566 | }, |
| 567 | + onToolInteraction: allowAllToolInteractions, | |
| 566 | 568 | onElicitation, |
| 567 | 569 | }).invoke({ |
| 568 | 570 | path, |
| @@ -24,7 +24,6 @@ import * as Schema from "effect/Schema"; · 23 unmodified lines | ||
| 24 | 24 | |
| 25 | 25 | import type { ResolvedSourceAuthMaterial } from "../source-auth-material"; |
| 26 | 26 | import type { ResolveSecretMaterial } from "../secret-material-providers"; |
| 27 | −import type { ControlPlaneStoreShape } from "../store"; | |
| 28 | 27 | |
| 29 | 28 | export type SourceAdapterFamily = "http_api" | "mcp" | "internal"; |
| 30 | 29 | export type SourceAdapterInputSchema = Schema.Schema<any, any, never>; |
| @@ -82,7 +81,6 @@ export type SourceAdapterOauth2SetupConfig = { · 51 unmodified lines | ||
| 82 | 81 | }; |
| 83 | 82 | |
| 84 | 83 | export type SourceAdapterInvokePersistedToolInput = { |
| 85 | − rows: ControlPlaneStoreShape; | |
| 86 | 84 | workspaceId: Source["workspaceId"]; |
| 87 | 85 | accountId: AccountId; |
| 88 | 86 | source: Source; |
| @@ -7,7 +7,7 @@ import { · 6 unmodified lines | ||
| 7 | 7 | type Source, |
| 8 | 8 | } from "#schema"; |
| 9 | 9 | |
| 10 | −import { resolveSourceAuthMaterial } from "./source-auth-material"; | |
| 10 | +import { resolveSourceAuthMaterialWithDeps } from "./source-auth-material"; | |
| 11 | 11 | |
| 12 | 12 | const makeSource = (overrides: Partial<Source> = {}): Source => ({ |
| 13 | 13 | id: SourceIdSchema.make("src_tool_artifacts"), |
| @@ -36,113 +36,119 @@ const makeSource = (overrides: Partial<Source> = {}): Source => ({ · 22 unmodified lines | ||
| 36 | 36 | describe("source-auth-material", () => { |
| 37 | 37 | describe("resolveSourceAuthMaterial", () => { |
| 38 | 38 | it("returns empty headers for auth.kind none", async () => { |
| 39 | − const auth = await Effect.runPromise(resolveSourceAuthMaterial({ | |
| 40 | − source: makeSource({ | |
| 41 | − auth: { kind: "none" }, | |
| 42 | − }), | |
| 43 | − resolveSecretMaterial: () => Effect.die("should not be called"), | |
| 44 | − })); | |
| 39 | + await Effect.runPromise(Effect.gen(function* () { | |
| 40 | + const auth = yield* resolveSourceAuthMaterialWithDeps({ | |
| 41 | + source: makeSource({ | |
| 42 | + auth: { kind: "none" }, | |
| 43 | + }), | |
| 44 | + resolveSecretMaterial: () => Effect.die("should not be called"), | |
| 45 | + }); | |
| 45 | 46 | |
| 46 | − expect(auth).toEqual({ | |
| 47 | − placements: [], | |
| 48 | − headers: {}, | |
| 49 | − queryParams: {}, | |
| 50 | − cookies: {}, | |
| 51 | − bodyValues: {}, | |
| 52 | − expiresAt: null, | |
| 53 | − refreshAfter: null, | |
| 54 | − }); | |
| 47 | + expect(auth).toEqual({ | |
| 48 | + placements: [], | |
| 49 | + headers: {}, | |
| 50 | + queryParams: {}, | |
| 51 | + cookies: {}, | |
| 52 | + bodyValues: {}, | |
| 53 | + expiresAt: null, | |
| 54 | + refreshAfter: null, | |
| 55 | + }); | |
| 56 | + })); | |
| 55 | 57 | }); |
| 56 | 58 | |
| 57 | 59 | it("resolves bearer auth headers from the configured token ref", async () => { |
| 58 | 60 | const calls: string[] = []; |
| 59 | 61 | |
| 60 | − const auth = await Effect.runPromise(resolveSourceAuthMaterial({ | |
| 61 | − source: makeSource({ | |
| 62 | − auth: { | |
| 63 | − kind: "bearer", | |
| 64 | − headerName: "X-Api-Key", | |
| 65 | − prefix: "Token ", | |
| 66 | − token: { | |
| 67 | − providerId: "local", | |
| 68 | − handle: "sec_bearer", | |
| 62 | + await Effect.runPromise(Effect.gen(function* () { | |
| 63 | + const auth = yield* resolveSourceAuthMaterialWithDeps({ | |
| 64 | + source: makeSource({ | |
| 65 | + auth: { | |
| 66 | + kind: "bearer", | |
| 67 | + headerName: "X-Api-Key", | |
| 68 | + prefix: "Token ", | |
| 69 | + token: { | |
| 70 | + providerId: "local", | |
| 71 | + handle: "sec_bearer", | |
| 72 | + }, | |
| 69 | 73 | }, |
| 74 | + }), | |
| 75 | + resolveSecretMaterial: ({ ref }) => { | |
| 76 | + calls.push(`${ref.providerId}:${ref.handle}`); | |
| 77 | + return Effect.succeed("resolved-bearer-token"); | |
| 70 | 78 | }, |
| 71 | − }), | |
| 72 | − resolveSecretMaterial: ({ ref }) => { | |
| 73 | − calls.push(`${ref.providerId}:${ref.handle}`); | |
| 74 | − return Effect.succeed("resolved-bearer-token"); | |
| 75 | − }, | |
| 76 | − })); | |
| 79 | + }); | |
| 77 | 80 | |
| 78 | − expect(calls).toEqual(["local:sec_bearer"]); | |
| 79 | − expect(auth).toEqual({ | |
| 80 | − placements: [ | |
| 81 | − { | |
| 82 | − location: "header", | |
| 83 | − name: "X-Api-Key", | |
| 84 | − value: "Token resolved-bearer-token", | |
| 81 | + expect(calls).toEqual(["local:sec_bearer"]); | |
| 82 | + expect(auth).toEqual({ | |
| 83 | + placements: [ | |
| 84 | + { | |
| 85 | + location: "header", | |
| 86 | + name: "X-Api-Key", | |
| 87 | + value: "Token resolved-bearer-token", | |
| 88 | + }, | |
| 89 | + ], | |
| 90 | + headers: { | |
| 91 | + "X-Api-Key": "Token resolved-bearer-token", | |
| 85 | 92 | }, |
| 86 | − ], | |
| 87 | − headers: { | |
| 88 | − "X-Api-Key": "Token resolved-bearer-token", | |
| 89 | − }, | |
| 90 | − queryParams: {}, | |
| 91 | − cookies: {}, | |
| 92 | − bodyValues: {}, | |
| 93 | − expiresAt: null, | |
| 94 | − refreshAfter: null, | |
| 95 | − }); | |
| 93 | + queryParams: {}, | |
| 94 | + cookies: {}, | |
| 95 | + bodyValues: {}, | |
| 96 | + expiresAt: null, | |
| 97 | + refreshAfter: null, | |
| 98 | + }); | |
| 99 | + })); | |
| 96 | 100 | }); |
| 97 | 101 | |
| 98 | 102 | it("uses the oauth access token ref and ignores the refresh token", async () => { |
| 99 | 103 | const calls: string[] = []; |
| 100 | 104 | |
| 101 | − const auth = await Effect.runPromise(resolveSourceAuthMaterial({ | |
| 102 | − source: makeSource({ | |
| 103 | − kind: "graphql", | |
| 104 | − endpoint: "https://example.com/graphql", | |
| 105 | − binding: { | |
| 106 | − defaultHeaders: null, | |
| 107 | − }, | |
| 108 | − auth: { | |
| 109 | − kind: "oauth2", | |
| 110 | − headerName: "Authorization", | |
| 111 | − prefix: "Bearer ", | |
| 112 | − accessToken: { | |
| 113 | − providerId: "local", | |
| 114 | − handle: "sec_access", | |
| 105 | + await Effect.runPromise(Effect.gen(function* () { | |
| 106 | + const auth = yield* resolveSourceAuthMaterialWithDeps({ | |
| 107 | + source: makeSource({ | |
| 108 | + kind: "graphql", | |
| 109 | + endpoint: "https://example.com/graphql", | |
| 110 | + binding: { | |
| 111 | + defaultHeaders: null, | |
| 115 | 112 | }, |
| 116 | − refreshToken: { | |
| 117 | − providerId: "local", | |
| 118 | − handle: "sec_refresh", | |
| 113 | + auth: { | |
| 114 | + kind: "oauth2", | |
| 115 | + headerName: "Authorization", | |
| 116 | + prefix: "Bearer ", | |
| 117 | + accessToken: { | |
| 118 | + providerId: "local", | |
| 119 | + handle: "sec_access", | |
| 120 | + }, | |
| 121 | + refreshToken: { | |
| 122 | + providerId: "local", | |
| 123 | + handle: "sec_refresh", | |
| 124 | + }, | |
| 119 | 125 | }, |
| 126 | + }), | |
| 127 | + resolveSecretMaterial: ({ ref }) => { | |
| 128 | + calls.push(`${ref.providerId}:${ref.handle}`); | |
| 129 | + return Effect.succeed("resolved-access-token"); | |
| 120 | 130 | }, |
| 121 | − }), | |
| 122 | − resolveSecretMaterial: ({ ref }) => { | |
| 123 | − calls.push(`${ref.providerId}:${ref.handle}`); | |
| 124 | − return Effect.succeed("resolved-access-token"); | |
| 125 | − }, | |
| 126 | − })); | |
| 131 | + }); | |
| 127 | 132 | |
| 128 | − expect(calls).toEqual(["local:sec_access"]); | |
| 129 | − expect(auth).toEqual({ | |
| 130 | − placements: [ | |
| 131 | − { | |
| 132 | − location: "header", | |
| 133 | − name: "Authorization", | |
| 134 | − value: "Bearer resolved-access-token", | |
| 133 | + expect(calls).toEqual(["local:sec_access"]); | |
| 134 | + expect(auth).toEqual({ | |
| 135 | + placements: [ | |
| 136 | + { | |
| 137 | + location: "header", | |
| 138 | + name: "Authorization", | |
| 139 | + value: "Bearer resolved-access-token", | |
| 140 | + }, | |
| 141 | + ], | |
| 142 | + headers: { | |
| 143 | + Authorization: "Bearer resolved-access-token", | |
| 135 | 144 | }, |
| 136 | − ], | |
| 137 | − headers: { | |
| 138 | − Authorization: "Bearer resolved-access-token", | |
| 139 | − }, | |
| 140 | − queryParams: {}, | |
| 141 | − cookies: {}, | |
| 142 | − bodyValues: {}, | |
| 143 | − expiresAt: null, | |
| 144 | − refreshAfter: null, | |
| 145 | − }); | |
| 145 | + queryParams: {}, | |
| 146 | + cookies: {}, | |
| 147 | + bodyValues: {}, | |
| 148 | + expiresAt: null, | |
| 149 | + refreshAfter: null, | |
| 150 | + }); | |
| 151 | + })); | |
| 146 | 152 | }); |
| 147 | 153 | }); |
| 148 | 154 | }); |
| @@ -4,10 +4,12 @@ import type { · 3 unmodified lines | ||
| 4 | 4 | Source, |
| 5 | 5 | SourceAuth, |
| 6 | 6 | } from "#schema"; |
| 7 | +import * as Context from "effect/Context"; | |
| 7 | 8 | import * as Effect from "effect/Effect"; |
| 9 | +import * as Layer from "effect/Layer"; | |
| 8 | 10 | import * as Option from "effect/Option"; |
| 9 | 11 | |
| 10 | −import type { ControlPlaneStoreShape } from "./store"; | |
| 12 | +import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store"; | |
| 11 | 13 | import { |
| 12 | 14 | authArtifactFromSourceAuth, |
| 13 | 15 | resolveAuthArtifactMaterial, |
| @@ -18,6 +20,7 @@ import type { · 4 unmodified lines | ||
| 18 | 20 | ResolveSecretMaterial, |
| 19 | 21 | SecretMaterialResolveContext, |
| 20 | 22 | } from "./secret-material-providers"; |
| 23 | +import { SecretMaterialResolverService } from "./secret-material-providers"; | |
| 21 | 24 | |
| 22 | 25 | const authForSlot = (input: { |
| 23 | 26 | source: Source; |
| @@ -38,7 +41,20 @@ const authForSlot = (input: { · 14 unmodified lines | ||
| 38 | 41 | return input.source.importAuth; |
| 39 | 42 | }; |
| 40 | 43 | |
| 41 | −export const resolveSourceAuthMaterial = (input: { | |
| 44 | +export type RuntimeSourceAuthMaterialShape = { | |
| 45 | + resolve: (input: { | |
| 46 | + source: Source; | |
| 47 | + slot?: CredentialSlot; | |
| 48 | + actorAccountId?: AccountId | null; | |
| 49 | + context?: SecretMaterialResolveContext; | |
| 50 | + }) => Effect.Effect<ResolvedSourceAuthMaterial, Error, never>; | |
| 51 | +}; | |
| 52 | + | |
| 53 | +export class RuntimeSourceAuthMaterialService extends Context.Tag( | |
| 54 | + "#runtime/RuntimeSourceAuthMaterialService", | |
| 55 | +)<RuntimeSourceAuthMaterialService, RuntimeSourceAuthMaterialShape>() {} | |
| 56 | + | |
| 57 | +export const resolveSourceAuthMaterialWithDeps = (input: { | |
| 42 | 58 | source: Source; |
| 43 | 59 | slot?: CredentialSlot; |
| 44 | 60 | actorAccountId?: AccountId | null; |
| @@ -106,4 +122,29 @@ export const resolveSourceAuthMaterial = (input: { · 61 unmodified lines | ||
| 106 | 122 | }); |
| 107 | 123 | }); |
| 108 | 124 | |
| 125 | +export const resolveSourceAuthMaterial = (input: { | |
| 126 | + source: Source; | |
| 127 | + slot?: CredentialSlot; | |
| 128 | + actorAccountId?: AccountId | null; | |
| 129 | + context?: SecretMaterialResolveContext; | |
| 130 | +}): Effect.Effect<ResolvedSourceAuthMaterial, Error, RuntimeSourceAuthMaterialService> => | |
| 131 | + Effect.flatMap(RuntimeSourceAuthMaterialService, (service) => service.resolve(input)); | |
| 132 | + | |
| 133 | +export const RuntimeSourceAuthMaterialLive = Layer.effect( | |
| 134 | + RuntimeSourceAuthMaterialService, | |
| 135 | + Effect.gen(function* () { | |
| 136 | + const rows = yield* ControlPlaneStore; | |
| 137 | + const resolveSecretMaterial = yield* SecretMaterialResolverService; | |
| 138 | + | |
| 139 | + return RuntimeSourceAuthMaterialService.of({ | |
| 140 | + resolve: (input) => | |
| 141 | + resolveSourceAuthMaterialWithDeps({ | |
| 142 | + ...input, | |
| 143 | + rows, | |
| 144 | + resolveSecretMaterial, | |
| 145 | + }), | |
| 146 | + }); | |
| 147 | + }), | |
| 148 | +); | |
| 149 | + | |
| 109 | 150 | export type { ResolvedSourceAuthMaterial } from "./auth-artifacts"; |
| @@ -7,7 +7,6 @@ import { · 6 unmodified lines | ||
| 7 | 7 | AccountId, |
| 8 | 8 | type CredentialSlot, |
| 9 | 9 | ExecutionIdSchema, |
| 10 | − type LocalExecutorConfig, | |
| 11 | 10 | McpSourceAuthSessionDataJsonSchema, |
| 12 | 11 | type McpSourceAuthSessionData, |
| 13 | 12 | OAuth2PkceSourceAuthSessionDataJsonSchema, |
| @@ -42,7 +41,8 @@ import { · 28 unmodified lines | ||
| 42 | 41 | type LiveExecutionManager, |
| 43 | 42 | } from "./live-execution"; |
| 44 | 43 | import { |
| 45 | − RuntimeLocalWorkspaceService, | |
| 44 | + getRuntimeLocalWorkspaceOption, | |
| 45 | + provideOptionalRuntimeLocalWorkspace, | |
| 46 | 46 | type RuntimeLocalWorkspaceState, |
| 47 | 47 | } from "./local-runtime-context"; |
| 48 | 48 | import { |
| @@ -61,15 +61,15 @@ import { · 12 unmodified lines | ||
| 61 | 61 | import { isSourceCredentialRequiredError } from "./source-adapters/shared"; |
| 62 | 62 | import { |
| 63 | 63 | createDefaultSecretMaterialDeleter, |
| 64 | − createDefaultSecretMaterialResolver, | |
| 65 | − createDefaultSecretMaterialStorer, | |
| 66 | 64 | type ResolveSecretMaterial, |
| 65 | + SecretMaterialResolverService, | |
| 66 | + SecretMaterialStorerService, | |
| 67 | 67 | type StoreSecretMaterial, |
| 68 | 68 | } from "./secret-material-providers"; |
| 69 | 69 | import { upsertOauth2AuthorizedUserLeaseFromTokenResponse } from "./auth-leases"; |
| 70 | 70 | import { |
| 71 | − persistMcpRecipeMaterializationFromManifest, | |
| 72 | − syncSourceMaterialization, | |
| 71 | + RuntimeSourceMaterializationService, | |
| 72 | + type RuntimeSourceMaterializationShape, | |
| 73 | 73 | } from "./source-materialization"; |
| 74 | 74 | import { |
| 75 | 75 | buildOAuth2AuthorizationUrl, |
| @@ -81,6 +81,8 @@ import { · 5 unmodified lines | ||
| 81 | 81 | loadSourceById, |
| 82 | 82 | loadSourcesInWorkspace, |
| 83 | 83 | persistSource, |
| 84 | + type RuntimeSourceStore, | |
| 85 | + RuntimeSourceStoreService, | |
| 84 | 86 | } from "./source-store"; |
| 85 | 87 | import type { WorkspaceStorageServices } from "./local-storage"; |
| 86 | 88 | import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store"; |
| @@ -358,7 +360,7 @@ const serializeJson = (value: unknown): string | null => { · 271 unmodified lines | ||
| 358 | 360 | return JSON.stringify(value); |
| 359 | 361 | }; |
| 360 | 362 | |
| 361 | −const updateSourceStatus = (rows: ControlPlaneStoreShape, source: Source, input: { | |
| 363 | +const updateSourceStatus = (sourceStore: RuntimeSourceStore, source: Source, input: { | |
| 362 | 364 | actorAccountId?: AccountId | null; |
| 363 | 365 | status: Source["status"]; |
| 364 | 366 | lastError?: string | null; |
| @@ -366,13 +368,13 @@ const updateSourceStatus = (rows: ControlPlaneStoreShape, source: Source, input: · 1 unmodified line | ||
| 366 | 368 | importAuth?: Source["importAuth"]; |
| 367 | 369 | }) => |
| 368 | 370 | Effect.gen(function* () { |
| 369 | − const latest = yield* loadSourceById(rows, { | |
| 371 | + const latest = yield* sourceStore.loadSourceById({ | |
| 370 | 372 | workspaceId: source.workspaceId, |
| 371 | 373 | sourceId: source.id, |
| 372 | 374 | actorAccountId: input.actorAccountId, |
| 373 | 375 | }); |
| 374 | 376 | |
| 375 | − return yield* persistSource(rows, { | |
| 377 | + return yield* sourceStore.persistSource({ | |
| 376 | 378 | ...latest, |
| 377 | 379 | status: input.status, |
| 378 | 380 | lastError: input.lastError ?? null, |
| @@ -857,6 +859,7 @@ const resolveExistingSourceOauthClient = (input: { · 478 unmodified lines | ||
| 857 | 859 | |
| 858 | 860 | const startOauth2PkceSourceCredentialSetup = (input: { |
| 859 | 861 | rows: ControlPlaneStoreShape; |
| 862 | + sourceStore: RuntimeSourceStore; | |
| 860 | 863 | source: Source; |
| 861 | 864 | actorAccountId?: AccountId | null; |
| 862 | 865 | executionId?: SourceAuthSession["executionId"]; |
| @@ -951,7 +954,7 @@ const startOauth2PkceSourceCredentialSetup = (input: { · 88 unmodified lines | ||
| 951 | 954 | updatedAt: now, |
| 952 | 955 | }); |
| 953 | 956 | |
| 954 | − const authRequiredSource = yield* updateSourceStatus(input.rows, input.source, { | |
| 957 | + const authRequiredSource = yield* updateSourceStatus(input.sourceStore, input.source, { | |
| 955 | 958 | actorAccountId: input.actorAccountId, |
| 956 | 959 | status: "auth_required", |
| 957 | 960 | lastError: null, |
| @@ -974,6 +977,8 @@ const startOauth2PkceSourceCredentialSetup = (input: { · 16 unmodified lines | ||
| 974 | 977 | |
| 975 | 978 | const connectMcpSourceInternal = (input: { |
| 976 | 979 | rows: ControlPlaneStoreShape; |
| 980 | + sourceStore: RuntimeSourceStore; | |
| 981 | + sourceMaterialization: RuntimeSourceMaterializationShape; | |
| 977 | 982 | getLocalServerBaseUrl?: () => string | undefined; |
| 978 | 983 | baseUrl?: string | null; |
| 979 | 984 | workspaceId: WorkspaceId; |
| @@ -995,7 +1000,7 @@ const connectMcpSourceInternal = (input: { · 15 unmodified lines | ||
| 995 | 1000 | const normalizedEndpoint = normalizeEndpoint(input.endpoint); |
| 996 | 1001 | const existing = yield* ( |
| 997 | 1002 | input.sourceId |
| 998 | − ? loadSourceById(input.rows, { | |
| 1003 | + ? input.sourceStore.loadSourceById({ | |
| 999 | 1004 | workspaceId: input.workspaceId, |
| 1000 | 1005 | sourceId: input.sourceId, |
| 1001 | 1006 | actorAccountId: input.actorAccountId, |
| @@ -1006,7 +1011,7 @@ const connectMcpSourceInternal = (input: { · 4 unmodified lines | ||
| 1006 | 1011 | : Effect.fail(new Error(`Expected MCP source, received ${source.kind}`)), |
| 1007 | 1012 | ), |
| 1008 | 1013 | ) |
| 1009 | − : loadSourcesInWorkspace(input.rows, input.workspaceId, { | |
| 1014 | + : input.sourceStore.loadSourcesInWorkspace(input.workspaceId, { | |
| 1010 | 1015 | actorAccountId: input.actorAccountId, |
| 1011 | 1016 | }).pipe( |
| 1012 | 1017 | Effect.map((sources) => |
| @@ -1079,14 +1084,12 @@ const connectMcpSourceInternal = (input: { · 66 unmodified lines | ||
| 1079 | 1084 | now, |
| 1080 | 1085 | }); |
| 1081 | 1086 | |
| 1082 | − const persistedDraft = yield* persistSource(input.rows, draftSource, { | |
| 1087 | + const persistedDraft = yield* input.sourceStore.persistSource(draftSource, { | |
| 1083 | 1088 | actorAccountId: input.actorAccountId, |
| 1084 | 1089 | }); |
| 1085 | − yield* syncSourceMaterialization({ | |
| 1086 | − rows: input.rows, | |
| 1090 | + yield* input.sourceMaterialization.sync({ | |
| 1087 | 1091 | source: persistedDraft, |
| 1088 | 1092 | actorAccountId: input.actorAccountId, |
| 1089 | − resolveSecretMaterial: input.resolveSecretMaterial, | |
| 1090 | 1093 | }); |
| 1091 | 1094 | |
| 1092 | 1095 | const discovered = yield* Effect.either( |
| @@ -1100,15 +1103,14 @@ const connectMcpSourceInternal = (input: { · 7 unmodified lines | ||
| 1100 | 1103 | onLeft: () => Effect.succeed(null), |
| 1101 | 1104 | onRight: (result) => |
| 1102 | 1105 | Effect.gen(function* () { |
| 1103 | − const connected = yield* updateSourceStatus(input.rows, persistedDraft, { | |
| 1106 | + const connected = yield* updateSourceStatus(input.sourceStore, persistedDraft, { | |
| 1104 | 1107 | actorAccountId: input.actorAccountId, |
| 1105 | 1108 | status: "connected", |
| 1106 | 1109 | lastError: null, |
| 1107 | 1110 | auth: { kind: "none" }, |
| 1108 | 1111 | }); |
| 1109 | 1112 | const indexed = yield* Effect.either( |
| 1110 | − persistMcpRecipeMaterializationFromManifest({ | |
| 1111 | − rows: input.rows, | |
| 1113 | + input.sourceMaterialization.persistMcpRecipeMaterializationFromManifest({ | |
| 1112 | 1114 | source: connected, |
| 1113 | 1115 | manifestEntries: result.manifest.tools, |
| 1114 | 1116 | }), |
| @@ -1116,7 +1118,7 @@ const connectMcpSourceInternal = (input: { · 1 unmodified line | ||
| 1116 | 1118 | |
| 1117 | 1119 | return yield* Either.match(indexed, { |
| 1118 | 1120 | onLeft: (error) => |
| 1119 | − updateSourceStatus(input.rows, connected, { | |
| 1121 | + updateSourceStatus(input.sourceStore, connected, { | |
| 1120 | 1122 | actorAccountId: input.actorAccountId, |
| 1121 | 1123 | status: "error", |
| 1122 | 1124 | lastError: error.message, |
| @@ -1156,7 +1158,7 @@ const connectMcpSourceInternal = (input: { · 33 unmodified lines | ||
| 1156 | 1158 | state, |
| 1157 | 1159 | }); |
| 1158 | 1160 | |
| 1159 | − const authRequiredSource = yield* updateSourceStatus(input.rows, persistedDraft, { | |
| 1161 | + const authRequiredSource = yield* updateSourceStatus(input.sourceStore, persistedDraft, { | |
| 1160 | 1162 | actorAccountId: input.actorAccountId, |
| 1161 | 1163 | status: "auth_required", |
| 1162 | 1164 | lastError: null, |
| @@ -1203,6 +1205,8 @@ const connectMcpSourceInternal = (input: { · 40 unmodified lines | ||
| 1203 | 1205 | |
| 1204 | 1206 | const addExecutorHttpSource = (input: { |
| 1205 | 1207 | rows: ControlPlaneStoreShape; |
| 1208 | + sourceStore: RuntimeSourceStore; | |
| 1209 | + sourceMaterialization: RuntimeSourceMaterializationShape; | |
| 1206 | 1210 | sourceInput: Extract<ExecutorAddSourceInput, { kind: "openapi" | "graphql" }>; |
| 1207 | 1211 | storeSecretMaterial: StoreSecretMaterial; |
| 1208 | 1212 | resolveSecretMaterial: ResolveSecretMaterial; |
| @@ -1214,8 +1218,7 @@ const addExecutorHttpSource = (input: { · 5 unmodified lines | ||
| 1214 | 1218 | const normalizedSpecUrl = input.sourceInput.kind === "openapi" |
| 1215 | 1219 | ? normalizeEndpoint(input.sourceInput.specUrl) |
| 1216 | 1220 | : null; |
| 1217 | − const existingSources = yield* loadSourcesInWorkspace( | |
| 1218 | − input.rows, | |
| 1221 | + const existingSources = yield* input.sourceStore.loadSourcesInWorkspace( | |
| 1219 | 1222 | input.sourceInput.workspaceId, |
| 1220 | 1223 | { |
| 1221 | 1224 | actorAccountId: input.sourceInput.actorAccountId, |
| @@ -1313,7 +1316,7 @@ const addExecutorHttpSource = (input: { · 91 unmodified lines | ||
| 1313 | 1316 | now, |
| 1314 | 1317 | }); |
| 1315 | 1318 | |
| 1316 | − const persistedDraft = yield* persistSource(input.rows, draftSource, { | |
| 1319 | + const persistedDraft = yield* input.sourceStore.persistSource(draftSource, { | |
| 1317 | 1320 | actorAccountId: input.sourceInput.actorAccountId, |
| 1318 | 1321 | }); |
| 1319 | 1322 | |
| @@ -1327,6 +1330,7 @@ const addExecutorHttpSource = (input: { · 7 unmodified lines | ||
| 1327 | 1330 | if (baseUrl) { |
| 1328 | 1331 | const oauthRequired = yield* startOauth2PkceSourceCredentialSetup({ |
| 1329 | 1332 | rows: input.rows, |
| 1333 | + sourceStore: input.sourceStore, | |
| 1330 | 1334 | source: persistedDraft, |
| 1331 | 1335 | actorAccountId: input.sourceInput.actorAccountId, |
| 1332 | 1336 | executionId: input.sourceInput.executionId, |
| @@ -1340,7 +1344,7 @@ const addExecutorHttpSource = (input: { · 7 unmodified lines | ||
| 1340 | 1344 | } |
| 1341 | 1345 | } |
| 1342 | 1346 | |
| 1343 | − const authRequiredSource = yield* updateSourceStatus(input.rows, persistedDraft, { | |
| 1347 | + const authRequiredSource = yield* updateSourceStatus(input.sourceStore, persistedDraft, { | |
| 1344 | 1348 | actorAccountId: input.sourceInput.actorAccountId, |
| 1345 | 1349 | status: "auth_required", |
| 1346 | 1350 | lastError: null, |
| @@ -1354,21 +1358,19 @@ const addExecutorHttpSource = (input: { · 7 unmodified lines | ||
| 1354 | 1358 | } |
| 1355 | 1359 | |
| 1356 | 1360 | const synced = yield* Effect.either( |
| 1357 | − syncSourceMaterialization({ | |
| 1358 | − rows: input.rows, | |
| 1361 | + input.sourceMaterialization.sync({ | |
| 1359 | 1362 | source: { |
| 1360 | 1363 | ...persistedDraft, |
| 1361 | 1364 | status: "connected", |
| 1362 | 1365 | }, |
| 1363 | 1366 | actorAccountId: input.sourceInput.actorAccountId, |
| 1364 | − resolveSecretMaterial: input.resolveSecretMaterial, | |
| 1365 | 1367 | }), |
| 1366 | 1368 | ); |
| 1367 | 1369 | |
| 1368 | 1370 | return yield* Either.match(synced, { |
| 1369 | 1371 | onLeft: (error) => |
| 1370 | 1372 | isSourceCredentialRequiredError(error) |
| 1371 | − ? updateSourceStatus(input.rows, persistedDraft, { | |
| 1373 | + ? updateSourceStatus(input.sourceStore, persistedDraft, { | |
| 1372 | 1374 | actorAccountId: input.sourceInput.actorAccountId, |
| 1373 | 1375 | status: "auth_required", |
| 1374 | 1376 | lastError: null, |
| @@ -1381,7 +1383,7 @@ const addExecutorHttpSource = (input: { · 6 unmodified lines | ||
| 1381 | 1383 | } satisfies ExecutorSourceAddResult) |
| 1382 | 1384 | ), |
| 1383 | 1385 | ) |
| 1384 | − : updateSourceStatus(input.rows, persistedDraft, { | |
| 1386 | + : updateSourceStatus(input.sourceStore, persistedDraft, { | |
| 1385 | 1387 | actorAccountId: input.sourceInput.actorAccountId, |
| 1386 | 1388 | status: "error", |
| 1387 | 1389 | lastError: error.message, |
| @@ -1389,7 +1391,7 @@ const addExecutorHttpSource = (input: { · 1 unmodified line | ||
| 1389 | 1391 | Effect.zipRight(Effect.fail(error)), |
| 1390 | 1392 | ), |
| 1391 | 1393 | onRight: () => |
| 1392 | − updateSourceStatus(input.rows, persistedDraft, { | |
| 1394 | + updateSourceStatus(input.sourceStore, persistedDraft, { | |
| 1393 | 1395 | actorAccountId: input.sourceInput.actorAccountId, |
| 1394 | 1396 | status: "connected", |
| 1395 | 1397 | lastError: null, |
| @@ -1406,6 +1408,8 @@ const addExecutorHttpSource = (input: { · 10 unmodified lines | ||
| 1406 | 1408 | |
| 1407 | 1409 | const addExecutorGoogleDiscoverySource = (input: { |
| 1408 | 1410 | rows: ControlPlaneStoreShape; |
| 1411 | + sourceStore: RuntimeSourceStore; | |
| 1412 | + sourceMaterialization: RuntimeSourceMaterializationShape; | |
| 1409 | 1413 | sourceInput: Extract<ExecutorAddSourceInput, { kind: "google_discovery" }>; |
| 1410 | 1414 | storeSecretMaterial: StoreSecretMaterial; |
| 1411 | 1415 | resolveSecretMaterial: ResolveSecretMaterial; |
| @@ -1419,8 +1423,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 7 unmodified lines | ||
| 1419 | 1423 | trimOrNull(input.sourceInput.discoveryUrl) |
| 1420 | 1424 | ?? defaultGoogleDiscoveryUrl(normalizedService, normalizedVersion), |
| 1421 | 1425 | ); |
| 1422 | − const existingSources = yield* loadSourcesInWorkspace( | |
| 1423 | − input.rows, | |
| 1426 | + const existingSources = yield* input.sourceStore.loadSourcesInWorkspace( | |
| 1424 | 1427 | input.sourceInput.workspaceId, |
| 1425 | 1428 | { |
| 1426 | 1429 | actorAccountId: input.sourceInput.actorAccountId, |
| @@ -1524,7 +1527,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 97 unmodified lines | ||
| 1524 | 1527 | now, |
| 1525 | 1528 | }); |
| 1526 | 1529 | |
| 1527 | − const persistedDraft = yield* persistSource(input.rows, draftSource, { | |
| 1530 | + const persistedDraft = yield* input.sourceStore.persistSource(draftSource, { | |
| 1528 | 1531 | actorAccountId: input.sourceInput.actorAccountId, |
| 1529 | 1532 | }); |
| 1530 | 1533 | |
| @@ -1547,6 +1550,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 16 unmodified lines | ||
| 1547 | 1550 | if (baseUrl) { |
| 1548 | 1551 | const oauthRequired = yield* startOauth2PkceSourceCredentialSetup({ |
| 1549 | 1552 | rows: input.rows, |
| 1553 | + sourceStore: input.sourceStore, | |
| 1550 | 1554 | source: persistedDraft, |
| 1551 | 1555 | actorAccountId: input.sourceInput.actorAccountId, |
| 1552 | 1556 | executionId: input.sourceInput.executionId, |
| @@ -1560,7 +1564,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 7 unmodified lines | ||
| 1560 | 1564 | } |
| 1561 | 1565 | } |
| 1562 | 1566 | |
| 1563 | − const authRequiredSource = yield* updateSourceStatus(input.rows, persistedDraft, { | |
| 1567 | + const authRequiredSource = yield* updateSourceStatus(input.sourceStore, persistedDraft, { | |
| 1564 | 1568 | actorAccountId: input.sourceInput.actorAccountId, |
| 1565 | 1569 | status: "auth_required", |
| 1566 | 1570 | lastError: null, |
| @@ -1574,21 +1578,19 @@ const addExecutorGoogleDiscoverySource = (input: { · 7 unmodified lines | ||
| 1574 | 1578 | } |
| 1575 | 1579 | |
| 1576 | 1580 | const synced = yield* Effect.either( |
| 1577 | − syncSourceMaterialization({ | |
| 1578 | − rows: input.rows, | |
| 1581 | + input.sourceMaterialization.sync({ | |
| 1579 | 1582 | source: { |
| 1580 | 1583 | ...persistedDraft, |
| 1581 | 1584 | status: "connected", |
| 1582 | 1585 | }, |
| 1583 | 1586 | actorAccountId: input.sourceInput.actorAccountId, |
| 1584 | − resolveSecretMaterial: input.resolveSecretMaterial, | |
| 1585 | 1587 | }), |
| 1586 | 1588 | ); |
| 1587 | 1589 | |
| 1588 | 1590 | return yield* Either.match(synced, { |
| 1589 | 1591 | onLeft: (error) => |
| 1590 | 1592 | isSourceCredentialRequiredError(error) |
| 1591 | − ? updateSourceStatus(input.rows, persistedDraft, { | |
| 1593 | + ? updateSourceStatus(input.sourceStore, persistedDraft, { | |
| 1592 | 1594 | actorAccountId: input.sourceInput.actorAccountId, |
| 1593 | 1595 | status: "auth_required", |
| 1594 | 1596 | lastError: null, |
| @@ -1601,7 +1603,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 6 unmodified lines | ||
| 1601 | 1603 | } satisfies ExecutorSourceAddResult) |
| 1602 | 1604 | ), |
| 1603 | 1605 | ) |
| 1604 | − : updateSourceStatus(input.rows, persistedDraft, { | |
| 1606 | + : updateSourceStatus(input.sourceStore, persistedDraft, { | |
| 1605 | 1607 | actorAccountId: input.sourceInput.actorAccountId, |
| 1606 | 1608 | status: "error", |
| 1607 | 1609 | lastError: error.message, |
| @@ -1609,7 +1611,7 @@ const addExecutorGoogleDiscoverySource = (input: { · 1 unmodified line | ||
| 1609 | 1611 | Effect.zipRight(Effect.fail(error)), |
| 1610 | 1612 | ), |
| 1611 | 1613 | onRight: () => |
| 1612 | − updateSourceStatus(input.rows, persistedDraft, { | |
| 1614 | + updateSourceStatus(input.sourceStore, persistedDraft, { | |
| 1613 | 1615 | actorAccountId: input.sourceInput.actorAccountId, |
| 1614 | 1616 | status: "connected", |
| 1615 | 1617 | lastError: null, |
| @@ -1668,19 +1670,13 @@ type RuntimeSourceAuthServiceShape = { · 52 unmodified lines | ||
| 1668 | 1670 | export const createRuntimeSourceAuthService = (input: { |
| 1669 | 1671 | rows: ControlPlaneStoreShape; |
| 1670 | 1672 | liveExecutionManager: LiveExecutionManager; |
| 1673 | + sourceStore: RuntimeSourceStore; | |
| 1674 | + sourceMaterialization: RuntimeSourceMaterializationShape; | |
| 1675 | + resolveSecretMaterial: ResolveSecretMaterial; | |
| 1676 | + storeSecretMaterial: StoreSecretMaterial; | |
| 1671 | 1677 | getLocalServerBaseUrl?: () => string | undefined; |
| 1672 | − localConfig?: LocalExecutorConfig | null; | |
| 1673 | − workspaceRoot?: string | null; | |
| 1674 | 1678 | localWorkspaceState?: RuntimeLocalWorkspaceState; |
| 1675 | 1679 | }) => { |
| 1676 | − const resolveSecretMaterial = createDefaultSecretMaterialResolver({ | |
| 1677 | − rows: input.rows, | |
| 1678 | − localConfig: input.localConfig, | |
| 1679 | − workspaceRoot: input.workspaceRoot, | |
| 1680 | − }); | |
| 1681 | − const storeSecretMaterial = createDefaultSecretMaterialStorer({ | |
| 1682 | − rows: input.rows, | |
| 1683 | − }); | |
| 1684 | 1680 | const mirrorLocalSourceResult = ( |
| 1685 | 1681 | result: ExecutorSourceAddResult, |
| 1686 | 1682 | ): Effect.Effect<ExecutorSourceAddResult, Error, WorkspaceStorageServices> => |
| @@ -1690,27 +1686,20 @@ export const createRuntimeSourceAuthService = (input: { · 3 unmodified lines | ||
| 1690 | 1686 | ): Effect.Effect<McpSourceConnectResult, Error, WorkspaceStorageServices> => |
| 1691 | 1687 | Effect.succeed(result); |
| 1692 | 1688 | const provideLocalWorkspace = <A, E, R>(effect: Effect.Effect<A, E, R>) => |
| 1693 | − input.localWorkspaceState | |
| 1694 | − ? effect.pipe( | |
| 1695 | − Effect.provideService( | |
| 1696 | − RuntimeLocalWorkspaceService, | |
| 1697 | − input.localWorkspaceState, | |
| 1698 | − ), | |
| 1699 | − ) | |
| 1700 | − : effect; | |
| 1689 | + provideOptionalRuntimeLocalWorkspace(effect, input.localWorkspaceState); | |
| 1701 | 1690 | |
| 1702 | 1691 | return { |
| 1703 | 1692 | getLocalServerBaseUrl: () => input.getLocalServerBaseUrl?.() ?? null, |
| 1704 | 1693 | |
| 1705 | 1694 | storeSecretMaterial: ({ purpose, value }) => |
| 1706 | − storeSecretMaterial({ | |
| 1695 | + input.storeSecretMaterial({ | |
| 1707 | 1696 | purpose, |
| 1708 | 1697 | value, |
| 1709 | 1698 | }), |
| 1710 | 1699 | |
| 1711 | 1700 | getSourceById: ({ workspaceId, sourceId, actorAccountId }) => |
| 1712 | 1701 | provideLocalWorkspace( |
| 1713 | − loadSourceById(input.rows, { | |
| 1702 | + input.sourceStore.loadSourceById({ | |
| 1714 | 1703 | workspaceId, |
| 1715 | 1704 | sourceId, |
| 1716 | 1705 | actorAccountId, |
| @@ -1722,26 +1711,32 @@ export const createRuntimeSourceAuthService = (input: { · 5 unmodified lines | ||
| 1722 | 1711 | (sourceInput.kind === "google_discovery" |
| 1723 | 1712 | ? addExecutorGoogleDiscoverySource({ |
| 1724 | 1713 | rows: input.rows, |
| 1714 | + sourceStore: input.sourceStore, | |
| 1715 | + sourceMaterialization: input.sourceMaterialization, | |
| 1725 | 1716 | sourceInput, |
| 1726 | − storeSecretMaterial, | |
| 1727 | − resolveSecretMaterial, | |
| 1717 | + storeSecretMaterial: input.storeSecretMaterial, | |
| 1718 | + resolveSecretMaterial: input.resolveSecretMaterial, | |
| 1728 | 1719 | getLocalServerBaseUrl: input.getLocalServerBaseUrl, |
| 1729 | 1720 | baseUrl: options?.baseUrl, |
| 1730 | 1721 | }) |
| 1731 | 1722 | : hasSourceAdapterFamily(sourceInput.kind ?? "mcp", "http_api") |
| 1732 | 1723 | ? addExecutorHttpSource({ |
| 1733 | 1724 | rows: input.rows, |
| 1725 | + sourceStore: input.sourceStore, | |
| 1726 | + sourceMaterialization: input.sourceMaterialization, | |
| 1734 | 1727 | sourceInput: sourceInput as Extract< |
| 1735 | 1728 | ExecutorAddSourceInput, |
| 1736 | 1729 | { kind: "openapi" | "graphql" } |
| 1737 | 1730 | >, |
| 1738 | − storeSecretMaterial, | |
| 1739 | − resolveSecretMaterial, | |
| 1731 | + storeSecretMaterial: input.storeSecretMaterial, | |
| 1732 | + resolveSecretMaterial: input.resolveSecretMaterial, | |
| 1740 | 1733 | getLocalServerBaseUrl: input.getLocalServerBaseUrl, |
| 1741 | 1734 | baseUrl: options?.baseUrl, |
| 1742 | 1735 | }) |
| 1743 | 1736 | : connectMcpSourceInternal({ |
| 1744 | 1737 | rows: input.rows, |
| 1738 | + sourceStore: input.sourceStore, | |
| 1739 | + sourceMaterialization: input.sourceMaterialization, | |
| 1745 | 1740 | getLocalServerBaseUrl: input.getLocalServerBaseUrl, |
| 1746 | 1741 | workspaceId: sourceInput.workspaceId, |
| 1747 | 1742 | actorAccountId: sourceInput.actorAccountId, |
| @@ -1752,7 +1747,7 @@ export const createRuntimeSourceAuthService = (input: { · 4 unmodified lines | ||
| 1752 | 1747 | namespace: sourceInput.namespace, |
| 1753 | 1748 | mcpDiscoveryElicitation: options?.mcpDiscoveryElicitation, |
| 1754 | 1749 | baseUrl: options?.baseUrl, |
| 1755 | − resolveSecretMaterial, | |
| 1750 | + resolveSecretMaterial: input.resolveSecretMaterial, | |
| 1756 | 1751 | })).pipe( |
| 1757 | 1752 | Effect.flatMap(mirrorLocalSourceResult), |
| 1758 | 1753 | ), |
| @@ -1762,6 +1757,8 @@ export const createRuntimeSourceAuthService = (input: { · 3 unmodified lines | ||
| 1762 | 1757 | provideLocalWorkspace( |
| 1763 | 1758 | connectMcpSourceInternal({ |
| 1764 | 1759 | rows: input.rows, |
| 1760 | + sourceStore: input.sourceStore, | |
| 1761 | + sourceMaterialization: input.sourceMaterialization, | |
| 1765 | 1762 | getLocalServerBaseUrl: input.getLocalServerBaseUrl, |
| 1766 | 1763 | workspaceId: sourceInput.workspaceId, |
| 1767 | 1764 | actorAccountId: sourceInput.actorAccountId, |
| @@ -1776,7 +1773,7 @@ export const createRuntimeSourceAuthService = (input: { · 8 unmodified lines | ||
| 1776 | 1773 | queryParams: sourceInput.queryParams, |
| 1777 | 1774 | headers: sourceInput.headers, |
| 1778 | 1775 | baseUrl: sourceInput.baseUrl, |
| 1779 | − resolveSecretMaterial, | |
| 1776 | + resolveSecretMaterial: input.resolveSecretMaterial, | |
| 1780 | 1777 | }).pipe( |
| 1781 | 1778 | Effect.flatMap(mirrorLocalMcpSourceResult), |
| 1782 | 1779 | ), |
| @@ -1912,13 +1909,13 @@ export const createRuntimeSourceAuthService = (input: { · 129 unmodified lines | ||
| 1912 | 1909 | displayName: readSourceOAuthSessionDisplayName(session.state), |
| 1913 | 1910 | endpoint: sessionData.endpoint, |
| 1914 | 1911 | }); |
| 1915 | − const accessTokenRef = yield* storeSecretMaterial({ | |
| 1912 | + const accessTokenRef = yield* input.storeSecretMaterial({ | |
| 1916 | 1913 | purpose: "oauth_access_token", |
| 1917 | 1914 | value: exchanged.tokens.access_token, |
| 1918 | 1915 | name: oauthSecretName, |
| 1919 | 1916 | }); |
| 1920 | 1917 | const refreshTokenRef = exchanged.tokens.refresh_token |
| 1921 | − ? yield* storeSecretMaterial({ | |
| 1918 | + ? yield* input.storeSecretMaterial({ | |
| 1922 | 1919 | purpose: "oauth_refresh_token", |
| 1923 | 1920 | value: exchanged.tokens.refresh_token, |
| 1924 | 1921 | name: `${oauthSecretName} Refresh`, |
| @@ -1991,7 +1988,7 @@ export const createRuntimeSourceAuthService = (input: { · 66 unmodified lines | ||
| 1991 | 1988 | ); |
| 1992 | 1989 | } |
| 1993 | 1990 | |
| 1994 | − const source = yield* loadSourceById(input.rows, { | |
| 1991 | + const source = yield* input.sourceStore.loadSourceById({ | |
| 1995 | 1992 | workspaceId: session.workspaceId, |
| 1996 | 1993 | sourceId: session.sourceId, |
| 1997 | 1994 | actorAccountId: session.actorAccountId, |
| @@ -2024,16 +2021,14 @@ export const createRuntimeSourceAuthService = (input: { · 26 unmodified lines | ||
| 2024 | 2021 | errorText: reason, |
| 2025 | 2022 | }), |
| 2026 | 2023 | ); |
| 2027 | − const failedSource = yield* updateSourceStatus(input.rows, source, { | |
| 2024 | + const failedSource = yield* updateSourceStatus(input.sourceStore, source, { | |
| 2028 | 2025 | actorAccountId: session.actorAccountId, |
| 2029 | 2026 | status: "error", |
| 2030 | 2027 | lastError: reason, |
| 2031 | 2028 | }); |
| 2032 | − yield* syncSourceMaterialization({ | |
| 2033 | − rows: input.rows, | |
| 2029 | + yield* input.sourceMaterialization.sync({ | |
| 2034 | 2030 | source: failedSource, |
| 2035 | 2031 | actorAccountId: session.actorAccountId, |
| 2036 | − resolveSecretMaterial, | |
| 2037 | 2032 | }); |
| 2038 | 2033 | yield* completeLiveInteraction({ |
| 2039 | 2034 | rows: input.rows, |
| @@ -2063,7 +2058,7 @@ export const createRuntimeSourceAuthService = (input: { · 23 unmodified lines | ||
| 2063 | 2058 | const oauthSessionData = decodeOauth2PkceSourceAuthSessionData(session); |
| 2064 | 2059 | let clientSecret: string | null = null; |
| 2065 | 2060 | if (oauthSessionData.clientSecret) { |
| 2066 | − clientSecret = yield* resolveSecretMaterial({ | |
| 2061 | + clientSecret = yield* input.resolveSecretMaterial({ | |
| 2067 | 2062 | ref: oauthSessionData.clientSecret, |
| 2068 | 2063 | }); |
| 2069 | 2064 | } |
| @@ -2084,7 +2079,7 @@ export const createRuntimeSourceAuthService = (input: { · 14 unmodified lines | ||
| 2084 | 2079 | ); |
| 2085 | 2080 | } |
| 2086 | 2081 | |
| 2087 | − const refreshTokenRef = yield* storeSecretMaterial({ | |
| 2082 | + const refreshTokenRef = yield* input.storeSecretMaterial({ | |
| 2088 | 2083 | purpose: "oauth_refresh_token", |
| 2089 | 2084 | value: refreshToken, |
| 2090 | 2085 | name: `${source.name} Refresh`, |
| @@ -2092,7 +2087,7 @@ export const createRuntimeSourceAuthService = (input: { · 1 unmodified line | ||
| 2092 | 2087 | const grantedScopes = trimOrNull(exchanged.scope) |
| 2093 | 2088 | ? exchanged.scope!.split(/\s+/).filter((scope) => scope.length > 0) |
| 2094 | 2089 | : [...oauthSessionData.scopes]; |
| 2095 | − connectedSource = yield* updateSourceStatus(input.rows, source, { | |
| 2090 | + connectedSource = yield* updateSourceStatus(input.sourceStore, source, { | |
| 2096 | 2091 | actorAccountId: session.actorAccountId, |
| 2097 | 2092 | status: "connected", |
| 2098 | 2093 | lastError: null, |
| @@ -2157,20 +2152,20 @@ export const createRuntimeSourceAuthService = (input: { · 58 unmodified lines | ||
| 2157 | 2152 | displayName: source.name, |
| 2158 | 2153 | endpoint: source.endpoint, |
| 2159 | 2154 | }); |
| 2160 | − const accessTokenRef = yield* storeSecretMaterial({ | |
| 2155 | + const accessTokenRef = yield* input.storeSecretMaterial({ | |
| 2161 | 2156 | purpose: "oauth_access_token", |
| 2162 | 2157 | value: exchanged.tokens.access_token, |
| 2163 | 2158 | name: oauthSecretName, |
| 2164 | 2159 | }); |
| 2165 | 2160 | const refreshTokenRef = exchanged.tokens.refresh_token |
| 2166 | − ? yield* storeSecretMaterial({ | |
| 2161 | + ? yield* input.storeSecretMaterial({ | |
| 2167 | 2162 | purpose: "oauth_refresh_token", |
| 2168 | 2163 | value: exchanged.tokens.refresh_token, |
| 2169 | 2164 | name: `${oauthSecretName} Refresh`, |
| 2170 | 2165 | }) |
| 2171 | 2166 | : null; |
| 2172 | 2167 | |
| 2173 | − connectedSource = yield* updateSourceStatus(input.rows, source, { | |
| 2168 | + connectedSource = yield* updateSourceStatus(input.sourceStore, source, { | |
| 2174 | 2169 | actorAccountId: session.actorAccountId, |
| 2175 | 2170 | status: "connected", |
| 2176 | 2171 | lastError: null, |
| @@ -2204,16 +2199,14 @@ export const createRuntimeSourceAuthService = (input: { · 27 unmodified lines | ||
| 2204 | 2199 | ); |
| 2205 | 2200 | } |
| 2206 | 2201 | const indexed = yield* Effect.either( |
| 2207 | − syncSourceMaterialization({ | |
| 2208 | − rows: input.rows, | |
| 2202 | + input.sourceMaterialization.sync({ | |
| 2209 | 2203 | source: connectedSource, |
| 2210 | 2204 | actorAccountId: session.actorAccountId, |
| 2211 | − resolveSecretMaterial, | |
| 2212 | 2205 | }), |
| 2213 | 2206 | ); |
| 2214 | 2207 | yield* Either.match(indexed, { |
| 2215 | 2208 | onLeft: (error) => |
| 2216 | − updateSourceStatus(input.rows, connectedSource, { | |
| 2209 | + updateSourceStatus(input.sourceStore, connectedSource, { | |
| 2217 | 2210 | actorAccountId: session.actorAccountId, |
| 2218 | 2211 | status: "error", |
| 2219 | 2212 | lastError: error.message, |
| @@ -2245,23 +2238,27 @@ export class RuntimeSourceAuthServiceTag extends Context.Tag( · 25 unmodified lines | ||
| 2245 | 2238 | |
| 2246 | 2239 | export const RuntimeSourceAuthServiceLive = (input: { |
| 2247 | 2240 | getLocalServerBaseUrl?: () => string | undefined; |
| 2248 | − localConfig?: LocalExecutorConfig | null; | |
| 2249 | − workspaceRoot?: string | null; | |
| 2250 | − localWorkspaceState?: RuntimeLocalWorkspaceState; | |
| 2251 | 2241 | } = {}) => |
| 2252 | 2242 | Layer.effect( |
| 2253 | 2243 | RuntimeSourceAuthServiceTag, |
| 2254 | 2244 | Effect.gen(function* () { |
| 2255 | 2245 | const rows = yield* ControlPlaneStore; |
| 2256 | 2246 | const liveExecutionManager = yield* LiveExecutionManagerService; |
| 2247 | + const sourceStore = yield* RuntimeSourceStoreService; | |
| 2248 | + const sourceMaterialization = yield* RuntimeSourceMaterializationService; | |
| 2249 | + const resolveSecretMaterial = yield* SecretMaterialResolverService; | |
| 2250 | + const storeSecretMaterial = yield* SecretMaterialStorerService; | |
| 2251 | + const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption(); | |
| 2257 | 2252 | |
| 2258 | 2253 | return createRuntimeSourceAuthService({ |
| 2259 | 2254 | rows, |
| 2260 | 2255 | liveExecutionManager, |
| 2256 | + sourceStore, | |
| 2257 | + sourceMaterialization, | |
| 2258 | + resolveSecretMaterial, | |
| 2259 | + storeSecretMaterial, | |
| 2261 | 2260 | getLocalServerBaseUrl: input.getLocalServerBaseUrl, |
| 2262 | − localConfig: input.localConfig, | |
| 2263 | − workspaceRoot: input.workspaceRoot, | |
| 2264 | − localWorkspaceState: input.localWorkspaceState, | |
| 2261 | + localWorkspaceState: runtimeLocalWorkspace ?? undefined, | |
| 2265 | 2262 | }); |
| 2266 | 2263 | }), |
| 2267 | 2264 | ); |
| @@ -26,14 +26,12 @@ import { · 25 unmodified lines | ||
| 26 | 26 | getSourceAdapterForOperation, |
| 27 | 27 | } from "./source-adapters"; |
| 28 | 28 | import { |
| 29 | − loadSourceWithRecipe, | |
| 29 | + RuntimeSourceRecipeStoreService, | |
| 30 | 30 | recipeToolMetadata, |
| 31 | 31 | recipeToolPath, |
| 32 | 32 | } from "./source-recipes-runtime"; |
| 33 | 33 | import type { SourceAdapterPersistedOperationMetadata } from "./source-adapters/types"; |
| 34 | 34 | import { namespaceFromSourceName } from "./source-names"; |
| 35 | −import { loadSourceById } from "./source-store"; | |
| 36 | −import { ControlPlaneStore } from "./store"; | |
| 37 | 35 | |
| 38 | 36 | const sourceInspectOps = { |
| 39 | 37 | bundle: operationErrors("sources.inspect.bundle"), |
| @@ -109,10 +107,9 @@ const loadSourceRecipeRecord = (input: { · 69 unmodified lines | ||
| 109 | 107 | workspaceId: WorkspaceId; |
| 110 | 108 | sourceId: SourceId; |
| 111 | 109 | }) => |
| 112 | − Effect.flatMap(ControlPlaneStore, (store) => | |
| 110 | + Effect.flatMap(RuntimeSourceRecipeStoreService, (sourceRecipeStore) => | |
| 113 | 111 | Effect.gen(function* () { |
| 114 | − const recipe = yield* loadSourceWithRecipe({ | |
| 115 | − rows: store, | |
| 112 | + const recipe = yield* sourceRecipeStore.loadSourceWithRecipe({ | |
| 116 | 113 | workspaceId: input.workspaceId, |
| 117 | 114 | sourceId: input.sourceId, |
| 118 | 115 | }).pipe( |
| @@ -390,10 +387,9 @@ export const getSourceInspectionSchemaBundle = (input: { · 271 unmodified lines | ||
| 390 | 387 | sourceId: SourceId; |
| 391 | 388 | schemaBundleId: string; |
| 392 | 389 | }) => |
| 393 | − Effect.flatMap(ControlPlaneStore, (store) => | |
| 390 | + Effect.flatMap(RuntimeSourceRecipeStoreService, (sourceRecipeStore) => | |
| 394 | 391 | Effect.gen(function* () { |
| 395 | − const recipe = yield* loadSourceWithRecipe({ | |
| 396 | − rows: store, | |
| 392 | + const recipe = yield* sourceRecipeStore.loadSourceWithRecipe({ | |
| 397 | 393 | workspaceId: input.workspaceId, |
| 398 | 394 | sourceId: input.sourceId, |
| 399 | 395 | }).pipe( |
| @@ -3,51 +3,99 @@ import type { · 2 unmodified lines | ||
| 3 | 3 | Source, |
| 4 | 4 | SourceStatus, |
| 5 | 5 | } from "#schema"; |
| 6 | +import * as Context from "effect/Context"; | |
| 6 | 7 | import * as Effect from "effect/Effect"; |
| 8 | +import * as Layer from "effect/Layer"; | |
| 7 | 9 | |
| 8 | −import type { ControlPlaneStoreShape } from "./store"; | |
| 9 | 10 | import { |
| 10 | − requireRuntimeLocalWorkspace, | |
| 11 | + RuntimeLocalWorkspaceService, | |
| 12 | + type RuntimeLocalWorkspaceState, | |
| 11 | 13 | } from "./local-runtime-context"; |
| 12 | 14 | import { |
| 13 | − type WorkspaceStorageServices, | |
| 14 | 15 | SourceArtifactStore, |
| 16 | + type SourceArtifactStoreShape, | |
| 15 | 17 | WorkspaceStateStore, |
| 18 | + type WorkspaceStateStoreShape, | |
| 16 | 19 | } from "./local-storage"; |
| 17 | 20 | import { |
| 18 | 21 | type LocalWorkspaceState, |
| 19 | 22 | } from "./local-workspace-state"; |
| 20 | −import { resolveSourceAuthMaterial } from "./source-auth-material"; | |
| 23 | +import { | |
| 24 | + RuntimeSourceAuthMaterialService, | |
| 25 | +} from "./source-auth-material"; | |
| 21 | 26 | import { |
| 22 | 27 | getSourceAdapterForSource, |
| 23 | 28 | } from "./source-adapters"; |
| 24 | 29 | import { |
| 25 | 30 | materializationFromMcpManifestEntries, |
| 26 | 31 | } from "./source-adapters/mcp"; |
| 27 | −import type { | |
| 28 | − ResolveSecretMaterial as ResolveSourceSecretMaterial, | |
| 29 | −} from "./secret-material-providers"; | |
| 32 | +import { SecretMaterialResolverService } from "./secret-material-providers"; | |
| 30 | 33 | |
| 31 | 34 | const shouldIndexSource = (source: Source): boolean => |
| 32 | 35 | source.enabled |
| 33 | 36 | && source.status === "connected" |
| 34 | 37 | && getSourceAdapterForSource(source).family !== "internal"; |
| 35 | 38 | |
| 36 | −export const syncSourceMaterialization = (input: { | |
| 37 | − rows: ControlPlaneStoreShape; | |
| 38 | − source: Source; | |
| 39 | − actorAccountId?: AccountId | null; | |
| 40 | − resolveSecretMaterial: ResolveSourceSecretMaterial; | |
| 41 | −}): Effect.Effect<void, Error, WorkspaceStorageServices> => | |
| 39 | +type RuntimeSourceMaterializationDeps = { | |
| 40 | + runtimeLocalWorkspace: RuntimeLocalWorkspaceState; | |
| 41 | + workspaceStateStore: WorkspaceStateStoreShape; | |
| 42 | + sourceArtifactStore: SourceArtifactStoreShape; | |
| 43 | + resolveSecretMaterial: Effect.Effect.Success<typeof SecretMaterialResolverService>; | |
| 44 | + sourceAuthMaterialService: Effect.Effect.Success<typeof RuntimeSourceAuthMaterialService>; | |
| 45 | +}; | |
| 46 | + | |
| 47 | +type SourceMaterializationServices = | |
| 48 | + | RuntimeLocalWorkspaceService | |
| 49 | + | WorkspaceStateStore | |
| 50 | + | SourceArtifactStore | |
| 51 | + | RuntimeSourceAuthMaterialService | |
| 52 | + | SecretMaterialResolverService; | |
| 53 | + | |
| 54 | +export type RuntimeSourceMaterializationShape = { | |
| 55 | + sync: (input: { | |
| 56 | + source: Source; | |
| 57 | + actorAccountId?: AccountId | null; | |
| 58 | + }) => Effect.Effect<void, Error, never>; | |
| 59 | + persistMcpRecipeMaterializationFromManifest: (input: { | |
| 60 | + source: Source; | |
| 61 | + manifestEntries: Parameters<typeof materializationFromMcpManifestEntries>[0]["manifestEntries"]; | |
| 62 | + }) => Effect.Effect<void, Error, never>; | |
| 63 | +}; | |
| 64 | + | |
| 65 | +export class RuntimeSourceMaterializationService extends Context.Tag( | |
| 66 | + "#runtime/RuntimeSourceMaterializationService", | |
| 67 | +)<RuntimeSourceMaterializationService, RuntimeSourceMaterializationShape>() {} | |
| 68 | + | |
| 69 | +const ensureRuntimeMaterializationWorkspace = ( | |
| 70 | + deps: RuntimeSourceMaterializationDeps, | |
| 71 | + workspaceId: Source["workspaceId"], | |
| 72 | +) => { | |
| 73 | + if (deps.runtimeLocalWorkspace.installation.workspaceId !== workspaceId) { | |
| 74 | + return Effect.fail( | |
| 75 | + new Error( | |
| 76 | + `Runtime local workspace mismatch: expected ${workspaceId}, got ${deps.runtimeLocalWorkspace.installation.workspaceId}`, | |
| 77 | + ), | |
| 78 | + ); | |
| 79 | + } | |
| 80 | + | |
| 81 | + return Effect.succeed(deps.runtimeLocalWorkspace.context); | |
| 82 | +}; | |
| 83 | + | |
| 84 | +const syncSourceMaterializationWithDeps = ( | |
| 85 | + deps: RuntimeSourceMaterializationDeps, | |
| 86 | + input: { | |
| 87 | + source: Source; | |
| 88 | + actorAccountId?: AccountId | null; | |
| 89 | + }, | |
| 90 | +): Effect.Effect<void, Error, never> => | |
| 42 | 91 | Effect.gen(function* () { |
| 43 | − const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.source.workspaceId); | |
| 44 | − const workspaceStateStore = yield* WorkspaceStateStore; | |
| 45 | − const sourceArtifactStore = yield* SourceArtifactStore; | |
| 92 | + const workspaceContext = yield* ensureRuntimeMaterializationWorkspace( | |
| 93 | + deps, | |
| 94 | + input.source.workspaceId, | |
| 95 | + ); | |
| 46 | 96 | |
| 47 | 97 | if (!shouldIndexSource(input.source)) { |
| 48 | − const state = yield* workspaceStateStore.load( | |
| 49 | − runtimeLocalWorkspace.context, | |
| 50 | − ); | |
| 98 | + const state = yield* deps.workspaceStateStore.load(workspaceContext); | |
| 51 | 99 | const existingSourceState = state.sources[input.source.id]; |
| 52 | 100 | const nextState: LocalWorkspaceState = { |
| 53 | 101 | ...state, |
| @@ -62,8 +110,8 @@ export const syncSourceMaterialization = (input: { · 8 unmodified lines | ||
| 62 | 110 | }, |
| 63 | 111 | }, |
| 64 | 112 | }; |
| 65 | − yield* workspaceStateStore.write({ | |
| 66 | − context: runtimeLocalWorkspace.context, | |
| 113 | + yield* deps.workspaceStateStore.write({ | |
| 114 | + context: workspaceContext, | |
| 67 | 115 | state: nextState, |
| 68 | 116 | }); |
| 69 | 117 | return; |
| @@ -72,28 +120,24 @@ export const syncSourceMaterialization = (input: { · 2 unmodified lines | ||
| 72 | 120 | const adapter = getSourceAdapterForSource(input.source); |
| 73 | 121 | const materialization = yield* adapter.materializeSource({ |
| 74 | 122 | source: input.source, |
| 75 | − resolveSecretMaterial: input.resolveSecretMaterial, | |
| 123 | + resolveSecretMaterial: deps.resolveSecretMaterial, | |
| 76 | 124 | resolveAuthMaterialForSlot: (slot) => |
| 77 | − resolveSourceAuthMaterial({ | |
| 78 | − rows: input.rows, | |
| 125 | + deps.sourceAuthMaterialService.resolve({ | |
| 79 | 126 | source: input.source, |
| 80 | 127 | slot, |
| 81 | 128 | actorAccountId: input.actorAccountId, |
| 82 | − resolveSecretMaterial: input.resolveSecretMaterial, | |
| 83 | 129 | }), |
| 84 | 130 | }); |
| 85 | − yield* sourceArtifactStore.write({ | |
| 86 | − context: runtimeLocalWorkspace.context, | |
| 131 | + yield* deps.sourceArtifactStore.write({ | |
| 132 | + context: workspaceContext, | |
| 87 | 133 | sourceId: input.source.id, |
| 88 | − artifact: sourceArtifactStore.build({ | |
| 134 | + artifact: deps.sourceArtifactStore.build({ | |
| 89 | 135 | source: input.source, |
| 90 | 136 | materialization, |
| 91 | 137 | }), |
| 92 | 138 | }); |
| 93 | 139 | |
| 94 | − const state = yield* workspaceStateStore.load( | |
| 95 | − runtimeLocalWorkspace.context, | |
| 96 | − ); | |
| 140 | + const state = yield* deps.workspaceStateStore.load(workspaceContext); | |
| 97 | 141 | const existingSourceState = state.sources[input.source.id]; |
| 98 | 142 | const nextState: LocalWorkspaceState = { |
| 99 | 143 | ...state, |
| @@ -108,32 +152,110 @@ export const syncSourceMaterialization = (input: { · 8 unmodified lines | ||
| 108 | 152 | }, |
| 109 | 153 | }, |
| 110 | 154 | }; |
| 111 | − yield* workspaceStateStore.write({ | |
| 112 | − context: runtimeLocalWorkspace.context, | |
| 155 | + yield* deps.workspaceStateStore.write({ | |
| 156 | + context: workspaceContext, | |
| 113 | 157 | state: nextState, |
| 114 | 158 | }); |
| 115 | 159 | }); |
| 116 | 160 | |
| 117 | −export const persistMcpRecipeMaterializationFromManifest = (input: { | |
| 118 | − rows: ControlPlaneStoreShape; | |
| 119 | − source: Source; | |
| 120 | − manifestEntries: Parameters<typeof materializationFromMcpManifestEntries>[0]["manifestEntries"]; | |
| 121 | −}): Effect.Effect<void, Error, WorkspaceStorageServices> => | |
| 161 | +const persistMcpRecipeMaterializationFromManifestWithDeps = ( | |
| 162 | + deps: RuntimeSourceMaterializationDeps, | |
| 163 | + input: { | |
| 164 | + source: Source; | |
| 165 | + manifestEntries: Parameters<typeof materializationFromMcpManifestEntries>[0]["manifestEntries"]; | |
| 166 | + }, | |
| 167 | +): Effect.Effect<void, Error, never> => | |
| 122 | 168 | Effect.gen(function* () { |
| 123 | − const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.source.workspaceId); | |
| 124 | − const sourceArtifactStore = yield* SourceArtifactStore; | |
| 169 | + const workspaceContext = yield* ensureRuntimeMaterializationWorkspace( | |
| 170 | + deps, | |
| 171 | + input.source.workspaceId, | |
| 172 | + ); | |
| 125 | 173 | const materialization = materializationFromMcpManifestEntries({ |
| 126 | 174 | recipeRevisionId: "src_recipe_rev_materialization" as never, |
| 127 | 175 | endpoint: input.source.endpoint, |
| 128 | 176 | manifestEntries: input.manifestEntries, |
| 129 | 177 | }); |
| 130 | 178 | |
| 131 | − yield* sourceArtifactStore.write({ | |
| 132 | − context: runtimeLocalWorkspace.context, | |
| 179 | + yield* deps.sourceArtifactStore.write({ | |
| 180 | + context: workspaceContext, | |
| 133 | 181 | sourceId: input.source.id, |
| 134 | − artifact: sourceArtifactStore.build({ | |
| 182 | + artifact: deps.sourceArtifactStore.build({ | |
| 135 | 183 | source: input.source, |
| 136 | 184 | materialization, |
| 137 | 185 | }), |
| 138 | 186 | }); |
| 139 | 187 | }); |
| 188 | + | |
| 189 | +export const syncSourceMaterialization = (input: { | |
| 190 | + source: Source; | |
| 191 | + actorAccountId?: AccountId | null; | |
| 192 | +}): Effect.Effect<void, Error, SourceMaterializationServices> => | |
| 193 | + Effect.gen(function* () { | |
| 194 | + const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService; | |
| 195 | + const workspaceStateStore = yield* WorkspaceStateStore; | |
| 196 | + const sourceArtifactStore = yield* SourceArtifactStore; | |
| 197 | + const resolveSecretMaterial = yield* SecretMaterialResolverService; | |
| 198 | + const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService; | |
| 199 | + | |
| 200 | + return yield* syncSourceMaterializationWithDeps( | |
| 201 | + { | |
| 202 | + runtimeLocalWorkspace, | |
| 203 | + workspaceStateStore, | |
| 204 | + sourceArtifactStore, | |
| 205 | + resolveSecretMaterial, | |
| 206 | + sourceAuthMaterialService, | |
| 207 | + }, | |
| 208 | + { | |
| 209 | + source: input.source, | |
| 210 | + actorAccountId: input.actorAccountId, | |
| 211 | + }, | |
| 212 | + ); | |
| 213 | + }); | |
| 214 | + | |
| 215 | +export const persistMcpRecipeMaterializationFromManifest = (input: { | |
| 216 | + source: Source; | |
| 217 | + manifestEntries: Parameters<typeof materializationFromMcpManifestEntries>[0]["manifestEntries"]; | |
| 218 | +}): Effect.Effect<void, Error, SourceMaterializationServices> => | |
| 219 | + Effect.gen(function* () { | |
| 220 | + const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService; | |
| 221 | + const workspaceStateStore = yield* WorkspaceStateStore; | |
| 222 | + const sourceArtifactStore = yield* SourceArtifactStore; | |
| 223 | + const resolveSecretMaterial = yield* SecretMaterialResolverService; | |
| 224 | + const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService; | |
| 225 | + | |
| 226 | + return yield* persistMcpRecipeMaterializationFromManifestWithDeps( | |
| 227 | + { | |
| 228 | + runtimeLocalWorkspace, | |
| 229 | + workspaceStateStore, | |
| 230 | + sourceArtifactStore, | |
| 231 | + resolveSecretMaterial, | |
| 232 | + sourceAuthMaterialService, | |
| 233 | + }, | |
| 234 | + input, | |
| 235 | + ); | |
| 236 | + }); | |
| 237 | + | |
| 238 | +export const RuntimeSourceMaterializationLive = Layer.effect( | |
| 239 | + RuntimeSourceMaterializationService, | |
| 240 | + Effect.gen(function* () { | |
| 241 | + const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService; | |
| 242 | + const workspaceStateStore = yield* WorkspaceStateStore; | |
| 243 | + const sourceArtifactStore = yield* SourceArtifactStore; | |
| 244 | + const resolveSecretMaterial = yield* SecretMaterialResolverService; | |
| 245 | + const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService; | |
| 246 | + | |
| 247 | + const deps: RuntimeSourceMaterializationDeps = { | |
| 248 | + runtimeLocalWorkspace, | |
| 249 | + workspaceStateStore, | |
| 250 | + sourceArtifactStore, | |
| 251 | + resolveSecretMaterial, | |
| 252 | + sourceAuthMaterialService, | |
| 253 | + }; | |
| 254 | + | |
| 255 | + return RuntimeSourceMaterializationService.of({ | |
| 256 | + sync: (input) => syncSourceMaterializationWithDeps(deps, input), | |
| 257 | + persistMcpRecipeMaterializationFromManifest: (input) => | |
| 258 | + persistMcpRecipeMaterializationFromManifestWithDeps(deps, input), | |
| 259 | + }); | |
| 260 | + }), | |
| 261 | +); | |
| @@ -12,24 +12,29 @@ import type { · 11 unmodified lines | ||
| 12 | 12 | StoredSourceRecipeRevisionRecord, |
| 13 | 13 | WorkspaceId, |
| 14 | 14 | } from "#schema"; |
| 15 | +import * as Context from "effect/Context"; | |
| 15 | 16 | import * as Effect from "effect/Effect"; |
| 17 | +import * as Layer from "effect/Layer"; | |
| 16 | 18 | import { LocalSourceArtifactMissingError } from "./local-errors"; |
| 17 | 19 | import { |
| 18 | − requireRuntimeLocalWorkspace, | |
| 20 | + RuntimeLocalWorkspaceService, | |
| 21 | + type RuntimeLocalWorkspaceState, | |
| 19 | 22 | } from "./local-runtime-context"; |
| 20 | 23 | import { |
| 21 | − type WorkspaceStorageServices, | |
| 22 | 24 | SourceArtifactStore, |
| 25 | + type SourceArtifactStoreShape, | |
| 23 | 26 | } from "./local-storage"; |
| 24 | 27 | import { namespaceFromSourceName } from "./source-names"; |
| 25 | 28 | import { |
| 26 | 29 | getSourceAdapterForOperation, |
| 27 | 30 | getSourceAdapterForSource, |
| 28 | 31 | } from "./source-adapters"; |
| 29 | −import type { ControlPlaneStoreShape } from "./store"; | |
| 30 | 32 | import type { SourceAdapterPersistedOperationMetadata } from "./source-adapters/types"; |
| 31 | 33 | import { firstSchemaBundle } from "./source-adapters/shared"; |
| 32 | −import { loadSourceById, loadSourcesInWorkspace } from "./source-store"; | |
| 34 | +import { | |
| 35 | + RuntimeSourceStoreService, | |
| 36 | + type RuntimeSourceStore, | |
| 37 | +} from "./source-store"; | |
| 33 | 38 | |
| 34 | 39 | type RecipeManifest = unknown | null; |
| 35 | 40 | |
| @@ -167,46 +172,119 @@ const primarySchemaBundleForRevision = (input: { · 131 unmodified lines | ||
| 167 | 172 | : null; |
| 168 | 173 | }; |
| 169 | 174 | |
| 170 | −export const loadWorkspaceSourceRecipes = (input: { | |
| 171 | − rows: ControlPlaneStoreShape; | |
| 175 | +const sourceRecordFromRecipeArtifact = (input: { | |
| 176 | + source: Source; | |
| 177 | + artifact: { | |
| 178 | + recipeId: StoredSourceRecord["recipeId"]; | |
| 179 | + revision: StoredSourceRecipeRevisionRecord; | |
| 180 | + }; | |
| 181 | +}): StoredSourceRecord => ({ | |
| 182 | + id: input.source.id, | |
| 183 | + workspaceId: input.source.workspaceId, | |
| 184 | + recipeId: input.artifact.recipeId, | |
| 185 | + recipeRevisionId: input.artifact.revision.id, | |
| 186 | + name: input.source.name, | |
| 187 | + kind: input.source.kind, | |
| 188 | + endpoint: input.source.endpoint, | |
| 189 | + status: input.source.status, | |
| 190 | + enabled: input.source.enabled, | |
| 191 | + namespace: input.source.namespace, | |
| 192 | + importAuthPolicy: input.source.importAuthPolicy, | |
| 193 | + bindingConfigJson: getSourceAdapterForSource(input.source).serializeBindingConfig(input.source), | |
| 194 | + sourceHash: input.source.sourceHash, | |
| 195 | + lastError: input.source.lastError, | |
| 196 | + createdAt: input.source.createdAt, | |
| 197 | + updatedAt: input.source.updatedAt, | |
| 198 | +}); | |
| 199 | + | |
| 200 | +type RuntimeSourceRecipeStoreShape = { | |
| 201 | + loadWorkspaceSourceRecipes: (input: { | |
| 202 | + workspaceId: WorkspaceId; | |
| 203 | + actorAccountId?: AccountId | null; | |
| 204 | + }) => Effect.Effect<readonly LoadedSourceRecipe[], Error, never>; | |
| 205 | + loadSourceWithRecipe: (input: { | |
| 206 | + workspaceId: WorkspaceId; | |
| 207 | + sourceId: Source["id"]; | |
| 208 | + actorAccountId?: AccountId | null; | |
| 209 | + }) => Effect.Effect<LoadedSourceRecipe, Error | LocalSourceArtifactMissingError, never>; | |
| 210 | + loadWorkspaceSourceRecipeToolIndex: (input: { | |
| 211 | + workspaceId: WorkspaceId; | |
| 212 | + actorAccountId?: AccountId | null; | |
| 213 | + includeSchemas: boolean; | |
| 214 | + }) => Effect.Effect<readonly LoadedSourceRecipeToolIndexEntry[], Error, never>; | |
| 215 | + loadWorkspaceSourceRecipeToolByPath: (input: { | |
| 216 | + workspaceId: WorkspaceId; | |
| 217 | + path: string; | |
| 218 | + actorAccountId?: AccountId | null; | |
| 219 | + includeSchemas: boolean; | |
| 220 | + }) => Effect.Effect<LoadedSourceRecipeToolIndexEntry | null, Error, never>; | |
| 221 | + loadWorkspaceSchemaBundle: (input: { | |
| 222 | + workspaceId: WorkspaceId; | |
| 223 | + id: string; | |
| 224 | + }) => Effect.Effect< | |
| 225 | + { id: string; kind: string; hash: string; refsJson: string } | null, | |
| 226 | + Error, | |
| 227 | + never | |
| 228 | + >; | |
| 229 | +}; | |
| 230 | + | |
| 231 | +export type RuntimeSourceRecipeStore = RuntimeSourceRecipeStoreShape; | |
| 232 | + | |
| 233 | +export class RuntimeSourceRecipeStoreService extends Context.Tag( | |
| 234 | + "#runtime/RuntimeSourceRecipeStoreService", | |
| 235 | +)<RuntimeSourceRecipeStoreService, RuntimeSourceRecipeStoreShape>() {} | |
| 236 | + | |
| 237 | +type RuntimeSourceRecipeStoreDeps = { | |
| 238 | + runtimeLocalWorkspace: RuntimeLocalWorkspaceState; | |
| 239 | + sourceStore: RuntimeSourceStore; | |
| 240 | + sourceArtifactStore: SourceArtifactStoreShape; | |
| 241 | +}; | |
| 242 | + | |
| 243 | +type SourceRecipeRuntimeServices = | |
| 244 | + | RuntimeLocalWorkspaceService | |
| 245 | + | RuntimeSourceStoreService | |
| 246 | + | SourceArtifactStore; | |
| 247 | + | |
| 248 | +const ensureRuntimeRecipeWorkspace = ( | |
| 249 | + deps: RuntimeSourceRecipeStoreDeps, | |
| 250 | + workspaceId: WorkspaceId, | |
| 251 | +) => { | |
| 252 | + if (deps.runtimeLocalWorkspace.installation.workspaceId !== workspaceId) { | |
| 253 | + return Effect.fail( | |
| 254 | + new Error( | |
| 255 | + `Runtime local workspace mismatch: expected ${workspaceId}, got ${deps.runtimeLocalWorkspace.installation.workspaceId}`, | |
| 256 | + ), | |
| 257 | + ); | |
| 258 | + } | |
| 259 | + | |
| 260 | + return Effect.succeed(deps.runtimeLocalWorkspace.context); | |
| 261 | +}; | |
| 262 | + | |
| 263 | +const loadWorkspaceSourceRecipesWithDeps = (deps: RuntimeSourceRecipeStoreDeps, input: { | |
| 172 | 264 | workspaceId: WorkspaceId; |
| 173 | 265 | actorAccountId?: AccountId | null; |
| 174 | −}): Effect.Effect<readonly LoadedSourceRecipe[], Error, WorkspaceStorageServices> => | |
| 266 | +}): Effect.Effect<readonly LoadedSourceRecipe[], Error, never> => | |
| 175 | 267 | Effect.gen(function* () { |
| 176 | − const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.workspaceId); | |
| 177 | − const sourceArtifactStore = yield* SourceArtifactStore; | |
| 178 | − const sources = yield* loadSourcesInWorkspace(input.rows, input.workspaceId, { | |
| 179 | − actorAccountId: input.actorAccountId, | |
| 180 | − }); | |
| 268 | + const workspaceContext = yield* ensureRuntimeRecipeWorkspace( | |
| 269 | + deps, | |
| 270 | + input.workspaceId, | |
| 271 | + ); | |
| 272 | + const sources = yield* deps.sourceStore.loadSourcesInWorkspace( | |
| 273 | + input.workspaceId, | |
| 274 | + { | |
| 275 | + actorAccountId: input.actorAccountId, | |
| 276 | + }, | |
| 277 | + ); | |
| 181 | 278 | const localRecipes = yield* Effect.forEach(sources, (source) => |
| 182 | 279 | Effect.gen(function* () { |
| 183 | − const artifact = yield* sourceArtifactStore.read({ | |
| 184 | − context: runtimeLocalWorkspace.context, | |
| 280 | + const artifact = yield* deps.sourceArtifactStore.read({ | |
| 281 | + context: workspaceContext, | |
| 185 | 282 | sourceId: source.id, |
| 186 | 283 | }); |
| 187 | 284 | if (artifact === null) { |
| 188 | 285 | return null; |
| 189 | 286 | } |
| 190 | 287 | |
| 191 | − const sourceRecord: StoredSourceRecord = { | |
| 192 | − id: source.id, | |
| 193 | − workspaceId: source.workspaceId, | |
| 194 | − recipeId: artifact.recipeId, | |
| 195 | − recipeRevisionId: artifact.revision.id, | |
| 196 | − name: source.name, | |
| 197 | − kind: source.kind, | |
| 198 | − endpoint: source.endpoint, | |
| 199 | − status: source.status, | |
| 200 | − enabled: source.enabled, | |
| 201 | − namespace: source.namespace, | |
| 202 | − importAuthPolicy: source.importAuthPolicy, | |
| 203 | − bindingConfigJson: getSourceAdapterForSource(source).serializeBindingConfig(source), | |
| 204 | − sourceHash: source.sourceHash, | |
| 205 | − lastError: source.lastError, | |
| 206 | − createdAt: source.createdAt, | |
| 207 | − updatedAt: source.updatedAt, | |
| 208 | − }; | |
| 209 | − | |
| 210 | 288 | const manifest = yield* parseManifestForRecipe({ |
| 211 | 289 | source, |
| 212 | 290 | revision: artifact.revision, |
| @@ -214,7 +292,7 @@ export const loadWorkspaceSourceRecipes = (input: { · 1 unmodified line | ||
| 214 | 292 | |
| 215 | 293 | return { |
| 216 | 294 | source, |
| 217 | − sourceRecord, | |
| 295 | + sourceRecord: sourceRecordFromRecipeArtifact({ source, artifact }), | |
| 218 | 296 | revision: artifact.revision, |
| 219 | 297 | documents: artifact.documents, |
| 220 | 298 | schemaBundles: artifact.schemaBundles, |
| @@ -226,22 +304,23 @@ export const loadWorkspaceSourceRecipes = (input: { · 5 unmodified lines | ||
| 226 | 304 | return localRecipes.filter((recipe): recipe is LoadedSourceRecipe => recipe !== null); |
| 227 | 305 | }); |
| 228 | 306 | |
| 229 | −export const loadSourceWithRecipe = (input: { | |
| 230 | − rows: ControlPlaneStoreShape; | |
| 307 | +const loadSourceWithRecipeWithDeps = (deps: RuntimeSourceRecipeStoreDeps, input: { | |
| 231 | 308 | workspaceId: WorkspaceId; |
| 232 | 309 | sourceId: Source["id"]; |
| 233 | 310 | actorAccountId?: AccountId | null; |
| 234 | −}): Effect.Effect<LoadedSourceRecipe, Error | LocalSourceArtifactMissingError, WorkspaceStorageServices> => | |
| 311 | +}): Effect.Effect<LoadedSourceRecipe, Error | LocalSourceArtifactMissingError, never> => | |
| 235 | 312 | Effect.gen(function* () { |
| 236 | − const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.workspaceId); | |
| 237 | − const sourceArtifactStore = yield* SourceArtifactStore; | |
| 238 | − const source = yield* loadSourceById(input.rows, { | |
| 313 | + const workspaceContext = yield* ensureRuntimeRecipeWorkspace( | |
| 314 | + deps, | |
| 315 | + input.workspaceId, | |
| 316 | + ); | |
| 317 | + const source = yield* deps.sourceStore.loadSourceById({ | |
| 239 | 318 | workspaceId: input.workspaceId, |
| 240 | 319 | sourceId: input.sourceId, |
| 241 | 320 | actorAccountId: input.actorAccountId, |
| 242 | 321 | }); |
| 243 | − const artifact = yield* sourceArtifactStore.read({ | |
| 244 | − context: runtimeLocalWorkspace.context, | |
| 322 | + const artifact = yield* deps.sourceArtifactStore.read({ | |
| 323 | + context: workspaceContext, | |
| 245 | 324 | sourceId: source.id, |
| 246 | 325 | }); |
| 247 | 326 | if (artifact === null) { |
| @@ -253,24 +332,6 @@ export const loadSourceWithRecipe = (input: { · 5 unmodified lines | ||
| 253 | 332 | ); |
| 254 | 333 | } |
| 255 | 334 | |
| 256 | − const sourceRecord: StoredSourceRecord = { | |
| 257 | − id: source.id, | |
| 258 | − workspaceId: source.workspaceId, | |
| 259 | − recipeId: artifact.recipeId, | |
| 260 | − recipeRevisionId: artifact.revision.id, | |
| 261 | − name: source.name, | |
| 262 | − kind: source.kind, | |
| 263 | − endpoint: source.endpoint, | |
| 264 | − status: source.status, | |
| 265 | − enabled: source.enabled, | |
| 266 | − namespace: source.namespace, | |
| 267 | − importAuthPolicy: source.importAuthPolicy, | |
| 268 | − bindingConfigJson: getSourceAdapterForSource(source).serializeBindingConfig(source), | |
| 269 | − sourceHash: source.sourceHash, | |
| 270 | − lastError: source.lastError, | |
| 271 | − createdAt: source.createdAt, | |
| 272 | − updatedAt: source.updatedAt, | |
| 273 | − }; | |
| 274 | 335 | const manifest = yield* parseManifestForRecipe({ |
| 275 | 336 | source, |
| 276 | 337 | revision: artifact.revision, |
| @@ -278,7 +339,7 @@ export const loadSourceWithRecipe = (input: { · 1 unmodified line | ||
| 278 | 339 | |
| 279 | 340 | return { |
| 280 | 341 | source, |
| 281 | − sourceRecord, | |
| 342 | + sourceRecord: sourceRecordFromRecipeArtifact({ source, artifact }), | |
| 282 | 343 | revision: artifact.revision, |
| 283 | 344 | documents: artifact.documents, |
| 284 | 345 | schemaBundles: artifact.schemaBundles, |
| @@ -287,6 +348,49 @@ export const loadSourceWithRecipe = (input: { · 2 unmodified lines | ||
| 287 | 348 | } satisfies LoadedSourceRecipe; |
| 288 | 349 | }); |
| 289 | 350 | |
| 351 | +export const loadWorkspaceSourceRecipes = (input: { | |
| 352 | + workspaceId: WorkspaceId; | |
| 353 | + actorAccountId?: AccountId | null; | |
| 354 | +}): Effect.Effect<readonly LoadedSourceRecipe[], Error, SourceRecipeRuntimeServices> => | |
| 355 | + Effect.gen(function* () { | |
| 356 | + const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService; | |
| 357 | + const sourceStore = yield* RuntimeSourceStoreService; | |
| 358 | + const sourceArtifactStore = yield* SourceArtifactStore; | |
| 359 | + | |
| 360 | + return yield* loadWorkspaceSourceRecipesWithDeps( | |
| 361 | + { | |
| 362 | + runtimeLocalWorkspace, | |
| 363 | + sourceStore, | |
| 364 | + sourceArtifactStore, | |
| 365 | + }, | |
| 366 | + input, | |
| 367 | + ); | |
| 368 | + }); | |
| 369 | + | |
| 370 | +export const loadSourceWithRecipe = (input: { | |
| 371 | + workspaceId: WorkspaceId; | |
| 372 | + sourceId: Source["id"]; | |
| 373 | + actorAccountId?: AccountId | null; | |
| 374 | +}): Effect.Effect< | |
| 375 | + LoadedSourceRecipe, | |
| 376 | + Error | LocalSourceArtifactMissingError, | |
| 377 | + SourceRecipeRuntimeServices | |
| 378 | +> => | |
| 379 | + Effect.gen(function* () { | |
| 380 | + const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService; | |
| 381 | + const sourceStore = yield* RuntimeSourceStoreService; | |
| 382 | + const sourceArtifactStore = yield* SourceArtifactStore; | |
| 383 | + | |
| 384 | + return yield* loadSourceWithRecipeWithDeps( | |
| 385 | + { | |
| 386 | + runtimeLocalWorkspace, | |
| 387 | + sourceStore, | |
| 388 | + sourceArtifactStore, | |
| 389 | + }, | |
| 390 | + input, | |
| 391 | + ); | |
| 392 | + }); | |
| 393 | + | |
| 290 | 394 | export const expandRecipeTools = (input: { |
| 291 | 395 | recipes: readonly LoadedSourceRecipe[]; |
| 292 | 396 | includeSchemas: boolean; |
| @@ -348,14 +452,16 @@ export const expandRecipeTools = (input: { · 55 unmodified lines | ||
| 348 | 452 | ); |
| 349 | 453 | |
| 350 | 454 | export const loadWorkspaceSourceRecipeToolIndex = (input: { |
| 351 | − rows: ControlPlaneStoreShape; | |
| 352 | 455 | workspaceId: WorkspaceId; |
| 353 | 456 | actorAccountId?: AccountId | null; |
| 354 | 457 | includeSchemas: boolean; |
| 355 | −}): Effect.Effect<readonly LoadedSourceRecipeToolIndexEntry[], Error, WorkspaceStorageServices> => | |
| 458 | +}): Effect.Effect< | |
| 459 | + readonly LoadedSourceRecipeToolIndexEntry[], | |
| 460 | + Error, | |
| 461 | + SourceRecipeRuntimeServices | |
| 462 | +> => | |
| 356 | 463 | Effect.gen(function* () { |
| 357 | 464 | const recipes = yield* loadWorkspaceSourceRecipes({ |
| 358 | − rows: input.rows, | |
| 359 | 465 | workspaceId: input.workspaceId, |
| 360 | 466 | actorAccountId: input.actorAccountId, |
| 361 | 467 | }); |
| @@ -377,15 +483,17 @@ export const loadWorkspaceSourceRecipeToolIndex = (input: { · 15 unmodified lines | ||
| 377 | 483 | }); |
| 378 | 484 | |
| 379 | 485 | export const loadWorkspaceSourceRecipeToolByPath = (input: { |
| 380 | − rows: ControlPlaneStoreShape; | |
| 381 | 486 | workspaceId: WorkspaceId; |
| 382 | 487 | path: string; |
| 383 | 488 | actorAccountId?: AccountId | null; |
| 384 | 489 | includeSchemas: boolean; |
| 385 | −}): Effect.Effect<LoadedSourceRecipeToolIndexEntry | null, Error, WorkspaceStorageServices> => | |
| 490 | +}): Effect.Effect< | |
| 491 | + LoadedSourceRecipeToolIndexEntry | null, | |
| 492 | + Error, | |
| 493 | + SourceRecipeRuntimeServices | |
| 494 | +> => | |
| 386 | 495 | Effect.gen(function* () { |
| 387 | 496 | const recipes = yield* loadWorkspaceSourceRecipes({ |
| 388 | − rows: input.rows, | |
| 389 | 497 | workspaceId: input.workspaceId, |
| 390 | 498 | actorAccountId: input.actorAccountId, |
| 391 | 499 | }); |
| @@ -408,3 +516,83 @@ export const loadWorkspaceSourceRecipeToolByPath = (input: { · 16 unmodified lines | ||
| 408 | 516 | } |
| 409 | 517 | : null; |
| 410 | 518 | }); |
| 519 | + | |
| 520 | +export const RuntimeSourceRecipeStoreLive = Layer.effect( | |
| 521 | + RuntimeSourceRecipeStoreService, | |
| 522 | + Effect.gen(function* () { | |
| 523 | + const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService; | |
| 524 | + const sourceStore = yield* RuntimeSourceStoreService; | |
| 525 | + const sourceArtifactStore = yield* SourceArtifactStore; | |
| 526 | + | |
| 527 | + const deps: RuntimeSourceRecipeStoreDeps = { | |
| 528 | + runtimeLocalWorkspace, | |
| 529 | + sourceStore, | |
| 530 | + sourceArtifactStore, | |
| 531 | + }; | |
| 532 | + | |
| 533 | + return RuntimeSourceRecipeStoreService.of({ | |
| 534 | + loadWorkspaceSourceRecipes: (input) => | |
| 535 | + loadWorkspaceSourceRecipesWithDeps(deps, input), | |
| 536 | + loadSourceWithRecipe: (input) => | |
| 537 | + loadSourceWithRecipeWithDeps(deps, input), | |
| 538 | + loadWorkspaceSourceRecipeToolIndex: (input) => | |
| 539 | + Effect.gen(function* () { | |
| 540 | + const recipes = yield* loadWorkspaceSourceRecipesWithDeps(deps, input); | |
| 541 | + const tools = yield* expandRecipeTools({ | |
| 542 | + recipes, | |
| 543 | + includeSchemas: input.includeSchemas, | |
| 544 | + }); | |
| 545 | + return tools.map((tool) => ({ | |
| 546 | + path: tool.path, | |
| 547 | + searchNamespace: tool.searchNamespace, | |
| 548 | + searchText: tool.searchText, | |
| 549 | + source: tool.source, | |
| 550 | + sourceRecord: tool.sourceRecord, | |
| 551 | + operation: tool.operation, | |
| 552 | + metadata: tool.metadata, | |
| 553 | + schemaBundleId: tool.schemaBundleId, | |
| 554 | + descriptor: tool.descriptor, | |
| 555 | + })); | |
| 556 | + }), | |
| 557 | + loadWorkspaceSourceRecipeToolByPath: (input) => | |
| 558 | + Effect.gen(function* () { | |
| 559 | + const recipes = yield* loadWorkspaceSourceRecipesWithDeps(deps, input); | |
| 560 | + const tools = yield* expandRecipeTools({ | |
| 561 | + recipes, | |
| 562 | + includeSchemas: input.includeSchemas, | |
| 563 | + }); | |
| 564 | + const tool = tools.find((entry) => entry.path === input.path) ?? null; | |
| 565 | + return tool | |
| 566 | + ? { | |
| 567 | + path: tool.path, | |
| 568 | + searchNamespace: tool.searchNamespace, | |
| 569 | + searchText: tool.searchText, | |
| 570 | + source: tool.source, | |
| 571 | + sourceRecord: tool.sourceRecord, | |
| 572 | + operation: tool.operation, | |
| 573 | + metadata: tool.metadata, | |
| 574 | + schemaBundleId: tool.schemaBundleId, | |
| 575 | + descriptor: tool.descriptor, | |
| 576 | + } | |
| 577 | + : null; | |
| 578 | + }), | |
| 579 | + loadWorkspaceSchemaBundle: (input) => | |
| 580 | + Effect.gen(function* () { | |
| 581 | + const recipes = yield* loadWorkspaceSourceRecipesWithDeps(deps, { | |
| 582 | + workspaceId: input.workspaceId, | |
| 583 | + }); | |
| 584 | + const localBundle = recipes | |
| 585 | + .flatMap((recipe) => recipe.schemaBundles) | |
| 586 | + .find((schemaBundle) => schemaBundle.id === input.id); | |
| 587 | + return localBundle | |
| 588 | + ? { | |
| 589 | + id: localBundle.id, | |
| 590 | + kind: localBundle.bundleKind, | |
| 591 | + hash: localBundle.contentHash, | |
| 592 | + refsJson: localBundle.refsJson, | |
| 593 | + } | |
| 594 | + : null; | |
| 595 | + }), | |
| 596 | + }); | |
| 597 | + }), | |
| 598 | +); | |
| @@ -9,7 +9,9 @@ import type { · 8 unmodified lines | ||
| 9 | 9 | WorkspaceId, |
| 10 | 10 | } from "#schema"; |
| 11 | 11 | import { SourceIdSchema } from "#schema"; |
| 12 | +import * as Context from "effect/Context"; | |
| 12 | 13 | import * as Effect from "effect/Effect"; |
| 14 | +import * as Layer from "effect/Layer"; | |
| 13 | 15 | |
| 14 | 16 | import { |
| 15 | 17 | stableSourceRecipeId, |
| @@ -34,6 +36,8 @@ import { · 18 unmodified lines | ||
| 34 | 36 | } from "./local-errors"; |
| 35 | 37 | import { |
| 36 | 38 | requireRuntimeLocalWorkspace, |
| 39 | + RuntimeLocalWorkspaceService, | |
| 40 | + type RuntimeLocalWorkspaceState, | |
| 37 | 41 | } from "./local-runtime-context"; |
| 38 | 42 | import type { |
| 39 | 43 | SourceArtifactStoreShape, |
| @@ -56,7 +60,7 @@ import { · 16 unmodified lines | ||
| 56 | 60 | import { createDefaultSecretMaterialDeleter } from "./secret-material-providers"; |
| 57 | 61 | import { authArtifactSecretMaterialRefs } from "./auth-artifacts"; |
| 58 | 62 | import { removeAuthLeaseAndSecrets } from "./auth-leases"; |
| 59 | −import type { ControlPlaneStoreShape } from "./store"; | |
| 63 | +import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store"; | |
| 60 | 64 | import { getSourceAdapter } from "./source-adapters"; |
| 61 | 65 | import { slugify } from "./slug"; |
| 62 | 66 | |
| @@ -145,6 +149,27 @@ const deriveLocalSourceId = ( · 82 unmodified lines | ||
| 145 | 149 | return SourceIdSchema.make(candidate); |
| 146 | 150 | }; |
| 147 | 151 | |
| 152 | +type RuntimeSourceStoreDeps = { | |
| 153 | + rows: ControlPlaneStoreShape; | |
| 154 | + runtimeLocalWorkspace: RuntimeLocalWorkspaceState; | |
| 155 | + workspaceConfigStore: WorkspaceConfigStoreShape; | |
| 156 | + workspaceStateStore: WorkspaceStateStoreShape; | |
| 157 | + sourceArtifactStore: SourceArtifactStoreShape; | |
| 158 | +}; | |
| 159 | + | |
| 160 | +type ResolvedSourceStoreWorkspace = { | |
| 161 | + context: ResolvedLocalWorkspaceContext; | |
| 162 | + installation: { | |
| 163 | + workspaceId: WorkspaceId; | |
| 164 | + accountId: AccountId; | |
| 165 | + }; | |
| 166 | + workspaceConfigStore: WorkspaceConfigStoreShape; | |
| 167 | + workspaceStateStore: WorkspaceStateStoreShape; | |
| 168 | + sourceArtifactStore: SourceArtifactStoreShape; | |
| 169 | + loadedConfig: LoadedLocalExecutorConfig; | |
| 170 | + workspaceState: LocalWorkspaceState; | |
| 171 | +}; | |
| 172 | + | |
| 148 | 173 | const resolveLocalConfigSecretProviderAlias = (config: LoadedLocalExecutorConfig["config"]): string | null => { |
| 149 | 174 | const defaultAlias = trimOrNull(config?.secrets?.defaults?.env); |
| 150 | 175 | if (defaultAlias !== null && config?.secrets?.providers?.[defaultAlias]) { |
| @@ -243,45 +268,71 @@ const configAuthFromSource = (input: { · 92 unmodified lines | ||
| 243 | 268 | return input.existingConfigAuth; |
| 244 | 269 | }; |
| 245 | 270 | |
| 246 | −const resolveRuntimeLocalWorkspace = (workspaceId: WorkspaceId): Effect.Effect<{ | |
| 247 | − context: ResolvedLocalWorkspaceContext; | |
| 248 | − installation: { | |
| 249 | − workspaceId: WorkspaceId; | |
| 250 | − accountId: AccountId; | |
| 251 | − }; | |
| 252 | − workspaceConfigStore: WorkspaceConfigStoreShape; | |
| 253 | − workspaceStateStore: WorkspaceStateStoreShape; | |
| 254 | − sourceArtifactStore: SourceArtifactStoreShape; | |
| 255 | − loadedConfig: LoadedLocalExecutorConfig; | |
| 256 | − workspaceState: LocalWorkspaceState; | |
| 257 | −}, | |
| 271 | +const resolveRuntimeLocalWorkspaceFromDeps = ( | |
| 272 | + deps: RuntimeSourceStoreDeps, | |
| 273 | + workspaceId: WorkspaceId, | |
| 274 | +): Effect.Effect<ResolvedSourceStoreWorkspace, | |
| 275 | + | RuntimeLocalWorkspaceUnavailableError | |
| 276 | + | RuntimeLocalWorkspaceMismatchError | |
| 277 | + | LocalFileSystemError | |
| 278 | + | LocalExecutorConfigDecodeError | |
| 279 | + | LocalWorkspaceStateDecodeError | |
| 280 | + | Error, | |
| 281 | +never> => | |
| 282 | + Effect.gen(function* () { | |
| 283 | + if (deps.runtimeLocalWorkspace.installation.workspaceId !== workspaceId) { | |
| 284 | + return yield* Effect.fail( | |
| 285 | + new RuntimeLocalWorkspaceMismatchError({ | |
| 286 | + message: `Runtime local workspace mismatch: expected ${workspaceId}, got ${deps.runtimeLocalWorkspace.installation.workspaceId}`, | |
| 287 | + requestedWorkspaceId: workspaceId, | |
| 288 | + activeWorkspaceId: deps.runtimeLocalWorkspace.installation.workspaceId, | |
| 289 | + }), | |
| 290 | + ); | |
| 291 | + } | |
| 292 | + | |
| 293 | + const loadedConfig = yield* deps.workspaceConfigStore.load( | |
| 294 | + deps.runtimeLocalWorkspace.context, | |
| 295 | + ); | |
| 296 | + const workspaceState = yield* deps.workspaceStateStore.load( | |
| 297 | + deps.runtimeLocalWorkspace.context, | |
| 298 | + ); | |
| 299 | + | |
| 300 | + return { | |
| 301 | + context: deps.runtimeLocalWorkspace.context, | |
| 302 | + installation: deps.runtimeLocalWorkspace.installation, | |
| 303 | + workspaceConfigStore: deps.workspaceConfigStore, | |
| 304 | + workspaceStateStore: deps.workspaceStateStore, | |
| 305 | + sourceArtifactStore: deps.sourceArtifactStore, | |
| 306 | + loadedConfig, | |
| 307 | + workspaceState, | |
| 308 | + }; | |
| 309 | + }); | |
| 310 | + | |
| 311 | +const loadRuntimeSourceStoreDeps = ( | |
| 312 | + rows: ControlPlaneStoreShape, | |
| 313 | + workspaceId: WorkspaceId, | |
| 314 | +): Effect.Effect< | |
| 315 | + RuntimeSourceStoreDeps, | |
| 258 | 316 | | RuntimeLocalWorkspaceUnavailableError |
| 259 | 317 | | RuntimeLocalWorkspaceMismatchError |
| 260 | 318 | | LocalFileSystemError |
| 261 | 319 | | LocalExecutorConfigDecodeError |
| 262 | 320 | | LocalWorkspaceStateDecodeError |
| 263 | 321 | | Error, |
| 264 | −WorkspaceStorageServices> => | |
| 322 | + WorkspaceStorageServices | |
| 323 | +> => | |
| 265 | 324 | Effect.gen(function* () { |
| 266 | 325 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId); |
| 267 | 326 | const workspaceConfigStore = yield* WorkspaceConfigStore; |
| 268 | 327 | const workspaceStateStore = yield* WorkspaceStateStore; |
| 269 | 328 | const sourceArtifactStore = yield* SourceArtifactStore; |
| 270 | − const loadedConfig = yield* workspaceConfigStore.load( | |
| 271 | − runtimeLocalWorkspace.context, | |
| 272 | − ); | |
| 273 | − const workspaceState = yield* workspaceStateStore.load( | |
| 274 | − runtimeLocalWorkspace.context, | |
| 275 | − ); | |
| 276 | 329 | |
| 277 | 330 | return { |
| 278 | − context: runtimeLocalWorkspace.context, | |
| 279 | − installation: runtimeLocalWorkspace.installation, | |
| 331 | + rows, | |
| 332 | + runtimeLocalWorkspace, | |
| 280 | 333 | workspaceConfigStore, |
| 281 | 334 | workspaceStateStore, |
| 282 | 335 | sourceArtifactStore, |
| 283 | − loadedConfig, | |
| 284 | − workspaceState, | |
| 285 | 336 | }; |
| 286 | 337 | }); |
| 287 | 338 | |
| @@ -396,8 +447,8 @@ const buildLocalSourceRecord = (input: { · 108 unmodified lines | ||
| 396 | 447 | }; |
| 397 | 448 | }); |
| 398 | 449 | |
| 399 | −export const loadSourcesInWorkspace = ( | |
| 400 | − rows: ControlPlaneStoreShape, | |
| 450 | +const loadSourcesInWorkspaceWithDeps = ( | |
| 451 | + deps: RuntimeSourceStoreDeps, | |
| 401 | 452 | workspaceId: WorkspaceId, |
| 402 | 453 | options: { |
| 403 | 454 | actorAccountId?: AccountId | null; |
| @@ -411,11 +462,11 @@ export const loadSourcesInWorkspace = ( · 7 unmodified lines | ||
| 411 | 462 | | LocalWorkspaceStateDecodeError |
| 412 | 463 | | LocalConfiguredSourceNotFoundError |
| 413 | 464 | | Error, |
| 414 | − WorkspaceStorageServices | |
| 465 | + never | |
| 415 | 466 | > => |
| 416 | 467 | Effect.gen(function* () { |
| 417 | − const localWorkspace = yield* resolveRuntimeLocalWorkspace(workspaceId); | |
| 418 | − const authArtifacts = yield* rows.authArtifacts.listByWorkspaceId(workspaceId); | |
| 468 | + const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(deps, workspaceId); | |
| 469 | + const authArtifacts = yield* deps.rows.authArtifacts.listByWorkspaceId(workspaceId); | |
| 419 | 470 | return yield* Effect.forEach( |
| 420 | 471 | Object.keys(localWorkspace.loadedConfig.config?.sources ?? {}), |
| 421 | 472 | (sourceId) => |
| @@ -435,7 +486,104 @@ export const loadSourcesInWorkspace = ( · 13 unmodified lines | ||
| 435 | 486 | ); |
| 436 | 487 | }); |
| 437 | 488 | |
| 438 | −export const loadSourceById = (rows: ControlPlaneStoreShape, input: { | |
| 489 | +export const loadSourcesInWorkspace = ( | |
| 490 | + rows: ControlPlaneStoreShape, | |
| 491 | + workspaceId: WorkspaceId, | |
| 492 | + options: { | |
| 493 | + actorAccountId?: AccountId | null; | |
| 494 | + } = {}, | |
| 495 | +): Effect.Effect< | |
| 496 | + readonly Source[], | |
| 497 | + | RuntimeLocalWorkspaceUnavailableError | |
| 498 | + | RuntimeLocalWorkspaceMismatchError | |
| 499 | + | LocalFileSystemError | |
| 500 | + | LocalExecutorConfigDecodeError | |
| 501 | + | LocalWorkspaceStateDecodeError | |
| 502 | + | LocalConfiguredSourceNotFoundError | |
| 503 | + | Error, | |
| 504 | + WorkspaceStorageServices | |
| 505 | +> => | |
| 506 | + Effect.flatMap( | |
| 507 | + loadRuntimeSourceStoreDeps(rows, workspaceId), | |
| 508 | + (deps) => loadSourcesInWorkspaceWithDeps(deps, workspaceId, options), | |
| 509 | + ); | |
| 510 | + | |
| 511 | +const listLinkedSecretSourcesInWorkspaceWithDeps = ( | |
| 512 | + deps: RuntimeSourceStoreDeps, | |
| 513 | + workspaceId: WorkspaceId, | |
| 514 | + options: { | |
| 515 | + actorAccountId?: AccountId | null; | |
| 516 | + } = {}, | |
| 517 | +): Effect.Effect< | |
| 518 | + Map<string, Array<{ sourceId: string; sourceName: string }>>, | |
| 519 | + | RuntimeLocalWorkspaceUnavailableError | |
| 520 | + | RuntimeLocalWorkspaceMismatchError | |
| 521 | + | LocalFileSystemError | |
| 522 | + | LocalExecutorConfigDecodeError | |
| 523 | + | LocalWorkspaceStateDecodeError | |
| 524 | + | LocalConfiguredSourceNotFoundError | |
| 525 | + | Error, | |
| 526 | + never | |
| 527 | +> => | |
| 528 | + Effect.gen(function* () { | |
| 529 | + const [sources, authArtifacts, materialIds] = yield* Effect.all([ | |
| 530 | + loadSourcesInWorkspaceWithDeps(deps, workspaceId, { | |
| 531 | + actorAccountId: options.actorAccountId, | |
| 532 | + }), | |
| 533 | + deps.rows.authArtifacts.listByWorkspaceId(workspaceId), | |
| 534 | + deps.rows.secretMaterials.listAll().pipe( | |
| 535 | + Effect.map((materials) => new Set(materials.map((material) => String(material.id)))), | |
| 536 | + ), | |
| 537 | + ]); | |
| 538 | + | |
| 539 | + const sourceNames = new Map( | |
| 540 | + sources.map((source) => [source.id, source.name] as const), | |
| 541 | + ); | |
| 542 | + const linkedSources = new Map<string, Array<{ sourceId: string; sourceName: string }>>(); | |
| 543 | + | |
| 544 | + for (const artifact of authArtifacts) { | |
| 545 | + for (const ref of authArtifactSecretMaterialRefs(artifact)) { | |
| 546 | + if (!materialIds.has(ref.handle)) { | |
| 547 | + continue; | |
| 548 | + } | |
| 549 | + | |
| 550 | + const existing = linkedSources.get(ref.handle) ?? []; | |
| 551 | + if (!existing.some((link) => link.sourceId === artifact.sourceId)) { | |
| 552 | + existing.push({ | |
| 553 | + sourceId: artifact.sourceId, | |
| 554 | + sourceName: sourceNames.get(artifact.sourceId) ?? artifact.sourceId, | |
| 555 | + }); | |
| 556 | + linkedSources.set(ref.handle, existing); | |
| 557 | + } | |
| 558 | + } | |
| 559 | + } | |
| 560 | + | |
| 561 | + return linkedSources; | |
| 562 | + }); | |
| 563 | + | |
| 564 | +export const listLinkedSecretSourcesInWorkspace = ( | |
| 565 | + rows: ControlPlaneStoreShape, | |
| 566 | + workspaceId: WorkspaceId, | |
| 567 | + options: { | |
| 568 | + actorAccountId?: AccountId | null; | |
| 569 | + } = {}, | |
| 570 | +): Effect.Effect< | |
| 571 | + Map<string, Array<{ sourceId: string; sourceName: string }>>, | |
| 572 | + | RuntimeLocalWorkspaceUnavailableError | |
| 573 | + | RuntimeLocalWorkspaceMismatchError | |
| 574 | + | LocalFileSystemError | |
| 575 | + | LocalExecutorConfigDecodeError | |
| 576 | + | LocalWorkspaceStateDecodeError | |
| 577 | + | LocalConfiguredSourceNotFoundError | |
| 578 | + | Error, | |
| 579 | + WorkspaceStorageServices | |
| 580 | +> => | |
| 581 | + Effect.flatMap( | |
| 582 | + loadRuntimeSourceStoreDeps(rows, workspaceId), | |
| 583 | + (deps) => listLinkedSecretSourcesInWorkspaceWithDeps(deps, workspaceId, options), | |
| 584 | + ); | |
| 585 | + | |
| 586 | +const loadSourceByIdWithDeps = (deps: RuntimeSourceStoreDeps, input: { | |
| 439 | 587 | workspaceId: WorkspaceId; |
| 440 | 588 | sourceId: Source["id"]; |
| 441 | 589 | actorAccountId?: AccountId | null; |
| @@ -448,11 +596,11 @@ export const loadSourceById = (rows: ControlPlaneStoreShape, input: { · 6 unmodified lines | ||
| 448 | 596 | | LocalWorkspaceStateDecodeError |
| 449 | 597 | | LocalConfiguredSourceNotFoundError |
| 450 | 598 | | Error, |
| 451 | − WorkspaceStorageServices | |
| 599 | + never | |
| 452 | 600 | > => |
| 453 | 601 | Effect.gen(function* () { |
| 454 | − const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId); | |
| 455 | − const authArtifacts = yield* rows.authArtifacts.listByWorkspaceId(input.workspaceId); | |
| 602 | + const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(deps, input.workspaceId); | |
| 603 | + const authArtifacts = yield* deps.rows.authArtifacts.listByWorkspaceId(input.workspaceId); | |
| 456 | 604 | if (!localWorkspace.loadedConfig.config?.sources?.[input.sourceId]) { |
| 457 | 605 | return yield* Effect.fail( |
| 458 | 606 | new LocalConfiguredSourceNotFoundError({ |
| @@ -476,6 +624,26 @@ export const loadSourceById = (rows: ControlPlaneStoreShape, input: { · 17 unmodified lines | ||
| 476 | 624 | return localSource.source; |
| 477 | 625 | }); |
| 478 | 626 | |
| 627 | +export const loadSourceById = (rows: ControlPlaneStoreShape, input: { | |
| 628 | + workspaceId: WorkspaceId; | |
| 629 | + sourceId: Source["id"]; | |
| 630 | + actorAccountId?: AccountId | null; | |
| 631 | +}): Effect.Effect< | |
| 632 | + Source, | |
| 633 | + | RuntimeLocalWorkspaceUnavailableError | |
| 634 | + | RuntimeLocalWorkspaceMismatchError | |
| 635 | + | LocalFileSystemError | |
| 636 | + | LocalExecutorConfigDecodeError | |
| 637 | + | LocalWorkspaceStateDecodeError | |
| 638 | + | LocalConfiguredSourceNotFoundError | |
| 639 | + | Error, | |
| 640 | + WorkspaceStorageServices | |
| 641 | +> => | |
| 642 | + Effect.flatMap( | |
| 643 | + loadRuntimeSourceStoreDeps(rows, input.workspaceId), | |
| 644 | + (deps) => loadSourceByIdWithDeps(deps, input), | |
| 645 | + ); | |
| 646 | + | |
| 479 | 647 | const configSourceFromLocalSource = (input: { |
| 480 | 648 | source: Source; |
| 481 | 649 | existingConfigAuth: LocalConfigSecretInput | undefined; |
| @@ -571,12 +739,12 @@ const removeAuthArtifactsForSource = (rows: ControlPlaneStoreShape, input: { · 89 unmodified lines | ||
| 571 | 739 | return existingAuthArtifacts.length; |
| 572 | 740 | }); |
| 573 | 741 | |
| 574 | −export const removeSourceById = (rows: ControlPlaneStoreShape, input: { | |
| 742 | +const removeSourceByIdWithDeps = (deps: RuntimeSourceStoreDeps, input: { | |
| 575 | 743 | workspaceId: WorkspaceId; |
| 576 | 744 | sourceId: Source["id"]; |
| 577 | −}): Effect.Effect<boolean, Error, WorkspaceStorageServices> => | |
| 745 | +}): Effect.Effect<boolean, Error, never> => | |
| 578 | 746 | Effect.gen(function* () { |
| 579 | − const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId); | |
| 747 | + const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(deps, input.workspaceId); | |
| 580 | 748 | if (!localWorkspace.loadedConfig.config?.sources?.[input.sourceId]) { |
| 581 | 749 | return false; |
| 582 | 750 | } |
| @@ -611,28 +779,37 @@ export const removeSourceById = (rows: ControlPlaneStoreShape, input: { · 28 unmodified lines | ||
| 611 | 779 | sourceId: input.sourceId, |
| 612 | 780 | }); |
| 613 | 781 | |
| 614 | − yield* rows.sourceAuthSessions.removeByWorkspaceAndSourceId( | |
| 782 | + yield* deps.rows.sourceAuthSessions.removeByWorkspaceAndSourceId( | |
| 615 | 783 | input.workspaceId, |
| 616 | 784 | input.sourceId, |
| 617 | 785 | ); |
| 618 | − yield* rows.sourceOauthClients.removeByWorkspaceAndSourceId({ | |
| 786 | + yield* deps.rows.sourceOauthClients.removeByWorkspaceAndSourceId({ | |
| 619 | 787 | workspaceId: input.workspaceId, |
| 620 | 788 | sourceId: input.sourceId, |
| 621 | 789 | }); |
| 622 | − yield* removeAuthArtifactsForSource(rows, input); | |
| 790 | + yield* removeAuthArtifactsForSource(deps.rows, input); | |
| 623 | 791 | |
| 624 | 792 | return true; |
| 625 | 793 | }); |
| 626 | 794 | |
| 627 | −export const persistSource = ( | |
| 628 | − rows: ControlPlaneStoreShape, | |
| 795 | +export const removeSourceById = (rows: ControlPlaneStoreShape, input: { | |
| 796 | + workspaceId: WorkspaceId; | |
| 797 | + sourceId: Source["id"]; | |
| 798 | +}): Effect.Effect<boolean, Error, WorkspaceStorageServices> => | |
| 799 | + Effect.flatMap( | |
| 800 | + loadRuntimeSourceStoreDeps(rows, input.workspaceId), | |
| 801 | + (deps) => removeSourceByIdWithDeps(deps, input), | |
| 802 | + ); | |
| 803 | + | |
| 804 | +const persistSourceWithDeps = ( | |
| 805 | + deps: RuntimeSourceStoreDeps, | |
| 629 | 806 | source: Source, |
| 630 | 807 | options: { |
| 631 | 808 | actorAccountId?: AccountId | null; |
| 632 | 809 | } = {}, |
| 633 | −): Effect.Effect<Source, Error, WorkspaceStorageServices> => | |
| 810 | +): Effect.Effect<Source, Error, never> => | |
| 634 | 811 | Effect.gen(function* () { |
| 635 | − const localWorkspace = yield* resolveRuntimeLocalWorkspace(source.workspaceId); | |
| 812 | + const localWorkspace = yield* resolveRuntimeLocalWorkspaceFromDeps(deps, source.workspaceId); | |
| 636 | 813 | const nextSource = { |
| 637 | 814 | ...source, |
| 638 | 815 | id: |
| @@ -644,7 +821,7 @@ export const persistSource = ( · 5 unmodified lines | ||
| 644 | 821 | new Set(Object.keys(localWorkspace.loadedConfig.config?.sources ?? {})), |
| 645 | 822 | ), |
| 646 | 823 | } satisfies Source; |
| 647 | − const existingAuthArtifacts = yield* rows.authArtifacts.listByWorkspaceAndSourceId({ | |
| 824 | + const existingAuthArtifacts = yield* deps.rows.authArtifacts.listByWorkspaceAndSourceId({ | |
| 648 | 825 | workspaceId: nextSource.workspaceId, |
| 649 | 826 | sourceId: nextSource.id, |
| 650 | 827 | }); |
| @@ -687,58 +864,58 @@ export const persistSource = ( · 36 unmodified lines | ||
| 687 | 864 | |
| 688 | 865 | if (runtimeAuthArtifact === null) { |
| 689 | 866 | if (existingRuntimeAuthArtifact !== null) { |
| 690 | − yield* removeAuthLeaseAndSecrets(rows, { | |
| 867 | + yield* removeAuthLeaseAndSecrets(deps.rows, { | |
| 691 | 868 | authArtifactId: existingRuntimeAuthArtifact.id, |
| 692 | 869 | }); |
| 693 | 870 | } |
| 694 | − yield* rows.authArtifacts.removeByWorkspaceSourceAndActor({ | |
| 871 | + yield* deps.rows.authArtifacts.removeByWorkspaceSourceAndActor({ | |
| 695 | 872 | workspaceId: nextSource.workspaceId, |
| 696 | 873 | sourceId: nextSource.id, |
| 697 | 874 | actorAccountId: options.actorAccountId ?? null, |
| 698 | 875 | slot: "runtime", |
| 699 | 876 | }); |
| 700 | 877 | } else { |
| 701 | − yield* rows.authArtifacts.upsert(runtimeAuthArtifact); | |
| 878 | + yield* deps.rows.authArtifacts.upsert(runtimeAuthArtifact); | |
| 702 | 879 | if ( |
| 703 | 880 | existingRuntimeAuthArtifact !== null |
| 704 | 881 | && existingRuntimeAuthArtifact.id !== runtimeAuthArtifact.id |
| 705 | 882 | ) { |
| 706 | − yield* removeAuthLeaseAndSecrets(rows, { | |
| 883 | + yield* removeAuthLeaseAndSecrets(deps.rows, { | |
| 707 | 884 | authArtifactId: existingRuntimeAuthArtifact.id, |
| 708 | 885 | }); |
| 709 | 886 | } |
| 710 | 887 | } |
| 711 | 888 | |
| 712 | − yield* cleanupAuthArtifactSecretRefs(rows, { | |
| 889 | + yield* cleanupAuthArtifactSecretRefs(deps.rows, { | |
| 713 | 890 | previous: existingRuntimeAuthArtifact ?? null, |
| 714 | 891 | next: runtimeAuthArtifact, |
| 715 | 892 | }); |
| 716 | 893 | |
| 717 | 894 | if (importAuthArtifact === null) { |
| 718 | 895 | if (existingImportAuthArtifact !== null) { |
| 719 | − yield* removeAuthLeaseAndSecrets(rows, { | |
| 896 | + yield* removeAuthLeaseAndSecrets(deps.rows, { | |
| 720 | 897 | authArtifactId: existingImportAuthArtifact.id, |
| 721 | 898 | }); |
| 722 | 899 | } |
| 723 | − yield* rows.authArtifacts.removeByWorkspaceSourceAndActor({ | |
| 900 | + yield* deps.rows.authArtifacts.removeByWorkspaceSourceAndActor({ | |
| 724 | 901 | workspaceId: nextSource.workspaceId, |
| 725 | 902 | sourceId: nextSource.id, |
| 726 | 903 | actorAccountId: options.actorAccountId ?? null, |
| 727 | 904 | slot: "import", |
| 728 | 905 | }); |
| 729 | 906 | } else { |
| 730 | − yield* rows.authArtifacts.upsert(importAuthArtifact); | |
| 907 | + yield* deps.rows.authArtifacts.upsert(importAuthArtifact); | |
| 731 | 908 | if ( |
| 732 | 909 | existingImportAuthArtifact !== null |
| 733 | 910 | && existingImportAuthArtifact.id !== importAuthArtifact.id |
| 734 | 911 | ) { |
| 735 | − yield* removeAuthLeaseAndSecrets(rows, { | |
| 912 | + yield* removeAuthLeaseAndSecrets(deps.rows, { | |
| 736 | 913 | authArtifactId: existingImportAuthArtifact.id, |
| 737 | 914 | }); |
| 738 | 915 | } |
| 739 | 916 | } |
| 740 | 917 | |
| 741 | − yield* cleanupAuthArtifactSecretRefs(rows, { | |
| 918 | + yield* cleanupAuthArtifactSecretRefs(deps.rows, { | |
| 742 | 919 | previous: existingImportAuthArtifact ?? null, |
| 743 | 920 | next: importAuthArtifact, |
| 744 | 921 | }); |
| @@ -762,9 +939,83 @@ export const persistSource = ( · 17 unmodified lines | ||
| 762 | 939 | state: workspaceState, |
| 763 | 940 | }); |
| 764 | 941 | |
| 765 | − return yield* loadSourceById(rows, { | |
| 942 | + return yield* loadSourceByIdWithDeps(deps, { | |
| 766 | 943 | workspaceId: nextSource.workspaceId, |
| 767 | 944 | sourceId: nextSource.id, |
| 768 | 945 | actorAccountId: options.actorAccountId, |
| 769 | 946 | }); |
| 770 | 947 | }); |
| 948 | + | |
| 949 | +export const persistSource = ( | |
| 950 | + rows: ControlPlaneStoreShape, | |
| 951 | + source: Source, | |
| 952 | + options: { | |
| 953 | + actorAccountId?: AccountId | null; | |
| 954 | + } = {}, | |
| 955 | +): Effect.Effect<Source, Error, WorkspaceStorageServices> => | |
| 956 | + Effect.flatMap( | |
| 957 | + loadRuntimeSourceStoreDeps(rows, source.workspaceId), | |
| 958 | + (deps) => persistSourceWithDeps(deps, source, options), | |
| 959 | + ); | |
| 960 | + | |
| 961 | +type RuntimeSourceStoreShape = { | |
| 962 | + loadSourcesInWorkspace: ( | |
| 963 | + workspaceId: WorkspaceId, | |
| 964 | + options?: { actorAccountId?: AccountId | null }, | |
| 965 | + ) => ReturnType<typeof loadSourcesInWorkspaceWithDeps>; | |
| 966 | + listLinkedSecretSourcesInWorkspace: ( | |
| 967 | + workspaceId: WorkspaceId, | |
| 968 | + options?: { actorAccountId?: AccountId | null }, | |
| 969 | + ) => ReturnType<typeof listLinkedSecretSourcesInWorkspaceWithDeps>; | |
| 970 | + loadSourceById: (input: { | |
| 971 | + workspaceId: WorkspaceId; | |
| 972 | + sourceId: Source["id"]; | |
| 973 | + actorAccountId?: AccountId | null; | |
| 974 | + }) => ReturnType<typeof loadSourceByIdWithDeps>; | |
| 975 | + removeSourceById: (input: { | |
| 976 | + workspaceId: WorkspaceId; | |
| 977 | + sourceId: Source["id"]; | |
| 978 | + }) => ReturnType<typeof removeSourceByIdWithDeps>; | |
| 979 | + persistSource: ( | |
| 980 | + source: Source, | |
| 981 | + options?: { actorAccountId?: AccountId | null }, | |
| 982 | + ) => ReturnType<typeof persistSourceWithDeps>; | |
| 983 | +}; | |
| 984 | + | |
| 985 | +export type RuntimeSourceStore = RuntimeSourceStoreShape; | |
| 986 | + | |
| 987 | +export class RuntimeSourceStoreService extends Context.Tag( | |
| 988 | + "#runtime/RuntimeSourceStoreService", | |
| 989 | +)<RuntimeSourceStoreService, RuntimeSourceStoreShape>() {} | |
| 990 | + | |
| 991 | +export const RuntimeSourceStoreLive = Layer.effect( | |
| 992 | + RuntimeSourceStoreService, | |
| 993 | + Effect.gen(function* () { | |
| 994 | + const rows = yield* ControlPlaneStore; | |
| 995 | + const runtimeLocalWorkspace = yield* RuntimeLocalWorkspaceService; | |
| 996 | + const workspaceConfigStore = yield* WorkspaceConfigStore; | |
| 997 | + const workspaceStateStore = yield* WorkspaceStateStore; | |
| 998 | + const sourceArtifactStore = yield* SourceArtifactStore; | |
| 999 | + | |
| 1000 | + const deps: RuntimeSourceStoreDeps = { | |
| 1001 | + rows, | |
| 1002 | + runtimeLocalWorkspace, | |
| 1003 | + workspaceConfigStore, | |
| 1004 | + workspaceStateStore, | |
| 1005 | + sourceArtifactStore, | |
| 1006 | + }; | |
| 1007 | + | |
| 1008 | + return RuntimeSourceStoreService.of({ | |
| 1009 | + loadSourcesInWorkspace: (workspaceId, options = {}) => | |
| 1010 | + loadSourcesInWorkspaceWithDeps(deps, workspaceId, options), | |
| 1011 | + listLinkedSecretSourcesInWorkspace: (workspaceId, options = {}) => | |
| 1012 | + listLinkedSecretSourcesInWorkspaceWithDeps(deps, workspaceId, options), | |
| 1013 | + loadSourceById: (input) => | |
| 1014 | + loadSourceByIdWithDeps(deps, input), | |
| 1015 | + removeSourceById: (input) => | |
| 1016 | + removeSourceByIdWithDeps(deps, input), | |
| 1017 | + persistSource: (source, options = {}) => | |
| 1018 | + persistSourceWithDeps(deps, source, options), | |
| 1019 | + }); | |
| 1020 | + }), | |
| 1021 | +); | |
| @@ -24,14 +24,10 @@ import { · 23 unmodified lines | ||
| 24 | 24 | import { |
| 25 | 25 | operationErrors, |
| 26 | 26 | } from "./operation-errors"; |
| 27 | −import { createDefaultSecretMaterialResolver } from "./secret-material-providers"; | |
| 28 | 27 | import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store"; |
| 29 | −import { syncSourceMaterialization } from "./source-materialization"; | |
| 28 | +import { RuntimeSourceMaterializationService } from "./source-materialization"; | |
| 30 | 29 | import { |
| 31 | − loadSourceById, | |
| 32 | − loadSourcesInWorkspace, | |
| 33 | − persistSource, | |
| 34 | − removeSourceById, | |
| 30 | + RuntimeSourceStoreService, | |
| 35 | 31 | } from "./source-store"; |
| 36 | 32 | |
| 37 | 33 | const sourceOps = { |
| @@ -47,6 +43,7 @@ const shouldAutoProbeSource = (source: Source): boolean => · 9 unmodified lines | ||
| 47 | 43 | |
| 48 | 44 | const syncArtifactsForSource = (input: { |
| 49 | 45 | store: ControlPlaneStoreShape; |
| 46 | + sourceStore: Effect.Effect.Success<typeof RuntimeSourceStoreService>; | |
| 50 | 47 | source: Source; |
| 51 | 48 | actorAccountId: AccountId; |
| 52 | 49 | operation: |
| @@ -54,9 +51,7 @@ const syncArtifactsForSource = (input: { · 1 unmodified line | ||
| 54 | 51 | | typeof sourceOps.update; |
| 55 | 52 | }) => |
| 56 | 53 | Effect.gen(function* () { |
| 57 | − const resolveSecretMaterial = createDefaultSecretMaterialResolver({ | |
| 58 | − rows: input.store, | |
| 59 | − }); | |
| 54 | + const materializationService = yield* RuntimeSourceMaterializationService; | |
| 60 | 55 | |
| 61 | 56 | // For HTTP-backed source kinds that can validate themselves from a remote |
| 62 | 57 | // document, automatically attempt to probe and connect. This mirrors the |
| @@ -68,11 +63,9 @@ const syncArtifactsForSource = (input: { · 5 unmodified lines | ||
| 68 | 63 | : input.source; |
| 69 | 64 | |
| 70 | 65 | const synced = yield* Effect.either( |
| 71 | − syncSourceMaterialization({ | |
| 72 | − rows: input.store, | |
| 66 | + materializationService.sync({ | |
| 73 | 67 | source: sourceForSync, |
| 74 | 68 | actorAccountId: input.actorAccountId, |
| 75 | − resolveSecretMaterial, | |
| 76 | 69 | }), |
| 77 | 70 | ); |
| 78 | 71 | |
| @@ -94,7 +87,7 @@ const syncArtifactsForSource = (input: { · 15 unmodified lines | ||
| 94 | 87 | ); |
| 95 | 88 | yield* mapPersistenceError( |
| 96 | 89 | input.operation.child("source_connected"), |
| 97 | − persistSource(input.store, connectedSource, { | |
| 90 | + input.sourceStore.persistSource(connectedSource, { | |
| 98 | 91 | actorAccountId: input.actorAccountId, |
| 99 | 92 | }), |
| 100 | 93 | ); |
| @@ -123,7 +116,7 @@ const syncArtifactsForSource = (input: { · 22 unmodified lines | ||
| 123 | 116 | |
| 124 | 117 | yield* mapPersistenceError( |
| 125 | 118 | input.operation.child("source_error"), |
| 126 | − persistSource(input.store, erroredSource, { | |
| 119 | + input.sourceStore.persistSource(erroredSource, { | |
| 127 | 120 | actorAccountId: input.actorAccountId, |
| 128 | 121 | }), |
| 129 | 122 | ); |
| @@ -142,7 +135,9 @@ export const listSources = (input: { · 12 unmodified lines | ||
| 142 | 135 | }) => |
| 143 | 136 | Effect.flatMap(ControlPlaneStore, (store) => |
| 144 | 137 | Effect.gen(function* () { |
| 145 | − return yield* loadSourcesInWorkspace(store, input.workspaceId, { | |
| 138 | + const sourceStore = yield* RuntimeSourceStoreService; | |
| 139 | + | |
| 140 | + return yield* sourceStore.loadSourcesInWorkspace(input.workspaceId, { | |
| 146 | 141 | actorAccountId: input.accountId, |
| 147 | 142 | }).pipe( |
| 148 | 143 | Effect.mapError((error) => |
| @@ -161,6 +156,7 @@ export const createSource = (input: { · 12 unmodified lines | ||
| 161 | 156 | }) => |
| 162 | 157 | Effect.flatMap(ControlPlaneStore, (store) => |
| 163 | 158 | Effect.gen(function* () { |
| 159 | + const sourceStore = yield* RuntimeSourceStoreService; | |
| 164 | 160 | const now = Date.now(); |
| 165 | 161 | |
| 166 | 162 | const source = yield* createSourceFromPayload({ |
| @@ -179,13 +175,14 @@ export const createSource = (input: { · 12 unmodified lines | ||
| 179 | 175 | |
| 180 | 176 | const persistedSource = yield* mapPersistenceError( |
| 181 | 177 | sourceOps.create.child("persist"), |
| 182 | − persistSource(store, source, { | |
| 178 | + sourceStore.persistSource(source, { | |
| 183 | 179 | actorAccountId: input.accountId, |
| 184 | 180 | }), |
| 185 | 181 | ); |
| 186 | 182 | |
| 187 | 183 | const synchronizedSource = yield* syncArtifactsForSource({ |
| 188 | 184 | store, |
| 185 | + sourceStore, | |
| 189 | 186 | source: persistedSource, |
| 190 | 187 | actorAccountId: input.accountId, |
| 191 | 188 | operation: sourceOps.create, |
| @@ -201,7 +198,9 @@ export const getSource = (input: { · 9 unmodified lines | ||
| 201 | 198 | }) => |
| 202 | 199 | Effect.flatMap(ControlPlaneStore, (store) => |
| 203 | 200 | Effect.gen(function* () { |
| 204 | − return yield* loadSourceById(store, { | |
| 201 | + const sourceStore = yield* RuntimeSourceStoreService; | |
| 202 | + | |
| 203 | + return yield* sourceStore.loadSourceById({ | |
| 205 | 204 | workspaceId: input.workspaceId, |
| 206 | 205 | sourceId: input.sourceId, |
| 207 | 206 | actorAccountId: input.accountId, |
| @@ -228,7 +227,8 @@ export const updateSource = (input: { · 20 unmodified lines | ||
| 228 | 227 | }) => |
| 229 | 228 | Effect.flatMap(ControlPlaneStore, (store) => |
| 230 | 229 | Effect.gen(function* () { |
| 231 | − const existingSource = yield* loadSourceById(store, { | |
| 230 | + const sourceStore = yield* RuntimeSourceStoreService; | |
| 231 | + const existingSource = yield* sourceStore.loadSourceById({ | |
| 232 | 232 | workspaceId: input.workspaceId, |
| 233 | 233 | sourceId: input.sourceId, |
| 234 | 234 | actorAccountId: input.accountId, |
| @@ -261,13 +261,14 @@ export const updateSource = (input: { · 26 unmodified lines | ||
| 261 | 261 | |
| 262 | 262 | const persistedSource = yield* mapPersistenceError( |
| 263 | 263 | sourceOps.update.child("persist"), |
| 264 | − persistSource(store, updatedSource, { | |
| 264 | + sourceStore.persistSource(updatedSource, { | |
| 265 | 265 | actorAccountId: input.accountId, |
| 266 | 266 | }), |
| 267 | 267 | ); |
| 268 | 268 | |
| 269 | 269 | const synchronizedSource = yield* syncArtifactsForSource({ |
| 270 | 270 | store, |
| 271 | + sourceStore, | |
| 271 | 272 | source: persistedSource, |
| 272 | 273 | actorAccountId: input.accountId, |
| 273 | 274 | operation: sourceOps.update, |
| @@ -282,9 +283,10 @@ export const removeSource = (input: { · 8 unmodified lines | ||
| 282 | 283 | }) => |
| 283 | 284 | Effect.flatMap(ControlPlaneStore, (store) => |
| 284 | 285 | Effect.gen(function* () { |
| 286 | + const sourceStore = yield* RuntimeSourceStoreService; | |
| 285 | 287 | const removed = yield* mapPersistenceError( |
| 286 | 288 | sourceOps.remove.child("remove"), |
| 287 | − removeSourceById(store, { | |
| 289 | + sourceStore.removeSourceById({ | |
| 288 | 290 | workspaceId: input.workspaceId, |
| 289 | 291 | sourceId: input.sourceId, |
| 290 | 292 | }), |
| @@ -124,11 +124,6 @@ export type ControlPlaneStoreShape = { · 123 unmodified lines | ||
| 124 | 124 | never |
| 125 | 125 | >; |
| 126 | 126 | removeById: (id: SecretMaterial["id"]) => Effect.Effect<boolean, Error, never>; |
| 127 | − listLinkedSources: () => Effect.Effect< | |
| 128 | − Map<string, Array<{ sourceId: string; sourceName: string }>>, | |
| 129 | − Error, | |
| 130 | − never | |
| 131 | − >; | |
| 132 | 127 | }; |
| 133 | 128 | executions: { |
| 134 | 129 | getById: ( |
| @@ -29,19 +29,14 @@ import { · 28 unmodified lines | ||
| 29 | 29 | type RuntimeSourceAuthService, |
| 30 | 30 | } from "./source-auth-service"; |
| 31 | 31 | import { |
| 32 | − loadSourceWithRecipe, | |
| 33 | − loadWorkspaceSourceRecipeToolByPath, | |
| 34 | − loadWorkspaceSourceRecipeToolIndex, | |
| 35 | − loadWorkspaceSourceRecipes, | |
| 32 | + RuntimeSourceRecipeStoreService, | |
| 36 | 33 | type LoadedSourceRecipeToolIndexEntry, |
| 37 | 34 | recipeToolCatalogEntry, |
| 38 | 35 | } from "./source-recipes-runtime"; |
| 39 | −import { resolveSourceAuthMaterial } from "./source-auth-material"; | |
| 36 | +import { RuntimeSourceAuthMaterialService } from "./source-auth-material"; | |
| 40 | 37 | import { namespaceFromSourceName } from "./source-names"; |
| 41 | 38 | import { getSourceAdapterForOperation } from "./source-adapters"; |
| 42 | 39 | import { |
| 43 | − createDefaultSecretMaterialResolver, | |
| 44 | − type ResolveSecretMaterial, | |
| 45 | 40 | type SecretMaterialResolveContext, |
| 46 | 41 | } from "./secret-material-providers"; |
| 47 | 42 | import { |
| @@ -51,12 +46,17 @@ import { · 3 unmodified lines | ||
| 51 | 46 | import { loadRuntimeLocalWorkspacePolicies } from "./policies-operations"; |
| 52 | 47 | import { |
| 53 | 48 | getRuntimeLocalWorkspaceOption, |
| 54 | − RuntimeLocalWorkspaceService, | |
| 49 | + provideOptionalRuntimeLocalWorkspace, | |
| 55 | 50 | type RuntimeLocalWorkspaceState, |
| 56 | 51 | } from "./local-runtime-context"; |
| 57 | −import { loadLocalToolRuntime, type LocalToolRuntime } from "./local-tools"; | |
| 52 | +import { | |
| 53 | + LocalToolRuntimeLoaderService, | |
| 54 | + type LocalToolRuntime, | |
| 55 | + type LocalToolRuntimeLoaderShape, | |
| 56 | +} from "./local-tools"; | |
| 58 | 57 | import { |
| 59 | 58 | SourceArtifactStore, |
| 59 | + makeWorkspaceStorageLayer, | |
| 60 | 60 | type SourceArtifactStoreShape, |
| 61 | 61 | WorkspaceConfigStore, |
| 62 | 62 | type WorkspaceConfigStoreShape, |
| @@ -64,7 +64,6 @@ import { · 1 unmodified line | ||
| 64 | 64 | type WorkspaceStateStoreShape, |
| 65 | 65 | type WorkspaceStorageServices, |
| 66 | 66 | } from "./local-storage"; |
| 67 | −import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store"; | |
| 68 | 67 | |
| 69 | 68 | const asToolPath = (value: string): ToolPath => value as ToolPath; |
| 70 | 69 | |
| @@ -147,14 +146,13 @@ const queryTokenWeight = (token: string): number => · 76 unmodified lines | ||
| 147 | 146 | LOW_SIGNAL_QUERY_TOKENS.has(token) ? 0.25 : 1; |
| 148 | 147 | |
| 149 | 148 | const loadWorkspaceRecipeTools = (input: { |
| 150 | − rows: ControlPlaneStoreShape; | |
| 151 | 149 | workspaceId: Source["workspaceId"]; |
| 152 | 150 | accountId: AccountId; |
| 151 | + sourceRecipeStore: Effect.Effect.Success<typeof RuntimeSourceRecipeStoreService>; | |
| 153 | 152 | includeSchemas: boolean; |
| 154 | 153 | }): Effect.Effect<readonly LoadedSourceRecipeToolIndexEntry[], Error, WorkspaceStorageServices> => |
| 155 | 154 | Effect.map( |
| 156 | − loadWorkspaceSourceRecipeToolIndex({ | |
| 157 | − rows: input.rows, | |
| 155 | + input.sourceRecipeStore.loadWorkspaceSourceRecipeToolIndex({ | |
| 158 | 156 | workspaceId: input.workspaceId, |
| 159 | 157 | actorAccountId: input.accountId, |
| 160 | 158 | includeSchemas: input.includeSchemas, |
| @@ -166,14 +164,13 @@ const loadWorkspaceRecipeTools = (input: { · 5 unmodified lines | ||
| 166 | 164 | ); |
| 167 | 165 | |
| 168 | 166 | const loadWorkspaceRecipeToolByPath = (input: { |
| 169 | − rows: ControlPlaneStoreShape; | |
| 170 | 167 | workspaceId: Source["workspaceId"]; |
| 171 | 168 | accountId: AccountId; |
| 169 | + sourceRecipeStore: Effect.Effect.Success<typeof RuntimeSourceRecipeStoreService>; | |
| 172 | 170 | path: string; |
| 173 | 171 | includeSchemas: boolean; |
| 174 | 172 | }): Effect.Effect<LoadedSourceRecipeToolIndexEntry | null, Error, WorkspaceStorageServices> => |
| 175 | − loadWorkspaceSourceRecipeToolByPath({ | |
| 176 | − rows: input.rows, | |
| 173 | + input.sourceRecipeStore.loadWorkspaceSourceRecipeToolByPath({ | |
| 177 | 174 | workspaceId: input.workspaceId, |
| 178 | 175 | path: input.path, |
| 179 | 176 | actorAccountId: input.accountId, |
| @@ -187,27 +184,25 @@ const loadWorkspaceRecipeToolByPath = (input: { · 7 unmodified lines | ||
| 187 | 184 | ); |
| 188 | 185 | |
| 189 | 186 | const loadWorkspaceSchemaBundle = (input: { |
| 190 | − rows: ControlPlaneStoreShape; | |
| 191 | 187 | workspaceId: Source["workspaceId"]; |
| 188 | + sourceRecipeStore: Effect.Effect.Success<typeof RuntimeSourceRecipeStoreService>; | |
| 192 | 189 | id: SourceRecipeSchemaBundleId; |
| 193 | 190 | }): Effect.Effect<ToolSchemaBundle | null, Error, WorkspaceStorageServices> => |
| 194 | − Effect.gen(function* () { | |
| 195 | − const recipes = yield* loadWorkspaceSourceRecipes({ | |
| 196 | − rows: input.rows, | |
| 191 | + Effect.map( | |
| 192 | + input.sourceRecipeStore.loadWorkspaceSchemaBundle({ | |
| 197 | 193 | workspaceId: input.workspaceId, |
| 198 | − }); | |
| 199 | − const localBundle = recipes | |
| 200 | − .flatMap((recipe) => recipe.schemaBundles) | |
| 201 | − .find((schemaBundle) => schemaBundle.id === input.id); | |
| 202 | − return localBundle | |
| 203 | − ? { | |
| 204 | − id: localBundle.id, | |
| 205 | − kind: localBundle.bundleKind, | |
| 206 | − hash: localBundle.contentHash, | |
| 207 | − refsJson: localBundle.refsJson, | |
| 208 | − } | |
| 209 | − : null; | |
| 210 | − }); | |
| 194 | + id: input.id, | |
| 195 | + }), | |
| 196 | + (bundle) => | |
| 197 | + bundle | |
| 198 | + ? { | |
| 199 | + id: bundle.id, | |
| 200 | + kind: bundle.kind as ToolSchemaBundle["kind"], | |
| 201 | + hash: bundle.hash, | |
| 202 | + refsJson: bundle.refsJson, | |
| 203 | + } | |
| 204 | + : null, | |
| 205 | + ); | |
| 211 | 206 | |
| 212 | 207 | const scoreRecipeTool = ( |
| 213 | 208 | queryTokens: readonly string[], |
| @@ -363,7 +358,6 @@ const toInvocationDescriptorFromRecipeTool = (input: { · 149 unmodified lines | ||
| 363 | 358 | }); |
| 364 | 359 | |
| 365 | 360 | const authorizePersistedToolInvocation = (input: { |
| 366 | − rows: ControlPlaneStoreShape; | |
| 367 | 361 | workspaceId: Source["workspaceId"]; |
| 368 | 362 | accountId: AccountId; |
| 369 | 363 | descriptor: InvocationDescriptor; |
| @@ -456,38 +450,31 @@ const provideRuntimeLocalWorkspace = <A, E, R>( · 86 unmodified lines | ||
| 456 | 450 | effect: Effect.Effect<A, E, R>, |
| 457 | 451 | runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null, |
| 458 | 452 | ): Effect.Effect<A, E, R> => |
| 459 | − runtimeLocalWorkspace === null | |
| 460 | − ? effect | |
| 461 | − : effect.pipe( | |
| 462 | − Effect.provideService( | |
| 463 | − RuntimeLocalWorkspaceService, | |
| 464 | − runtimeLocalWorkspace, | |
| 465 | − ), | |
| 466 | − ); | |
| 453 | + provideOptionalRuntimeLocalWorkspace(effect, runtimeLocalWorkspace); | |
| 467 | 454 | |
| 468 | 455 | const createWorkspaceRecipeCatalog = (input: { |
| 469 | 456 | workspaceId: Source["workspaceId"]; |
| 470 | 457 | accountId: AccountId; |
| 471 | − rows: ControlPlaneStoreShape; | |
| 458 | + sourceRecipeStore: Effect.Effect.Success<typeof RuntimeSourceRecipeStoreService>; | |
| 472 | 459 | workspaceConfigStore: WorkspaceConfigStoreShape; |
| 473 | 460 | workspaceStateStore: WorkspaceStateStoreShape; |
| 474 | 461 | sourceArtifactStore: SourceArtifactStoreShape; |
| 475 | 462 | runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null; |
| 476 | 463 | }): ToolCatalog => { |
| 477 | − const workspaceStorageLayer = Layer.mergeAll( | |
| 478 | − Layer.succeed(WorkspaceConfigStore, input.workspaceConfigStore), | |
| 479 | − Layer.succeed(WorkspaceStateStore, input.workspaceStateStore), | |
| 480 | − Layer.succeed(SourceArtifactStore, input.sourceArtifactStore), | |
| 481 | − ); | |
| 464 | + const workspaceStorageLayer = makeWorkspaceStorageLayer({ | |
| 465 | + workspaceConfigStore: input.workspaceConfigStore, | |
| 466 | + workspaceStateStore: input.workspaceStateStore, | |
| 467 | + sourceArtifactStore: input.sourceArtifactStore, | |
| 468 | + }); | |
| 482 | 469 | const provideWorkspaceStorage = <A, E, R>(effect: Effect.Effect<A, E, R>) => |
| 483 | 470 | effect.pipe(Effect.provide(workspaceStorageLayer)); |
| 484 | 471 | |
| 485 | 472 | const createSharedCatalog = (includeSchemas: boolean): Effect.Effect<ToolCatalog, Error, never> => |
| 486 | 473 | provideWorkspaceStorage(Effect.gen(function* () { |
| 487 | 474 | const recipeTools = yield* loadWorkspaceRecipeTools({ |
| 488 | − rows: input.rows, | |
| 489 | 475 | workspaceId: input.workspaceId, |
| 490 | 476 | accountId: input.accountId, |
| 477 | + sourceRecipeStore: input.sourceRecipeStore, | |
| 491 | 478 | includeSchemas, |
| 492 | 479 | }); |
| 493 | 480 | |
| @@ -500,8 +487,8 @@ const createWorkspaceRecipeCatalog = (input: { · 6 unmodified lines | ||
| 500 | 487 | ), |
| 501 | 488 | getSchemaBundle: ({ id }) => |
| 502 | 489 | provideWorkspaceStorage(loadWorkspaceSchemaBundle({ |
| 503 | − rows: input.rows, | |
| 504 | 490 | workspaceId: input.workspaceId, |
| 491 | + sourceRecipeStore: input.sourceRecipeStore, | |
| 505 | 492 | id: SourceRecipeSchemaBundleIdSchema.make(id), |
| 506 | 493 | })), |
| 507 | 494 | }); |
| @@ -562,11 +549,11 @@ const createWorkspaceRecipeCatalog = (input: { · 54 unmodified lines | ||
| 562 | 549 | const createWorkspaceToolInvoker = (input: { |
| 563 | 550 | workspaceId: Source["workspaceId"]; |
| 564 | 551 | accountId: AccountId; |
| 565 | − rows: ControlPlaneStoreShape; | |
| 552 | + sourceRecipeStore: Effect.Effect.Success<typeof RuntimeSourceRecipeStoreService>; | |
| 566 | 553 | workspaceConfigStore: WorkspaceConfigStoreShape; |
| 567 | 554 | workspaceStateStore: WorkspaceStateStoreShape; |
| 568 | 555 | sourceArtifactStore: SourceArtifactStoreShape; |
| 569 | − resolveSecretMaterial: ResolveSecretMaterial; | |
| 556 | + sourceAuthMaterialService: Effect.Effect.Success<typeof RuntimeSourceAuthMaterialService>; | |
| 570 | 557 | sourceAuthService: RuntimeSourceAuthService; |
| 571 | 558 | runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null; |
| 572 | 559 | localToolRuntime: LocalToolRuntime; |
| @@ -577,11 +564,11 @@ const createWorkspaceToolInvoker = (input: { · 4 unmodified lines | ||
| 577 | 564 | catalog: ToolCatalog; |
| 578 | 565 | toolInvoker: ToolInvoker; |
| 579 | 566 | } => { |
| 580 | − const workspaceStorageLayer = Layer.mergeAll( | |
| 581 | − Layer.succeed(WorkspaceConfigStore, input.workspaceConfigStore), | |
| 582 | − Layer.succeed(WorkspaceStateStore, input.workspaceStateStore), | |
| 583 | − Layer.succeed(SourceArtifactStore, input.sourceArtifactStore), | |
| 584 | − ); | |
| 567 | + const workspaceStorageLayer = makeWorkspaceStorageLayer({ | |
| 568 | + workspaceConfigStore: input.workspaceConfigStore, | |
| 569 | + workspaceStateStore: input.workspaceStateStore, | |
| 570 | + sourceArtifactStore: input.sourceArtifactStore, | |
| 571 | + }); | |
| 585 | 572 | const provideWorkspaceStorage = <A, E, R>(effect: Effect.Effect<A, E, R>) => |
| 586 | 573 | effect.pipe(Effect.provide(workspaceStorageLayer)); |
| 587 | 574 | |
| @@ -594,7 +581,7 @@ const createWorkspaceToolInvoker = (input: { · 6 unmodified lines | ||
| 594 | 581 | const recipeCatalog = createWorkspaceRecipeCatalog({ |
| 595 | 582 | workspaceId: input.workspaceId, |
| 596 | 583 | accountId: input.accountId, |
| 597 | − rows: input.rows, | |
| 584 | + sourceRecipeStore: input.sourceRecipeStore, | |
| 598 | 585 | workspaceConfigStore: input.workspaceConfigStore, |
| 599 | 586 | workspaceStateStore: input.workspaceStateStore, |
| 600 | 587 | sourceArtifactStore: input.sourceArtifactStore, |
| @@ -635,9 +622,9 @@ const createWorkspaceToolInvoker = (input: { · 34 unmodified lines | ||
| 635 | 622 | provideRuntimeLocalWorkspace( |
| 636 | 623 | provideWorkspaceStorage(Effect.gen(function* () { |
| 637 | 624 | const recipeTool = yield* loadWorkspaceRecipeToolByPath({ |
| 638 | − rows: input.rows, | |
| 639 | 625 | workspaceId: input.workspaceId, |
| 640 | 626 | accountId: input.accountId, |
| 627 | + sourceRecipeStore: input.sourceRecipeStore, | |
| 641 | 628 | path: invocation.path, |
| 642 | 629 | includeSchemas: false, |
| 643 | 630 | }); |
| @@ -648,7 +635,6 @@ const createWorkspaceToolInvoker = (input: { · 4 unmodified lines | ||
| 648 | 635 | } |
| 649 | 636 | |
| 650 | 637 | yield* authorizePersistedToolInvocation({ |
| 651 | − rows: input.rows, | |
| 652 | 638 | workspaceId: input.workspaceId, |
| 653 | 639 | accountId: input.accountId, |
| 654 | 640 | descriptor: toInvocationDescriptorFromRecipeTool({ |
| @@ -660,24 +646,21 @@ const createWorkspaceToolInvoker = (input: { · 5 unmodified lines | ||
| 660 | 646 | onElicitation: input.onElicitation, |
| 661 | 647 | }); |
| 662 | 648 | |
| 663 | − const auth = yield* resolveSourceAuthMaterial({ | |
| 664 | − rows: input.rows, | |
| 649 | + const auth = yield* input.sourceAuthMaterialService.resolve({ | |
| 665 | 650 | source: recipeTool.source, |
| 666 | 651 | actorAccountId: input.accountId, |
| 667 | − resolveSecretMaterial: input.resolveSecretMaterial, | |
| 668 | 652 | context: toSecretResolutionContext(invocation.context), |
| 669 | 653 | }); |
| 670 | 654 | const schemaBundle = recipeTool.schemaBundleId |
| 671 | 655 | ? yield* loadWorkspaceSchemaBundle({ |
| 672 | − rows: input.rows, | |
| 673 | 656 | workspaceId: input.workspaceId, |
| 657 | + sourceRecipeStore: input.sourceRecipeStore, | |
| 674 | 658 | id: SourceRecipeSchemaBundleIdSchema.make( |
| 675 | 659 | recipeTool.schemaBundleId, |
| 676 | 660 | ), |
| 677 | 661 | }) |
| 678 | 662 | : null; |
| 679 | − const recipe = yield* loadSourceWithRecipe({ | |
| 680 | − rows: input.rows, | |
| 663 | + const recipe = yield* input.sourceRecipeStore.loadSourceWithRecipe({ | |
| 681 | 664 | workspaceId: input.workspaceId, |
| 682 | 665 | sourceId: recipeTool.source.id, |
| 683 | 666 | actorAccountId: input.accountId, |
| @@ -686,7 +669,6 @@ const createWorkspaceToolInvoker = (input: { · 2 unmodified lines | ||
| 686 | 669 | return yield* getSourceAdapterForOperation( |
| 687 | 670 | recipeTool.operation, |
| 688 | 671 | ).invokePersistedTool({ |
| 689 | − rows: input.rows, | |
| 690 | 672 | workspaceId: input.workspaceId, |
| 691 | 673 | accountId: input.accountId, |
| 692 | 674 | source: recipeTool.source, |
| @@ -718,20 +700,15 @@ const createWorkspaceToolInvoker = (input: { · 25 unmodified lines | ||
| 718 | 700 | }; |
| 719 | 701 | |
| 720 | 702 | export const createWorkspaceExecutionEnvironmentResolver = (input: { |
| 721 | − rows: ControlPlaneStoreShape; | |
| 722 | − resolveSecretMaterial?: ResolveSecretMaterial; | |
| 703 | + sourceAuthMaterialService: Effect.Effect.Success<typeof RuntimeSourceAuthMaterialService>; | |
| 723 | 704 | sourceAuthService: RuntimeSourceAuthService; |
| 705 | + sourceRecipeStore: Effect.Effect.Success<typeof RuntimeSourceRecipeStoreService>; | |
| 706 | + localToolRuntimeLoader: LocalToolRuntimeLoaderShape; | |
| 724 | 707 | workspaceConfigStore: WorkspaceConfigStoreShape; |
| 725 | 708 | workspaceStateStore: WorkspaceStateStoreShape; |
| 726 | 709 | sourceArtifactStore: SourceArtifactStoreShape; |
| 727 | −}): ResolveExecutionEnvironment => { | |
| 728 | − const resolveSecretMaterial = | |
| 729 | − input.resolveSecretMaterial ?? | |
| 730 | − createDefaultSecretMaterialResolver({ | |
| 731 | − rows: input.rows, | |
| 732 | − }); | |
| 733 | − | |
| 734 | − return ({ workspaceId, accountId, onElicitation }) => | |
| 710 | +}): ResolveExecutionEnvironment => | |
| 711 | + ({ workspaceId, accountId, onElicitation }) => | |
| 735 | 712 | Effect.gen(function* () { |
| 736 | 713 | const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption(); |
| 737 | 714 | const localToolRuntime = |
| @@ -742,15 +719,15 @@ export const createWorkspaceExecutionEnvironmentResolver = (input: { · 4 unmodified lines | ||
| 742 | 719 | toolInvoker: makeToolInvokerFromTools({ tools: {} }), |
| 743 | 720 | toolPaths: new Set<string>(), |
| 744 | 721 | } |
| 745 | − : yield* loadLocalToolRuntime(runtimeLocalWorkspace.context); | |
| 722 | + : yield* input.localToolRuntimeLoader.load(runtimeLocalWorkspace.context); | |
| 746 | 723 | const { catalog, toolInvoker } = createWorkspaceToolInvoker({ |
| 747 | 724 | workspaceId, |
| 748 | 725 | accountId, |
| 749 | − rows: input.rows, | |
| 726 | + sourceRecipeStore: input.sourceRecipeStore, | |
| 750 | 727 | workspaceConfigStore: input.workspaceConfigStore, |
| 751 | 728 | workspaceStateStore: input.workspaceStateStore, |
| 752 | 729 | sourceArtifactStore: input.sourceArtifactStore, |
| 753 | − resolveSecretMaterial, | |
| 730 | + sourceAuthMaterialService: input.sourceAuthMaterialService, | |
| 754 | 731 | sourceAuthService: input.sourceAuthService, |
| 755 | 732 | runtimeLocalWorkspace, |
| 756 | 733 | localToolRuntime, |
| @@ -765,7 +742,6 @@ export const createWorkspaceExecutionEnvironmentResolver = (input: { · 8 unmodified lines | ||
| 765 | 742 | catalog, |
| 766 | 743 | } satisfies ExecutionEnvironment; |
| 767 | 744 | }); |
| 768 | −}; | |
| 769 | 745 | |
| 770 | 746 | export class RuntimeExecutionResolverService extends Context.Tag( |
| 771 | 747 | "#runtime/RuntimeExecutionResolverService", |
| @@ -777,7 +753,6 @@ export class RuntimeExecutionResolverService extends Context.Tag( · 5 unmodified lines | ||
| 777 | 753 | export const RuntimeExecutionResolverLive = ( |
| 778 | 754 | input: { |
| 779 | 755 | executionResolver?: ResolveExecutionEnvironment; |
| 780 | − resolveSecretMaterial?: ResolveSecretMaterial; | |
| 781 | 756 | } = {}, |
| 782 | 757 | ) => |
| 783 | 758 | input.executionResolver |
| @@ -785,16 +760,19 @@ export const RuntimeExecutionResolverLive = ( · 1 unmodified line | ||
| 785 | 760 | : Layer.effect( |
| 786 | 761 | RuntimeExecutionResolverService, |
| 787 | 762 | Effect.gen(function* () { |
| 788 | − const rows = yield* ControlPlaneStore; | |
| 763 | + const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService; | |
| 789 | 764 | const sourceAuthService = yield* RuntimeSourceAuthServiceTag; |
| 765 | + const sourceRecipeStore = yield* RuntimeSourceRecipeStoreService; | |
| 766 | + const localToolRuntimeLoader = yield* LocalToolRuntimeLoaderService; | |
| 790 | 767 | const workspaceConfigStore = yield* WorkspaceConfigStore; |
| 791 | 768 | const workspaceStateStore = yield* WorkspaceStateStore; |
| 792 | 769 | const sourceArtifactStore = yield* SourceArtifactStore; |
| 793 | 770 | |
| 794 | 771 | return createWorkspaceExecutionEnvironmentResolver({ |
| 795 | − rows, | |
| 796 | 772 | sourceAuthService, |
| 797 | − resolveSecretMaterial: input.resolveSecretMaterial, | |
| 773 | + sourceAuthMaterialService, | |
| 774 | + sourceRecipeStore, | |
| 775 | + localToolRuntimeLoader, | |
| 798 | 776 | workspaceConfigStore, |
| 799 | 777 | workspaceStateStore, |
| 800 | 778 | sourceArtifactStore, |