History

refactor(server): use Effect filesystem for workspace reads

Cursor Agent committed 3 months ago

Commit d8e264e1 parent 53e3c982 files · +103 −95Browse files

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

Unified · Split

apps/server/src/workspace/WorkspaceFileSystem.ts+79−92
@@ -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
@@ -140,30 +138,32 @@ export const make = Effect.gen(function* () { · 115 unmodified lines
140 relativePath: input.relativePath,138 relativePath: input.relativePath,
141 });139 });
142 140
143 const realWorkspaceRoot = yield* Effect.tryPromise({141 const realWorkspaceRoot = yield* fileSystem.realPath(input.cwd).pipe(
144 try: () => NodeFSP.realpath(input.cwd),142 Effect.mapError(
145 catch: (cause) =>143 (cause) =>
146 new WorkspaceFileSystemOperationError({144 new WorkspaceFileSystemOperationError({
147 workspaceRoot: input.cwd,145 workspaceRoot: input.cwd,
148 relativePath: input.relativePath,146 relativePath: input.relativePath,
149 resolvedPath: target.absolutePath,147 resolvedPath: target.absolutePath,
150 operationPath: input.cwd,148 operationPath: input.cwd,
151 operation: "realpath-workspace-root",149 operation: "realpath-workspace-root",
152 cause,150 cause,
153 }),151 }),
154 });152 ),
155 const realTargetPath = yield* Effect.tryPromise({153 );
156 try: () => NodeFSP.realpath(target.absolutePath),154 const realTargetPath = yield* fileSystem.realPath(target.absolutePath).pipe(
157 catch: (cause) =>155 Effect.mapError(
158 new WorkspaceFileSystemOperationError({156 (cause) =>
159 workspaceRoot: input.cwd,157 new WorkspaceFileSystemOperationError({
160 relativePath: input.relativePath,158 workspaceRoot: input.cwd,
161 resolvedPath: target.absolutePath,159 relativePath: input.relativePath,
162 operationPath: target.absolutePath,160 resolvedPath: target.absolutePath,
163 operation: "realpath-target",161 operationPath: target.absolutePath,
164 cause,162 operation: "realpath-target",
165 }),163 cause,
166 });164 }),
165 ),
166 );
167 const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath);167 const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath);
168 if (168 if (
169 relativeRealPath.startsWith(`..${path.sep}`) ||169 relativeRealPath.startsWith(`..${path.sep}`) ||
@@ -178,85 +178,72 @@ export const make = Effect.gen(function* () { · 8 unmodified lines
178 });178 });
179 }179 }
180 180
181 return yield* Effect.acquireUseRelease(181 return yield* Effect.gen(function* () {
182 Effect.tryPromise({182 const handle = yield* fileSystem.open(realTargetPath, { flag: "r" }).pipe(
183 try: () => NodeFSP.open(realTargetPath, "r"),183 Effect.mapError(
184 catch: (cause) =>184 (cause) =>
185 new WorkspaceFileSystemOperationError({185 new WorkspaceFileSystemOperationError({
186 workspaceRoot: input.cwd,
187 relativePath: input.relativePath,
188 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 });
208 if (!stat.isFile()) {
209 return yield* new WorkspacePathNotFileError({
210 workspaceRoot: input.cwd,186 workspaceRoot: input.cwd,
211 relativePath: input.relativePath,187 relativePath: input.relativePath,
212 resolvedPath: realTargetPath,188 resolvedPath: realTargetPath,
213 });189 operationPath: realTargetPath,
214 }190 operation: "open",
215 191 cause,
216 const bytesToRead = Math.min(stat.size, PROJECT_READ_FILE_MAX_BYTES);192 }),
217 const buffer = Buffer.alloc(bytesToRead);193 ),
218 const { bytesRead } = yield* Effect.tryPromise({194 );
219 try: () => handle.read(buffer, 0, bytesToRead, 0),195 const stat = yield* handle.stat.pipe(
220 catch: (cause) =>196 Effect.mapError(
221 new WorkspaceFileSystemOperationError({197 (cause) =>
222 workspaceRoot: input.cwd,198 new WorkspaceFileSystemOperationError({
223 relativePath: input.relativePath,
224 resolvedPath: realTargetPath,
225 operationPath: realTargetPath,
226 operation: "read",
227 cause,
228 }),
229 });
230 const fileBytes = buffer.subarray(0, bytesRead);
231 if (fileBytes.includes(0)) {
232 return yield* new WorkspaceBinaryFileError({
233 workspaceRoot: input.cwd,199 workspaceRoot: input.cwd,
234 relativePath: input.relativePath,200 relativePath: input.relativePath,
235 resolvedPath: realTargetPath,201 resolvedPath: realTargetPath,
236 });202 operationPath: realTargetPath,
237 }203 operation: "stat",
204 cause,
205 }),
206 ),
207 );
208 if (stat.type !== "File") {
209 return yield* new WorkspacePathNotFileError({
210 workspaceRoot: input.cwd,
211 relativePath: input.relativePath,
212 resolvedPath: realTargetPath,
213 });
214 }
238 215
239 return {216 const byteLength = Number(stat.size);
240 relativePath: target.relativePath,217 const bytesToRead = Math.min(byteLength, PROJECT_READ_FILE_MAX_BYTES);
241 contents: new TextDecoder("utf-8").decode(fileBytes),218 const fileBytes = yield* handle.readAlloc(FileSystem.Size(bytesToRead)).pipe(
242 byteLength: stat.size,219 Effect.mapError(
243 truncated: stat.size > PROJECT_READ_FILE_MAX_BYTES,220 (cause) =>
244 };
245 }),
246 (handle) =>
247 Effect.tryPromise({
248 try: () => handle.close(),
249 catch: (cause) =>
250 new WorkspaceFileSystemOperationError({221 new WorkspaceFileSystemOperationError({
251 workspaceRoot: input.cwd,222 workspaceRoot: input.cwd,
252 relativePath: input.relativePath,223 relativePath: input.relativePath,
253 resolvedPath: realTargetPath,224 resolvedPath: realTargetPath,
254 operationPath: realTargetPath,225 operationPath: realTargetPath,
255 operation: "close",226 operation: "read",
256 cause,227 cause,
257 }),228 }),
258 }),229 ),
259 );230 Effect.map(Option.getOrElse(() => new Uint8Array())),
231 );
232 if (fileBytes.includes(0)) {
233 return yield* new WorkspaceBinaryFileError({
234 workspaceRoot: input.cwd,
235 relativePath: input.relativePath,
236 resolvedPath: realTargetPath,
237 });
238 }
239
240 return {
241 relativePath: target.relativePath,
242 contents: new TextDecoder("utf-8").decode(fileBytes),
243 byteLength,
244 truncated: byteLength > PROJECT_READ_FILE_MAX_BYTES,
245 };
246 }).pipe(Effect.scoped);
260 });247 });
261 248
262 const writeFile: WorkspaceFileSystem["Service"]["writeFile"] = Effect.fn(249 const writeFile: WorkspaceFileSystem["Service"]["writeFile"] = Effect.fn(