You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Browse filesBrowse the repository at this point in the historyBrowse files
agent
committed
docs: describe the shipped symbolic-link surface
The filesystem specification said symbolic links were an internal
primitive, that Workspace.fs exposed neither symlink nor readlink,
that there was no lstat, and that an existing file's mode could not be
changed. The shipped API contradicts all four: WorkspaceFilesystem
exposes symlink, readlink, lstat, and chmod, the stub mirrors them
across the Workers RPC boundary, and the Dynamic Worker filesystem
adapters rely on them for the node:fs behaviour a shell expects.
Removing the methods would be a breaking change and would leave those
adapters without a way to serve ln -s, readlink, or test -L, so the
document follows the code.
Each of the four methods gains a section with its return value and its
errors, the comparison with node:fs/promises maps them rather than
striking them out, and the note on symbolic links now states the two
rules that cover the surface: intermediate segments are always
followed, and a trailing link is followed by everything except lstat
and readlink. The rename and find entries added alongside are
documented in the same pass.
Closes#118.
Document the symbolic-link filesystem surface. `docs/04_filesystem_interface.md` claimed that symbolic links were internal and that `Workspace.fs` had no `symlink`, `readlink`, `lstat`, or `chmod`, none of which matched the shipped API. Those four methods now have sections of their own covering return values and the `ENOENT`, `EINVAL`, and `ELOOP` cases, the comparison with `node:fs/promises` maps them, and the specification explains that `stat` follows a trailing link while `lstat` reports the link itself.
@@ -321,12 +447,12 @@ so handlers from Node code port over directly.
321
447
| Code | When |
322
448
| --- | --- |
323
449
|`ENOENT`| Path does not exist and `force` is not true. Also raised by `stat` when a parent segment turns out to be a file. |
324
-
|`ENOTEMPTY`| Path is a non-empty directory and `recursive` is not true. |
325
-
|`ENOTDIR`| A parent path segment is a file (raised explicitly by `mkdir` and `writeFile`; `find` raises it when its `directory` argument is a file). |
326
-
|`EISDIR`| Expected a file, got a directory (e.g. `readFile` on a dir, `writeFile` on `/`). |
327
-
|`EEXIST`|`mkdir` without `recursive: true` on an existing path. |
328
-
|`EINVAL`| Invalid path or unsupported options. |
329
-
|`ELOOP`|Symlink traversal exceeded 40 hops. Thrown by the internal resolver when the `node:vfs` adapter wires up a cycle. |
450
+
|`ENOTEMPTY`| Path is a non-empty directory and `recursive` is not true. Also raised by `rename` when the destination directory has children. |
451
+
|`ENOTDIR`| A parent path segment is a file (raised explicitly by `mkdir` and `writeFile`; `find` raises it when its `directory` argument is a file; `rename` raises it when a directory would replace a non-directory). |
452
+
|`EISDIR`| Expected a file, got a directory (e.g. `readFile` on a dir, `writeFile` on `/`, `rename` of a file onto a directory). |
453
+
|`EEXIST`|`mkdir` without `recursive: true` on an existing path, or `symlink` onto an existing path. |
454
+
|`EINVAL`| Invalid path or unsupported options: `readlink` on something that is not a symbolic link, `rename` of the root or of a directory into itself. |
455
+
|`ELOOP`|Symbolic-link traversal exceeded 40 hops. Every path-walking method shares that budget, so a cycle surfaces from `stat`, `readFile`, `writeFile`, `mkdir`, and the rest alike. |
330
456
|`EPERM`| Operation is forbidden, e.g. deleting the workspace root. |
331
457
|`EIO`| Backing storage failed unexpectedly. |
332
458
|`EACCES`|*Reserved for future mount layer (see [06. Mount Interface](./06_mount_interface.md)).* No code path in `workspace-fs` currently throws it. |
@@ -379,28 +505,35 @@ maps to `Workspace.fs`:
379
505
|`rm`|`rm`|`{ recursive: true }` for non-empty dirs. |
|`stat` / `lstat`|`stat`| No `lstat`; `stat` follows symlinks. See note below. |
508
+
|`stat` / `lstat`|`stat`/ `lstat`|`stat` follows a trailing symbolic link; `lstat` reports the link. |
383
509
|`truncate`| — | Read, slice, write. |
384
-
|`chmod`|—|Pass `mode` to `writeFile` / `mkdir` at create time. There is no way to chmod an existing file without rewriting its bytes. |
510
+
|`chmod`|`chmod`|Mode masked to twelve bits; follows a trailing symbolic link. `mode` can also be passed to `writeFile` / `mkdir` at create time. |
385
511
|`chown`| — | No ownership model. |
386
512
|`utimes`| — |`mtime` is managed by the VFS. |
387
513
|`cp` / `copyFile`| — | Read + write. |
388
-
|`rename`|—|Read + write + delete. |
514
+
|`rename`|`rename`|One transaction; replaces a destination of the same kind. |
389
515
|`realpath`| — | Paths are already canonical. |
390
-
|`symlink` / `readlink`|— | Not on the public surface; see note below. |
516
+
|`symlink` / `readlink`|`symlink` / `readlink`| Same argument order as Node. Targets are stored verbatim and may dangle. |
391
517
|`watch`| — | Low-level primitive in `fs/watch.ts` (`createWatcher`, `createWatchAsyncIterable`, `WatchHandle`, `WatchOptions`); not exposed on the `WorkspaceFilesystem` class. |
392
518
|`open` / `FileHandle`| — | Use streams instead. |
393
-
|`glob`|`find`| Limited glob support (`*`, `**`, `**/`, and `?`). |
519
+
|`glob`|`find`| Limited glob support (`*`, `**`, `**/`, and `?`), plus `exclude` for pruning subtrees. |
394
520
| — |`grep`| Not in `node:fs`; literal by default, with optional regular expressions. |
395
521
| — |`find`| Recursive directory walk with an optional glob, relative-rooted. |
396
522
| — |`ls`| Flat list of file paths under a directory (segment-aware). |
397
523
398
-
### Note: symlinks
399
-
400
-
Symlinks exist as an **internal primitive** used by the `node:vfs`
401
-
adapter — the schema supports a `'symlink'` node type with a
402
-
`link_target`, and the resolver in `fs/resolve.ts` follows them with a
403
-
40-hop cap (throws `ELOOP` on overflow). They are **not** part of the
404
-
public `WorkspaceFilesystem` surface: there are no `fs.symlink` or
405
-
`fs.readlink` methods on `Workspace.fs`, and callers should treat all
406
-
visible paths as if they pointed straight at real files.
524
+
### Note: symbolic links
525
+
526
+
Symbolic links are part of the public surface. The schema carries a
527
+
`'symlink'` node type with a `link_target`, the resolver in
528
+
`fs/resolve.ts` follows them with a 40-hop cap (throws `ELOOP` on
529
+
overflow), and `Workspace.fs` exposes `symlink`, `readlink`, and
530
+
`lstat` on top of that. `WorkspaceFilesystemStub` mirrors all three
531
+
across the Workers RPC boundary, which is how the Dynamic Worker
532
+
filesystem adapters provide the `node:fs` behavior a shell expects from
533
+
`ln -s`, `readlink`, and `test -L`.
534
+
535
+
Two rules cover the whole surface. Intermediate segments are always
536
+
followed, so a link to a directory behaves like the directory for every
537
+
method, `mkdir` included. A trailing link is followed by everything
538
+
except `lstat` and `readlink`, which are the two methods whose purpose
0 commit comments