History

refactor workspace reads to Effect FileSystem

Cursor Agent committed 3 months ago

Commit 217fbf11 parent ebe8afb2 files · +138 −118Browse 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+87−89
@@ -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,84 +178,82 @@ export const make = Effect.gen(function* () { · 8 unmodified lines
178 });178 });
179 }179 }
180 180
181 return yield* Effect.acquireUseRelease(181 return yield* Effect.scoped(
182 Effect.tryPromise({182 Effect.gen(function* () {
183 try: () => NodeFSP.open(realTargetPath, "r"),183 const handle = yield* fileSystem.open(realTargetPath, { flag: "r" }).pipe(
184 catch: (cause) =>184 Effect.mapError(
185 new WorkspaceFileSystemOperationError({185 (cause) =>
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({186 new WorkspaceFileSystemOperationError({
200 workspaceRoot: input.cwd,187 workspaceRoot: input.cwd,
201 relativePath: input.relativePath,188 relativePath: input.relativePath,
202 resolvedPath: realTargetPath,189 resolvedPath: realTargetPath,
203 operationPath: realTargetPath,190 operationPath: realTargetPath,
204 operation: "stat",191 operation: "open",
205 cause,192 cause,
206 }),193 }),
207 });194 ),
208 if (!stat.isFile()) {195 );
209 return yield* new WorkspacePathNotFileError({196 const stat = yield* handle.stat.pipe(
210 workspaceRoot: input.cwd,197 Effect.mapError(
211 relativePath: input.relativePath,198 (cause) =>
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({199 new WorkspaceFileSystemOperationError({
222 workspaceRoot: input.cwd,200 workspaceRoot: input.cwd,
223 relativePath: input.relativePath,201 relativePath: input.relativePath,
224 resolvedPath: realTargetPath,202 resolvedPath: realTargetPath,
225 operationPath: realTargetPath,203 operationPath: realTargetPath,
226 operation: "read",204 operation: "stat",
227 cause,205 cause,
228 }),206 }),
207 ),
208 );
209 if (stat.type !== "File") {
210 return yield* new WorkspacePathNotFileError({
211 workspaceRoot: input.cwd,
212 relativePath: input.relativePath,
213 resolvedPath: realTargetPath,
229 });214 });
230 const fileBytes = buffer.subarray(0, bytesRead);215 }
231 if (fileBytes.includes(0)) {
232 return yield* new WorkspaceBinaryFileError({
233 workspaceRoot: input.cwd,
234 relativePath: input.relativePath,
235 resolvedPath: realTargetPath,
236 });
237 }
238 216
239 return {217 const truncated = stat.size > FileSystem.Size(PROJECT_READ_FILE_MAX_BYTES);
240 relativePath: target.relativePath,218 const byteLength = Number(stat.size);
241 contents: new TextDecoder("utf-8").decode(fileBytes),219 const bytesToRead = truncated ? PROJECT_READ_FILE_MAX_BYTES : byteLength;
242 byteLength: stat.size,220 const fileBytes =
243 truncated: stat.size > PROJECT_READ_FILE_MAX_BYTES,221 bytesToRead === 0
244 };222 ? new Uint8Array()
245 }),223 : yield* handle.readAlloc(bytesToRead).pipe(
246 (handle) =>224 Effect.map(
247 Effect.tryPromise({225 Option.match({
248 try: () => handle.close(),226 onNone: () => new Uint8Array(),
249 catch: (cause) =>227 onSome: (bytes) => bytes,
250 new WorkspaceFileSystemOperationError({228 }),
251 workspaceRoot: input.cwd,229 ),
252 relativePath: input.relativePath,230 Effect.mapError(
253 resolvedPath: realTargetPath,231 (cause) =>
254 operationPath: realTargetPath,232 new WorkspaceFileSystemOperationError({
255 operation: "close",233 workspaceRoot: input.cwd,
256 cause,234 relativePath: input.relativePath,
257 }),235 resolvedPath: realTargetPath,
258 }),236 operationPath: realTargetPath,
237 operation: "read",
238 cause,
239 }),
240 ),
241 );
242 if (fileBytes.includes(0)) {
243 return yield* new WorkspaceBinaryFileError({
244 workspaceRoot: input.cwd,
245 relativePath: input.relativePath,
246 resolvedPath: realTargetPath,
247 });
248 }
249
250 return {
251 relativePath: target.relativePath,
252 contents: new TextDecoder("utf-8").decode(fileBytes),
253 byteLength,
254 truncated,
255 };
256 }),
259 );257 );
260 });258 });
261 259