Skip to content

Commit a00f02f

Browse files
kitlangtonopencode
authored andcommitted
refactor: unwrap FileTime namespace + self-reexport (#22940)
1 parent af2c647 commit a00f02f

1 file changed

Lines changed: 104 additions & 104 deletions

File tree

packages/opencode/src/file/time.ts

Lines changed: 104 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -5,109 +5,109 @@ import { Flag } from "@/flag/flag"
55
import type { SessionID } from "@/session/schema"
66
import { Log } from "../util"
77

8-
export namespace FileTime {
9-
const log = Log.create({ service: "file.time" })
10-
11-
export type Stamp = {
12-
readonly read: Date
13-
readonly mtime: number | undefined
14-
readonly size: number | undefined
15-
}
16-
17-
const session = (reads: Map<SessionID, Map<string, Stamp>>, sessionID: SessionID) => {
18-
const value = reads.get(sessionID)
19-
if (value) return value
20-
21-
const next = new Map<string, Stamp>()
22-
reads.set(sessionID, next)
23-
return next
24-
}
25-
26-
interface State {
27-
reads: Map<SessionID, Map<string, Stamp>>
28-
locks: Map<string, Semaphore.Semaphore>
29-
}
30-
31-
export interface Interface {
32-
readonly read: (sessionID: SessionID, file: string) => Effect.Effect<void>
33-
readonly get: (sessionID: SessionID, file: string) => Effect.Effect<Date | undefined>
34-
readonly assert: (sessionID: SessionID, filepath: string) => Effect.Effect<void>
35-
readonly withLock: <T>(filepath: string, fn: () => Effect.Effect<T>) => Effect.Effect<T>
36-
}
37-
38-
export class Service extends Context.Service<Service, Interface>()("@opencode/FileTime") {}
39-
40-
export const layer = Layer.effect(
41-
Service,
42-
Effect.gen(function* () {
43-
const fsys = yield* AppFileSystem.Service
44-
const disableCheck = yield* Flag.OPENCODE_DISABLE_FILETIME_CHECK
45-
46-
const stamp = Effect.fnUntraced(function* (file: string) {
47-
const info = yield* fsys.stat(file).pipe(Effect.catch(() => Effect.void))
48-
return {
49-
read: yield* DateTime.nowAsDate,
50-
mtime: info ? Option.getOrUndefined(info.mtime)?.getTime() : undefined,
51-
size: info ? Number(info.size) : undefined,
52-
}
53-
})
54-
const state = yield* InstanceState.make<State>(
55-
Effect.fn("FileTime.state")(() =>
56-
Effect.succeed({
57-
reads: new Map<SessionID, Map<string, Stamp>>(),
58-
locks: new Map<string, Semaphore.Semaphore>(),
59-
}),
60-
),
61-
)
8+
const log = Log.create({ service: "file.time" })
9+
10+
export type Stamp = {
11+
readonly read: Date
12+
readonly mtime: number | undefined
13+
readonly size: number | undefined
14+
}
15+
16+
const session = (reads: Map<SessionID, Map<string, Stamp>>, sessionID: SessionID) => {
17+
const value = reads.get(sessionID)
18+
if (value) return value
19+
20+
const next = new Map<string, Stamp>()
21+
reads.set(sessionID, next)
22+
return next
23+
}
24+
25+
interface State {
26+
reads: Map<SessionID, Map<string, Stamp>>
27+
locks: Map<string, Semaphore.Semaphore>
28+
}
6229

63-
const getLock = Effect.fn("FileTime.lock")(function* (filepath: string) {
64-
filepath = AppFileSystem.normalizePath(filepath)
65-
const locks = (yield* InstanceState.get(state)).locks
66-
const lock = locks.get(filepath)
67-
if (lock) return lock
68-
69-
const next = Semaphore.makeUnsafe(1)
70-
locks.set(filepath, next)
71-
return next
72-
})
73-
74-
const read = Effect.fn("FileTime.read")(function* (sessionID: SessionID, file: string) {
75-
file = AppFileSystem.normalizePath(file)
76-
const reads = (yield* InstanceState.get(state)).reads
77-
log.info("read", { sessionID, file })
78-
session(reads, sessionID).set(file, yield* stamp(file))
79-
})
80-
81-
const get = Effect.fn("FileTime.get")(function* (sessionID: SessionID, file: string) {
82-
file = AppFileSystem.normalizePath(file)
83-
const reads = (yield* InstanceState.get(state)).reads
84-
return reads.get(sessionID)?.get(file)?.read
85-
})
86-
87-
const assert = Effect.fn("FileTime.assert")(function* (sessionID: SessionID, filepath: string) {
88-
if (disableCheck) return
89-
filepath = AppFileSystem.normalizePath(filepath)
90-
91-
const reads = (yield* InstanceState.get(state)).reads
92-
const time = reads.get(sessionID)?.get(filepath)
93-
if (!time) throw new Error(`You must read file ${filepath} before overwriting it. Use the Read tool first`)
94-
95-
const next = yield* stamp(filepath)
96-
const changed = next.mtime !== time.mtime || next.size !== time.size
97-
if (!changed) return
98-
99-
throw new Error(
100-
`File ${filepath} has been modified since it was last read.\nLast modification: ${new Date(next.mtime ?? next.read.getTime()).toISOString()}\nLast read: ${time.read.toISOString()}\n\nPlease read the file again before modifying it.`,
101-
)
102-
})
103-
104-
const withLock = Effect.fn("FileTime.withLock")(function* <T>(filepath: string, fn: () => Effect.Effect<T>) {
105-
return yield* fn().pipe((yield* getLock(filepath)).withPermits(1))
106-
})
107-
108-
return Service.of({ read, get, assert, withLock })
109-
}),
110-
).pipe(Layer.orDie)
111-
112-
export const defaultLayer = layer.pipe(Layer.provide(AppFileSystem.defaultLayer))
30+
export interface Interface {
31+
readonly read: (sessionID: SessionID, file: string) => Effect.Effect<void>
32+
readonly get: (sessionID: SessionID, file: string) => Effect.Effect<Date | undefined>
33+
readonly assert: (sessionID: SessionID, filepath: string) => Effect.Effect<void>
34+
readonly withLock: <T>(filepath: string, fn: () => Effect.Effect<T>) => Effect.Effect<T>
11335
}
36+
37+
export class Service extends Context.Service<Service, Interface>()("@opencode/FileTime") {}
38+
39+
export const layer = Layer.effect(
40+
Service,
41+
Effect.gen(function* () {
42+
const fsys = yield* AppFileSystem.Service
43+
const disableCheck = yield* Flag.OPENCODE_DISABLE_FILETIME_CHECK
44+
45+
const stamp = Effect.fnUntraced(function* (file: string) {
46+
const info = yield* fsys.stat(file).pipe(Effect.catch(() => Effect.void))
47+
return {
48+
read: yield* DateTime.nowAsDate,
49+
mtime: info ? Option.getOrUndefined(info.mtime)?.getTime() : undefined,
50+
size: info ? Number(info.size) : undefined,
51+
}
52+
})
53+
const state = yield* InstanceState.make<State>(
54+
Effect.fn("FileTime.state")(() =>
55+
Effect.succeed({
56+
reads: new Map<SessionID, Map<string, Stamp>>(),
57+
locks: new Map<string, Semaphore.Semaphore>(),
58+
}),
59+
),
60+
)
61+
62+
const getLock = Effect.fn("FileTime.lock")(function* (filepath: string) {
63+
filepath = AppFileSystem.normalizePath(filepath)
64+
const locks = (yield* InstanceState.get(state)).locks
65+
const lock = locks.get(filepath)
66+
if (lock) return lock
67+
68+
const next = Semaphore.makeUnsafe(1)
69+
locks.set(filepath, next)
70+
return next
71+
})
72+
73+
const read = Effect.fn("FileTime.read")(function* (sessionID: SessionID, file: string) {
74+
file = AppFileSystem.normalizePath(file)
75+
const reads = (yield* InstanceState.get(state)).reads
76+
log.info("read", { sessionID, file })
77+
session(reads, sessionID).set(file, yield* stamp(file))
78+
})
79+
80+
const get = Effect.fn("FileTime.get")(function* (sessionID: SessionID, file: string) {
81+
file = AppFileSystem.normalizePath(file)
82+
const reads = (yield* InstanceState.get(state)).reads
83+
return reads.get(sessionID)?.get(file)?.read
84+
})
85+
86+
const assert = Effect.fn("FileTime.assert")(function* (sessionID: SessionID, filepath: string) {
87+
if (disableCheck) return
88+
filepath = AppFileSystem.normalizePath(filepath)
89+
90+
const reads = (yield* InstanceState.get(state)).reads
91+
const time = reads.get(sessionID)?.get(filepath)
92+
if (!time) throw new Error(`You must read file ${filepath} before overwriting it. Use the Read tool first`)
93+
94+
const next = yield* stamp(filepath)
95+
const changed = next.mtime !== time.mtime || next.size !== time.size
96+
if (!changed) return
97+
98+
throw new Error(
99+
`File ${filepath} has been modified since it was last read.\nLast modification: ${new Date(next.mtime ?? next.read.getTime()).toISOString()}\nLast read: ${time.read.toISOString()}\n\nPlease read the file again before modifying it.`,
100+
)
101+
})
102+
103+
const withLock = Effect.fn("FileTime.withLock")(function* <T>(filepath: string, fn: () => Effect.Effect<T>) {
104+
return yield* fn().pipe((yield* getLock(filepath)).withPermits(1))
105+
})
106+
107+
return Service.of({ read, get, assert, withLock })
108+
}),
109+
).pipe(Layer.orDie)
110+
111+
export const defaultLayer = layer.pipe(Layer.provide(AppFileSystem.defaultLayer))
112+
113+
export * as FileTime from "./time"

0 commit comments

Comments
 (0)