Unified · Split
| @@ -1,6 +1,9 @@ | ||
| 1 | 1 | import type { WorkspaceId } from "#schema"; |
| 2 | 2 | import * as Effect from "effect/Effect"; |
| 3 | 3 | |
| 4 | +import { | |
| 5 | + RuntimeLocalWorkspaceMismatchError, | |
| 6 | +} from "../runtime/local-errors"; | |
| 4 | 7 | import { requireRuntimeLocalWorkspace } from "../runtime/local-runtime-context"; |
| 5 | 8 | import { ControlPlaneForbiddenError } from "./errors"; |
| 6 | 9 | |
| @@ -13,7 +16,10 @@ export const resolveRequestedLocalWorkspace = ( · 6 unmodified lines | ||
| 13 | 16 | new ControlPlaneForbiddenError({ |
| 14 | 17 | operation, |
| 15 | 18 | message: "Requested workspace is not the active local workspace", |
| 16 | − details: cause instanceof Error ? cause.message : String(cause), | |
| 19 | + details: | |
| 20 | + cause instanceof RuntimeLocalWorkspaceMismatchError | |
| 21 | + ? `requestedWorkspaceId=${cause.requestedWorkspaceId} activeWorkspaceId=${cause.activeWorkspaceId}` | |
| 22 | + : "Runtime local workspace is unavailable", | |
| 17 | 23 | }) |
| 18 | 24 | ), |
| 19 | 25 | ); |
| @@ -17,6 +17,11 @@ import { · 16 unmodified lines | ||
| 17 | 17 | } from "#schema"; |
| 18 | 18 | import * as Effect from "effect/Effect"; |
| 19 | 19 | import * as Schema from "effect/Schema"; |
| 20 | +import { | |
| 21 | + LocalExecutorConfigDecodeError, | |
| 22 | + LocalFileSystemError, | |
| 23 | + unknownLocalErrorDetails, | |
| 24 | +} from "./local-errors"; | |
| 20 | 25 | |
| 21 | 26 | const decodeLocalExecutorConfig = Schema.decodeUnknownSync(LocalExecutorConfigSchema); |
| 22 | 27 | |
| @@ -34,10 +39,13 @@ const provideNodeFileSystem = <A, E, R>( · 11 unmodified lines | ||
| 34 | 39 | Exclude<R, FileSystem.FileSystem> |
| 35 | 40 | >; |
| 36 | 41 | |
| 37 | −const mapFileSystemError = (path: string, action: string) => (cause: unknown): Error => { | |
| 38 | − const message = cause instanceof Error ? cause.message : String(cause); | |
| 39 | − return new Error(`Failed to ${action} ${path}: ${message}`); | |
| 40 | −}; | |
| 42 | +const mapFileSystemError = (path: string, action: string) => (cause: unknown) => | |
| 43 | + new LocalFileSystemError({ | |
| 44 | + message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`, | |
| 45 | + action, | |
| 46 | + path, | |
| 47 | + details: unknownLocalErrorDetails(cause), | |
| 48 | + }); | |
| 41 | 49 | |
| 42 | 50 | const trimOrUndefined = (value: string | undefined | null): string | undefined => { |
| 43 | 51 | const trimmed = value?.trim(); |
| @@ -106,7 +114,7 @@ export const resolveHomeConfigPath = (input: { · 62 unmodified lines | ||
| 106 | 114 | env?: NodeJS.ProcessEnv; |
| 107 | 115 | platform?: NodeJS.Platform; |
| 108 | 116 | homeDirectory?: string; |
| 109 | −} = {}): Effect.Effect<string, Error> => | |
| 117 | +} = {}): Effect.Effect<string, LocalFileSystemError> => | |
| 110 | 118 | provideNodeFileSystem(Effect.gen(function* () { |
| 111 | 119 | const fs = yield* FileSystem.FileSystem; |
| 112 | 120 | const candidates = resolveDefaultHomeConfigCandidates(input); |
| @@ -152,13 +160,23 @@ const parseJsonc = (input: { path: string; content: string }): LocalExecutorConf · 39 unmodified lines | ||
| 152 | 160 | allowTrailingComma: true, |
| 153 | 161 | }); |
| 154 | 162 | if (errors.length > 0) { |
| 155 | − throw new Error(formatJsoncParseErrors(input.content, errors)); | |
| 163 | + throw new LocalExecutorConfigDecodeError({ | |
| 164 | + message: `Invalid executor config at ${input.path}: ${formatJsoncParseErrors(input.content, errors)}`, | |
| 165 | + path: input.path, | |
| 166 | + details: formatJsoncParseErrors(input.content, errors), | |
| 167 | + }); | |
| 156 | 168 | } |
| 157 | 169 | |
| 158 | 170 | return decodeLocalExecutorConfig(parsed); |
| 159 | 171 | } catch (cause) { |
| 160 | − const message = cause instanceof Error ? cause.message : String(cause); | |
| 161 | − throw new Error(`Invalid executor config at ${input.path}: ${message}`); | |
| 172 | + if (cause instanceof LocalExecutorConfigDecodeError) { | |
| 173 | + throw cause; | |
| 174 | + } | |
| 175 | + throw new LocalExecutorConfigDecodeError({ | |
| 176 | + message: `Invalid executor config at ${input.path}: ${unknownLocalErrorDetails(cause)}`, | |
| 177 | + path: input.path, | |
| 178 | + details: unknownLocalErrorDetails(cause), | |
| 179 | + }); | |
| 162 | 180 | } |
| 163 | 181 | }; |
| 164 | 182 | |
| @@ -320,7 +338,7 @@ export type LoadedLocalExecutorConfig = { · 155 unmodified lines | ||
| 320 | 338 | export const resolveLocalWorkspaceContext = (input: { |
| 321 | 339 | cwd?: string; |
| 322 | 340 | workspaceRoot?: string; |
| 323 | −} = {}): Effect.Effect<ResolvedLocalWorkspaceContext, Error> => | |
| 341 | +} = {}): Effect.Effect<ResolvedLocalWorkspaceContext, LocalFileSystemError> => | |
| 324 | 342 | provideNodeFileSystem(Effect.gen(function* () { |
| 325 | 343 | const cwd = resolve(input.cwd ?? process.cwd()); |
| 326 | 344 | const workspaceRoot = resolve( |
| @@ -344,7 +362,7 @@ export const resolveLocalWorkspaceContext = (input: { · 17 unmodified lines | ||
| 344 | 362 | |
| 345 | 363 | export const readOptionalLocalExecutorConfig = ( |
| 346 | 364 | path: string, |
| 347 | −): Effect.Effect<LocalExecutorConfig | null, Error> => | |
| 365 | +): Effect.Effect<LocalExecutorConfig | null, LocalFileSystemError | LocalExecutorConfigDecodeError> => | |
| 348 | 366 | provideNodeFileSystem(Effect.gen(function* () { |
| 349 | 367 | const fs = yield* FileSystem.FileSystem; |
| 350 | 368 | const exists = yield* fs.exists(path).pipe( |
| @@ -360,13 +378,22 @@ export const readOptionalLocalExecutorConfig = ( · 9 unmodified lines | ||
| 360 | 378 | return yield* Effect.try({ |
| 361 | 379 | try: () => parseJsonc({ path, content }), |
| 362 | 380 | catch: (cause) => |
| 363 | − cause instanceof Error ? cause : new Error(String(cause)), | |
| 381 | + cause instanceof LocalExecutorConfigDecodeError | |
| 382 | + ? cause | |
| 383 | + : new LocalExecutorConfigDecodeError({ | |
| 384 | + message: `Invalid executor config at ${path}: ${unknownLocalErrorDetails(cause)}`, | |
| 385 | + path, | |
| 386 | + details: unknownLocalErrorDetails(cause), | |
| 387 | + }), | |
| 364 | 388 | }); |
| 365 | 389 | })); |
| 366 | 390 | |
| 367 | 391 | export const loadLocalExecutorConfig = ( |
| 368 | 392 | context: ResolvedLocalWorkspaceContext, |
| 369 | −): Effect.Effect<LoadedLocalExecutorConfig, Error> => | |
| 393 | +): Effect.Effect< | |
| 394 | + LoadedLocalExecutorConfig, | |
| 395 | + LocalFileSystemError | LocalExecutorConfigDecodeError | |
| 396 | +> => | |
| 370 | 397 | Effect.gen(function* () { |
| 371 | 398 | const [homeConfig, projectConfig] = yield* Effect.all([ |
| 372 | 399 | readOptionalLocalExecutorConfig(context.homeConfigPath), |
| @@ -388,7 +415,7 @@ export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string = · 15 unmodified lines | ||
| 388 | 415 | export const writeProjectLocalExecutorConfig = (input: { |
| 389 | 416 | context: ResolvedLocalWorkspaceContext; |
| 390 | 417 | config: LocalExecutorConfig; |
| 391 | −}): Effect.Effect<void, Error> => | |
| 418 | +}): Effect.Effect<void, LocalFileSystemError> => | |
| 392 | 419 | provideNodeFileSystem(Effect.gen(function* () { |
| 393 | 420 | const fs = yield* FileSystem.FileSystem; |
| 394 | 421 | yield* fs.makeDirectory(input.context.configDirectory, { recursive: true }).pipe( |
| @@ -0,0 +1,54 @@ | ||
| 1 | +import * as Data from "effect/Data"; | |
| 2 | + | |
| 3 | +export const unknownLocalErrorDetails = (cause: unknown): string => | |
| 4 | + cause instanceof Error ? cause.message : String(cause); | |
| 5 | + | |
| 6 | +export class LocalFileSystemError extends Data.TaggedError("LocalFileSystemError")<{ | |
| 7 | + readonly message: string; | |
| 8 | + readonly action: string; | |
| 9 | + readonly path: string; | |
| 10 | + readonly details: string; | |
| 11 | +}> {} | |
| 12 | + | |
| 13 | +export class LocalExecutorConfigDecodeError extends Data.TaggedError("LocalExecutorConfigDecodeError")<{ | |
| 14 | + readonly message: string; | |
| 15 | + readonly path: string; | |
| 16 | + readonly details: string; | |
| 17 | +}> {} | |
| 18 | + | |
| 19 | +export class LocalWorkspaceStateDecodeError extends Data.TaggedError("LocalWorkspaceStateDecodeError")<{ | |
| 20 | + readonly message: string; | |
| 21 | + readonly path: string; | |
| 22 | + readonly details: string; | |
| 23 | +}> {} | |
| 24 | + | |
| 25 | +export class LocalSourceArtifactDecodeError extends Data.TaggedError("LocalSourceArtifactDecodeError")<{ | |
| 26 | + readonly message: string; | |
| 27 | + readonly path: string; | |
| 28 | + readonly details: string; | |
| 29 | +}> {} | |
| 30 | + | |
| 31 | +export class RuntimeLocalWorkspaceUnavailableError extends Data.TaggedError("RuntimeLocalWorkspaceUnavailableError")<{ | |
| 32 | + readonly message: string; | |
| 33 | +}> {} | |
| 34 | + | |
| 35 | +export class RuntimeLocalWorkspaceMismatchError extends Data.TaggedError("RuntimeLocalWorkspaceMismatchError")<{ | |
| 36 | + readonly message: string; | |
| 37 | + readonly requestedWorkspaceId: string; | |
| 38 | + readonly activeWorkspaceId: string; | |
| 39 | +}> {} | |
| 40 | + | |
| 41 | +export class LocalConfiguredSourceNotFoundError extends Data.TaggedError("LocalConfiguredSourceNotFoundError")<{ | |
| 42 | + readonly message: string; | |
| 43 | + readonly sourceId: string; | |
| 44 | +}> {} | |
| 45 | + | |
| 46 | +export class LocalSourceArtifactMissingError extends Data.TaggedError("LocalSourceArtifactMissingError")<{ | |
| 47 | + readonly message: string; | |
| 48 | + readonly sourceId: string; | |
| 49 | +}> {} | |
| 50 | + | |
| 51 | +export class LocalUnsupportedSourceKindError extends Data.TaggedError("LocalUnsupportedSourceKindError")<{ | |
| 52 | + readonly message: string; | |
| 53 | + readonly kind: string; | |
| 54 | +}> {} | |
| @@ -7,6 +7,10 @@ import type { · 6 unmodified lines | ||
| 7 | 7 | LoadedLocalExecutorConfig, |
| 8 | 8 | ResolvedLocalWorkspaceContext, |
| 9 | 9 | } from "./local-config"; |
| 10 | +import { | |
| 11 | + RuntimeLocalWorkspaceMismatchError, | |
| 12 | + RuntimeLocalWorkspaceUnavailableError, | |
| 13 | +} from "./local-errors"; | |
| 10 | 14 | |
| 11 | 15 | export type RuntimeLocalWorkspaceState = { |
| 12 | 16 | context: ResolvedLocalWorkspaceContext; |
| @@ -28,24 +32,37 @@ export const getRuntimeLocalWorkspaceOption = () => · 15 unmodified lines | ||
| 28 | 32 | Effect.map((option) => (Option.isSome(option) ? option.value : null)), |
| 29 | 33 | ) as Effect.Effect<RuntimeLocalWorkspaceState | null, never, never>; |
| 30 | 34 | |
| 31 | −export const requireRuntimeLocalWorkspace = (workspaceId?: WorkspaceId) => | |
| 32 | − Effect.flatMap(getRuntimeLocalWorkspaceOption(), (runtimeLocalWorkspace) => { | |
| 35 | +export const requireRuntimeLocalWorkspace = ( | |
| 36 | + workspaceId?: WorkspaceId, | |
| 37 | +): Effect.Effect< | |
| 38 | + RuntimeLocalWorkspaceState, | |
| 39 | + RuntimeLocalWorkspaceUnavailableError | RuntimeLocalWorkspaceMismatchError, | |
| 40 | + never | |
| 41 | +> => | |
| 42 | + Effect.gen(function* () { | |
| 43 | + const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption(); | |
| 33 | 44 | if (runtimeLocalWorkspace === null) { |
| 34 | − return Effect.fail(new Error("Runtime local workspace is unavailable")); | |
| 45 | + return yield* Effect.fail( | |
| 46 | + new RuntimeLocalWorkspaceUnavailableError({ | |
| 47 | + message: "Runtime local workspace is unavailable", | |
| 48 | + }), | |
| 49 | + ); | |
| 35 | 50 | } |
| 36 | 51 | |
| 37 | 52 | if ( |
| 38 | 53 | workspaceId !== undefined |
| 39 | 54 | && runtimeLocalWorkspace.installation.workspaceId !== workspaceId |
| 40 | 55 | ) { |
| 41 | − return Effect.fail( | |
| 42 | − new Error( | |
| 43 | − `Workspace ${workspaceId} is not the active local workspace ${runtimeLocalWorkspace.installation.workspaceId}`, | |
| 44 | − ), | |
| 56 | + return yield* Effect.fail( | |
| 57 | + new RuntimeLocalWorkspaceMismatchError({ | |
| 58 | + message: `Workspace ${workspaceId} is not the active local workspace ${runtimeLocalWorkspace.installation.workspaceId}`, | |
| 59 | + requestedWorkspaceId: workspaceId, | |
| 60 | + activeWorkspaceId: runtimeLocalWorkspace.installation.workspaceId, | |
| 61 | + }), | |
| 45 | 62 | ); |
| 46 | 63 | } |
| 47 | 64 | |
| 48 | − return Effect.succeed(runtimeLocalWorkspace); | |
| 65 | + return runtimeLocalWorkspace; | |
| 49 | 66 | }); |
| 50 | 67 | |
| 51 | 68 | export const requireRuntimeLocalAccountId = (workspaceId?: WorkspaceId) => |
| @@ -17,6 +17,11 @@ import * as Effect from "effect/Effect"; · 16 unmodified lines | ||
| 17 | 17 | import * as Schema from "effect/Schema"; |
| 18 | 18 | |
| 19 | 19 | import type { ResolvedLocalWorkspaceContext } from "./local-config"; |
| 20 | +import { | |
| 21 | + LocalFileSystemError, | |
| 22 | + LocalSourceArtifactDecodeError, | |
| 23 | + unknownLocalErrorDetails, | |
| 24 | +} from "./local-errors"; | |
| 20 | 25 | import { |
| 21 | 26 | createSourceRecipeRevisionRecord, |
| 22 | 27 | stableSourceRecipeId, |
| @@ -52,10 +57,13 @@ const provideNodeFileSystem = <A, E, R>( · 29 unmodified lines | ||
| 52 | 57 | Exclude<R, FileSystem.FileSystem> |
| 53 | 58 | >; |
| 54 | 59 | |
| 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 | −}; | |
| 60 | +const mapFileSystemError = (path: string, action: string) => (cause: unknown) => | |
| 61 | + new LocalFileSystemError({ | |
| 62 | + message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`, | |
| 63 | + action, | |
| 64 | + path, | |
| 65 | + details: unknownLocalErrorDetails(cause), | |
| 66 | + }); | |
| 59 | 67 | |
| 60 | 68 | const localSourceArtifactPath = (input: { |
| 61 | 69 | context: ResolvedLocalWorkspaceContext; |
| @@ -157,7 +165,10 @@ export const buildLocalSourceArtifact = (input: { · 95 unmodified lines | ||
| 157 | 165 | export const readLocalSourceArtifact = (input: { |
| 158 | 166 | context: ResolvedLocalWorkspaceContext; |
| 159 | 167 | sourceId: string; |
| 160 | −}): Effect.Effect<LocalSourceArtifact | null, Error> => | |
| 168 | +}): Effect.Effect< | |
| 169 | + LocalSourceArtifact | null, | |
| 170 | + LocalFileSystemError | LocalSourceArtifactDecodeError | |
| 171 | +> => | |
| 161 | 172 | provideNodeFileSystem(Effect.gen(function* () { |
| 162 | 173 | const fs = yield* FileSystem.FileSystem; |
| 163 | 174 | const path = localSourceArtifactPath(input); |
| @@ -174,8 +185,11 @@ export const readLocalSourceArtifact = (input: { · 10 unmodified lines | ||
| 174 | 185 | return yield* Effect.try({ |
| 175 | 186 | try: () => decodeLocalSourceArtifact(JSON.parse(content) as unknown), |
| 176 | 187 | catch: (cause) => { |
| 177 | − const message = cause instanceof Error ? cause.message : String(cause); | |
| 178 | − return new Error(`Invalid local source artifact at ${path}: ${message}`); | |
| 188 | + return new LocalSourceArtifactDecodeError({ | |
| 189 | + message: `Invalid local source artifact at ${path}: ${unknownLocalErrorDetails(cause)}`, | |
| 190 | + path, | |
| 191 | + details: unknownLocalErrorDetails(cause), | |
| 192 | + }); | |
| 179 | 193 | }, |
| 180 | 194 | }); |
| 181 | 195 | })); |
| @@ -184,7 +198,7 @@ export const writeLocalSourceArtifact = (input: { · 2 unmodified lines | ||
| 184 | 198 | context: ResolvedLocalWorkspaceContext; |
| 185 | 199 | sourceId: string; |
| 186 | 200 | artifact: LocalSourceArtifact; |
| 187 | −}): Effect.Effect<void, Error> => | |
| 201 | +}): Effect.Effect<void, LocalFileSystemError> => | |
| 188 | 202 | provideNodeFileSystem(Effect.gen(function* () { |
| 189 | 203 | const fs = yield* FileSystem.FileSystem; |
| 190 | 204 | const directory = join(input.context.artifactsDirectory, "sources"); |
| @@ -200,7 +214,7 @@ export const writeLocalSourceArtifact = (input: { · 9 unmodified lines | ||
| 200 | 214 | export const removeLocalSourceArtifact = (input: { |
| 201 | 215 | context: ResolvedLocalWorkspaceContext; |
| 202 | 216 | sourceId: string; |
| 203 | −}): Effect.Effect<void, Error> => | |
| 217 | +}): Effect.Effect<void, LocalFileSystemError> => | |
| 204 | 218 | provideNodeFileSystem(Effect.gen(function* () { |
| 205 | 219 | const fs = yield* FileSystem.FileSystem; |
| 206 | 220 | const path = localSourceArtifactPath(input); |
| @@ -11,6 +11,11 @@ import * as Effect from "effect/Effect"; · 10 unmodified lines | ||
| 11 | 11 | import * as Schema from "effect/Schema"; |
| 12 | 12 | |
| 13 | 13 | import type { ResolvedLocalWorkspaceContext } from "./local-config"; |
| 14 | +import { | |
| 15 | + LocalFileSystemError, | |
| 16 | + LocalWorkspaceStateDecodeError, | |
| 17 | + unknownLocalErrorDetails, | |
| 18 | +} from "./local-errors"; | |
| 14 | 19 | |
| 15 | 20 | const WORKSPACE_STATE_BASENAME = "workspace-state.json"; |
| 16 | 21 | |
| @@ -55,10 +60,13 @@ const provideNodeFileSystem = <A, E, R>( · 38 unmodified lines | ||
| 55 | 60 | Exclude<R, FileSystem.FileSystem> |
| 56 | 61 | >; |
| 57 | 62 | |
| 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 | −}; | |
| 63 | +const mapFileSystemError = (path: string, action: string) => (cause: unknown) => | |
| 64 | + new LocalFileSystemError({ | |
| 65 | + message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`, | |
| 66 | + action, | |
| 67 | + path, | |
| 68 | + details: unknownLocalErrorDetails(cause), | |
| 69 | + }); | |
| 62 | 70 | |
| 63 | 71 | const defaultLocalWorkspaceState = (): LocalWorkspaceState => ({ |
| 64 | 72 | version: 1, |
| @@ -72,7 +80,7 @@ export const localWorkspaceStatePath = ( · 7 unmodified lines | ||
| 72 | 80 | |
| 73 | 81 | export const loadLocalWorkspaceState = ( |
| 74 | 82 | context: ResolvedLocalWorkspaceContext, |
| 75 | −): Effect.Effect<LocalWorkspaceState, Error> => | |
| 83 | +): Effect.Effect<LocalWorkspaceState, LocalFileSystemError | LocalWorkspaceStateDecodeError> => | |
| 76 | 84 | provideNodeFileSystem(Effect.gen(function* () { |
| 77 | 85 | const fs = yield* FileSystem.FileSystem; |
| 78 | 86 | const path = localWorkspaceStatePath(context); |
| @@ -89,8 +97,11 @@ export const loadLocalWorkspaceState = ( · 10 unmodified lines | ||
| 89 | 97 | return yield* Effect.try({ |
| 90 | 98 | try: () => decodeLocalWorkspaceState(JSON.parse(content) as unknown), |
| 91 | 99 | catch: (cause) => { |
| 92 | − const message = cause instanceof Error ? cause.message : String(cause); | |
| 93 | − return new Error(`Invalid local workspace state at ${path}: ${message}`); | |
| 100 | + return new LocalWorkspaceStateDecodeError({ | |
| 101 | + message: `Invalid local workspace state at ${path}: ${unknownLocalErrorDetails(cause)}`, | |
| 102 | + path, | |
| 103 | + details: unknownLocalErrorDetails(cause), | |
| 104 | + }); | |
| 94 | 105 | }, |
| 95 | 106 | }); |
| 96 | 107 | })); |
| @@ -98,7 +109,7 @@ export const loadLocalWorkspaceState = ( · 1 unmodified line | ||
| 98 | 109 | export const writeLocalWorkspaceState = (input: { |
| 99 | 110 | context: ResolvedLocalWorkspaceContext; |
| 100 | 111 | state: LocalWorkspaceState; |
| 101 | −}): Effect.Effect<void, Error> => | |
| 112 | +}): Effect.Effect<void, LocalFileSystemError> => | |
| 102 | 113 | provideNodeFileSystem(Effect.gen(function* () { |
| 103 | 114 | const fs = yield* FileSystem.FileSystem; |
| 104 | 115 | yield* fs.makeDirectory(input.context.stateDirectory, { recursive: true }).pipe( |
| @@ -17,6 +17,7 @@ import * as Effect from "effect/Effect"; · 16 unmodified lines | ||
| 17 | 17 | import { |
| 18 | 18 | readLocalSourceArtifact, |
| 19 | 19 | } from "./local-source-artifacts"; |
| 20 | +import { LocalSourceArtifactMissingError } from "./local-errors"; | |
| 20 | 21 | import { |
| 21 | 22 | requireRuntimeLocalWorkspace, |
| 22 | 23 | } from "./local-runtime-context"; |
| @@ -218,7 +219,7 @@ export const loadSourceWithRecipe = (input: { · 195 unmodified lines | ||
| 218 | 219 | workspaceId: WorkspaceId; |
| 219 | 220 | sourceId: Source["id"]; |
| 220 | 221 | actorAccountId?: AccountId | null; |
| 221 | −}): Effect.Effect<LoadedSourceRecipe, Error, never> => | |
| 222 | +}): Effect.Effect<LoadedSourceRecipe, Error | LocalSourceArtifactMissingError, never> => | |
| 222 | 223 | Effect.gen(function* () { |
| 223 | 224 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.workspaceId); |
| 224 | 225 | const source = yield* loadSourceById(input.rows, { |
| @@ -232,7 +233,10 @@ export const loadSourceWithRecipe = (input: { · 7 unmodified lines | ||
| 232 | 233 | }); |
| 233 | 234 | if (artifact === null) { |
| 234 | 235 | return yield* Effect.fail( |
| 235 | − new Error(`Recipe artifact missing for source ${input.sourceId}`), | |
| 236 | + new LocalSourceArtifactMissingError({ | |
| 237 | + message: `Recipe artifact missing for source ${input.sourceId}`, | |
| 238 | + sourceId: input.sourceId, | |
| 239 | + }), | |
| 236 | 240 | ); |
| 237 | 241 | } |
| 238 | 242 | |
| @@ -30,6 +30,15 @@ import { · 29 unmodified lines | ||
| 30 | 30 | readLocalSourceArtifact, |
| 31 | 31 | removeLocalSourceArtifact, |
| 32 | 32 | } from "./local-source-artifacts"; |
| 33 | +import { | |
| 34 | + LocalConfiguredSourceNotFoundError, | |
| 35 | + LocalExecutorConfigDecodeError, | |
| 36 | + LocalFileSystemError, | |
| 37 | + LocalUnsupportedSourceKindError, | |
| 38 | + LocalWorkspaceStateDecodeError, | |
| 39 | + RuntimeLocalWorkspaceMismatchError, | |
| 40 | + RuntimeLocalWorkspaceUnavailableError, | |
| 41 | +} from "./local-errors"; | |
| 33 | 42 | import { |
| 34 | 43 | requireRuntimeLocalWorkspace, |
| 35 | 44 | } from "./local-runtime-context"; |
| @@ -231,7 +240,21 @@ const configAuthFromSource = (input: { · 195 unmodified lines | ||
| 231 | 240 | return input.existingConfigAuth; |
| 232 | 241 | }; |
| 233 | 242 | |
| 234 | −const resolveRuntimeLocalWorkspace = (workspaceId: WorkspaceId) => | |
| 243 | +const resolveRuntimeLocalWorkspace = (workspaceId: WorkspaceId): Effect.Effect<{ | |
| 244 | + context: ResolvedLocalWorkspaceContext; | |
| 245 | + installation: { | |
| 246 | + workspaceId: WorkspaceId; | |
| 247 | + accountId: AccountId; | |
| 248 | + }; | |
| 249 | + loadedConfig: LoadedLocalExecutorConfig; | |
| 250 | + workspaceState: LocalWorkspaceState; | |
| 251 | +}, | |
| 252 | + | RuntimeLocalWorkspaceUnavailableError | |
| 253 | + | RuntimeLocalWorkspaceMismatchError | |
| 254 | + | LocalFileSystemError | |
| 255 | + | LocalExecutorConfigDecodeError | |
| 256 | + | LocalWorkspaceStateDecodeError, | |
| 257 | +never> => | |
| 235 | 258 | Effect.gen(function* () { |
| 236 | 259 | const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId); |
| 237 | 260 | const loadedConfig = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context); |
| @@ -256,11 +279,16 @@ const buildLocalSourceRecord = (input: { · 18 unmodified lines | ||
| 256 | 279 | }): Effect.Effect<{ |
| 257 | 280 | source: Source; |
| 258 | 281 | sourceId: SourceId; |
| 259 | −}, Error, never> => | |
| 282 | +}, LocalConfiguredSourceNotFoundError | Error, never> => | |
| 260 | 283 | Effect.gen(function* () { |
| 261 | 284 | const sourceConfig = input.loadedConfig.config?.sources?.[input.sourceId]; |
| 262 | 285 | if (!sourceConfig) { |
| 263 | − return yield* Effect.fail(new Error(`Configured source not found for id ${input.sourceId}`)); | |
| 286 | + return yield* Effect.fail( | |
| 287 | + new LocalConfiguredSourceNotFoundError({ | |
| 288 | + message: `Configured source not found for id ${input.sourceId}`, | |
| 289 | + sourceId: input.sourceId, | |
| 290 | + }), | |
| 291 | + ); | |
| 264 | 292 | } |
| 265 | 293 | |
| 266 | 294 | const existingState = input.workspaceState.sources[input.sourceId]; |
| @@ -356,7 +384,17 @@ export const loadSourcesInWorkspace = ( · 89 unmodified lines | ||
| 356 | 384 | options: { |
| 357 | 385 | actorAccountId?: AccountId | null; |
| 358 | 386 | } = {}, |
| 359 | −) => | |
| 387 | +): Effect.Effect< | |
| 388 | + readonly Source[], | |
| 389 | + | RuntimeLocalWorkspaceUnavailableError | |
| 390 | + | RuntimeLocalWorkspaceMismatchError | |
| 391 | + | LocalFileSystemError | |
| 392 | + | LocalExecutorConfigDecodeError | |
| 393 | + | LocalWorkspaceStateDecodeError | |
| 394 | + | LocalConfiguredSourceNotFoundError | |
| 395 | + | Error, | |
| 396 | + never | |
| 397 | +> => | |
| 360 | 398 | Effect.gen(function* () { |
| 361 | 399 | const localWorkspace = yield* resolveRuntimeLocalWorkspace(workspaceId); |
| 362 | 400 | const authArtifacts = yield* rows.authArtifacts.listByWorkspaceId(workspaceId); |
| @@ -382,13 +420,26 @@ export const loadSourceById = (rows: SqlControlPlaneRows, input: { · 19 unmodified lines | ||
| 382 | 420 | workspaceId: WorkspaceId; |
| 383 | 421 | sourceId: Source["id"]; |
| 384 | 422 | actorAccountId?: AccountId | null; |
| 385 | −}) => | |
| 423 | +}): Effect.Effect< | |
| 424 | + Source, | |
| 425 | + | RuntimeLocalWorkspaceUnavailableError | |
| 426 | + | RuntimeLocalWorkspaceMismatchError | |
| 427 | + | LocalFileSystemError | |
| 428 | + | LocalExecutorConfigDecodeError | |
| 429 | + | LocalWorkspaceStateDecodeError | |
| 430 | + | LocalConfiguredSourceNotFoundError | |
| 431 | + | Error, | |
| 432 | + never | |
| 433 | +> => | |
| 386 | 434 | Effect.gen(function* () { |
| 387 | 435 | const localWorkspace = yield* resolveRuntimeLocalWorkspace(input.workspaceId); |
| 388 | 436 | const authArtifacts = yield* rows.authArtifacts.listByWorkspaceId(input.workspaceId); |
| 389 | 437 | if (!localWorkspace.loadedConfig.config?.sources?.[input.sourceId]) { |
| 390 | 438 | return yield* Effect.fail( |
| 391 | − new Error(`Source not found: workspaceId=${input.workspaceId} sourceId=${input.sourceId}`), | |
| 439 | + new LocalConfiguredSourceNotFoundError({ | |
| 440 | + message: `Source not found: workspaceId=${input.workspaceId} sourceId=${input.sourceId}`, | |
| 441 | + sourceId: input.sourceId, | |
| 442 | + }), | |
| 392 | 443 | ); |
| 393 | 444 | } |
| 394 | 445 | |
| @@ -456,7 +507,10 @@ const configSourceFromLocalSource = (input: { · 61 unmodified lines | ||
| 456 | 507 | binding: cloneJson(input.source.binding) as Extract<LocalConfigSource, { kind: "mcp" }>["binding"], |
| 457 | 508 | }; |
| 458 | 509 | default: |
| 459 | − throw new Error(`Unsupported source kind for local config: ${input.source.kind}`); | |
| 510 | + throw new LocalUnsupportedSourceKindError({ | |
| 511 | + message: `Unsupported source kind for local config: ${input.source.kind}`, | |
| 512 | + kind: input.source.kind, | |
| 513 | + }); | |
| 460 | 514 | } |
| 461 | 515 | }; |
| 462 | 516 | |