Merge commit '36c66dd290d3ce6eb1ccd310d0c658d4a32bb8eb' as 'repos/effect'

This commit is contained in:
-Puter
2026-07-19 03:25:10 +05:30
parent dd1071cc10
commit 2daf979036
2214 changed files with 673090 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,214 @@
import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem"
import { assert, describe, expect, it } from "@effect/vitest"
import { Array } from "effect"
import * as Effect from "effect/Effect"
import * as Fs from "effect/FileSystem"
import * as Stream from "effect/Stream"
const runPromise = <E, A>(self: Effect.Effect<A, E, Fs.FileSystem>) =>
Effect.runPromise(
Effect.provide(self, NodeFileSystem.layer)
)
describe("FileSystem", () => {
it("readFile", () =>
runPromise(Effect.gen(function*() {
const fs = yield* Fs.FileSystem
const data = yield* fs.readFile(`${__dirname}/fixtures/text.txt`)
const text = new TextDecoder().decode(data)
expect(text.trim()).toEqual("lorem ipsum dolar sit amet")
})))
it("makeTempDirectory", () =>
runPromise(Effect.gen(function*() {
const fs = yield* Fs.FileSystem
let dir = ""
yield* Effect.scoped(Effect.gen(function*() {
dir = yield* fs.makeTempDirectory()
const stat = yield* fs.stat(dir)
expect(stat.type).toEqual("Directory")
}))
const stat = yield* fs.stat(dir)
expect(stat.type).toEqual("Directory")
})))
it("makeTempDirectoryScoped", () =>
runPromise(Effect.gen(function*() {
const fs = yield* Fs.FileSystem
let dir = ""
yield* Effect.scoped(
Effect.gen(function*() {
dir = yield* fs.makeTempDirectoryScoped()
const stat = yield* fs.stat(dir)
expect(stat.type).toEqual("Directory")
})
)
const error = yield* Effect.flip(fs.stat(dir))
assert(error.reason._tag === "NotFound")
})))
it("truncate", () =>
runPromise(Effect.gen(function*() {
const fs = yield* Fs.FileSystem
const file = yield* fs.makeTempFile()
const text = "hello world"
yield* fs.writeFile(file, new TextEncoder().encode(text))
const before = yield* Effect.map(fs.readFile(file), (_) => new TextDecoder().decode(_))
expect(before).toEqual(text)
yield* fs.truncate(file)
const after = yield* Effect.map(fs.readFile(file), (_) => new TextDecoder().decode(_))
expect(after).toEqual("")
})))
it("should track the cursor position when reading", () =>
runPromise(Effect.gen(function*() {
const fs = yield* Fs.FileSystem
yield* Effect.gen(function*() {
let text: string
const file = yield* fs.open(`${__dirname}/fixtures/text.txt`)
text = yield* file.readAlloc(Fs.Size(5)).pipe(
Effect.flatMap(Effect.fromOption),
Effect.map((_) => new TextDecoder().decode(_))
)
expect(text).toBe("lorem")
yield* file.seek(Fs.Size(7), "current")
text = yield* file.readAlloc(Fs.Size(5)).pipe(
Effect.flatMap(Effect.fromOption),
Effect.map((_) => new TextDecoder().decode(_))
)
expect(text).toBe("dolar")
yield* file.seek(Fs.Size(1), "current")
text = yield* file.readAlloc(Fs.Size(8)).pipe(
Effect.flatMap(Effect.fromOption),
Effect.map((_) => new TextDecoder().decode(_))
)
expect(text).toBe("sit amet")
yield* file.seek(Fs.Size(0), "start")
text = yield* file.readAlloc(Fs.Size(11)).pipe(
Effect.flatMap(Effect.fromOption),
Effect.map((_) => new TextDecoder().decode(_))
)
expect(text).toBe("lorem ipsum")
text = yield* fs.stream(`${__dirname}/fixtures/text.txt`, { offset: Fs.Size(6), bytesToRead: Fs.Size(5) }).pipe(
Stream.map((_) => new TextDecoder().decode(_)),
Stream.runCollect,
Effect.map(Array.join(""))
)
expect(text).toBe("ipsum")
}).pipe(
Effect.scoped
)
})))
it("should track the cursor position when writing", () =>
runPromise(Effect.gen(function*() {
const fs = yield* Fs.FileSystem
yield* Effect.gen(function*() {
let text: string
const path = yield* fs.makeTempFileScoped()
const file = yield* fs.open(path, { flag: "w+" })
yield* file.write(new TextEncoder().encode("lorem ipsum"))
yield* file.write(new TextEncoder().encode(" "))
yield* file.write(new TextEncoder().encode("dolor sit amet"))
text = yield* fs.readFileString(path)
expect(text).toBe("lorem ipsum dolor sit amet")
yield* file.seek(Fs.Size(-4), "current")
yield* file.write(new TextEncoder().encode("hello world"))
text = yield* fs.readFileString(path)
expect(text).toBe("lorem ipsum dolor sit hello world")
yield* file.seek(Fs.Size(6), "start")
yield* file.write(new TextEncoder().encode("blabl"))
text = yield* fs.readFileString(path)
expect(text).toBe("lorem blabl dolor sit hello world")
}).pipe(
Effect.scoped
)
})))
it("should maintain a read cursor in append mode", () =>
runPromise(Effect.gen(function*() {
const fs = yield* Fs.FileSystem
yield* Effect.gen(function*() {
let text: string
const path = yield* fs.makeTempFileScoped()
const file = yield* fs.open(path, { flag: "a+" })
yield* file.write(new TextEncoder().encode("foo"))
yield* file.seek(Fs.Size(0), "start")
yield* file.write(new TextEncoder().encode("bar"))
text = yield* fs.readFileString(path)
expect(text).toBe("foobar")
text = yield* file.readAlloc(Fs.Size(3)).pipe(
Effect.flatMap(Effect.fromOption),
Effect.map((_) => new TextDecoder().decode(_))
)
expect(text).toBe("foo")
yield* file.write(new TextEncoder().encode("baz"))
text = yield* fs.readFileString(path)
expect(text).toBe("foobarbaz")
text = yield* file.readAlloc(Fs.Size(6)).pipe(
Effect.flatMap(Effect.fromOption),
Effect.map((_) => new TextDecoder().decode(_))
)
expect(text).toBe("barbaz")
}).pipe(
Effect.scoped
)
})))
it("should keep the current cursor if truncating doesn't affect it", () =>
runPromise(Effect.gen(function*() {
const fs = yield* Fs.FileSystem
yield* Effect.gen(function*() {
const path = yield* fs.makeTempFileScoped()
const file = yield* fs.open(path, { flag: "w+" })
yield* file.write(new TextEncoder().encode("lorem ipsum dolor sit amet"))
yield* file.seek(Fs.Size(6), "start")
yield* file.truncate(Fs.Size(11))
const cursor = yield* file.seek(Fs.Size(0), "current")
expect(cursor).toBe(Fs.Size(6))
}).pipe(
Effect.scoped
)
})))
it("should update the current cursor if truncating affects it", () =>
runPromise(Effect.gen(function*() {
const fs = yield* Fs.FileSystem
yield* Effect.gen(function*() {
const path = yield* fs.makeTempFileScoped()
const file = yield* fs.open(path, { flag: "w+" })
yield* file.write(new TextEncoder().encode("lorem ipsum dolor sit amet"))
yield* file.truncate(Fs.Size(11))
const cursor = yield* file.seek(Fs.Size(0), "current")
expect(cursor).toBe(Fs.Size(11))
}).pipe(
Effect.scoped
)
})))
})

View File

@@ -0,0 +1,153 @@
import * as NodeSink from "@effect/platform-node-shared/NodeSink"
import * as NodeStream from "@effect/platform-node-shared/NodeStream"
import { assert, describe, it } from "@effect/vitest"
import { Effect } from "effect"
import * as Data from "effect/Data"
import * as Latch from "effect/Latch"
import * as Queue from "effect/Queue"
import * as Stream from "effect/Stream"
import { createReadStream } from "fs"
import { join } from "path"
import { Writable } from "stream"
import * as Tar from "tar"
const TEST_TARBALL = join(__dirname, "fixtures", "helloworld.tar.gz")
describe("Sink", () => {
it.effect("should write to a stream", () =>
Effect.gen(function*() {
const items: Array<string> = []
const destroyLatch = yield* Latch.make()
yield* Stream.make("a", "b", "c").pipe(
Stream.run(NodeSink.fromWritable({
evaluate: () =>
new Writable({
construct(callback) {
callback()
},
write(chunk, _encoding, callback) {
items.push(chunk.toString())
callback()
},
destroy(_error, callback) {
destroyLatch.openUnsafe()
callback(null)
}
}),
onError: () => "error"
}))
)
assert.deepEqual(items, ["a", "b", "c"])
yield* destroyLatch.await
}))
it.effect("write error", () =>
Effect.gen(function*() {
const items: Array<string> = []
const sink = NodeSink.fromWritable({
evaluate: () =>
new Writable({
construct(callback) {
callback()
},
write(chunk, _encoding, callback) {
items.push(chunk.toString())
callback()
},
destroy(_error, callback) {
callback(null)
}
}),
onError: () => "error"
})
const result = yield* Stream.fail("a").pipe(Stream.run(sink), Effect.flip)
assert.deepEqual(items, [])
assert.strictEqual(result, "a")
}))
it.live("endOnClose false", () =>
Effect.gen(function*() {
const items: Array<string> = []
let destroyed = false
const sink = NodeSink.fromWritable({
evaluate: () =>
new Writable({
construct(callback) {
callback()
},
write(chunk, _encoding, callback) {
items.push(chunk.toString())
callback()
},
destroy(_error, callback) {
destroyed = true
callback(null)
}
}),
onError: () => "error",
endOnDone: false
})
yield* Stream.run(Stream.make("a", "b", "c"), sink)
yield* Effect.sleep(10)
assert.deepEqual(items, ["a", "b", "c"])
assert.strictEqual(destroyed, false)
}))
it.effect("should handle non-compliant node streams", () =>
Effect.gen(function*() {
const stream = NodeStream.fromReadable<Uint8Array, "error">({
evaluate: () => createReadStream(TEST_TARBALL),
onError: () => "error"
})
const items = yield* entries(stream).pipe(
Stream.flatMap((entry) =>
NodeStream.fromReadable({
evaluate: () => (entry as any),
onError: (error) => new TarError({ error })
}).pipe(
Stream.map((content) => ({
path: entry.path,
content: Buffer.from(content).toString("utf-8")
}))
)
),
Stream.runCollect
)
assert.deepEqual(items, [
{ path: "./tar/world.txt", content: "world\n" },
{ path: "./tar/hello.txt", content: "hello\n" }
])
}))
})
class TarError extends Data.TaggedError("TarError")<{
readonly error: unknown
}> {}
const entries = <R, E>(
input: Stream.Stream<Uint8Array, E, R>
): Stream.Stream<Tar.ReadEntry, TarError | E, R> =>
Effect.gen(function*() {
const parser = new Tar.Parser()
yield* input.pipe(
Stream.run(
NodeSink.fromWritable({
evaluate: () => parser,
onError: (error) => new TarError({ error })
})
),
Effect.forkScoped
)
return Stream.callback<Tar.ReadEntry, TarError>((queue) =>
Effect.sync(() => {
parser.on("entry", (entry) => {
Queue.offerUnsafe(queue, entry)
})
parser.on("close", () => {
Queue.endUnsafe(queue)
})
})
)
}).pipe(Stream.unwrap)

View File

@@ -0,0 +1,173 @@
import * as NodeStream from "@effect/platform-node-shared/NodeStream"
import { assert, describe, it } from "@effect/vitest"
import { Effect } from "effect"
import * as Array from "effect/Array"
import * as Channel from "effect/Channel"
import * as Console from "effect/Console"
import * as Stream from "effect/Stream"
import { Duplex, Readable, Transform } from "node:stream"
import * as Zlib from "node:zlib"
describe("Stream", () => {
it.effect("should read a stream", () =>
Effect.gen(function*() {
const stream = NodeStream.fromReadable<"error", string>({
evaluate: () => Readable.from(["a", "b", "c"]),
onError: () => "error"
})
const items = yield* Stream.runCollect(stream)
assert.deepEqual(items, ["a", "b", "c"])
}))
it.effect("fromDuplex", () =>
Effect.gen(function*() {
const result = yield* Stream.fromArray(["a", "b", "c"]).pipe(
Stream.pipeThroughChannelOrFail(NodeStream.fromDuplex({
evaluate: () =>
new Transform({
transform(chunk, _encoding, callback) {
callback(null, chunk.toString().toUpperCase())
}
}),
onError: () => "error" as const
})),
Stream.decodeText(),
Stream.mkString
)
assert.strictEqual(result, "ABC")
}))
it.effect("fromDuplex failure", () =>
Effect.gen(function*() {
const result = yield* Stream.fromArray(["a", "b", "c"]).pipe(
Stream.pipeThroughChannelOrFail(NodeStream.fromDuplex({
evaluate: () =>
new Transform({
transform(_chunk, _encoding, callback) {
callback(new Error())
}
}),
onError: () => "error" as const
})),
Stream.runDrain,
Effect.flip
)
assert.strictEqual(result, "error")
}))
it.effect("pipeThroughDuplex", () =>
Effect.gen(function*() {
const result = yield* Stream.fromArray(["a", "b", "c"]).pipe(
NodeStream.pipeThroughDuplex({
evaluate: () =>
new Transform({
transform(chunk, _encoding, callback) {
callback(null, chunk.toString().toUpperCase())
}
}),
onError: () => "error" as const
}),
Stream.decodeText(),
Stream.mkString
)
assert.strictEqual(result, "ABC")
}))
it.effect("pipeThroughDuplex write error", () =>
Effect.gen(function*() {
const result = yield* Stream.fromArray(["a", "b", "c"]).pipe(
NodeStream.pipeThroughDuplex({
evaluate: () =>
new Duplex({
read() {},
write(_chunk, _encoding, callback) {
callback(new Error())
}
}),
onError: () => "error" as const
}),
Stream.runDrain,
Effect.flip
)
assert.strictEqual(result, "error")
}))
it.effect("pipeThroughSimple", () =>
Effect.gen(function*() {
const result = yield* Stream.fromArray(["a", Buffer.from("b"), "c"]).pipe(
NodeStream.pipeThroughSimple(
() =>
new Transform({
transform(chunk, _encoding, callback) {
callback(null, chunk.toString().toUpperCase())
}
})
),
Stream.decodeText(),
Stream.mkString
)
assert.strictEqual(result, "ABC")
}))
it.effect("fromDuplex should work with node:zlib", () =>
Effect.gen(function*() {
const text = "abcdefg1234567890"
const encoder = new TextEncoder()
const input = encoder.encode(text)
const stream = NodeStream.fromReadable<Uint8Array, "error">({
evaluate: () => Readable.from([input]),
onError: () => "error"
})
const deflate = NodeStream.fromDuplex({
evaluate: () => Zlib.createGzip(),
onError: () => "error" as const
})
const inflate = NodeStream.fromDuplex<never, Uint8Array, Uint8Array, "error">({
evaluate: () => Zlib.createUnzip(),
onError: () => "error" as const
})
const channel = Channel.pipeToOrFail(deflate, inflate)
const result = yield* stream.pipe(
Stream.pipeThroughChannelOrFail(channel),
Stream.decodeText(),
Stream.mkString
)
assert.strictEqual(result, text)
}))
it.effect("toReadable roundtrip", () =>
Effect.gen(function*() {
const stream = Stream.range(0, 10000).pipe(
Stream.map((n) => String(n))
)
const readable = yield* NodeStream.toReadable(stream)
const outStream = NodeStream.fromReadable({
evaluate: () => readable,
onError: () => "error" as const
})
const items = yield* outStream.pipe(
Stream.decodeText(),
Stream.runCollect
)
assert.strictEqual(items.join(""), Array.range(0, 10000).join(""))
}))
it.effect("toReadable with error", () =>
Effect.gen(function*() {
const stream = Stream.fail("error")
const readable = yield* NodeStream.toReadable(stream)
const outStream = NodeStream.fromReadable({
evaluate: () => readable
})
const error = yield* outStream.pipe(
Stream.runCollect,
Effect.tapError((_) => Console.log(_)),
Effect.flip
)
assert.deepEqual(error.cause, "error")
}))
})

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
echo "this should not run because it doesn't have execute permissions"

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# This script spawns child processes and then exits early
echo "Parent process started with PID $$"
# Spawn multiple child processes that will outlive the parent
for i in {1..3}; do
(
# Child process
child_pid=$BASHPID
echo "Child $i started with PID $child_pid"
# Spawn a grandchild that runs for a long time
(
grandchild_pid=$BASHPID
echo "Grandchild of child $i started with PID $grandchild_pid"
# Keep running for 30 seconds
sleep 30
) &
# Keep the child running
sleep 30
) &
done
# Give children time to start
sleep 0.5
# Exit early (simulating a crash or early termination)
echo "Parent exiting early with status 1..."
exit 1

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# This script spawns child processes to test process group cleanup
echo "Parent process started with PID $$"
# Spawn multiple child processes
for i in {1..3}; do
(
# Child process
child_pid=$BASHPID
echo "Child $i started with PID $child_pid"
# Spawn a grandchild that runs for a long time
(
grandchild_pid=$BASHPID
echo "Grandchild of child $i started with PID $grandchild_pid"
# Keep running for 60 seconds
for j in {1..60}; do
sleep 1
done
) &
# Keep the child running
for j in {1..60}; do
sleep 1
done
) &
done
# Keep the parent running
echo "Parent process waiting..."
wait

View File

@@ -0,0 +1 @@
value

View File

@@ -0,0 +1 @@
123

View File

@@ -0,0 +1 @@
hello

View File

@@ -0,0 +1 @@
keepitsafe

View File

@@ -0,0 +1 @@
lorem ipsum dolar sit amet