History

Refine server Effect usage

Cursor Agent committed 4 months ago

Commit ea6ebcd1 parent 7b9eef73 files · +237 −166Browse files

Showing the changes to apps/server/src/workspace/WorkspaceFileSystem.ts (1 of 3 files). Show all

Unified · Split

apps/server/src/workspace/WorkspaceFileSystem.ts+74−124
@@ -1,4 +1,3 @@
1// @effect-diagnostics nodeBuiltinImport:off
2/**1/**
3 * WorkspaceFileSystem - Effect service contract for workspace file mutations.2 * WorkspaceFileSystem - Effect service contract for workspace file mutations.
4 *3 *
@@ -7,8 +6,6 @@ · 2 unmodified lines
7 *6 *
8 * @module WorkspaceFileSystem7 * @module WorkspaceFileSystem
9 */8 */
10import * as NodeFSP from "node:fs/promises";
11
12import type {9import type {
13 ProjectReadFileInput,10 ProjectReadFileInput,
14 ProjectReadFileResult,11 ProjectReadFileResult,
@@ -19,6 +16,7 @@ import * as Context from "effect/Context"; · 4 unmodified lines
19import * as Effect from "effect/Effect";16import * as Effect from "effect/Effect";
20import * as FileSystem from "effect/FileSystem";17import * as FileSystem from "effect/FileSystem";
21import * as Layer from "effect/Layer";18import * as Layer from "effect/Layer";
19import * as Option from "effect/Option";
22import * as Path from "effect/Path";20import * as Path from "effect/Path";
23import * as Schema from "effect/Schema";21import * as Schema from "effect/Schema";
24 22
@@ -26,6 +24,7 @@ import * as WorkspaceEntries from "./WorkspaceEntries.ts"; · 1 unmodified line
26import * as WorkspacePaths from "./WorkspacePaths.ts";24import * as WorkspacePaths from "./WorkspacePaths.ts";
27 25
28const PROJECT_READ_FILE_MAX_BYTES = 1024 * 1024;26const PROJECT_READ_FILE_MAX_BYTES = 1024 * 1024;
27const PROJECT_READ_FILE_MAX_SIZE = FileSystem.Size(PROJECT_READ_FILE_MAX_BYTES);
29 28
30export class WorkspaceFileSystemOperationError extends Schema.TaggedErrorClass<WorkspaceFileSystemOperationError>()(29export class WorkspaceFileSystemOperationError extends Schema.TaggedErrorClass<WorkspaceFileSystemOperationError>()(
31 "WorkspaceFileSystemOperationError",30 "WorkspaceFileSystemOperationError",
@@ -132,6 +131,21 @@ export const make = Effect.gen(function* () { · 100 unmodified lines
132 const workspacePaths = yield* WorkspacePaths.WorkspacePaths;131 const workspacePaths = yield* WorkspacePaths.WorkspacePaths;
133 const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries;132 const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries;
134 133
134 const operationError = (
135 input: ProjectReadFileInput | ProjectWriteFileInput,
136 resolvedPath: string,
137 operationPath: string,
138 operation: WorkspaceFileSystemOperationError["operation"],
139 ) => (cause: unknown) =>
140 new WorkspaceFileSystemOperationError({
141 workspaceRoot: input.cwd,
142 relativePath: input.relativePath,
143 resolvedPath,
144 operationPath,
145 operation,
146 cause,
147 });
148
135 const readFile: WorkspaceFileSystem["Service"]["readFile"] = Effect.fn(149 const readFile: WorkspaceFileSystem["Service"]["readFile"] = Effect.fn(
136 "WorkspaceFileSystem.readFile",150 "WorkspaceFileSystem.readFile",
137 )(function* (input) {151 )(function* (input) {
@@ -140,30 +154,20 @@ export const make = Effect.gen(function* () { · 2 unmodified lines
140 relativePath: input.relativePath,154 relativePath: input.relativePath,
141 });155 });
142 156
143 const realWorkspaceRoot = yield* Effect.tryPromise({157 const realWorkspaceRoot = yield* fileSystem
144 try: () => NodeFSP.realpath(input.cwd),158 .realPath(input.cwd)
145 catch: (cause) =>159 .pipe(
146 new WorkspaceFileSystemOperationError({160 Effect.mapError(
147 workspaceRoot: input.cwd,161 operationError(input, target.absolutePath, input.cwd, "realpath-workspace-root"),
148 relativePath: input.relativePath,162 ),
149 resolvedPath: target.absolutePath,163 );
150 operationPath: input.cwd,164 const realTargetPath = yield* fileSystem
151 operation: "realpath-workspace-root",165 .realPath(target.absolutePath)
152 cause,166 .pipe(
153 }),167 Effect.mapError(
154 });168 operationError(input, target.absolutePath, target.absolutePath, "realpath-target"),
155 const realTargetPath = yield* Effect.tryPromise({169 ),
156 try: () => NodeFSP.realpath(target.absolutePath),170 );
157 catch: (cause) =>
158 new WorkspaceFileSystemOperationError({
159 workspaceRoot: input.cwd,
160 relativePath: input.relativePath,
161 resolvedPath: target.absolutePath,
162 operationPath: target.absolutePath,
163 operation: "realpath-target",
164 cause,
165 }),
166 });
167 const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath);171 const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath);
168 if (172 if (
169 relativeRealPath.startsWith(`..${path.sep}`) ||173 relativeRealPath.startsWith(`..${path.sep}`) ||
@@ -178,84 +182,46 @@ export const make = Effect.gen(function* () { · 8 unmodified lines
178 });182 });
179 }183 }
180 184
181 return yield* Effect.acquireUseRelease(185 return yield* Effect.scoped(
182 Effect.tryPromise({186 Effect.gen(function* () {
183 try: () => NodeFSP.open(realTargetPath, "r"),187 const file = yield* fileSystem
184 catch: (cause) =>188 .open(realTargetPath, { flag: "r" })
185 new WorkspaceFileSystemOperationError({189 .pipe(Effect.mapError(operationError(input, realTargetPath, realTargetPath, "open")));
190 const stat = yield* file.stat.pipe(
191 Effect.mapError(operationError(input, realTargetPath, realTargetPath, "stat")),
192 );
193 if (stat.type !== "File") {
194 return yield* new WorkspacePathNotFileError({
186 workspaceRoot: input.cwd,195 workspaceRoot: input.cwd,
187 relativePath: input.relativePath,196 relativePath: input.relativePath,
188 resolvedPath: realTargetPath,197 resolvedPath: realTargetPath,
189 operationPath: realTargetPath,
190 operation: "open",
191 cause,
192 }),
193 }),
194 (handle) =>
195 Effect.gen(function* () {
196 const stat = yield* Effect.tryPromise({
197 try: () => handle.stat(),
198 catch: (cause) =>
199 new WorkspaceFileSystemOperationError({
200 workspaceRoot: input.cwd,
201 relativePath: input.relativePath,
202 resolvedPath: realTargetPath,
203 operationPath: realTargetPath,
204 operation: "stat",
205 cause,
206 }),
207 });198 });
208 if (!stat.isFile()) {199 }
209 return yield* new WorkspacePathNotFileError({200
210 workspaceRoot: input.cwd,201 const bytesToRead =
211 relativePath: input.relativePath,202 stat.size > PROJECT_READ_FILE_MAX_SIZE ? PROJECT_READ_FILE_MAX_BYTES : Number(stat.size);
212 resolvedPath: realTargetPath,203 const fileBytes =
213 });204 bytesToRead === 0
214 }205 ? new Uint8Array()
215 206 : yield* file.readAlloc(bytesToRead).pipe(
216 const bytesToRead = Math.min(stat.size, PROJECT_READ_FILE_MAX_BYTES);207 Effect.map(Option.getOrElse(() => new Uint8Array())),
217 const buffer = Buffer.alloc(bytesToRead);208 Effect.mapError(operationError(input, realTargetPath, realTargetPath, "read")),
218 const { bytesRead } = yield* Effect.tryPromise({209 );
219 try: () => handle.read(buffer, 0, bytesToRead, 0),210 if (fileBytes.includes(0)) {
220 catch: (cause) =>211 return yield* new WorkspaceBinaryFileError({
221 new WorkspaceFileSystemOperationError({212 workspaceRoot: input.cwd,
222 workspaceRoot: input.cwd,213 relativePath: input.relativePath,
223 relativePath: input.relativePath,214 resolvedPath: realTargetPath,
224 resolvedPath: realTargetPath,
225 operationPath: realTargetPath,
226 operation: "read",
227 cause,
228 }),
229 });215 });
230 const fileBytes = buffer.subarray(0, bytesRead);216 }
231 if (fileBytes.includes(0)) {217
232 return yield* new WorkspaceBinaryFileError({218 return {
233 workspaceRoot: input.cwd,219 relativePath: target.relativePath,
234 relativePath: input.relativePath,220 contents: new TextDecoder("utf-8").decode(fileBytes),
235 resolvedPath: realTargetPath,221 byteLength: Number(stat.size),
236 });222 truncated: stat.size > PROJECT_READ_FILE_MAX_SIZE,
237 }223 };
238 224 }),
239 return {
240 relativePath: target.relativePath,
241 contents: new TextDecoder("utf-8").decode(fileBytes),
242 byteLength: stat.size,
243 truncated: stat.size > PROJECT_READ_FILE_MAX_BYTES,
244 };
245 }),
246 (handle) =>
247 Effect.tryPromise({
248 try: () => handle.close(),
249 catch: (cause) =>
250 new WorkspaceFileSystemOperationError({
251 workspaceRoot: input.cwd,
252 relativePath: input.relativePath,
253 resolvedPath: realTargetPath,
254 operationPath: realTargetPath,
255 operation: "close",
256 cause,
257 }),
258 }),
259 );225 );
260 });226 });
261 227
@@ -267,31 +233,15 @@ export const make = Effect.gen(function* () { · 5 unmodified lines
267 relativePath: input.relativePath,233 relativePath: input.relativePath,
268 });234 });
269 235
270 yield* fileSystem.makeDirectory(path.dirname(target.absolutePath), { recursive: true }).pipe(236 yield* fileSystem
271 Effect.mapError(237 .makeDirectory(path.dirname(target.absolutePath), { recursive: true })
272 (cause) =>238 .pipe(
273 new WorkspaceFileSystemOperationError({239 Effect.mapError(
274 workspaceRoot: input.cwd,240 operationError(input, target.absolutePath, path.dirname(target.absolutePath), "make-directory"),
275 relativePath: input.relativePath,241 ),
276 resolvedPath: target.absolutePath,242 );
277 operationPath: path.dirname(target.absolutePath),
278 operation: "make-directory",
279 cause,
280 }),
281 ),
282 );
283 yield* fileSystem.writeFileString(target.absolutePath, input.contents).pipe(243 yield* fileSystem.writeFileString(target.absolutePath, input.contents).pipe(
284 Effect.mapError(244 Effect.mapError(operationError(input, target.absolutePath, target.absolutePath, "write-file")),
285 (cause) =>
286 new WorkspaceFileSystemOperationError({
287 workspaceRoot: input.cwd,
288 relativePath: input.relativePath,
289 resolvedPath: target.absolutePath,
290 operationPath: target.absolutePath,
291 operation: "write-file",
292 cause,
293 }),
294 ),
295 );245 );
296 yield* workspaceEntries.refresh(input.cwd);246 yield* workspaceEntries.refresh(input.cwd);
297 return { relativePath: target.relativePath };247 return { relativePath: target.relativePath };