History

refactor workspace reads to Effect FileSystem

Cursor Agent committed 3 months ago

Commit 51f34031 parent fdca1542 files · +122 −113Browse 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+74−84
@@ -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,7 +6,6 @@ · 2 unmodified lines
7 *6 *
8 * @module WorkspaceFileSystem7 * @module WorkspaceFileSystem
9 */8 */
10import * as NodeFSP from "node:fs/promises";
11 9
12import type {10import type {
13 ProjectReadFileInput,11 ProjectReadFileInput,
@@ -19,6 +17,7 @@ import * as Context from "effect/Context"; · 5 unmodified lines
19import * as Effect from "effect/Effect";17import * as Effect from "effect/Effect";
20import * as FileSystem from "effect/FileSystem";18import * as FileSystem from "effect/FileSystem";
21import * as Layer from "effect/Layer";19import * as Layer from "effect/Layer";
20import * as Option from "effect/Option";
22import * as Path from "effect/Path";21import * as Path from "effect/Path";
23import * as Schema from "effect/Schema";22import * as Schema from "effect/Schema";
24 23
@@ -140,30 +139,32 @@ export const make = Effect.gen(function* () { · 115 unmodified lines
140 relativePath: input.relativePath,139 relativePath: input.relativePath,
141 });140 });
142 141
143 const realWorkspaceRoot = yield* Effect.tryPromise({142 const realWorkspaceRoot = yield* fileSystem.realPath(input.cwd).pipe(
144 try: () => NodeFSP.realpath(input.cwd),143 Effect.mapError(
145 catch: (cause) =>144 (cause) =>
146 new WorkspaceFileSystemOperationError({145 new WorkspaceFileSystemOperationError({
147 workspaceRoot: input.cwd,146 workspaceRoot: input.cwd,
148 relativePath: input.relativePath,147 relativePath: input.relativePath,
149 resolvedPath: target.absolutePath,148 resolvedPath: target.absolutePath,
150 operationPath: input.cwd,149 operationPath: input.cwd,
151 operation: "realpath-workspace-root",150 operation: "realpath-workspace-root",
152 cause,151 cause,
153 }),152 }),
154 });153 ),
155 const realTargetPath = yield* Effect.tryPromise({154 );
156 try: () => NodeFSP.realpath(target.absolutePath),155 const realTargetPath = yield* fileSystem.realPath(target.absolutePath).pipe(
157 catch: (cause) =>156 Effect.mapError(
158 new WorkspaceFileSystemOperationError({157 (cause) =>
159 workspaceRoot: input.cwd,158 new WorkspaceFileSystemOperationError({
160 relativePath: input.relativePath,159 workspaceRoot: input.cwd,
161 resolvedPath: target.absolutePath,160 relativePath: input.relativePath,
162 operationPath: target.absolutePath,161 resolvedPath: target.absolutePath,
163 operation: "realpath-target",162 operationPath: target.absolutePath,
164 cause,163 operation: "realpath-target",
165 }),164 cause,
166 });165 }),
166 ),
167 );
167 const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath);168 const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath);
168 if (169 if (
169 relativeRealPath.startsWith(`..${path.sep}`) ||170 relativeRealPath.startsWith(`..${path.sep}`) ||
@@ -178,24 +179,24 @@ export const make = Effect.gen(function* () { · 8 unmodified lines
178 });179 });
179 }180 }
180 181
181 return yield* Effect.acquireUseRelease(182 return yield* Effect.scoped(
182 Effect.tryPromise({183 Effect.gen(function* () {
183 try: () => NodeFSP.open(realTargetPath, "r"),184 const handle = yield* fileSystem.open(realTargetPath, { flag: "r" }).pipe(
184 catch: (cause) =>185 Effect.mapError(
185 new WorkspaceFileSystemOperationError({186 (cause) =>
186 workspaceRoot: input.cwd,187 new WorkspaceFileSystemOperationError({
187 relativePath: input.relativePath,188 workspaceRoot: input.cwd,
188 resolvedPath: realTargetPath,189 relativePath: input.relativePath,
189 operationPath: realTargetPath,190 resolvedPath: realTargetPath,
190 operation: "open",191 operationPath: realTargetPath,
191 cause,192 operation: "open",
192 }),193 cause,
193 }),194 }),
194 (handle) =>195 ),
195 Effect.gen(function* () {196 );
196 const stat = yield* Effect.tryPromise({197 const stat = yield* handle.stat.pipe(
197 try: () => handle.stat(),198 Effect.mapError(
198 catch: (cause) =>199 (cause) =>
199 new WorkspaceFileSystemOperationError({200 new WorkspaceFileSystemOperationError({
200 workspaceRoot: input.cwd,201 workspaceRoot: input.cwd,
201 relativePath: input.relativePath,202 relativePath: input.relativePath,
@@ -204,20 +205,21 @@ export const make = Effect.gen(function* () { · 2 unmodified lines
204 operation: "stat",205 operation: "stat",
205 cause,206 cause,
206 }),207 }),
208 ),
209 );
210 if (stat.type !== "File") {
211 return yield* new WorkspacePathNotFileError({
212 workspaceRoot: input.cwd,
213 relativePath: input.relativePath,
214 resolvedPath: realTargetPath,
207 });215 });
208 if (!stat.isFile()) {216 }
209 return yield* new WorkspacePathNotFileError({
210 workspaceRoot: input.cwd,
211 relativePath: input.relativePath,
212 resolvedPath: realTargetPath,
213 });
214 }
215 217
216 const bytesToRead = Math.min(stat.size, PROJECT_READ_FILE_MAX_BYTES);218 const byteLength = Number(stat.size);
217 const buffer = Buffer.alloc(bytesToRead);219 const bytesToRead = Math.min(byteLength, PROJECT_READ_FILE_MAX_BYTES);
218 const { bytesRead } = yield* Effect.tryPromise({220 const maybeFileBytes = yield* handle.readAlloc(bytesToRead).pipe(
219 try: () => handle.read(buffer, 0, bytesToRead, 0),221 Effect.mapError(
220 catch: (cause) =>222 (cause) =>
221 new WorkspaceFileSystemOperationError({223 new WorkspaceFileSystemOperationError({
222 workspaceRoot: input.cwd,224 workspaceRoot: input.cwd,
223 relativePath: input.relativePath,225 relativePath: input.relativePath,
@@ -226,36 +228,24 @@ export const make = Effect.gen(function* () { · 2 unmodified lines
226 operation: "read",228 operation: "read",
227 cause,229 cause,
228 }),230 }),
231 ),
232 );
233 const fileBytes = Option.getOrElse(maybeFileBytes, () => new Uint8Array());
234 if (fileBytes.includes(0)) {
235 return yield* new WorkspaceBinaryFileError({
236 workspaceRoot: input.cwd,
237 relativePath: input.relativePath,
238 resolvedPath: realTargetPath,
229 });239 });
230 const fileBytes = buffer.subarray(0, bytesRead);240 }
231 if (fileBytes.includes(0)) {
232 return yield* new WorkspaceBinaryFileError({
233 workspaceRoot: input.cwd,
234 relativePath: input.relativePath,
235 resolvedPath: realTargetPath,
236 });
237 }
238 241
239 return {242 return {
240 relativePath: target.relativePath,243 relativePath: target.relativePath,
241 contents: new TextDecoder("utf-8").decode(fileBytes),244 contents: new TextDecoder("utf-8").decode(fileBytes),
242 byteLength: stat.size,245 byteLength,
243 truncated: stat.size > PROJECT_READ_FILE_MAX_BYTES,246 truncated: byteLength > PROJECT_READ_FILE_MAX_BYTES,
244 };247 };
245 }),248 }),
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 );249 );
260 });250 });
261 251