1// @effect-diagnostics nodeBuiltinImport:off -- Effect's Node FileSystem is async, and pre-ready startup must not yield before the Clerk bridge registers its privileged scheme.
2import * as NodeFS from "node:fs";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Layer from "effect/Layer";
6import * as PlatformError from "effect/PlatformError";
7
8const systemErrorTag = (cause: unknown): PlatformError.SystemErrorTag => {
9 switch ((cause as NodeJS.ErrnoException | undefined)?.code) {
10 case "EEXIST":
11 return "AlreadyExists";
12 case "ENOENT":
13 return "NotFound";
14 case "EACCES":
15 case "EPERM":
16 return "PermissionDenied";
17 default:
18 return "Unknown";
19 }
20};
21
22const syncFs = <A>(method: string, path: string, run: () => A) =>
23 Effect.try({
24 try: run,
25 catch: (cause) =>
26 PlatformError.systemError({
27 _tag: systemErrorTag(cause),
28 module: "FileSystem",
29 method,
30 pathOrDescriptor: path,
31 cause,
32 }),
33 });
34
35/**
36 * A synchronous FileSystem for startup work that runs before Electron's ready.
37 * Electron emits ready as soon as startup yields to the event loop. Only the
38 * operations userData resolution needs are implemented.
39 *
40 * @public Service construction is part of the canonical Effect module API.
41 */
42export const make = FileSystem.makeNoop({
43 // Like Effect's exists: only a missing path is false, other access errors fail.
44 exists: (path) =>
45 syncFs("exists", path, () => {
46 NodeFS.accessSync(path);
47 return true;
48 }).pipe(
49 Effect.catchIf(
50 (error) => error.reason._tag === "NotFound",
51 () => Effect.succeed(false),
52 ),
53 ),
54 readFileString: (path) => syncFs("readFileString", path, () => NodeFS.readFileSync(path, "utf8")),
55 makeDirectory: (path, options) =>
56 syncFs("makeDirectory", path, () => {
57 NodeFS.mkdirSync(path, { recursive: options?.recursive ?? false });
58 }),
59 writeFileString: (path, data, options) =>
60 syncFs("writeFileString", path, () =>
61 NodeFS.writeFileSync(path, data, { flag: options?.flag ?? "w" }),
62 ),
63});
64
65export const layer = Layer.succeed(FileSystem.FileSystem, make);