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
21 /**
32 * WorkspaceFileSystem - Effect service contract for workspace file mutations.
43 *
@@ -7,8 +6,6 @@ · 2 unmodified lines
76 *
87 * @module WorkspaceFileSystem
98 */
10−import * as NodeFSP from "node:fs/promises";
11−
129 import type {
1310 ProjectReadFileInput,
1411 ProjectReadFileResult,
@@ -19,6 +16,7 @@ import * as Context from "effect/Context"; · 4 unmodified lines
1916 import * as Effect from "effect/Effect";
2017 import * as FileSystem from "effect/FileSystem";
2118 import * as Layer from "effect/Layer";
19+import * as Option from "effect/Option";
2220 import * as Path from "effect/Path";
2321 import * as Schema from "effect/Schema";
2422
@@ -26,6 +24,7 @@ import * as WorkspaceEntries from "./WorkspaceEntries.ts"; · 1 unmodified line
2624 import * as WorkspacePaths from "./WorkspacePaths.ts";
2725
2826 const PROJECT_READ_FILE_MAX_BYTES = 1024 * 1024;
27+const PROJECT_READ_FILE_MAX_SIZE = FileSystem.Size(PROJECT_READ_FILE_MAX_BYTES);
2928
3029 export class WorkspaceFileSystemOperationError extends Schema.TaggedErrorClass<WorkspaceFileSystemOperationError>()(
3130 "WorkspaceFileSystemOperationError",
@@ -132,6 +131,21 @@ export const make = Effect.gen(function* () { · 100 unmodified lines
132131 const workspacePaths = yield* WorkspacePaths.WorkspacePaths;
133132 const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries;
134133
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+
135149 const readFile: WorkspaceFileSystem["Service"]["readFile"] = Effect.fn(
136150 "WorkspaceFileSystem.readFile",
137151 )(function* (input) {
@@ -140,30 +154,20 @@ export const make = Effect.gen(function* () { · 2 unmodified lines
140154 relativePath: input.relativePath,
141155 });
142156
143− const realWorkspaceRoot = yield* Effect.tryPromise({
144− try: () => NodeFSP.realpath(input.cwd),
145− catch: (cause) =>
146− new WorkspaceFileSystemOperationError({
147− workspaceRoot: input.cwd,
148− relativePath: input.relativePath,
149− resolvedPath: target.absolutePath,
150− operationPath: input.cwd,
151− operation: "realpath-workspace-root",
152− cause,
153− }),
154− });
155− const realTargetPath = yield* Effect.tryPromise({
156− try: () => NodeFSP.realpath(target.absolutePath),
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− });
157+ const realWorkspaceRoot = yield* fileSystem
158+ .realPath(input.cwd)
159+ .pipe(
160+ Effect.mapError(
161+ operationError(input, target.absolutePath, input.cwd, "realpath-workspace-root"),
162+ ),
163+ );
164+ const realTargetPath = yield* fileSystem
165+ .realPath(target.absolutePath)
166+ .pipe(
167+ Effect.mapError(
168+ operationError(input, target.absolutePath, target.absolutePath, "realpath-target"),
169+ ),
170+ );
167171 const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath);
168172 if (
169173 relativeRealPath.startsWith(`..${path.sep}`) ||
@@ -178,84 +182,46 @@ export const make = Effect.gen(function* () { · 8 unmodified lines
178182 });
179183 }
180184
181− return yield* Effect.acquireUseRelease(
182− Effect.tryPromise({
183− try: () => NodeFSP.open(realTargetPath, "r"),
184− catch: (cause) =>
185− new WorkspaceFileSystemOperationError({
185+ return yield* Effect.scoped(
186+ Effect.gen(function* () {
187+ const file = yield* fileSystem
188+ .open(realTargetPath, { flag: "r" })
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({
186195 workspaceRoot: input.cwd,
187196 relativePath: input.relativePath,
188197 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− }),
207198 });
208− if (!stat.isFile()) {
209− return yield* new WorkspacePathNotFileError({
210− workspaceRoot: input.cwd,
211− relativePath: input.relativePath,
212− resolvedPath: realTargetPath,
213− });
214− }
215−
216− const bytesToRead = Math.min(stat.size, PROJECT_READ_FILE_MAX_BYTES);
217− const buffer = Buffer.alloc(bytesToRead);
218− const { bytesRead } = yield* Effect.tryPromise({
219− try: () => handle.read(buffer, 0, bytesToRead, 0),
220− catch: (cause) =>
221− new WorkspaceFileSystemOperationError({
222− workspaceRoot: input.cwd,
223− relativePath: input.relativePath,
224− resolvedPath: realTargetPath,
225− operationPath: realTargetPath,
226− operation: "read",
227− cause,
228− }),
199+ }
200+
201+ const bytesToRead =
202+ stat.size > PROJECT_READ_FILE_MAX_SIZE ? PROJECT_READ_FILE_MAX_BYTES : Number(stat.size);
203+ const fileBytes =
204+ bytesToRead === 0
205+ ? new Uint8Array()
206+ : yield* file.readAlloc(bytesToRead).pipe(
207+ Effect.map(Option.getOrElse(() => new Uint8Array())),
208+ Effect.mapError(operationError(input, realTargetPath, realTargetPath, "read")),
209+ );
210+ if (fileBytes.includes(0)) {
211+ return yield* new WorkspaceBinaryFileError({
212+ workspaceRoot: input.cwd,
213+ relativePath: input.relativePath,
214+ resolvedPath: realTargetPath,
229215 });
230− const fileBytes = buffer.subarray(0, bytesRead);
231− if (fileBytes.includes(0)) {
232− return yield* new WorkspaceBinaryFileError({
233− workspaceRoot: input.cwd,
234− relativePath: input.relativePath,
235− resolvedPath: realTargetPath,
236− });
237− }
238−
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− }),
216+ }
217+
218+ return {
219+ relativePath: target.relativePath,
220+ contents: new TextDecoder("utf-8").decode(fileBytes),
221+ byteLength: Number(stat.size),
222+ truncated: stat.size > PROJECT_READ_FILE_MAX_SIZE,
223+ };
224+ }),
259225 );
260226 });
261227
@@ -267,31 +233,15 @@ export const make = Effect.gen(function* () { · 5 unmodified lines
267233 relativePath: input.relativePath,
268234 });
269235
270− yield* fileSystem.makeDirectory(path.dirname(target.absolutePath), { recursive: true }).pipe(
271− Effect.mapError(
272− (cause) =>
273− new WorkspaceFileSystemOperationError({
274− workspaceRoot: input.cwd,
275− relativePath: input.relativePath,
276− resolvedPath: target.absolutePath,
277− operationPath: path.dirname(target.absolutePath),
278− operation: "make-directory",
279− cause,
280− }),
281− ),
282− );
236+ yield* fileSystem
237+ .makeDirectory(path.dirname(target.absolutePath), { recursive: true })
238+ .pipe(
239+ Effect.mapError(
240+ operationError(input, target.absolutePath, path.dirname(target.absolutePath), "make-directory"),
241+ ),
242+ );
283243 yield* fileSystem.writeFileString(target.absolutePath, input.contents).pipe(
284− Effect.mapError(
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− ),
244+ Effect.mapError(operationError(input, target.absolutePath, target.absolutePath, "write-file")),
295245 );
296246 yield* workspaceEntries.refresh(input.cwd);
297247 return { relativePath: target.relativePath };