History

Use Effect-native local workspace IO

Rhys Sullivan committed 8 months ago

Commit 402a78c1 parent 1dd8a8d18 files · +518 −632Browse files

Unified · Split

18 files
  1. apps/executor/src/server/server.test.ts +5 −4
  2. packages/control-plane/src/runtime/index.ts +5 −10
  3. packages/control-plane/src/runtime/local-config.test.ts +9 −23
  4. packages/control-plane/src/runtime/local-config.ts +164 −116
  5. packages/control-plane/src/runtime/local-installation.test.ts +3 −3
  6. packages/control-plane/src/runtime/local-operations.ts +8 −1
  7. packages/control-plane/src/runtime/local-source-artifacts.ts +63 −42
  8. packages/control-plane/src/runtime/local-workspace-state.ts +51 −27
  9. packages/control-plane/src/runtime/local-workspace-sync.ts +4 −13
  10. packages/control-plane/src/runtime/policies-operations.ts +14 −26
  11. packages/control-plane/src/runtime/source-auth-service.test.ts +4 −12
  12. packages/control-plane/src/runtime/source-inspection.test.ts +8 −16
  13. packages/control-plane/src/runtime/source-materialization.ts +22 −50
  14. packages/control-plane/src/runtime/source-recipes-runtime.test.ts +14 −21
  15. packages/control-plane/src/runtime/source-recipes-runtime.ts +6 −16
  16. packages/control-plane/src/runtime/source-store.test.ts +4 −20
  17. packages/control-plane/src/runtime/source-store.ts +26 −63
  18. packages/control-plane/src/runtime/workspace-execution-environment.test.ts +108 −169
apps/executor/src/server/server.test.ts+5−4
@@ -92,14 +92,14 @@ const writeConfiguredLocalMcpSource = (input: { · 91 unmodified lines
9292 name?: string;
9393 namespace?: string;
9494 }) =>
95− Effect.promise(async () => {
95+ Effect.gen(function* () {
9696 const sourceId = SourceIdSchema.make(input.configKey);
97− const context = await resolveLocalWorkspaceContext({
97+ const context = yield* resolveLocalWorkspaceContext({
9898 workspaceRoot: input.workspaceRoot,
9999 });
100100 const installation = deriveLocalInstallation(context);
101101
102− await writeProjectLocalExecutorConfig({
102+ yield* writeProjectLocalExecutorConfig({
103103 context,
104104 config: {
105105 sources: {
@@ -162,7 +162,7 @@ const writeConfiguredLocalMcpSource = (input: { · 56 unmodified lines
162162 }],
163163 });
164164
165− await writeLocalSourceArtifact({
165+ yield* writeLocalSourceArtifact({
166166 context,
167167 sourceId,
168168 artifact: buildLocalSourceArtifact({
@@ -718,6 +718,7 @@ describe("local-executor-server", () => { · 549 unmodified lines
718718 expect(execution.execution.status).toBe("completed");
719719 expect(execution.execution.resultJson).toBe(JSON.stringify({ sum: 42 }));
720720 }),
721+ 15_000,
721722 );
722723
723724 it.scoped("includes completed MCP return values in text content", () =>
packages/control-plane/src/runtime/index.ts+5−10
@@ -193,13 +193,10 @@ export const createControlPlaneRuntime = ( · 192 unmodified lines
193193 const persistence = Context.get(baseContext, SqlControlPlanePersistenceService);
194194 const rows = Context.get(baseContext, SqlControlPlaneRowsService);
195195
196− const localWorkspaceContext = yield* Effect.tryPromise({
197− try: () =>
198− resolveLocalWorkspaceContext({
199− workspaceRoot: options.workspaceRoot,
200− }),
201− catch: toLocalRuntimeBootstrapError,
196+ const localWorkspaceContext = yield* resolveLocalWorkspaceContext({
197+ workspaceRoot: options.workspaceRoot,
202198 }).pipe(
199+ Effect.mapError(toLocalRuntimeBootstrapError),
203200 Effect.catchAll((error) =>
204201 closeScope(scope).pipe(
205202 Effect.zipRight(Effect.fail(error)),
@@ -217,10 +214,8 @@ export const createControlPlaneRuntime = ( · 11 unmodified lines
217214 )),
218215 );
219216
220− const loadedLocalConfig = yield* Effect.tryPromise({
221− try: () => loadLocalExecutorConfig(localWorkspaceContext),
222− catch: toLocalRuntimeBootstrapError,
223− }).pipe(
217+ const loadedLocalConfig = yield* loadLocalExecutorConfig(localWorkspaceContext).pipe(
218+ Effect.mapError(toLocalRuntimeBootstrapError),
224219 Effect.catchAll((error) =>
225220 closeScope(scope).pipe(
226221 Effect.zipRight(Effect.fail(error)),
packages/control-plane/src/runtime/local-config.test.ts+9−23
@@ -43,12 +43,8 @@ describe("local-config", () => { · 42 unmodified lines
4343 ),
4444 );
4545
46− const context = yield* Effect.promise(() =>
47− resolveLocalWorkspaceContext({ workspaceRoot }),
48− );
49− const loaded = yield* Effect.promise(() =>
50− loadLocalExecutorConfig(context),
51− );
46+ const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
47+ const loaded = yield* loadLocalExecutorConfig(context);
5248
5349 expect(loaded.config?.sources?.github?.kind).toBe("openapi");
5450 expect(loaded.config?.sources?.github?.connection.endpoint).toBe(
@@ -80,16 +76,8 @@ describe("local-config", () => { · 25 unmodified lines
8076 ),
8177 );
8278
83− const context = yield* Effect.promise(() =>
84− resolveLocalWorkspaceContext({ workspaceRoot }),
85− );
86− const failure = yield* Effect.flip(
87− Effect.tryPromise({
88− try: () => loadLocalExecutorConfig(context),
89− catch: (cause) =>
90− cause instanceof Error ? cause : new Error(String(cause)),
91− }),
92− );
79+ const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
80+ const failure = yield* Effect.flip(loadLocalExecutorConfig(context));
9381
9482 expect(failure.message).toContain("Invalid executor config");
9583 expect(failure.message).toContain("line 5, column 7");
@@ -128,13 +116,11 @@ describe("local-config", () => { · 32 unmodified lines
128116 ),
129117 );
130118
131− const resolvedPath = yield* Effect.promise(() =>
132− resolveHomeConfigPath({
133− platform: "darwin",
134− homeDirectory,
135− env: {},
136− }),
137− );
119+ const resolvedPath = yield* resolveHomeConfigPath({
120+ platform: "darwin",
121+ homeDirectory,
122+ env: {},
123+ });
138124
139125 expect(resolvedPath).toBe(join(legacyConfigDirectory, "executor.jsonc"));
140126 }),
packages/control-plane/src/runtime/local-config.ts+164−116
@@ -1,7 +1,8 @@
11 import { createHash } from "node:crypto";
22 import { homedir } from "node:os";
33 import { basename, dirname, isAbsolute, join, resolve } from "node:path";
4−import { promises as fs } from "node:fs";
4+import { FileSystem } from "@effect/platform";
5+import { NodeFileSystem } from "@effect/platform-node";
56 import {
67 type ParseError as JsoncParseError,
78 parse as parseJsoncDocument,
@@ -15,6 +16,7 @@ import { · 7 unmodified lines
1516 type LocalConfigSecretProvider,
1617 type LocalConfigSource,
1718 } from "#schema";
19+import * as Effect from "effect/Effect";
1820 import * as Schema from "effect/Schema";
1921
2022 const decodeLocalExecutorConfig = Schema.decodeUnknownSync(LocalExecutorConfigSchema);
@@ -30,22 +32,18 @@ const normalizeSlashPath = (value: string): string => · 9 unmodified lines
3032 const stableHash = (value: string): string =>
3133 createHash("sha256").update(value).digest("hex").slice(0, 16);
3234
33−const fileExists = async (path: string): Promise<boolean> => {
34− try {
35− await fs.access(path);
36− return true;
37− } catch {
38− return false;
39− }
40−};
41−
42−const pathExists = async (path: string): Promise<boolean> => {
43− try {
44− await fs.access(path);
45− return true;
46− } catch {
47− return false;
48− }
35+const provideNodeFileSystem = <A, E, R>(
36+ effect: Effect.Effect<A, E, R | FileSystem.FileSystem>,
37+): Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem>> =>
38+ effect.pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect<
39+ A,
40+ E,
41+ Exclude<R, FileSystem.FileSystem>
42+ >;
43+
44+const mapFileSystemError = (path: string, action: string) => (cause: unknown): Error => {
45+ const message = cause instanceof Error ? cause.message : String(cause);
46+ return new Error(`Failed to ${action} ${path}: ${message}`);
4947 };
5048
5149 const trimOrUndefined = (value: string | undefined | null): string | undefined => {
@@ -111,21 +109,26 @@ export const resolveDefaultHomeConfigCandidates = (input: { · 59 unmodified lines
111109 ]);
112110 };
113111
114−export const resolveHomeConfigPath = async (input: {
112+export const resolveHomeConfigPath = (input: {
115113 env?: NodeJS.ProcessEnv;
116114 platform?: NodeJS.Platform;
117115 homeDirectory?: string;
118−} = {}): Promise<string> => {
119− const candidates = resolveDefaultHomeConfigCandidates(input);
120−
121− for (const candidate of candidates) {
122− if (await fileExists(candidate)) {
123− return candidate;
116+} = {}): Effect.Effect<string, Error> =>
117+ provideNodeFileSystem(Effect.gen(function* () {
118+ const fs = yield* FileSystem.FileSystem;
119+ const candidates = resolveDefaultHomeConfigCandidates(input);
120+
121+ for (const candidate of candidates) {
122+ const exists = yield* fs.exists(candidate).pipe(
123+ Effect.mapError(mapFileSystemError(candidate, "check config path")),
124+ );
125+ if (exists) {
126+ return candidate;
127+ }
124128 }
125− }
126129
127− return candidates[0]!;
128−};
130+ return candidates[0]!;
131+ }));
129132
130133 const formatJsoncParseErrors = (content: string, errors: readonly JsoncParseError[]): string => {
131134 const lines = content.split("\n");
@@ -233,47 +236,74 @@ export const mergeLocalExecutorConfigs = ( · 101 unmodified lines
233236 });
234237 };
235238
236−const resolveProjectConfigPath = async (workspaceRoot: string): Promise<string> => {
237− const jsoncPath = join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_BASENAME);
238− if (await fileExists(jsoncPath)) {
239− return jsoncPath;
240− }
241−
242− const jsonPath = join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_FALLBACK_BASENAME);
243− if (await fileExists(jsonPath)) {
244− return jsonPath;
245− }
246−
247− return jsoncPath;
248−};
239+const resolveProjectConfigPathEffect = (workspaceRoot: string) =>
240+ Effect.gen(function* () {
241+ const fs = yield* FileSystem.FileSystem;
242+ const jsoncPath = join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_BASENAME);
243+ const jsoncExists = yield* fs.exists(jsoncPath).pipe(
244+ Effect.mapError(mapFileSystemError(jsoncPath, "check project config path")),
245+ );
246+ if (jsoncExists) {
247+ return jsoncPath;
248+ }
249249
250−const hasProjectConfig = async (workspaceRoot: string): Promise<boolean> =>
251− fileExists(join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_BASENAME))
252− || fileExists(join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_FALLBACK_BASENAME));
250+ const jsonPath = join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_FALLBACK_BASENAME);
251+ const jsonExists = yield* fs.exists(jsonPath).pipe(
252+ Effect.mapError(mapFileSystemError(jsonPath, "check project config path")),
253+ );
254+ return jsonExists ? jsonPath : jsoncPath;
255+ });
253256
254−const resolveWorkspaceRootFromCwd = async (cwd: string): Promise<string> => {
255− let current = resolve(cwd);
256− let nearestProjectConfigRoot: string | null = null;
257− let nearestGitRoot: string | null = null;
257+const hasProjectConfigEffect = (workspaceRoot: string) =>
258+ Effect.gen(function* () {
259+ const fs = yield* FileSystem.FileSystem;
260+ const jsoncPath = join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_BASENAME);
261+ const jsonPath = join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_FALLBACK_BASENAME);
262+ const [jsoncExists, jsonExists] = yield* Effect.all([
263+ fs.exists(jsoncPath).pipe(
264+ Effect.mapError(mapFileSystemError(jsoncPath, "check project config path")),
265+ ),
266+ fs.exists(jsonPath).pipe(
267+ Effect.mapError(mapFileSystemError(jsonPath, "check project config path")),
268+ ),
269+ ]);
270+ return jsoncExists || jsonExists;
271+ });
258272
259− while (true) {
260− if (nearestProjectConfigRoot === null && (await hasProjectConfig(current))) {
261− nearestProjectConfigRoot = current;
262− }
273+const resolveWorkspaceRootFromCwdEffect = (cwd: string) =>
274+ Effect.gen(function* () {
275+ const fs = yield* FileSystem.FileSystem;
276+ let current = resolve(cwd);
277+ let nearestProjectConfigRoot: string | null = null;
278+ let nearestGitRoot: string | null = null;
279+
280+ while (true) {
281+ if (nearestProjectConfigRoot === null) {
282+ const hasProjectConfig = yield* hasProjectConfigEffect(current);
283+ if (hasProjectConfig) {
284+ nearestProjectConfigRoot = current;
285+ }
286+ }
263287
264− if (nearestGitRoot === null && (await pathExists(join(current, ".git")))) {
265− nearestGitRoot = current;
266− }
288+ if (nearestGitRoot === null) {
289+ const gitPath = join(current, ".git");
290+ const gitExists = yield* fs.exists(gitPath).pipe(
291+ Effect.mapError(mapFileSystemError(gitPath, "check git root")),
292+ );
293+ if (gitExists) {
294+ nearestGitRoot = current;
295+ }
296+ }
267297
268− const parent = dirname(current);
269− if (parent === current) {
270− break;
298+ const parent = dirname(current);
299+ if (parent === current) {
300+ break;
301+ }
302+ current = parent;
271303 }
272− current = parent;
273− }
274304
275− return nearestProjectConfigRoot ?? nearestGitRoot ?? resolve(cwd);
276−};
305+ return nearestProjectConfigRoot ?? nearestGitRoot ?? resolve(cwd);
306+ });
277307
278308 export type ResolvedLocalWorkspaceContext = {
279309 cwd: string;
@@ -295,73 +325,91 @@ export type LoadedLocalExecutorConfig = { · 15 unmodified lines
295325 projectConfigPath: string;
296326 };
297327
298−export const resolveLocalWorkspaceContext = async (input: {
328+export const resolveLocalWorkspaceContext = (input: {
299329 cwd?: string;
300330 workspaceRoot?: string;
301−} = {}): Promise<ResolvedLocalWorkspaceContext> => {
302− const cwd = resolve(input.cwd ?? process.cwd());
303− const workspaceRoot = resolve(
304− input.workspaceRoot ?? (await resolveWorkspaceRootFromCwd(cwd)),
305− );
306− const workspaceName = basename(workspaceRoot) || "workspace";
307− const projectConfigPath = await resolveProjectConfigPath(workspaceRoot);
308− const homeConfigPath = await resolveHomeConfigPath();
309−
310− return {
311− cwd,
312− workspaceRoot,
313− workspaceName,
314− configDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY),
315− projectConfigPath,
316− homeConfigPath,
317− artifactsDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, "artifacts"),
318− stateDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, "state"),
319− installationId: `local_${stableHash(normalizeSlashPath(workspaceRoot))}`,
320− };
321−};
322−
323−export const readOptionalLocalExecutorConfig = async (
331+} = {}): Effect.Effect<ResolvedLocalWorkspaceContext, Error> =>
332+ provideNodeFileSystem(Effect.gen(function* () {
333+ const cwd = resolve(input.cwd ?? process.cwd());
334+ const workspaceRoot = resolve(
335+ input.workspaceRoot ?? (yield* resolveWorkspaceRootFromCwdEffect(cwd)),
336+ );
337+ const workspaceName = basename(workspaceRoot) || "workspace";
338+ const projectConfigPath = yield* resolveProjectConfigPathEffect(workspaceRoot);
339+ const homeConfigPath = yield* resolveHomeConfigPath();
340+
341+ return {
342+ cwd,
343+ workspaceRoot,
344+ workspaceName,
345+ configDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY),
346+ projectConfigPath,
347+ homeConfigPath,
348+ artifactsDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, "artifacts"),
349+ stateDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, "state"),
350+ installationId: `local_${stableHash(normalizeSlashPath(workspaceRoot))}`,
351+ };
352+ }));
353+
354+export const readOptionalLocalExecutorConfig = (
324355 path: string,
325−): Promise<LocalExecutorConfig | null> => {
326− if (!(await fileExists(path))) {
327− return null;
328− }
356+): Effect.Effect<LocalExecutorConfig | null, Error> =>
357+ provideNodeFileSystem(Effect.gen(function* () {
358+ const fs = yield* FileSystem.FileSystem;
359+ const exists = yield* fs.exists(path).pipe(
360+ Effect.mapError(mapFileSystemError(path, "check config path")),
361+ );
362+ if (!exists) {
363+ return null;
364+ }
329365
330− const content = await fs.readFile(path, "utf8");
331− return parseJsonc({ path, content });
332−};
366+ const content = yield* fs.readFileString(path, "utf8").pipe(
367+ Effect.mapError(mapFileSystemError(path, "read config")),
368+ );
369+ return yield* Effect.try({
370+ try: () => parseJsonc({ path, content }),
371+ catch: (cause) =>
372+ cause instanceof Error ? cause : new Error(String(cause)),
373+ });
374+ }));
333375
334−export const loadLocalExecutorConfig = async (
376+export const loadLocalExecutorConfig = (
335377 context: ResolvedLocalWorkspaceContext,
336−): Promise<LoadedLocalExecutorConfig> => {
337− const [homeConfig, projectConfig] = await Promise.all([
338− readOptionalLocalExecutorConfig(context.homeConfigPath),
339− readOptionalLocalExecutorConfig(context.projectConfigPath),
340− ]);
341−
342− return {
343− config: mergeLocalExecutorConfigs(homeConfig, projectConfig),
344− homeConfig,
345− projectConfig,
346− homeConfigPath: context.homeConfigPath,
347− projectConfigPath: context.projectConfigPath,
348− };
349−};
378+): Effect.Effect<LoadedLocalExecutorConfig, Error> =>
379+ Effect.gen(function* () {
380+ const [homeConfig, projectConfig] = yield* Effect.all([
381+ readOptionalLocalExecutorConfig(context.homeConfigPath),
382+ readOptionalLocalExecutorConfig(context.projectConfigPath),
383+ ]);
384+
385+ return {
386+ config: mergeLocalExecutorConfigs(homeConfig, projectConfig),
387+ homeConfig,
388+ projectConfig,
389+ homeConfigPath: context.homeConfigPath,
390+ projectConfigPath: context.projectConfigPath,
391+ };
392+ });
350393
351394 export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string =>
352395 `${JSON.stringify(config, null, 2)}\n`;
353396
354−export const writeProjectLocalExecutorConfig = async (input: {
397+export const writeProjectLocalExecutorConfig = (input: {
355398 context: ResolvedLocalWorkspaceContext;
356399 config: LocalExecutorConfig;
357−}): Promise<void> => {
358− await fs.mkdir(input.context.configDirectory, { recursive: true });
359− await fs.writeFile(
360− input.context.projectConfigPath,
361− encodeLocalExecutorConfig(input.config),
362− "utf8",
363− );
364−};
400+}): Effect.Effect<void, Error> =>
401+ provideNodeFileSystem(Effect.gen(function* () {
402+ const fs = yield* FileSystem.FileSystem;
403+ yield* fs.makeDirectory(input.context.configDirectory, { recursive: true }).pipe(
404+ Effect.mapError(mapFileSystemError(input.context.configDirectory, "create config directory")),
405+ );
406+ yield* fs.writeFileString(
407+ input.context.projectConfigPath,
408+ encodeLocalExecutorConfig(input.config),
409+ ).pipe(
410+ Effect.mapError(mapFileSystemError(input.context.projectConfigPath, "write config")),
411+ );
412+ }));
365413
366414 export const resolveConfigRelativePath = (input: {
367415 path: string;
packages/control-plane/src/runtime/local-installation.test.ts+3−3
@@ -36,9 +36,9 @@ describe("local-installation", () => { · 35 unmodified lines
3636 it.scoped("is idempotent when loading the default local installation", () =>
3737 Effect.gen(function* () {
3838 const runtime = yield* makeRuntime;
39− const context = yield* Effect.promise(() =>
40− resolveLocalWorkspaceContext({ workspaceRoot: TEST_WORKSPACE_ROOT }),
41− );
39+ const context = yield* resolveLocalWorkspaceContext({
40+ workspaceRoot: TEST_WORKSPACE_ROOT,
41+ });
4242
4343 const first = runtime.localInstallation;
4444 const second = yield* getOrProvisionLocalInstallation({
packages/control-plane/src/runtime/local-operations.ts+8−1
@@ -185,7 +185,14 @@ export const getLocalInstallation = () => · 184 unmodified lines
185185 const store = yield* ControlPlaneStore;
186186 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();
187187 const context = runtimeLocalWorkspace?.context
188− ?? (yield* Effect.promise(() => resolveLocalWorkspaceContext()));
188+ ?? (yield* resolveLocalWorkspaceContext().pipe(
189+ Effect.mapError((error) =>
190+ localOps.installation.unknownStorage(
191+ error,
192+ "Failed resolving local workspace context",
193+ ),
194+ ),
195+ ));
189196
190197 return yield* loadLocalInstallation(store, context);
191198 });
packages/control-plane/src/runtime/local-source-artifacts.ts+63−42
@@ -1,5 +1,6 @@
1−import { promises as fs } from "node:fs";
21 import { join } from "node:path";
2+import { FileSystem } from "@effect/platform";
3+import { NodeFileSystem } from "@effect/platform-node";
34
45 import {
56 SourceIdSchema,
@@ -12,6 +13,7 @@ import { · 6 unmodified lines
1213 type Source,
1314 type SourceRecipeId,
1415 } from "#schema";
16+import * as Effect from "effect/Effect";
1517 import * as Schema from "effect/Schema";
1618
1719 import type { ResolvedLocalWorkspaceContext } from "./local-config";
@@ -41,6 +43,20 @@ export type LocalSourceArtifact = typeof LocalSourceArtifactSchema.Type; · 23 unmodified lines
4143
4244 const decodeLocalSourceArtifact = Schema.decodeUnknownSync(LocalSourceArtifactSchema);
4345
46+const provideNodeFileSystem = <A, E, R>(
47+ effect: Effect.Effect<A, E, R | FileSystem.FileSystem>,
48+): Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem>> =>
49+ effect.pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect<
50+ A,
51+ E,
52+ Exclude<R, FileSystem.FileSystem>
53+ >;
54+
55+const mapFileSystemError = (path: string, action: string) => (cause: unknown): Error => {
56+ const message = cause instanceof Error ? cause.message : String(cause);
57+ return new Error(`Failed to ${action} ${path}: ${message}`);
58+};
59+
4460 const localSourceArtifactPath = (input: {
4561 context: ResolvedLocalWorkspaceContext;
4662 sourceId: string;
@@ -138,58 +154,63 @@ export const buildLocalSourceArtifact = (input: { · 91 unmodified lines
138154 };
139155 };
140156
141−export const readLocalSourceArtifact = async (input: {
157+export const readLocalSourceArtifact = (input: {
142158 context: ResolvedLocalWorkspaceContext;
143159 sourceId: string;
144−}): Promise<LocalSourceArtifact | null> => {
145− const path = localSourceArtifactPath(input);
146−
147− try {
148− const content = await fs.readFile(path, "utf8");
149− return decodeLocalSourceArtifact(JSON.parse(content) as unknown);
150− } catch (cause) {
151− if (
152− cause instanceof Error
153− && ("code" in cause)
154− && (cause as NodeJS.ErrnoException).code === "ENOENT"
155− ) {
160+}): Effect.Effect<LocalSourceArtifact | null, Error> =>
161+ provideNodeFileSystem(Effect.gen(function* () {
162+ const fs = yield* FileSystem.FileSystem;
163+ const path = localSourceArtifactPath(input);
164+ const exists = yield* fs.exists(path).pipe(
165+ Effect.mapError(mapFileSystemError(path, "check source artifact path")),
166+ );
167+ if (!exists) {
156168 return null;
157169 }
158170
159− const message = cause instanceof Error ? cause.message : String(cause);
160− throw new Error(`Invalid local source artifact at ${path}: ${message}`);
161− }
162−};
171+ const content = yield* fs.readFileString(path, "utf8").pipe(
172+ Effect.mapError(mapFileSystemError(path, "read source artifact")),
173+ );
174+ return yield* Effect.try({
175+ try: () => decodeLocalSourceArtifact(JSON.parse(content) as unknown),
176+ catch: (cause) => {
177+ const message = cause instanceof Error ? cause.message : String(cause);
178+ return new Error(`Invalid local source artifact at ${path}: ${message}`);
179+ },
180+ });
181+ }));
163182
164−export const writeLocalSourceArtifact = async (input: {
183+export const writeLocalSourceArtifact = (input: {
165184 context: ResolvedLocalWorkspaceContext;
166185 sourceId: string;
167186 artifact: LocalSourceArtifact;
168−}): Promise<void> => {
169− const path = localSourceArtifactPath(input);
170− await fs.mkdir(join(input.context.artifactsDirectory, "sources"), {
171− recursive: true,
172− });
173− await fs.writeFile(path, `${JSON.stringify(input.artifact, null, 2)}\n`, "utf8");
174−};
187+}): Effect.Effect<void, Error> =>
188+ provideNodeFileSystem(Effect.gen(function* () {
189+ const fs = yield* FileSystem.FileSystem;
190+ const directory = join(input.context.artifactsDirectory, "sources");
191+ const path = localSourceArtifactPath(input);
192+ yield* fs.makeDirectory(directory, { recursive: true }).pipe(
193+ Effect.mapError(mapFileSystemError(directory, "create source artifact directory")),
194+ );
195+ yield* fs.writeFileString(path, `${JSON.stringify(input.artifact, null, 2)}\n`).pipe(
196+ Effect.mapError(mapFileSystemError(path, "write source artifact")),
197+ );
198+ }));
175199
176−export const removeLocalSourceArtifact = async (input: {
200+export const removeLocalSourceArtifact = (input: {
177201 context: ResolvedLocalWorkspaceContext;
178202 sourceId: string;
179−}): Promise<void> => {
180− const path = localSourceArtifactPath(input);
181−
182− try {
183− await fs.unlink(path);
184− } catch (cause) {
185− if (
186− cause instanceof Error
187− && ("code" in cause)
188− && (cause as NodeJS.ErrnoException).code === "ENOENT"
189− ) {
203+}): Effect.Effect<void, Error> =>
204+ provideNodeFileSystem(Effect.gen(function* () {
205+ const fs = yield* FileSystem.FileSystem;
206+ const path = localSourceArtifactPath(input);
207+ const exists = yield* fs.exists(path).pipe(
208+ Effect.mapError(mapFileSystemError(path, "check source artifact path")),
209+ );
210+ if (!exists) {
190211 return;
191212 }
192−
193− throw cause;
194− }
195−};
213+ yield* fs.remove(path).pipe(
214+ Effect.mapError(mapFileSystemError(path, "remove source artifact")),
215+ );
216+ }));
packages/control-plane/src/runtime/local-workspace-state.ts+51−27
@@ -1,11 +1,13 @@
1−import { promises as fs } from "node:fs";
21 import { join } from "node:path";
2+import { FileSystem } from "@effect/platform";
3+import { NodeFileSystem } from "@effect/platform-node";
34
45 import {
56 PolicyIdSchema,
67 SourceStatusSchema,
78 TimestampMsSchema,
89 } from "#schema";
10+import * as Effect from "effect/Effect";
911 import * as Schema from "effect/Schema";
1012
1113 import type { ResolvedLocalWorkspaceContext } from "./local-config";
@@ -44,6 +46,20 @@ export type LocalWorkspaceState = typeof LocalWorkspaceStateSchema.Type; · 32 unmodified lines
4446
4547 const decodeLocalWorkspaceState = Schema.decodeUnknownSync(LocalWorkspaceStateSchema);
4648
49+const provideNodeFileSystem = <A, E, R>(
50+ effect: Effect.Effect<A, E, R | FileSystem.FileSystem>,
51+): Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem>> =>
52+ effect.pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect<
53+ A,
54+ E,
55+ Exclude<R, FileSystem.FileSystem>
56+ >;
57+
58+const mapFileSystemError = (path: string, action: string) => (cause: unknown): Error => {
59+ const message = cause instanceof Error ? cause.message : String(cause);
60+ return new Error(`Failed to ${action} ${path}: ${message}`);
61+};
62+
4763 const defaultLocalWorkspaceState = (): LocalWorkspaceState => ({
4864 version: 1,
4965 sources: {},
@@ -54,36 +70,44 @@ export const localWorkspaceStatePath = ( · 4 unmodified lines
5470 context: ResolvedLocalWorkspaceContext,
5571 ): string => join(context.stateDirectory, WORKSPACE_STATE_BASENAME);
5672
57−export const loadLocalWorkspaceState = async (
73+export const loadLocalWorkspaceState = (
5874 context: ResolvedLocalWorkspaceContext,
59−): Promise<LocalWorkspaceState> => {
60− const path = localWorkspaceStatePath(context);
61−
62− try {
63− const content = await fs.readFile(path, "utf8");
64− return decodeLocalWorkspaceState(JSON.parse(content) as unknown);
65− } catch (cause) {
66− if (
67− cause instanceof Error
68− && ("code" in cause)
69− && (cause as NodeJS.ErrnoException).code === "ENOENT"
70− ) {
75+): Effect.Effect<LocalWorkspaceState, Error> =>
76+ provideNodeFileSystem(Effect.gen(function* () {
77+ const fs = yield* FileSystem.FileSystem;
78+ const path = localWorkspaceStatePath(context);
79+ const exists = yield* fs.exists(path).pipe(
80+ Effect.mapError(mapFileSystemError(path, "check workspace state path")),
81+ );
82+ if (!exists) {
7183 return defaultLocalWorkspaceState();
7284 }
7385
74− const message = cause instanceof Error ? cause.message : String(cause);
75− throw new Error(`Invalid local workspace state at ${path}: ${message}`);
76− }
77−};
86+ const content = yield* fs.readFileString(path, "utf8").pipe(
87+ Effect.mapError(mapFileSystemError(path, "read workspace state")),
88+ );
89+ return yield* Effect.try({
90+ try: () => decodeLocalWorkspaceState(JSON.parse(content) as unknown),
91+ catch: (cause) => {
92+ const message = cause instanceof Error ? cause.message : String(cause);
93+ return new Error(`Invalid local workspace state at ${path}: ${message}`);
94+ },
95+ });
96+ }));
7897
79−export const writeLocalWorkspaceState = async (input: {
98+export const writeLocalWorkspaceState = (input: {
8099 context: ResolvedLocalWorkspaceContext;
81100 state: LocalWorkspaceState;
82−}): Promise<void> => {
83− await fs.mkdir(input.context.stateDirectory, { recursive: true });
84− await fs.writeFile(
85− localWorkspaceStatePath(input.context),
86− `${JSON.stringify(input.state, null, 2)}\n`,
87− "utf8",
88− );
89−};
101+}): Effect.Effect<void, Error> =>
102+ provideNodeFileSystem(Effect.gen(function* () {
103+ const fs = yield* FileSystem.FileSystem;
104+ yield* fs.makeDirectory(input.context.stateDirectory, { recursive: true }).pipe(
105+ Effect.mapError(mapFileSystemError(input.context.stateDirectory, "create state directory")),
106+ );
107+ yield* fs.writeFileString(
108+ localWorkspaceStatePath(input.context),
109+ `${JSON.stringify(input.state, null, 2)}\n`,
110+ ).pipe(
111+ Effect.mapError(mapFileSystemError(localWorkspaceStatePath(input.context), "write workspace state")),
112+ );
113+ }));
packages/control-plane/src/runtime/local-workspace-sync.ts+4−13
@@ -46,11 +46,7 @@ const pruneLocalWorkspaceState = (input: { · 45 unmodified lines
4646 loadedConfig: LoadedLocalExecutorConfig;
4747 }): Effect.Effect<LocalWorkspaceState, Error, never> =>
4848 Effect.gen(function* () {
49− const currentState = yield* Effect.tryPromise({
50− try: () => loadLocalWorkspaceState(input.context),
51− catch: (cause) =>
52− cause instanceof Error ? cause : new Error(String(cause)),
53− });
49+ const currentState = yield* loadLocalWorkspaceState(input.context);
5450
5551 const configuredSourceIds = new Set(
5652 Object.keys(input.loadedConfig.config?.sources ?? {}),
@@ -77,14 +73,9 @@ const pruneLocalWorkspaceState = (input: { · 20 unmodified lines
7773 return currentState;
7874 }
7975
80− yield* Effect.tryPromise({
81− try: () =>
82− writeLocalWorkspaceState({
83− context: input.context,
84− state: nextState,
85− }),
86− catch: (cause) =>
87− cause instanceof Error ? cause : new Error(String(cause)),
76+ yield* writeLocalWorkspaceState({
77+ context: input.context,
78+ state: nextState,
8879 });
8980
9081 return nextState;
packages/control-plane/src/runtime/policies-operations.ts+14−26
@@ -22,6 +22,7 @@ import { · 21 unmodified lines
2222 } from "./local-runtime-context";
2323 import {
2424 loadLocalWorkspaceState,
25+ type LocalWorkspaceState,
2526 writeLocalWorkspaceState,
2627 } from "./local-workspace-state";
2728 import {
@@ -59,16 +60,8 @@ const localPolicyIdForConfigKey = (input: { · 31 unmodified lines
5960 export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) =>
6061 Effect.gen(function* () {
6162 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);
62− const loadedConfig = yield* Effect.tryPromise({
63− try: () => loadLocalExecutorConfig(runtimeLocalWorkspace.context),
64− catch: (cause) =>
65− cause instanceof Error ? cause : new Error(String(cause)),
66− });
67− const workspaceState = yield* Effect.tryPromise({
68− try: () => loadLocalWorkspaceState(runtimeLocalWorkspace.context),
69− catch: (cause) =>
70− cause instanceof Error ? cause : new Error(String(cause)),
71− });
63+ const loadedConfig = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context);
64+ const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
7265
7366 const configEntries = Object.entries(loadedConfig.config?.policies ?? {});
7467 const policies = configEntries.map(([configKey, configPolicy]) => {
@@ -135,23 +128,18 @@ const writeLocalPolicyFiles = (input: { · 60 unmodified lines
135128 operation: OperationErrors;
136129 context: Parameters<typeof writeProjectLocalExecutorConfig>[0]["context"];
137130 projectConfig: LocalExecutorConfig;
138− workspaceState: Awaited<ReturnType<typeof loadLocalWorkspaceState>>;
131+ workspaceState: LocalWorkspaceState;
139132 }) =>
140− Effect.tryPromise({
141− try: () =>
142− Promise.all([
143− writeProjectLocalExecutorConfig({
144− context: input.context,
145− config: input.projectConfig,
146− }),
147− writeLocalWorkspaceState({
148− context: input.context,
149− state: input.workspaceState,
150− }),
151− ]).then(() => undefined),
152− catch: (cause) =>
153− cause instanceof Error ? cause : new Error(String(cause)),
154− }).pipe(
133+ Effect.all([
134+ writeProjectLocalExecutorConfig({
135+ context: input.context,
136+ config: input.projectConfig,
137+ }),
138+ writeLocalWorkspaceState({
139+ context: input.context,
140+ state: input.workspaceState,
141+ }),
142+ ], { discard: true }).pipe(
155143 Effect.mapError((cause) =>
156144 input.operation.unknownStorage(
157145 cause,
packages/control-plane/src/runtime/source-auth-service.test.ts+4−12
@@ -186,16 +186,8 @@ const makeRuntimeLocalWorkspaceState = async ( · 185 unmodified lines
186186 const workspaceRoot = mkdtempSync(
187187 join(tmpdir(), "executor-source-auth-service-"),
188188 );
189− const context = yield* Effect.tryPromise({
190− try: () => resolveLocalWorkspaceContext({ workspaceRoot }),
191− catch: (cause) =>
192− cause instanceof Error ? cause : new Error(String(cause)),
193− });
194− const loadedConfig = yield* Effect.tryPromise({
195− try: () => loadLocalExecutorConfig(context),
196− catch: (cause) =>
197− cause instanceof Error ? cause : new Error(String(cause)),
198− });
189+ const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
190+ const loadedConfig = yield* loadLocalExecutorConfig(context);
199191
200192 return {
201193 context,
@@ -496,10 +488,10 @@ describe("source-auth-service", () => { · 294 unmodified lines
496488 expect(Option.isNone(oldSecret)).toBe(true);
497489 }
498490
499− const localArtifact = await readLocalSourceArtifact({
491+ const localArtifact = await Effect.runPromise(readLocalSourceArtifact({
500492 context: runtimeLocalWorkspace.context,
501493 sourceId: connectedSource.id,
502− });
494+ }));
503495 expect(localArtifact).not.toBeNull();
504496 expect(localArtifact?.operations.length).toBeGreaterThan(0);
505497 expect(
packages/control-plane/src/runtime/source-inspection.test.ts+8−16
@@ -59,16 +59,8 @@ const makeRuntimeLocalWorkspaceState = ( · 58 unmodified lines
5959 const workspaceRoot = mkdtempSync(
6060 join(tmpdir(), "executor-source-inspection-"),
6161 );
62− const context = yield* Effect.tryPromise({
63− try: () => resolveLocalWorkspaceContext({ workspaceRoot }),
64− catch: (cause) =>
65− cause instanceof Error ? cause : new Error(String(cause)),
66− });
67− const loadedConfig = yield* Effect.tryPromise({
68− try: () => loadLocalExecutorConfig(context),
69− catch: (cause) =>
70− cause instanceof Error ? cause : new Error(String(cause)),
71− });
62+ const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
63+ const loadedConfig = yield* loadLocalExecutorConfig(context);
7264
7365 return {
7466 context,
@@ -215,15 +207,15 @@ describe("source-inspection", () => { · 140 unmodified lines
215207 workspaceId,
216208 sourceId,
217209 });
218− await writeProjectLocalExecutorConfig({
210+ await Effect.runPromise(writeProjectLocalExecutorConfig({
219211 context: runtimeLocalWorkspace.context,
220212 config: {
221213 sources: {
222214 [sourceId]: configSourceFromSource(source),
223215 },
224216 },
225− });
226− await writeLocalSourceArtifact({
217+ }));
218+ await Effect.runPromise(writeLocalSourceArtifact({
227219 context: runtimeLocalWorkspace.context,
228220 sourceId,
229221 artifact: buildLocalSourceArtifact({
@@ -271,8 +263,8 @@ describe("source-inspection", () => { · 41 unmodified lines
271263 })],
272264 },
273265 }),
274− });
275− await writeLocalWorkspaceState({
266+ }));
267+ await Effect.runPromise(writeLocalWorkspaceState({
276268 context: runtimeLocalWorkspace.context,
277269 state: {
278270 version: 1,
@@ -287,7 +279,7 @@ describe("source-inspection", () => { · 8 unmodified lines
287279 },
288280 policies: {},
289281 },
290− });
282+ }));
291283
292284 const inspection = await Effect.runPromise(
293285 getSourceInspection({
packages/control-plane/src/runtime/source-materialization.ts+22−50
@@ -45,11 +45,7 @@ export const syncSourceMaterialization = (input: { · 44 unmodified lines
4545 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.source.workspaceId);
4646
4747 if (!shouldIndexSource(input.source)) {
48− const state = yield* Effect.tryPromise({
49− try: () => loadLocalWorkspaceState(runtimeLocalWorkspace.context),
50− catch: (cause) =>
51− cause instanceof Error ? cause : new Error(String(cause)),
52− });
48+ const state = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
5349 const existingSourceState = state.sources[input.source.id];
5450 const nextState: LocalWorkspaceState = {
5551 ...state,
@@ -64,14 +60,9 @@ export const syncSourceMaterialization = (input: { · 8 unmodified lines
6460 },
6561 },
6662 };
67− yield* Effect.tryPromise({
68− try: () =>
69− writeLocalWorkspaceState({
70− context: runtimeLocalWorkspace.context,
71− state: nextState,
72− }),
73− catch: (cause) =>
74− cause instanceof Error ? cause : new Error(String(cause)),
63+ yield* writeLocalWorkspaceState({
64+ context: runtimeLocalWorkspace.context,
65+ state: nextState,
7566 });
7667 return;
7768 }
@@ -89,25 +80,16 @@ export const syncSourceMaterialization = (input: { · 11 unmodified lines
8980 resolveSecretMaterial: input.resolveSecretMaterial,
9081 }),
9182 });
92− yield* Effect.tryPromise({
93− try: () =>
94− writeLocalSourceArtifact({
95− context: runtimeLocalWorkspace.context,
96− sourceId: input.source.id,
97− artifact: buildLocalSourceArtifact({
98− source: input.source,
99− materialization,
100− }),
101− }),
102− catch: (cause) =>
103− cause instanceof Error ? cause : new Error(String(cause)),
83+ yield* writeLocalSourceArtifact({
84+ context: runtimeLocalWorkspace.context,
85+ sourceId: input.source.id,
86+ artifact: buildLocalSourceArtifact({
87+ source: input.source,
88+ materialization,
89+ }),
10490 });
10591
106− const state = yield* Effect.tryPromise({
107− try: () => loadLocalWorkspaceState(runtimeLocalWorkspace.context),
108− catch: (cause) =>
109− cause instanceof Error ? cause : new Error(String(cause)),
110− });
92+ const state = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
11193 const existingSourceState = state.sources[input.source.id];
11294 const nextState: LocalWorkspaceState = {
11395 ...state,
@@ -122,14 +104,9 @@ export const syncSourceMaterialization = (input: { · 8 unmodified lines
122104 },
123105 },
124106 };
125− yield* Effect.tryPromise({
126− try: () =>
127− writeLocalWorkspaceState({
128− context: runtimeLocalWorkspace.context,
129− state: nextState,
130− }),
131− catch: (cause) =>
132− cause instanceof Error ? cause : new Error(String(cause)),
107+ yield* writeLocalWorkspaceState({
108+ context: runtimeLocalWorkspace.context,
109+ state: nextState,
133110 });
134111 });
135112
@@ -148,17 +125,12 @@ export const persistMcpRecipeMaterializationFromManifest = (input: { · 12 unmodified lines
148125 manifestEntries: input.manifestEntries,
149126 });
150127
151− yield* Effect.tryPromise({
152− try: () =>
153− writeLocalSourceArtifact({
154− context: runtimeLocalWorkspace.context,
155− sourceId: input.source.id,
156− artifact: buildLocalSourceArtifact({
157− source: input.source,
158− materialization,
159− }),
160− }),
161− catch: (cause) =>
162− cause instanceof Error ? cause : new Error(String(cause)),
128+ yield* writeLocalSourceArtifact({
129+ context: runtimeLocalWorkspace.context,
130+ sourceId: input.source.id,
131+ artifact: buildLocalSourceArtifact({
132+ source: input.source,
133+ materialization,
134+ }),
163135 });
164136 });
packages/control-plane/src/runtime/source-recipes-runtime.test.ts+14−21
@@ -66,16 +66,8 @@ const makeRuntimeLocalWorkspaceState = ( · 65 unmodified lines
6666 const workspaceRoot = mkdtempSync(
6767 join(tmpdir(), "executor-source-recipes-runtime-"),
6868 );
69− const context = yield* Effect.tryPromise({
70− try: () => resolveLocalWorkspaceContext({ workspaceRoot }),
71− catch: (cause) =>
72− cause instanceof Error ? cause : new Error(String(cause)),
73− });
74− const loadedConfig = yield* Effect.tryPromise({
75− try: () => loadLocalExecutorConfig(context),
76− catch: (cause) =>
77− cause instanceof Error ? cause : new Error(String(cause)),
78− });
69+ const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
70+ const loadedConfig = yield* loadLocalExecutorConfig(context);
7971
8072 return {
8173 context,
@@ -362,18 +354,18 @@ const seedLocalWorkspaceSources = async (input: { · 280 unmodified lines
362354 };
363355 }>;
364356 }) => {
365− await writeProjectLocalExecutorConfig({
357+ await Effect.runPromise(writeProjectLocalExecutorConfig({
366358 context: input.runtimeLocalWorkspace.context,
367359 config: {
368360 sources: Object.fromEntries(
369361 input.fixtures.map(({ source }) => [source.id, configSourceFromSource(source)]),
370362 ),
371363 },
372− });
364+ }));
373365
374366 await Promise.all(
375367 input.fixtures.map(({ source, materialization }) =>
376− writeLocalSourceArtifact({
368+ Effect.runPromise(writeLocalSourceArtifact({
377369 context: input.runtimeLocalWorkspace.context,
378370 sourceId: source.id,
379371 artifact: buildLocalSourceArtifact({
@@ -381,9 +373,10 @@ const seedLocalWorkspaceSources = async (input: { · 1 unmodified line
381373 materialization,
382374 }),
383375 })),
376+ ),
384377 );
385378
386− await writeLocalWorkspaceState({
379+ await Effect.runPromise(writeLocalWorkspaceState({
387380 context: input.runtimeLocalWorkspace.context,
388381 state: {
389382 version: 1,
@@ -401,7 +394,7 @@ const seedLocalWorkspaceSources = async (input: { · 11 unmodified lines
401394 ),
402395 policies: {},
403396 },
404− });
397+ }));
405398 };
406399
407400 describe("source-recipes-runtime", () => {
@@ -774,7 +767,7 @@ describe("source-recipes-runtime", () => { · 366 unmodified lines
774767 const runtimeLocalWorkspace =
775768 await makeRuntimeLocalWorkspaceState(workspaceId);
776769
777− await writeProjectLocalExecutorConfig({
770+ await Effect.runPromise(writeProjectLocalExecutorConfig({
778771 context: runtimeLocalWorkspace.context,
779772 config: {
780773 sources: {
@@ -787,8 +780,8 @@ describe("source-recipes-runtime", () => { · 6 unmodified lines
787780 ),
788781 },
789782 },
790− });
791− await writeLocalWorkspaceState({
783+ }));
784+ await Effect.runPromise(writeLocalWorkspaceState({
792785 context: runtimeLocalWorkspace.context,
793786 state: {
794787 version: 1,
@@ -803,7 +796,7 @@ describe("source-recipes-runtime", () => { · 8 unmodified lines
803796 },
804797 policies: {},
805798 },
806− });
799+ }));
807800
808801 await expect(
809802 Effect.runPromise(
@@ -834,7 +827,7 @@ describe("source-recipes-runtime", () => { · 24 unmodified lines
834827 operations: [],
835828 },
836829 });
837− await writeLocalSourceArtifact({
830+ await Effect.runPromise(writeLocalSourceArtifact({
838831 context: runtimeLocalWorkspace.context,
839832 sourceId,
840833 artifact: {
@@ -844,7 +837,7 @@ describe("source-recipes-runtime", () => { · 3 unmodified lines
844837 manifestJson: "{bad-json",
845838 },
846839 },
847− });
840+ }));
848841
849842 await expect(
850843 Effect.runPromise(
packages/control-plane/src/runtime/source-recipes-runtime.ts+6−16
@@ -167,14 +167,9 @@ export const loadWorkspaceSourceRecipes = (input: { · 166 unmodified lines
167167 });
168168 const localRecipes = yield* Effect.forEach(sources, (source) =>
169169 Effect.gen(function* () {
170− const artifact = yield* Effect.tryPromise({
171− try: () =>
172− readLocalSourceArtifact({
173− context: runtimeLocalWorkspace.context,
174− sourceId: source.id,
175− }),
176− catch: (cause) =>
177− cause instanceof Error ? cause : new Error(String(cause)),
170+ const artifact = yield* readLocalSourceArtifact({
171+ context: runtimeLocalWorkspace.context,
172+ sourceId: source.id,
178173 });
179174 if (artifact === null) {
180175 return null;
@@ -231,14 +226,9 @@ export const loadSourceWithRecipe = (input: { · 50 unmodified lines
231226 sourceId: input.sourceId,
232227 actorAccountId: input.actorAccountId,
233228 });
234− const artifact = yield* Effect.tryPromise({
235− try: () =>
236− readLocalSourceArtifact({
237− context: runtimeLocalWorkspace.context,
238− sourceId: source.id,
239− }),
240− catch: (cause) =>
241− cause instanceof Error ? cause : new Error(String(cause)),
229+ const artifact = yield* readLocalSourceArtifact({
230+ context: runtimeLocalWorkspace.context,
231+ sourceId: source.id,
242232 });
243233 if (artifact === null) {
244234 return yield* Effect.fail(
packages/control-plane/src/runtime/source-store.test.ts+4−20
@@ -56,16 +56,8 @@ const makeRuntimeLocalWorkspace = (input: { · 55 unmodified lines
5656 }) =>
5757 Effect.gen(function* () {
5858 const workspaceRoot = mkdtempSync(join(tmpdir(), "executor-source-store-"));
59− const context = yield* Effect.tryPromise({
60− try: () => resolveLocalWorkspaceContext({ workspaceRoot }),
61− catch: (cause) =>
62− cause instanceof Error ? cause : new Error(String(cause)),
63− });
64− const loadedConfig = yield* Effect.tryPromise({
65− try: () => loadLocalExecutorConfig(context),
66− catch: (cause) =>
67− cause instanceof Error ? cause : new Error(String(cause)),
68− });
59+ const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
60+ const loadedConfig = yield* loadLocalExecutorConfig(context);
6961
7062 return {
7163 context,
@@ -245,17 +237,9 @@ describe("source-store", () => { · 173 unmodified lines
245237 expect(Option.isNone(yield* persistence.rows.secretMaterials.getById(secondTokenId))).toBe(true);
246238 expect(yield* persistence.rows.authArtifacts.listByWorkspaceId(workspaceId)).toHaveLength(0);
247239 expect(yield* persistence.rows.sourceAuthSessions.listByWorkspaceId(workspaceId)).toHaveLength(0);
248− const config = yield* Effect.tryPromise({
249− try: () => loadLocalExecutorConfig(runtimeLocalWorkspace.context),
250− catch: (cause) =>
251− cause instanceof Error ? cause : new Error(String(cause)),
252− });
240+ const config = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context);
253241 expect(config.config?.sources?.[sourceId]).toBeUndefined();
254− const workspaceState = yield* Effect.tryPromise({
255− try: () => loadLocalWorkspaceState(runtimeLocalWorkspace.context),
256− catch: (cause) =>
257− cause instanceof Error ? cause : new Error(String(cause)),
258− });
242+ const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
259243 expect(workspaceState.sources[sourceId]).toBeUndefined();
260244 }),
261245 );
packages/control-plane/src/runtime/source-store.ts+26−63
@@ -234,16 +234,8 @@ const configAuthFromSource = (input: { · 233 unmodified lines
234234 const resolveRuntimeLocalWorkspace = (workspaceId: WorkspaceId) =>
235235 Effect.gen(function* () {
236236 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);
237− const loadedConfig = yield* Effect.tryPromise({
238− try: () => loadLocalExecutorConfig(runtimeLocalWorkspace.context),
239− catch: (cause) =>
240− cause instanceof Error ? cause : new Error(String(cause)),
241− });
242− const workspaceState = yield* Effect.tryPromise({
243− try: () => loadLocalWorkspaceState(runtimeLocalWorkspace.context),
244− catch: (cause) =>
245− cause instanceof Error ? cause : new Error(String(cause)),
246− });
237+ const loadedConfig = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context);
238+ const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
247239
248240 return {
249241 context: runtimeLocalWorkspace.context,
@@ -297,13 +289,9 @@ const buildLocalSourceRecord = (input: { · 47 unmodified lines
297289 updatedAt: existingState?.updatedAt ?? Date.now(),
298290 });
299291
300− const artifact = yield* Effect.tryPromise({
301− try: () => readLocalSourceArtifact({
302− context: input.context,
303− sourceId: input.sourceId,
304− }),
305− catch: (cause) =>
306− cause instanceof Error ? cause : new Error(String(cause)),
292+ const artifact = yield* readLocalSourceArtifact({
293+ context: input.context,
294+ sourceId: input.sourceId,
307295 }).pipe(
308296 Effect.catchAll(() => Effect.succeed(null)),
309297 );
@@ -524,17 +512,12 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 214 unmodified lines
524512 ...(projectConfig.sources ?? {}),
525513 };
526514 delete sources[input.sourceId];
527− yield* Effect.tryPromise({
528− try: () =>
529− writeProjectLocalExecutorConfig({
530− context: localWorkspace.context,
531− config: {
532− ...projectConfig,
533− sources,
534− },
535− }),
536− catch: (cause) =>
537− cause instanceof Error ? cause : new Error(String(cause)),
515+ yield* writeProjectLocalExecutorConfig({
516+ context: localWorkspace.context,
517+ config: {
518+ ...projectConfig,
519+ sources,
520+ },
538521 });
539522
540523 const {
@@ -545,23 +528,13 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 4 unmodified lines
545528 ...localWorkspace.workspaceState,
546529 sources: remainingSources,
547530 };
548− yield* Effect.tryPromise({
549− try: () =>
550− writeLocalWorkspaceState({
551− context: localWorkspace.context,
552− state: workspaceState,
553− }),
554− catch: (cause) =>
555− cause instanceof Error ? cause : new Error(String(cause)),
531+ yield* writeLocalWorkspaceState({
532+ context: localWorkspace.context,
533+ state: workspaceState,
556534 });
557− yield* Effect.tryPromise({
558− try: () =>
559− removeLocalSourceArtifact({
560− context: localWorkspace.context,
561− sourceId: input.sourceId,
562− }),
563− catch: (cause) =>
564− cause instanceof Error ? cause : new Error(String(cause)),
535+ yield* removeLocalSourceArtifact({
536+ context: localWorkspace.context,
537+ sourceId: input.sourceId,
565538 });
566539
567540 yield* rows.sourceAuthSessions.removeByWorkspaceAndSourceId(
@@ -621,17 +594,12 @@ export const persistSource = ( · 53 unmodified lines
621594 existingConfigAuth: existingConfigSource?.connection.auth,
622595 config: localWorkspace.loadedConfig.config,
623596 });
624− yield* Effect.tryPromise({
625− try: () =>
626− writeProjectLocalExecutorConfig({
627− context: localWorkspace.context,
628− config: {
629− ...projectConfig,
630− sources,
631− },
632− }),
633− catch: (cause) =>
634− cause instanceof Error ? cause : new Error(String(cause)),
597+ yield* writeProjectLocalExecutorConfig({
598+ context: localWorkspace.context,
599+ config: {
600+ ...projectConfig,
601+ sources,
602+ },
635603 });
636604
637605 const { runtimeAuthArtifact, importAuthArtifact } = splitSourceForStorage({
@@ -715,14 +683,9 @@ export const persistSource = ( · 77 unmodified lines
715683 },
716684 },
717685 };
718− yield* Effect.tryPromise({
719− try: () =>
720− writeLocalWorkspaceState({
721− context: localWorkspace.context,
722− state: workspaceState,
723− }),
724− catch: (cause) =>
725− cause instanceof Error ? cause : new Error(String(cause)),
686+ yield* writeLocalWorkspaceState({
687+ context: localWorkspace.context,
688+ state: workspaceState,
726689 });
727690
728691 return yield* loadSourceById(rows, {
packages/control-plane/src/runtime/workspace-execution-environment.test.ts+108−169
@@ -631,80 +631,65 @@ const persistConnectedEchoTool = (input: { · 630 unmodified lines
631631 updatedAt: now,
632632 };
633633
634− yield* Effect.tryPromise({
635− try: () =>
636− writeProjectLocalExecutorConfig({
637− context: input.runtimeLocalWorkspace.context,
638− config: {
639− sources: {
640− [input.sourceId]: {
641− kind: "mcp",
642− name: source.name,
643− ...(source.namespace ? { namespace: source.namespace } : {}),
644− connection: {
645− endpoint: source.endpoint,
646− },
647− binding: {
648− transport: "streamable-http",
649− },
650− },
634+ yield* writeProjectLocalExecutorConfig({
635+ context: input.runtimeLocalWorkspace.context,
636+ config: {
637+ sources: {
638+ [input.sourceId]: {
639+ kind: "mcp",
640+ name: source.name,
641+ ...(source.namespace ? { namespace: source.namespace } : {}),
642+ connection: {
643+ endpoint: source.endpoint,
644+ },
645+ binding: {
646+ transport: "streamable-http",
651647 },
652648 },
653− }),
654− catch: (cause) =>
655− cause instanceof Error ? cause : new Error(String(cause)),
649+ },
650+ },
656651 });
657− yield* Effect.tryPromise({
658− try: () =>
659− writeLocalSourceArtifact({
660− context: input.runtimeLocalWorkspace.context,
661− sourceId: source.id,
662− artifact: buildLocalSourceArtifact({
663− source,
664− materialization: materializationFromMcpManifestEntries({
665− recipeRevisionId: "src_recipe_rev_materialization" as never,
666− endpoint: source.endpoint,
667− manifestEntries: [
668− {
669− toolId: "echo",
670− toolName: "echo",
671− description: "Echo the provided string",
672− inputSchemaJson: JSON.stringify({
673− type: "object",
674− properties: {
675− value: { type: "string" },
676− },
677− required: ["value"],
678− additionalProperties: false,
679− }),
652+ yield* writeLocalSourceArtifact({
653+ context: input.runtimeLocalWorkspace.context,
654+ sourceId: source.id,
655+ artifact: buildLocalSourceArtifact({
656+ source,
657+ materialization: materializationFromMcpManifestEntries({
658+ recipeRevisionId: "src_recipe_rev_materialization" as never,
659+ endpoint: source.endpoint,
660+ manifestEntries: [
661+ {
662+ toolId: "echo",
663+ toolName: "echo",
664+ description: "Echo the provided string",
665+ inputSchemaJson: JSON.stringify({
666+ type: "object",
667+ properties: {
668+ value: { type: "string" },
680669 },
681− ],
682− }),
683− }),
670+ required: ["value"],
671+ additionalProperties: false,
672+ }),
673+ },
674+ ],
684675 }),
685− catch: (cause) =>
686− cause instanceof Error ? cause : new Error(String(cause)),
676+ }),
687677 });
688− yield* Effect.tryPromise({
689− try: () =>
690− writeLocalWorkspaceState({
691− context: input.runtimeLocalWorkspace.context,
692− state: {
693− version: 1,
694− sources: {
695− [input.sourceId]: {
696− status: "connected",
697− lastError: null,
698− sourceHash: null,
699− createdAt: now,
700− updatedAt: now,
701− },
702− },
703− policies: {},
678+ yield* writeLocalWorkspaceState({
679+ context: input.runtimeLocalWorkspace.context,
680+ state: {
681+ version: 1,
682+ sources: {
683+ [input.sourceId]: {
684+ status: "connected",
685+ lastError: null,
686+ sourceHash: null,
687+ createdAt: now,
688+ updatedAt: now,
704689 },
705− }),
706− catch: (cause) =>
707− cause instanceof Error ? cause : new Error(String(cause)),
690+ },
691+ policies: {},
692+ },
708693 });
709694 }) as Effect.Effect<void, Error, never>;
710695
@@ -931,38 +916,31 @@ const persistConnectedGithubOpenApiSource = (input: { · 220 unmodified lines
931916 ),
932917 );
933918 const definitions = compileOpenApiToolDefinitions(manifest);
934− yield* Effect.tryPromise({
935− try: () =>
936− writeProjectLocalExecutorConfig({
937− context: input.runtimeLocalWorkspace.context,
938− config: {
939− sources: {
940− [input.sourceId]: {
941− kind: "openapi",
942− name: source.name,
943− ...(source.namespace ? { namespace: source.namespace } : {}),
944− connection: {
945− endpoint: source.endpoint,
946− ...(configAuth !== undefined ? { auth: configAuth } : {}),
947− },
948− binding: {
949− specUrl: String(source.binding.specUrl),
950− defaultHeaders: null,
951− },
952− },
919+ yield* writeProjectLocalExecutorConfig({
920+ context: input.runtimeLocalWorkspace.context,
921+ config: {
922+ sources: {
923+ [input.sourceId]: {
924+ kind: "openapi",
925+ name: source.name,
926+ ...(source.namespace ? { namespace: source.namespace } : {}),
927+ connection: {
928+ endpoint: source.endpoint,
929+ ...(configAuth !== undefined ? { auth: configAuth } : {}),
930+ },
931+ binding: {
932+ specUrl: String(source.binding.specUrl),
933+ defaultHeaders: null,
953934 },
954935 },
955− }),
956− catch: (cause) =>
957− cause instanceof Error ? cause : new Error(String(cause)),
936+ },
937+ },
958938 });
959939 const refEntries = Object.entries(manifest.refHintTable ?? {});
960− yield* Effect.tryPromise({
961− try: () =>
962− writeLocalSourceArtifact({
963− context: input.runtimeLocalWorkspace.context,
964− sourceId: source.id,
965− artifact: buildLocalSourceArtifact({
940+ yield* writeLocalSourceArtifact({
941+ context: input.runtimeLocalWorkspace.context,
942+ sourceId: source.id,
943+ artifact: buildLocalSourceArtifact({
966944 source: {
967945 ...source,
968946 sourceHash: manifest.sourceHash,
@@ -1055,30 +1033,22 @@ const persistConnectedGithubOpenApiSource = (input: { · 86 unmodified lines
10551033 }),
10561034 },
10571035 }),
1058− }),
1059− catch: (cause) =>
1060− cause instanceof Error ? cause : new Error(String(cause)),
10611036 });
1062− yield* Effect.tryPromise({
1063− try: () =>
1064− writeLocalWorkspaceState({
1065− context: input.runtimeLocalWorkspace.context,
1066− state: {
1067− version: 1,
1068− sources: {
1069− [input.sourceId]: {
1070− status: "connected",
1071− lastError: null,
1072− sourceHash: manifest.sourceHash,
1073− createdAt: now,
1074− updatedAt: now,
1075− },
1076− },
1077− policies: {},
1037+ yield* writeLocalWorkspaceState({
1038+ context: input.runtimeLocalWorkspace.context,
1039+ state: {
1040+ version: 1,
1041+ sources: {
1042+ [input.sourceId]: {
1043+ status: "connected",
1044+ lastError: null,
1045+ sourceHash: manifest.sourceHash,
1046+ createdAt: now,
1047+ updatedAt: now,
10781048 },
1079− }),
1080− catch: (cause) =>
1081− cause instanceof Error ? cause : new Error(String(cause)),
1049+ },
1050+ policies: {},
1051+ },
10821052 });
10831053
10841054 return;
@@ -1087,16 +1057,8 @@ const persistConnectedGithubOpenApiSource = (input: { · 2 unmodified lines
10871057 const makeRuntimeLocalWorkspaceState = (workspaceId: WorkspaceId): Effect.Effect<RuntimeLocalWorkspaceState, Error, never> =>
10881058 Effect.gen(function* () {
10891059 const workspaceRoot = mkdtempSync(join(tmpdir(), "executor-workspace-env-"));
1090− const context = yield* Effect.tryPromise({
1091− try: () => resolveLocalWorkspaceContext({ workspaceRoot }),
1092− catch: (cause) =>
1093− cause instanceof Error ? cause : new Error(String(cause)),
1094− });
1095− const loadedConfig = yield* Effect.tryPromise({
1096− try: () => loadLocalExecutorConfig(context),
1097− catch: (cause) =>
1098− cause instanceof Error ? cause : new Error(String(cause)),
1099− });
1060+ const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
1061+ const loadedConfig = yield* loadLocalExecutorConfig(context);
11001062 return {
11011063 context,
11021064 installation: {
@@ -1377,34 +1339,21 @@ describe("workspace-execution-environment", () => { · 274 unmodified lines
13771339 encodeURIComponent("exec_add_openapi:executor.sources.add:"),
13781340 );
13791341
1380− const loadedConfig = yield* Effect.tryPromise({
1381− try: () => loadLocalExecutorConfig(runtimeLocalWorkspace.context),
1382− catch: (cause) =>
1383− cause instanceof Error ? cause : new Error(String(cause)),
1384− });
1342+ const loadedConfig = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context);
13851343 expect(loadedConfig.config?.sources?.[added.id]).toMatchObject({
13861344 kind: "openapi",
13871345 connection: {
13881346 endpoint: `${specServer.baseUrl}/`,
13891347 },
13901348 });
1391− const artifact = yield* Effect.tryPromise({
1392− try: () =>
1393− readLocalSourceArtifact({
1394− context: runtimeLocalWorkspace.context,
1395− sourceId: added.id,
1396− }),
1397− catch: (cause) =>
1398− cause instanceof Error ? cause : new Error(String(cause)),
1349+ const artifact = yield* readLocalSourceArtifact({
1350+ context: runtimeLocalWorkspace.context,
1351+ sourceId: added.id,
13991352 });
14001353 expect(artifact?.documents[0]?.contentText).toContain(
14011354 '"operationId":"repos.getRepo"',
14021355 );
1403− const workspaceState = yield* Effect.tryPromise({
1404− try: () => loadLocalWorkspaceState(runtimeLocalWorkspace.context),
1405− catch: (cause) =>
1406− cause instanceof Error ? cause : new Error(String(cause)),
1407− });
1356+ const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
14081357 expect(workspaceState.sources[added.id]?.status).toBe("connected");
14091358
14101359 const freshEnvironment = yield* resolveEnvironment({
@@ -1511,14 +1460,9 @@ describe("workspace-execution-environment", () => { · 100 unmodified lines
15111460 expect(added.auth.kind).toBe("bearer");
15121461 expect(added.auth.token?.providerId).toBe("postgres");
15131462
1514− const artifact = yield* Effect.tryPromise({
1515− try: () =>
1516− readLocalSourceArtifact({
1517− context: runtimeLocalWorkspace.context,
1518− sourceId: added.id,
1519− }),
1520− catch: (cause) =>
1521− cause instanceof Error ? cause : new Error(String(cause)),
1463+ const artifact = yield* readLocalSourceArtifact({
1464+ context: runtimeLocalWorkspace.context,
1465+ sourceId: added.id,
15221466 });
15231467 expect(artifact?.documents[0]?.contentText).toContain('"__schema"');
15241468 expect(artifact?.schemaBundles.length).toBe(1);
@@ -1526,22 +1470,17 @@ describe("workspace-execution-environment", () => { · 1 unmodified line
15261470 throw new Error(`Missing local source artifact for ${added.id}`);
15271471 }
15281472
1529− yield* Effect.tryPromise({
1530− try: () =>
1531− writeLocalSourceArtifact({
1532− context: runtimeLocalWorkspace.context,
1533− sourceId: added.id,
1534− artifact: {
1535− ...artifact,
1536− revision: {
1537− ...artifact.revision,
1538− manifestJson: null,
1539− updatedAt: Date.now(),
1540− },
1541− },
1542− }),
1543− catch: (cause) =>
1544− cause instanceof Error ? cause : new Error(String(cause)),
1473+ yield* writeLocalSourceArtifact({
1474+ context: runtimeLocalWorkspace.context,
1475+ sourceId: added.id,
1476+ artifact: {
1477+ ...artifact,
1478+ revision: {
1479+ ...artifact.revision,
1480+ manifestJson: null,
1481+ updatedAt: Date.now(),
1482+ },
1483+ },
15451484 });
15461485
15471486 const freshEnvironment = yield* resolveEnvironment({