@effect-vfs/core 0.7.0 → 0.8.0

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.
Files changed (61) hide show
  1. package/README.md +15 -238
  2. package/dist/LiveVolume.d.ts.map +1 -1
  3. package/dist/LiveVolume.js +9 -9
  4. package/dist/Search.d.ts +730 -0
  5. package/dist/Search.d.ts.map +1 -0
  6. package/dist/Search.js +568 -0
  7. package/dist/SnapshotDelta.d.ts +234 -0
  8. package/dist/SnapshotDelta.d.ts.map +1 -1
  9. package/dist/SnapshotDelta.js +107 -0
  10. package/dist/VirtualFileSystem.d.ts +53 -1
  11. package/dist/VirtualFileSystem.d.ts.map +1 -1
  12. package/dist/VirtualFileSystem.js +59 -1
  13. package/dist/index.d.ts +7 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +7 -0
  16. package/dist/internal/canonicalBase64.js +3 -3
  17. package/dist/internal/fixture.js +3 -3
  18. package/dist/internal/glob.d.ts +66 -0
  19. package/dist/internal/glob.d.ts.map +1 -0
  20. package/dist/internal/glob.js +248 -0
  21. package/dist/internal/image.d.ts +7 -1
  22. package/dist/internal/image.d.ts.map +1 -1
  23. package/dist/internal/image.js +9 -7
  24. package/dist/internal/liveImage.d.ts +1 -1
  25. package/dist/internal/liveImage.d.ts.map +1 -1
  26. package/dist/internal/liveImage.js +2 -10
  27. package/dist/internal/overlayDiff.js +2 -2
  28. package/dist/internal/path.d.ts.map +1 -1
  29. package/dist/internal/path.js +3 -3
  30. package/dist/internal/resolution.d.ts +135 -0
  31. package/dist/internal/resolution.d.ts.map +1 -0
  32. package/dist/internal/resolution.js +277 -0
  33. package/dist/internal/search.d.ts +24 -0
  34. package/dist/internal/search.d.ts.map +1 -0
  35. package/dist/internal/search.js +78 -0
  36. package/dist/internal/searchContent.d.ts +77 -0
  37. package/dist/internal/searchContent.d.ts.map +1 -0
  38. package/dist/internal/searchContent.js +223 -0
  39. package/dist/internal/searchModel.d.ts +83 -0
  40. package/dist/internal/searchModel.d.ts.map +1 -0
  41. package/dist/internal/searchModel.js +23 -0
  42. package/dist/internal/snapshotDelta.d.ts +470 -1
  43. package/dist/internal/snapshotDelta.d.ts.map +1 -1
  44. package/dist/internal/snapshotDelta.js +60 -26
  45. package/dist/internal/snapshotMerge.d.ts +54 -0
  46. package/dist/internal/snapshotMerge.d.ts.map +1 -0
  47. package/dist/internal/snapshotMerge.js +959 -0
  48. package/dist/internal/tokenRegistry.d.ts +77 -0
  49. package/dist/internal/tokenRegistry.d.ts.map +1 -0
  50. package/dist/internal/tokenRegistry.js +128 -0
  51. package/dist/internal/tree.js +2 -2
  52. package/dist/internal/virtualFileSystem.d.ts +32 -17
  53. package/dist/internal/virtualFileSystem.d.ts.map +1 -1
  54. package/dist/internal/virtualFileSystem.js +640 -993
  55. package/dist/internal/volumeLimits.d.ts +53 -0
  56. package/dist/internal/volumeLimits.d.ts.map +1 -0
  57. package/dist/internal/volumeLimits.js +63 -0
  58. package/dist/internal/volumeState.d.ts +2 -0
  59. package/dist/internal/volumeState.d.ts.map +1 -1
  60. package/dist/internal/volumeState.js +9 -0
  61. package/package.json +10 -6
package/README.md CHANGED
@@ -16,29 +16,17 @@ for the exact permission, path, timestamp, quota, and atomicity rules.
16
16
  npm install @effect-vfs/core@latest
17
17
  ```
18
18
 
19
- The package declares its exact Effect version as a peer dependency. Volume constructors and snapshot delta functions require Effect’s `Crypto` service. On Node, install the matching
19
+ The package declares its exact Effect version as a peer dependency. Volume constructors and snapshot delta functions require Effect's `Crypto` service. On Node, install the matching
20
20
  provider and supply `NodeCrypto.layer` when running these effects:
21
21
 
22
22
  ```sh
23
23
  npm install "@effect/platform-node-shared@$(npm view @effect-vfs/core peerDependencies.effect)"
24
24
  ```
25
25
 
26
- ## The mental model
27
-
28
- The API has three levels:
29
-
30
- - A `Volume` owns one isolated namespace and its file contents.
31
- - A `Caller` accesses that volume with its own identity, umask, and current directory.
32
- - File and directory handles are scoped capabilities. Effect closes them when their scope ends.
33
-
34
- Callers created from the same volume see the same files. A new volume starts with independent state. This separation
35
- lets you model access by several users without reaching for process globals or the host filesystem.
36
-
37
26
  ## Create an isolated workspace
38
27
 
39
- This example creates a bounded workspace, gives an unprivileged caller access to one directory, and uses a scoped file
40
- handle to read the result. It demonstrates the main benefit of the core API: storage, credentials, limits, and resource
41
- lifetime are explicit values that can be composed in one Effect program.
28
+ A volume owns the files. A caller supplies identity, permissions, and a working directory. Scoped handles close when
29
+ their Effect scope ends. This example gives an unprivileged caller access to one directory in a bounded volume.
42
30
 
43
31
  ```ts
44
32
  import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
@@ -89,230 +77,19 @@ callers default to umask `0o022`; the developer's `0o027` mask turns the request
89
77
  The root package exports `VirtualFileSystem` as a namespace. The equivalent direct module import is
90
78
  `import * as Vfs from "@effect-vfs/core/VirtualFileSystem"`.
91
79
 
92
- ## Preserve path bytes exactly
93
-
94
- JavaScript strings cannot represent every filename allowed by a byte-oriented filesystem. `BytePath` keeps arbitrary
95
- non-NUL path bytes intact. This matters when reproducing archives, protocol fixtures, or Unix directory trees that
96
- contain names which are not valid UTF-8.
97
-
98
- ```ts
99
- import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
100
- import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
101
- import { Effect } from "effect"
102
-
103
- const program = Effect.gen(function*() {
104
- const volume = yield* Vfs.make()
105
- const fs = yield* volume.caller()
106
-
107
- // Absolute path whose final component is the single byte 0xff.
108
- const opaquePath = yield* Vfs.pathFromBytes(new Uint8Array([0x2f, 0xff]))
109
- yield* fs.writeFile(opaquePath, new Uint8Array([1, 2, 3]), {
110
- access: "write",
111
- create: "exclusive"
112
- })
113
-
114
- const listing = yield* fs.readDirectory("/")
115
- const roundTrip = yield* Vfs.pathToBytes(opaquePath)
116
- return { names: listing.value.map((entry) => Array.from(entry.name)), roundTrip: Array.from(roundTrip) }
117
- })
118
-
119
- console.log(await Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))))
120
- // { names: [[255]], roundTrip: [47, 255] }
121
- ```
122
-
123
- The constructors and byte-returning operations copy their buffers, so later mutation cannot change stored paths.
124
- Directory listings, symbolic-link targets, and resolved paths come back as bytes; decoding them to text is the caller's
125
- choice, and a strict decoder reports a name that is not valid UTF-8.
126
-
127
- ## Build fixtures and restore snapshots
128
-
129
- Fixtures make tests deterministic without a setup sequence. Snapshots let you capture that prepared state, serialize
130
- it, and create independent workspaces from the same image. This is useful for test isolation, preview environments, and
131
- resettable sandboxes.
132
-
133
- ```ts
134
- import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
135
- import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
136
- import { Effect } from "effect"
137
- import * as ByteSize from "effect/ByteSize"
138
-
139
- const decodeLimits = {
140
- maxEncodedBytes: ByteSize.megabytes(1),
141
- maxRecords: 1_000,
142
- maxEntries: 1_000,
143
- maxDecodedBytes: ByteSize.megabytes(1)
144
- }
145
-
146
- const program = Effect.gen(function*() {
147
- const template = yield* Vfs.fromFixture({
148
- entries: [
149
- { kind: "directory", path: "/project" },
150
- {
151
- kind: "file",
152
- path: "/project/settings.json",
153
- bytes: new TextEncoder().encode("{\"theme\":\"dark\"}")
154
- }
155
- ]
156
- })
157
-
158
- const encoded = yield* Vfs.encodeSnapshot(yield* template.snapshot)
159
- const snapshot = yield* Vfs.decodeSnapshot(encoded, decodeLimits)
160
- const workspaceA = yield* Vfs.fromSnapshot(snapshot)
161
- const workspaceB = yield* Vfs.fromSnapshot(snapshot)
162
- const a = yield* workspaceA.caller()
163
- const b = yield* workspaceB.caller()
164
-
165
- yield* a.writeFile("/project/settings.json", new TextEncoder().encode("{\"theme\":\"light\"}"), {
166
- access: "write",
167
- truncate: true
168
- })
169
-
170
- return new TextDecoder().decode(yield* b.readFile("/project/settings.json"))
171
- })
172
-
173
- console.log(await Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer)))) // {"theme":"dark"}
174
- ```
175
-
176
- Fixture paths must be absolute and unique, and parent directories must be listed explicitly. Fixtures can also contain
177
- symbolic links, metadata, and forward hard links.
178
-
179
- Snapshot decoding requires explicit work limits because encoded bytes may come from an untrusted source. A snapshot
180
- contains the reachable namespace and metadata. It excludes callers, open handles, cursor positions, watch
181
- subscriptions, and unlinked content. Each restored volume is independent.
182
-
183
- ## Overlays and snapshot deltas
184
-
185
- An overlay creates a writable workspace from an immutable snapshot. Untouched file bytes remain shared until a
186
- workspace edits them. See the [overlay guide](../../apps/docs/app/content/guides/overlay-filesystems.mdx) for the
187
- workflow and conflict rules.
188
-
189
- Snapshot deltas encode exact changes between compatible images. They have separate decode and application limits;
190
- check those limits before accepting an untrusted delta. See the
191
- [snapshot delta API](../../apps/docs/app/content/api/core/snapshot-delta.mdx) for operations and defaults.
192
-
193
- ## Use scoped handles for incremental I/O
194
-
195
- Whole-file operations are convenient, but handles give each open file an independent `bigint` cursor and support
196
- incremental reads, positional I/O, seeking, and truncation. `Effect.scoped` guarantees cleanup on success, failure, or
197
- interruption.
198
-
199
- ```ts
200
- import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
201
- import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
202
- import { Effect } from "effect"
203
-
204
- const program = Effect.scoped(Effect.gen(function*() {
205
- const fs = yield* (yield* Vfs.make()).caller()
206
- yield* fs.writeFile("/events.log", new TextEncoder().encode("one\ntwo\n"), {
207
- access: "write",
208
- create: "exclusive"
209
- })
210
-
211
- const file = yield* fs.open("/events.log", { access: "read" })
212
- const first = yield* file.read(4) // Advances this handle's cursor.
213
- const second = yield* file.read(4)
214
- const preview = yield* file.pread(3, 0n) // Does not move the cursor.
215
-
216
- const decode = (bytes: Uint8Array) => new TextDecoder().decode(bytes)
217
-
218
- // A positioned read also says whether it reached the end of the file.
219
- return [decode(first), decode(second), decode(preview.bytes), preview.eof]
220
- }))
221
-
222
- console.log(await Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer)))) // ["one\n", "two\n", "one", false]
223
- ```
224
-
225
- Handles also expose an explicit `close` effect when early release matters. Calling explicit close twice fails, while
226
- scope cleanup remains safe after an explicit close.
227
-
228
- ## Handle expected failures as data
229
-
230
- Every failure is a `VfsError` with a stable `code` and the `operation` that failed. A rejected option names its
231
- `field`, a path-addressed failure names its `path` as bytes, and a classified failure keeps its `cause`. Codec and
232
- store failures share the class with their own codes. Interruption and defects remain separate from these expected
233
- failures.
234
-
235
- ```ts
236
- import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
237
- import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
238
- import { Effect } from "effect"
239
-
240
- const program = Effect.gen(function*() {
241
- const fs = yield* (yield* Vfs.make()).caller()
242
-
243
- return yield* fs.readFile("/optional.json").pipe(
244
- Effect.catchTag("VfsError", (error) =>
245
- error.code === "NotFound"
246
- ? Effect.succeed(new TextEncoder().encode("{}"))
247
- : Effect.fail(error))
248
- )
249
- })
250
-
251
- const bytes = await Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer)))
252
- console.log(new TextDecoder().decode(bytes)) // {}
253
- ```
254
-
255
- ## Provide a volume and a caller as Effect services
256
-
257
- `Volume` and `Caller` are services with static layers. `Volume.layer()` builds a fresh empty volume,
258
- `Volume.layerFromSnapshot`, `Volume.layerFromFixture` and `Volume.layerOverlay` build one from existing state, and
259
- `Volume.layerLive` opens a durable one through the `LiveImageStore` in context. `Caller.layer(options)` makes a root
260
- caller on the volume in context. A caller supplied through the service keeps its original volume, identity, umask and
261
- working directory.
262
-
263
- ```ts
264
- import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
265
- import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
266
- import { Effect } from "effect"
267
-
268
- const loadConfig = Effect.gen(function*() {
269
- const fs = yield* Vfs.Caller
270
- return yield* fs.readFile("/app/config.json")
271
- })
272
-
273
- const program = Effect.gen(function*() {
274
- const fs = yield* Vfs.Caller
275
- yield* fs.mkdir("/app")
276
- yield* fs.writeFile("/app/config.json", new TextEncoder().encode("{}"), {
277
- access: "write",
278
- create: "exclusive"
279
- })
280
- return yield* loadConfig
281
- })
282
-
283
- const bytes = await Effect.runPromise(
284
- program.pipe(Effect.provide(Vfs.Caller.layer()), Effect.provide(Vfs.Volume.layer()), Effect.provide(NodeCrypto.layer))
285
- )
286
- console.log(new TextDecoder().decode(bytes)) // {}
287
- ```
288
-
289
- ## When to use core or memory
290
-
291
- Choose `@effect-vfs/core` when you need direct access to volumes, callers, credentials, byte paths, quotas, watches,
292
- fixtures, or snapshots. Choose `@effect-vfs/memory` when existing code expects Effect's `FileSystem` service and you
293
- want an in-memory implementation. The memory adapter is built on this core, so you can create a core volume and bind
294
- the adapter to it when you need both interfaces.
80
+ ## Guides and reference
295
81
 
296
- ## Compatibility and behavioral limits
82
+ - [Fixtures and snapshots](../../apps/docs/app/content/guides/fixtures-and-snapshots.mdx) covers seeded trees, codecs, and portable deltas.
83
+ - [Overlay workspaces](../../apps/docs/app/content/guides/overlay-filesystems.mdx) covers independent edits and `mergeSnapshotDeltas` conflicts.
84
+ - [Snapshot search](../../apps/docs/app/content/guides/snapshot-search.mdx) covers filename globs, content matches, and bounded results.
85
+ - [Filesystem model](../../apps/docs/app/content/concepts/filesystem-model.mdx) explains callers, handles, permissions, and byte paths.
86
+ - [API reference](../../apps/docs/app/content/api/core/virtual-file-system.mdx) lists constructors, services, and operations.
297
87
 
298
- This package is experimental. Its public API may change between minor releases while Effect v4 remains a release
299
- candidate.
88
+ ## Limits
300
89
 
301
- - Components are limited to 255 bytes. Symbolic-link traversal is limited to 40 links.
302
- - Omitted logical quotas are unbounded by configuration, apart from fixed file and component bounds.
303
- - Absolute paths ignore a supplied directory base. Relative paths can use a live, same-volume directory handle.
304
- - Operations coordinate through one permit per volume. At most 65 operations are admitted at once by
305
- default, including the active operation; set `maxPendingOperations` to change the waiting budget.
306
- Excess work fails with retryable `VfsError` code `VolumeBusy`
307
- before mutation or storage commit. Interruption while waiting makes no change; interruption after a commit
308
- does not roll it back.
309
- - Each watch subscriber retains at most 256 events by default; set `maxWatchEvents` to change this bound
310
- (minimum 2). A subscriber that loses events receives `Rescan` with path `/` after its buffered events.
311
- Rescan the volume, then continue consuming the watch; repeat the scan if another `Rescan` arrives.
312
- Delivered events retain their order. The memory `FileSystem.watch` stream fails with a platform error
313
- identified by `MemoryFileSystem.isWatchOverflow`. Open a new watch before rescanning its path so changes
314
- during the scan remain observable.
315
- - `sync` checks handle liveness. An in-memory volume provides no host or crash durability.
90
+ The package is experimental. Public APIs may change between minor releases.
316
91
 
317
- For the complete contract, read the
318
- [implemented profile](https://github.com/lloydrichards/effect-virtual-fs/blob/main/.okf/profiles/implemented-filesystem.md).
92
+ Paths preserve arbitrary non-NUL bytes. Components are limited to 255 bytes, and symlink traversal to 40 links.
93
+ Volume options bound entries, file sizes, pending operations, and watch queues. In-memory volumes provide no host or
94
+ crash durability. See [compatibility and limits](../../apps/docs/app/content/reference/compatibility-and-limits.mdx)
95
+ for the complete contract.
@@ -1 +1 @@
1
- {"version":3,"file":"LiveVolume.d.ts","sourceRoot":"","sources":["../src/LiveVolume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACxC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAA;AAG1C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjF,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAErF;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAA;;IAW9D,4EAA4E;0BACtD,gBAAgB;2BACf,CAAC,OAAO,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,GAAG,eAAe,CAAC;qBACxF,CAAC,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC;;AAZtE;;;;;;;GAOG;AACH,qBAAa,cAAe,SAAQ,mBAKG;CAAG;AAE1C;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,QAAQ,CAAA;IACzC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAC/B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CACpD,MAAM,EACN,QAAQ,EACR,cAAc,GAAG,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAuC5C,CAAA;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CACvC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,CAAC,OAAO,CAAC,EAAE,aAAa,KAAK,MAAM,CAAC,MAAM,CACxE,UAAU,EACV,QAAQ,EACR,MAAM,CAAC,MAAM,CAKZ,CAAA;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,EAAE,CACtB,KAAK,EAAE,UAAU,EACjB,aAAa,EAAE,QAAQ,CAAC,QAAQ,EAChC,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,EAC3D,UAAU,CAAC,EAAE,gBAAgB,KAC1B,MAAM,CAAC,MAAM,CAChB,YAAY,EACZ,QAAQ,EACR,MAAM,CAAC,MAAM,CACU,CAAA"}
1
+ {"version":3,"file":"LiveVolume.d.ts","sourceRoot":"","sources":["../src/LiveVolume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACxC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAA;AAI1C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjF,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAErF;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAA;;IAW9D,4EAA4E;0BACtD,gBAAgB;2BACf,CAAC,OAAO,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,GAAG,eAAe,CAAC;qBACxF,CAAC,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC;;AAZtE;;;;;;;GAOG;AACH,qBAAa,cAAe,SAAQ,mBAKG;CAAG;AAE1C;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,QAAQ,CAAA;IACzC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAC/B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CACpD,MAAM,EACN,QAAQ,EACR,cAAc,GAAG,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CA4C5C,CAAA;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CACvC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,CAAC,OAAO,CAAC,EAAE,aAAa,KAAK,MAAM,CAAC,MAAM,CACxE,UAAU,EACV,QAAQ,EACR,MAAM,CAAC,MAAM,CAKZ,CAAA;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,EAAE,CACtB,KAAK,EAAE,UAAU,EACjB,aAAa,EAAE,QAAQ,CAAC,QAAQ,EAChC,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,EAC3D,UAAU,CAAC,EAAE,gBAAgB,KAC1B,MAAM,CAAC,MAAM,CAChB,YAAY,EACZ,QAAQ,EACR,MAAM,CAAC,MAAM,CACU,CAAA"}
@@ -12,6 +12,7 @@ import { Context, Effect } from "effect";
12
12
  import * as ByteSize from "effect/ByteSize";
13
13
  import { argumentFailure, retargetFailure } from "./internal/errors.js";
14
14
  import * as Model from "./internal/virtualFileSystem.js";
15
+ import * as Limits from "./internal/volumeLimits.js";
15
16
  import { VfsError } from "./VfsError.js";
16
17
  /**
17
18
  * One exclusively owned store. Its Layer holds the storage resource until the
@@ -33,7 +34,11 @@ export class LiveImageStore extends Context.Service()("@effect-vfs/core/LiveImag
33
34
  */
34
35
  export const open = Effect.fn("LiveVolume.open")(function* (options) {
35
36
  const store = yield* LiveImageStore;
36
- const initial = yield* prepareEmptyImage(options.volume).pipe(
37
+ const { decoded, initial } = yield* Effect.gen(function* () {
38
+ const decoded = yield* Effect.fromResult(Limits.configuration(options.volume, "LiveVolume.open"));
39
+ const initial = yield* prepareEmptyImage(decoded.options);
40
+ return { decoded, initial };
41
+ }).pipe(
37
42
  // Names the nested option that failed, such as `volume.maxEntries`.
38
43
  Effect.mapError((cause) => {
39
44
  const field = "field" in cause ? cause.field : undefined;
@@ -43,16 +48,11 @@ export const open = Effect.fn("LiveVolume.open")(function* (options) {
43
48
  return yield* argumentFailure("LiveVolume.open", "maxImageBytes");
44
49
  }
45
50
  const image = yield* store.loadOrCreate(initial);
46
- const session = yield* openImage(image, options.maxImageBytes, store.commit, store.durability ?? "memory-only").pipe(Effect.mapError((cause) => new VfsError({ code: "CorruptStore", operation: "LiveVolume.open", cause })));
51
+ const requested = decoded.limits;
52
+ const session = yield* Model.openImageVolume(image, options.maxImageBytes, store.commit, store.durability ?? "memory-only", decoded.options).pipe(Effect.mapError((cause) => new VfsError({ code: "CorruptStore", operation: "LiveVolume.open", cause })));
47
53
  yield* Effect.addFinalizer(() => session.shutdown);
48
54
  const volume = session.volume;
49
- if (volume.limits.maxEntries !== options.volume.maxEntries || volume.limits.maxBytes === undefined ||
50
- volume.limits.maxPathBytes === undefined || options.volume.maxBytes === undefined ||
51
- options.volume.maxFileBytes === undefined || options.volume.maxPathBytes === undefined ||
52
- ByteSize.toBigInt(volume.limits.maxBytes) !== ByteSize.toBigInt(options.volume.maxBytes) ||
53
- ByteSize.toBigInt(volume.limits.maxFileBytes) !== ByteSize.toBigInt(options.volume.maxFileBytes) ||
54
- ByteSize.toBigInt(volume.limits.maxPathBytes) !== ByteSize.toBigInt(options.volume.maxPathBytes) ||
55
- (options.volume.identity !== undefined && options.volume.identity !== volume.identity)) {
55
+ if (!Limits.compatible(volume.limits, requested, volume.identity, decoded.identity)) {
56
56
  return yield* new VfsError({ code: "IncompatibleStore", operation: "LiveVolume.open" });
57
57
  }
58
58
  return volume;