mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-08 22:13:18 +00:00
refactor(core): remove unused file mutation methods (#40667)
This commit is contained in:
parent
5aa0413fea
commit
0a0fc09533
2 changed files with 2 additions and 254 deletions
|
|
@ -1,8 +1,7 @@
|
|||
export * as FileMutation from "./file-mutation"
|
||||
|
||||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { Context, Effect, Layer, Schema } from "effect"
|
||||
import { dirname } from "path"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { KeyedMutex } from "./effect/keyed-mutex"
|
||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
||||
import { Bom } from "@opencode-ai/util/bom"
|
||||
|
|
@ -22,22 +21,6 @@ export interface TextWriteInput {
|
|||
readonly content: string
|
||||
}
|
||||
|
||||
export interface ConditionalWriteInput extends WriteInput {
|
||||
readonly expected: Uint8Array
|
||||
}
|
||||
|
||||
export interface RemoveInput {
|
||||
readonly target: Target
|
||||
}
|
||||
|
||||
export class StaleContentError extends Schema.TaggedErrorClass<StaleContentError>()("FileMutation.StaleContentError", {
|
||||
path: Schema.String,
|
||||
}) {}
|
||||
|
||||
export class TargetExistsError extends Schema.TaggedErrorClass<TargetExistsError>()("FileMutation.TargetExistsError", {
|
||||
path: Schema.String,
|
||||
}) {}
|
||||
|
||||
export interface WriteResult {
|
||||
readonly operation: "write"
|
||||
readonly target: string
|
||||
|
|
@ -45,24 +28,10 @@ export interface WriteResult {
|
|||
readonly existed: boolean
|
||||
}
|
||||
|
||||
export interface RemoveResult {
|
||||
readonly operation: "remove"
|
||||
readonly target: string
|
||||
readonly resource: string
|
||||
readonly existed: boolean
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
/** Create without replacing an existing target. */
|
||||
readonly create: (input: WriteInput) => Effect.Effect<WriteResult, TargetExistsError | FSUtil.Error>
|
||||
readonly write: (input: WriteInput) => Effect.Effect<WriteResult, FSUtil.Error>
|
||||
/** Write text while retaining an existing UTF-8 BOM and emitting at most one BOM. */
|
||||
readonly writeTextPreservingBom: (input: TextWriteInput) => Effect.Effect<WriteResult, FSUtil.Error>
|
||||
/** Commit only if an existing target still has the expected bytes. */
|
||||
readonly writeIfUnchanged: (
|
||||
input: ConditionalWriteInput,
|
||||
) => Effect.Effect<WriteResult, StaleContentError | FSUtil.Error>
|
||||
readonly remove: (input: RemoveInput) => Effect.Effect<RemoveResult, FSUtil.Error>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/FileMutation") {}
|
||||
|
|
@ -89,13 +58,6 @@ const layer = Layer.effect(
|
|||
existed,
|
||||
})
|
||||
|
||||
const removeResult = (target: Target, existed: boolean): RemoveResult => ({
|
||||
operation: "remove",
|
||||
target: target.canonical,
|
||||
resource: target.resource,
|
||||
existed,
|
||||
})
|
||||
|
||||
const write = Effect.fn("FileMutation.write")((input: WriteInput) =>
|
||||
withTargetLock(input.target)(
|
||||
Effect.gen(function* () {
|
||||
|
|
@ -122,62 +84,10 @@ const layer = Layer.effect(
|
|||
),
|
||||
)
|
||||
|
||||
const create = Effect.fn("FileMutation.create")((input: WriteInput) =>
|
||||
withTargetLock(input.target)(
|
||||
Effect.gen(function* () {
|
||||
const write =
|
||||
typeof input.content === "string"
|
||||
? fs.writeFileString(input.target.canonical, input.content, { flag: "wx" })
|
||||
: fs.writeFile(input.target.canonical, input.content, { flag: "wx" })
|
||||
yield* write.pipe(
|
||||
Effect.catchReason("PlatformError", "NotFound", () =>
|
||||
fs.ensureDir(dirname(input.target.canonical)).pipe(Effect.andThen(write)),
|
||||
),
|
||||
Effect.catchReason("PlatformError", "AlreadyExists", () =>
|
||||
Effect.fail(new TargetExistsError({ path: input.target.canonical })),
|
||||
),
|
||||
)
|
||||
return writeResult(input.target, false)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const writeIfUnchanged = Effect.fn("FileMutation.writeIfUnchanged")((input: ConditionalWriteInput) =>
|
||||
withTargetLock(input.target)(
|
||||
Effect.gen(function* () {
|
||||
const current = yield* fs.readFile(input.target.canonical)
|
||||
if (!sameBytes(current, input.expected)) {
|
||||
return yield* new StaleContentError({ path: input.target.canonical })
|
||||
}
|
||||
yield* typeof input.content === "string"
|
||||
? fs.writeFileString(input.target.canonical, input.content)
|
||||
: fs.writeFile(input.target.canonical, input.content)
|
||||
return writeResult(input.target, true)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const remove = Effect.fn("FileMutation.remove")((input: RemoveInput) =>
|
||||
withTargetLock(input.target)(
|
||||
Effect.gen(function* () {
|
||||
const existed = yield* fs.remove(input.target.canonical).pipe(
|
||||
Effect.as(true),
|
||||
Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(false)),
|
||||
)
|
||||
return removeResult(input.target, existed)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
return Service.of({ create, write, writeTextPreservingBom, writeIfUnchanged, remove })
|
||||
return Service.of({ write, writeTextPreservingBom })
|
||||
}),
|
||||
)
|
||||
|
||||
function sameBytes(left: Uint8Array, right: Uint8Array) {
|
||||
if (left.length !== right.length) return false
|
||||
return left.every((byte, index) => byte === right[index])
|
||||
}
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [FSUtil.node] })
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -89,68 +89,6 @@ describe("FileMutation", () => {
|
|||
),
|
||||
)
|
||||
|
||||
it.live("rejects create when a prospective target appears after resolution", () =>
|
||||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
const targetPath = path.join(directory, "appeared.txt")
|
||||
const target = yield* (yield* LocationMutation.Service).resolve({ path: "appeared.txt" })
|
||||
yield* Effect.promise(() => fs.writeFile(targetPath, "winner"))
|
||||
|
||||
expect(
|
||||
yield* (yield* FileMutation.Service).create({ target, content: "replacement" }).pipe(Effect.flip),
|
||||
).toMatchObject({
|
||||
_tag: "FileMutation.TargetExistsError",
|
||||
})
|
||||
expect(yield* Effect.promise(() => fs.readFile(targetPath, "utf8"))).toBe("winner")
|
||||
}).pipe(provide(directory)),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("creates when an existing target disappears after resolution", () =>
|
||||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
const targetPath = path.join(directory, "removed.txt")
|
||||
yield* Effect.promise(() => fs.writeFile(targetPath, "before"))
|
||||
const target = yield* (yield* LocationMutation.Service).resolve({ path: "removed.txt" })
|
||||
yield* Effect.promise(() => fs.rm(targetPath))
|
||||
|
||||
expect(yield* (yield* FileMutation.Service).create({ target, content: "after" })).toEqual({
|
||||
operation: "write",
|
||||
target: target.canonical,
|
||||
resource: "removed.txt",
|
||||
existed: false,
|
||||
})
|
||||
expect(yield* Effect.promise(() => fs.readFile(targetPath, "utf8"))).toBe("after")
|
||||
}).pipe(provide(directory)),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("removes an existing internal file", () =>
|
||||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
const targetPath = path.join(directory, "remove.txt")
|
||||
yield* Effect.promise(() => fs.writeFile(targetPath, "remove"))
|
||||
const target = yield* (yield* LocationMutation.Service).resolve({ path: "remove.txt" })
|
||||
const result = yield* (yield* FileMutation.Service).remove({ target })
|
||||
|
||||
expect(result).toEqual({
|
||||
operation: "remove",
|
||||
target: target.canonical,
|
||||
resource: "remove.txt",
|
||||
existed: true,
|
||||
})
|
||||
expect(
|
||||
yield* Effect.promise(() =>
|
||||
fs.stat(targetPath).then(
|
||||
() => true,
|
||||
() => false,
|
||||
),
|
||||
),
|
||||
).toBe(false)
|
||||
}).pipe(provide(directory)),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("writes an explicitly resolved external target", () =>
|
||||
withTmp((directory) =>
|
||||
withTmp((outside) =>
|
||||
|
|
@ -171,49 +109,6 @@ describe("FileMutation", () => {
|
|||
),
|
||||
)
|
||||
|
||||
it.live("removes an explicitly resolved external target", () =>
|
||||
withTmp((directory) =>
|
||||
withTmp((outside) =>
|
||||
Effect.gen(function* () {
|
||||
const targetPath = path.join(outside, "external.txt")
|
||||
yield* Effect.promise(() => fs.writeFile(targetPath, "external"))
|
||||
const target = yield* (yield* LocationMutation.Service).resolve({ path: targetPath })
|
||||
const result = yield* (yield* FileMutation.Service).remove({ target })
|
||||
|
||||
expect(result).toEqual({
|
||||
operation: "remove",
|
||||
target: target.canonical,
|
||||
resource: target.resource,
|
||||
existed: true,
|
||||
})
|
||||
expect(
|
||||
yield* Effect.promise(() =>
|
||||
fs.stat(targetPath).then(
|
||||
() => true,
|
||||
() => false,
|
||||
),
|
||||
),
|
||||
).toBe(false)
|
||||
}).pipe(provide(directory)),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("reports a missing target as not removed without checking existence first", () =>
|
||||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
const target = yield* (yield* LocationMutation.Service).resolve({ path: "missing.txt" })
|
||||
|
||||
expect(yield* (yield* FileMutation.Service).remove({ target })).toEqual({
|
||||
operation: "remove",
|
||||
target: target.canonical,
|
||||
resource: "missing.txt",
|
||||
existed: false,
|
||||
})
|
||||
}).pipe(provide(directory)),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("serializes concurrent writes to the same canonical target", () =>
|
||||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
|
|
@ -257,63 +152,6 @@ describe("FileMutation", () => {
|
|||
),
|
||||
)
|
||||
|
||||
it.live("allows only one concurrent conditional write based on the same bytes", () =>
|
||||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
const targetPath = path.join(directory, "shared.txt")
|
||||
yield* Effect.promise(() => fs.writeFile(targetPath, "initial"))
|
||||
const firstStarted = yield* Deferred.make<void>()
|
||||
const releaseFirst = yield* Deferred.make<void>()
|
||||
let writes = 0
|
||||
const filesystem = instrumentWrites((write) =>
|
||||
Effect.gen(function* () {
|
||||
writes++
|
||||
if (writes === 1) {
|
||||
yield* Deferred.succeed(firstStarted, undefined)
|
||||
yield* Deferred.await(releaseFirst)
|
||||
}
|
||||
yield* write
|
||||
}),
|
||||
)
|
||||
|
||||
yield* Effect.gen(function* () {
|
||||
const mutation = yield* LocationMutation.Service
|
||||
const files = yield* FileMutation.Service
|
||||
const target = yield* mutation.resolve({ path: "shared.txt" })
|
||||
const expected = new TextEncoder().encode("initial")
|
||||
const first = yield* files.writeIfUnchanged({ target, expected, content: "first" }).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(firstStarted)
|
||||
const second = yield* files
|
||||
.writeIfUnchanged({ target, expected, content: "second" })
|
||||
.pipe(Effect.flip, Effect.forkChild)
|
||||
|
||||
yield* Deferred.succeed(releaseFirst, undefined)
|
||||
yield* Fiber.join(first)
|
||||
expect(yield* Fiber.join(second)).toMatchObject({ _tag: "FileMutation.StaleContentError" })
|
||||
expect(yield* Effect.promise(() => fs.readFile(targetPath, "utf8"))).toBe("first")
|
||||
expect(writes).toBe(1)
|
||||
}).pipe(provide(directory, filesystem))
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("rejects a conditional write when target content is already stale", () =>
|
||||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
const targetPath = path.join(directory, "stale.txt")
|
||||
yield* Effect.promise(() => fs.writeFile(targetPath, "current"))
|
||||
const target = yield* (yield* LocationMutation.Service).resolve({ path: "stale.txt" })
|
||||
|
||||
expect(
|
||||
yield* (yield* FileMutation.Service)
|
||||
.writeIfUnchanged({ target, expected: new TextEncoder().encode("older"), content: "replacement" })
|
||||
.pipe(Effect.flip),
|
||||
).toMatchObject({ _tag: "FileMutation.StaleContentError", path: target.canonical })
|
||||
expect(yield* Effect.promise(() => fs.readFile(targetPath, "utf8"))).toBe("current")
|
||||
}).pipe(provide(directory)),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("allows distinct canonical targets to proceed independently", () =>
|
||||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue