@effect/platform-node 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,10 @@
1
- import { pipe } from "@effect/data/Function"
1
+ import { identity, pipe } from "@effect/data/Function"
2
2
  import * as Option from "@effect/data/Option"
3
3
  import * as Effect from "@effect/io/Effect"
4
4
  import * as Layer from "@effect/io/Layer"
5
5
  import { effectify } from "@effect/platform/Effectify"
6
6
  import * as Error from "@effect/platform/Error"
7
7
  import * as FileSystem from "@effect/platform/FileSystem"
8
- import * as File from "@effect/platform/FileSystem/File"
9
8
  import * as Crypto from "node:crypto"
10
9
  import * as NFS from "node:fs"
11
10
  import * as OS from "node:os"
@@ -54,7 +53,7 @@ const handleErrnoException = (method: string) =>
54
53
  reason,
55
54
  module: "FileSystem",
56
55
  method,
57
- pathOrDescriptor: path.toString(),
56
+ pathOrDescriptor: path as string | number,
58
57
  syscall: err.syscall,
59
58
  message: err.message
60
59
  })
@@ -88,6 +87,22 @@ const access = (() => {
88
87
  }
89
88
  })()
90
89
 
90
+ // == copy
91
+
92
+ const copy = (() => {
93
+ const nodeCp = effectify(
94
+ NFS.cp,
95
+ handleErrnoException("copy"),
96
+ handleBadArgument("copy")
97
+ )
98
+ return (fromPath: string, toPath: string, options?: FileSystem.CopyOptions) =>
99
+ nodeCp(fromPath, toPath, {
100
+ force: options?.overwrite ?? false,
101
+ preserveTimestamps: options?.preserveTimestamps ?? false,
102
+ recursive: true
103
+ })
104
+ })()
105
+
91
106
  // == copyFile
92
107
 
93
108
  const copyFile = (() => {
@@ -162,7 +177,7 @@ const makeTempDirectoryFactory = (method: string) => {
162
177
  ? Path.join(options.directory, ".")
163
178
  : OS.tmpdir()
164
179
 
165
- return nodeMkdtemp(prefix ? Path.join(directory, prefix) : directory)
180
+ return nodeMkdtemp(prefix ? Path.join(directory, prefix) : directory + "/")
166
181
  })
167
182
  }
168
183
  const makeTempDirectory = makeTempDirectoryFactory("makeTempDirectory")
@@ -175,7 +190,11 @@ const removeFactory = (method: string) => {
175
190
  handleErrnoException(method),
176
191
  handleBadArgument(method)
177
192
  )
178
- return (path: string, options?: FileSystem.RemoveOptions) => nodeRm(path, { recursive: options?.recursive ?? false })
193
+ return (path: string, options?: FileSystem.RemoveOptions) =>
194
+ nodeRm(
195
+ path,
196
+ { recursive: options?.recursive ?? false }
197
+ )
179
198
  }
180
199
  const remove = removeFactory("remove")
181
200
 
@@ -193,31 +212,18 @@ const makeTempDirectoryScoped = (() => {
193
212
  )
194
213
  })()
195
214
 
196
- // == makeTempFile
197
-
198
- const makeTempFile = (() => {
199
- const makeDirectory = makeTempDirectoryFactory("makeTempFile")
200
- const randomHexString = (bytes: number) => Effect.sync(() => Crypto.randomBytes(bytes).toString("hex"))
201
- return (options?: FileSystem.MakeTempFileOptions) =>
202
- pipe(
203
- Effect.zip(makeDirectory(options), randomHexString(6)),
204
- Effect.map(([directory, random]) => Path.join(directory, random)),
205
- Effect.flatMap((path) => open(path, { flag: "w+" }))
206
- )
207
- })()
208
-
209
215
  // == open
210
216
 
211
- const open = (() => {
217
+ const openFactory = (method: string) => {
212
218
  const nodeOpen = effectify(
213
219
  NFS.open,
214
- handleErrnoException("open"),
215
- handleBadArgument("open")
220
+ handleErrnoException(method),
221
+ handleBadArgument(method)
216
222
  )
217
223
  const nodeClose = effectify(
218
224
  NFS.close,
219
- handleErrnoException("open"),
220
- handleBadArgument("open")
225
+ handleErrnoException(method),
226
+ handleBadArgument(method)
221
227
  )
222
228
 
223
229
  return (path: string, options?: FileSystem.OpenFileOptions) =>
@@ -226,9 +232,10 @@ const open = (() => {
226
232
  nodeOpen(path, options?.flag ?? "r", options?.mode),
227
233
  (fd) => Effect.orDie(nodeClose(fd))
228
234
  ),
229
- Effect.map((fd) => makeFile(File.Descriptor(fd)))
235
+ Effect.map((fd) => makeFile(FileSystem.FileDescriptor(fd)))
230
236
  )
231
- })()
237
+ }
238
+ const open = openFactory("open")
232
239
 
233
240
  const makeFile = (() => {
234
241
  const nodeReadFactory = (method: string) =>
@@ -259,11 +266,11 @@ const makeFile = (() => {
259
266
  const nodeWrite = nodeWriteFactory("write")
260
267
  const nodeWriteAll = nodeWriteFactory("writeAll")
261
268
 
262
- class FileImpl {
263
- readonly [File.FileTypeId] = File.FileTypeId
269
+ class FileImpl implements FileSystem.File {
270
+ readonly [FileSystem.FileTypeId] = identity
264
271
 
265
272
  constructor(
266
- readonly fd: File.File.Descriptor
273
+ readonly fd: FileSystem.File.Descriptor
267
274
  ) {}
268
275
 
269
276
  get stat() {
@@ -272,7 +279,7 @@ const makeFile = (() => {
272
279
 
273
280
  read(
274
281
  buffer: Uint8Array,
275
- options?: File.FileReadOptions
282
+ options?: FileSystem.FileReadOptions
276
283
  ) {
277
284
  return Effect.map(
278
285
  nodeRead(this.fd, {
@@ -284,7 +291,7 @@ const makeFile = (() => {
284
291
  )
285
292
  }
286
293
 
287
- readAlloc(size: FileSystem.Size, options?: File.FileReadOptions | undefined) {
294
+ readAlloc(size: FileSystem.Size, options?: FileSystem.FileReadOptions | undefined) {
288
295
  return Effect.flatMap(
289
296
  Effect.sync(() => Buffer.allocUnsafeSlow(Number(size))),
290
297
  (buffer) =>
@@ -323,16 +330,51 @@ const makeFile = (() => {
323
330
  return Effect.flatMap(
324
331
  nodeWriteAll(this.fd, buffer),
325
332
  (bytesWritten) => {
326
- if (bytesWritten === buffer.length) {
327
- return Effect.unit()
333
+ if (bytesWritten === 0) {
334
+ return Effect.fail(Error.SystemError({
335
+ module: "FileSystem",
336
+ method: "writeAll",
337
+ reason: "WriteZero",
338
+ pathOrDescriptor: this.fd,
339
+ message: "write returned 0 bytes written"
340
+ }))
341
+ } else if (bytesWritten < buffer.length) {
342
+ return this.writeAll(buffer.subarray(bytesWritten))
328
343
  }
329
- return this.writeAll(buffer.subarray(bytesWritten))
344
+ return Effect.unit()
330
345
  }
331
346
  )
332
347
  }
333
348
  }
334
349
 
335
- return (fd: File.File.Descriptor) => new FileImpl(fd) as File.File
350
+ return (fd: FileSystem.File.Descriptor): FileSystem.File => new FileImpl(fd)
351
+ })()
352
+
353
+ // == makeTempFile
354
+
355
+ const makeTempFileFactory = (method: string) => {
356
+ const makeDirectory = makeTempDirectoryFactory(method)
357
+ const open = openFactory(method)
358
+ const randomHexString = (bytes: number) => Effect.sync(() => Crypto.randomBytes(bytes).toString("hex"))
359
+ return (options?: FileSystem.MakeTempFileOptions) =>
360
+ pipe(
361
+ Effect.zip(makeDirectory(options), randomHexString(6)),
362
+ Effect.map(([directory, random]) => Path.join(directory, random)),
363
+ Effect.tap((path) => Effect.scoped(open(path, { flag: "w+" })))
364
+ )
365
+ }
366
+ const makeTempFile = makeTempFileFactory("makeTempFile")
367
+
368
+ // == makeTempFileScoped
369
+
370
+ const makeTempFileScoped = (() => {
371
+ const makeFile = makeTempFileFactory("makeTempFileScoped")
372
+ const removeFile = removeFactory("makeTempFileScoped")
373
+ return (options?: FileSystem.MakeTempFileOptions) =>
374
+ Effect.acquireRelease(
375
+ makeFile(options),
376
+ (file) => Effect.orDie(removeFile(file))
377
+ )
336
378
  })()
337
379
 
338
380
  // == readDirectory
@@ -404,7 +446,7 @@ const rename = (() => {
404
446
 
405
447
  // == stat
406
448
 
407
- const makeFileInfo = (stat: NFS.Stats): File.File.Info => ({
449
+ const makeFileInfo = (stat: NFS.Stats): FileSystem.File.Info => ({
408
450
  type: stat.isFile() ?
409
451
  "File" :
410
452
  stat.isDirectory() ?
@@ -465,15 +507,15 @@ const truncate = (() => {
465
507
  return (path: string, length?: FileSystem.Size) => nodeTruncate(path, Number(length))
466
508
  })()
467
509
 
468
- // == utime
510
+ // == utimes
469
511
 
470
- const utime = (() => {
471
- const nodeUtime = effectify(
512
+ const utimes = (() => {
513
+ const nodeUtimes = effectify(
472
514
  NFS.utimes,
473
515
  handleErrnoException("utime"),
474
516
  handleBadArgument("utime")
475
517
  )
476
- return (path: string, atime: number | Date, mtime: number | Date) => nodeUtime(path, atime, mtime)
518
+ return (path: string, atime: number | Date, mtime: number | Date) => nodeUtimes(path, atime, mtime)
477
519
  })()
478
520
 
479
521
  // == writeFile
@@ -501,14 +543,16 @@ const writeFile = (path: string, data: Uint8Array, options?: FileSystem.WriteFil
501
543
 
502
544
  const fileSystemImpl = FileSystem.make({
503
545
  access,
504
- copyFile,
505
546
  chmod,
506
547
  chown,
548
+ copy,
549
+ copyFile,
507
550
  link,
508
551
  makeDirectory,
509
552
  makeTempDirectory,
510
553
  makeTempDirectoryScoped,
511
554
  makeTempFile,
555
+ makeTempFileScoped,
512
556
  open,
513
557
  readDirectory,
514
558
  readFile,
@@ -519,7 +563,7 @@ const fileSystemImpl = FileSystem.make({
519
563
  stat,
520
564
  symlink,
521
565
  truncate,
522
- utime,
566
+ utimes,
523
567
  writeFile
524
568
  })
525
569
 
@@ -0,0 +1,62 @@
1
+ import { Tag } from "@effect/data/Context"
2
+ import * as Effect from "@effect/io/Effect"
3
+ import * as Layer from "@effect/io/Layer"
4
+ import { BadArgument } from "@effect/platform/Error"
5
+ import type { Path as _Path } from "@effect/platform/Path"
6
+ import * as NodePath from "node:path"
7
+ import * as NodeUrl from "node:url"
8
+
9
+ /** @internal */
10
+ export const Path = Tag<_Path>()
11
+
12
+ const fromFileUrl = (url: URL): Effect.Effect<never, BadArgument, string> =>
13
+ Effect.tryCatch(
14
+ () => NodeUrl.fileURLToPath(url),
15
+ (error) =>
16
+ BadArgument({
17
+ module: "Path",
18
+ method: "fromFileUrl",
19
+ message: `${error}`
20
+ })
21
+ )
22
+
23
+ const toFileUrl = (path: string): Effect.Effect<never, BadArgument, URL> =>
24
+ Effect.tryCatch(
25
+ () => NodeUrl.pathToFileURL(path),
26
+ (error) =>
27
+ BadArgument({
28
+ module: "Path",
29
+ method: "toFileUrl",
30
+ message: `${error}`
31
+ })
32
+ )
33
+
34
+ /** @internal */
35
+ export const layerPosix = Layer.succeed(
36
+ Path,
37
+ Path.of({
38
+ ...NodePath.posix,
39
+ fromFileUrl,
40
+ toFileUrl
41
+ })
42
+ )
43
+
44
+ /** @internal */
45
+ export const layerWin32 = Layer.succeed(
46
+ Path,
47
+ Path.of({
48
+ ...NodePath.win32,
49
+ fromFileUrl,
50
+ toFileUrl
51
+ })
52
+ )
53
+
54
+ /** @internal */
55
+ export const layer = Layer.succeed(
56
+ Path,
57
+ Path.of({
58
+ ...NodePath,
59
+ fromFileUrl,
60
+ toFileUrl
61
+ })
62
+ )
@@ -3,17 +3,15 @@ import { pipe } from "@effect/data/Function"
3
3
  import * as Option from "@effect/data/Option"
4
4
  import * as Effect from "@effect/io/Effect"
5
5
  import type { FromReadableOptions } from "@effect/platform-node/Stream"
6
- import { Size } from "@effect/platform/FileSystem"
6
+ import type { Size } from "@effect/platform/FileSystem"
7
7
  import * as Stream from "@effect/stream/Stream"
8
8
  import type { Readable } from "node:stream"
9
9
 
10
- const DEFAULT_CHUNK_SIZE = Size(64 * 1024)
11
-
12
10
  /** @internal */
13
11
  export const fromReadable = <E, A>(
14
12
  evaluate: LazyArg<Readable>,
15
13
  onError: (error: unknown) => E,
16
- { chunkSize = DEFAULT_CHUNK_SIZE }: FromReadableOptions = {}
14
+ { chunkSize = Option.none() }: FromReadableOptions = {}
17
15
  ): Stream.Stream<never, E, A> =>
18
16
  pipe(
19
17
  Effect.acquireRelease(Effect.sync(evaluate), (stream) =>
@@ -49,9 +47,9 @@ export const fromReadable = <E, A>(
49
47
 
50
48
  const readChunk = <A>(
51
49
  stream: Readable,
52
- size: Size
50
+ size: Option.Option<Size>
53
51
  ): Effect.Effect<never, Option.Option<never>, A> =>
54
52
  pipe(
55
- Effect.sync(() => stream.read(Number(size)) as A | null),
56
- Effect.flatMap((a) => (a ? Effect.succeed(a) : Effect.fail(Option.none())))
53
+ Effect.sync(() => (size._tag === "Some" ? stream.read(Number(size)) : stream.read()) as A | null),
54
+ Effect.flatMap((_) => (_ ? Effect.succeed(_) : Effect.fail(Option.none())))
57
55
  )