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
{{ message }}
Repository navigation
Commit 51a4454
Browse filesBrowse the repository at this point in the historyBrowse files
`mountx/webdav` serves an `FsDriver` over WebDAV: OPTIONS, HEAD, GET, PUT,
DELETE, MKCOL, COPY, MOVE, PROPFIND and PROPPATCH, in the transport shape the
rest of `src/` uses — constants, a pure protocol layer, a session that never
rejects, and a server that is the only file importing `node:http`. Like
`mountx/s3` it produces no mountpoint, so it stays outside `mountx/auto`.
Class 2 is absent and says so: no LOCK/UNLOCK, and the `DAV` header answers
`1, 3` rather than claiming a class whose methods would be 405 (invariant 5).
That costs a writable mount on macOS's `mount_webdav` and on the Windows
redirector, which is written down at the session's header and tracked in
`.agents/roadmap.md` rather than discovered later.
Two recursive operations refuse to follow a symbolic link, and both would be
destructive if they did: DELETE takes an `lstat` and recurses on the readdir
dirent, so it removes the link rather than emptying what it points at; COPY
reports a link to a collection with 403 instead of descending into one, since
a link back to an ancestor makes the walk revisit a subtree it is still
writing into.
`src/webdav/protocol.ts` imports `src/s3/xml.ts` — the second deliberate
cross-transport dependency after 9P -> fuse/flags.ts — rather than hardening a
second bounded XML parser. What that costs is namespaces, documented in full
at that file's header.
Tests: the wire on its own, the session in-process against the memory driver,
the server over real sockets, and an oracle running real rclone and curl,
gated on `command -v` so `pnpm test` stays green without them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .agents/architecture.md
+37-3Lines changed: 37 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -36,6 +36,7 @@ Deviations are noted per area below.
36
36
|`lock.ts`|`PathLock` — `RENAME` takes it, `READ`/`WRITE` run outside it |
37
37
|`subtree.ts`|`remapSubtree()` — the rename rewrite; internal, deliberately not in the public `path.ts`|
38
38
|`ownership.ts`| who a new entry belongs to: `inode_init_owner()`'s set-gid rule, plus the `lchown`/`chmod` that applies it. Internal; used by the two NFS sessions' `#claim`|
39
+
|`http.ts`| RFC 9110's `HTTP-date`, `Range` and `ETag` quoting — the one copy, shared by the two HTTP transports; `mountx/s3` re-exports every symbol under its own name |
39
40
|`auto.ts`|`mountx/auto` — probe, then FUSE → 9P → NFS, each behind `await import()`|
40
41
41
42
### Drivers (`src/drivers/`)
@@ -122,6 +123,25 @@ is no RFC; everything is transcribed from Amazon's docs and named where it is us
122
123
|`session.ts`| one request in, one reply out, streaming **both ways**. Derived ETags, multipart staged under a reserved prefix |
123
124
|`server.ts`| loopback-only without credentials; ordered drain on `close()`|
124
125
126
+
## WebDAV (`src/webdav/`, exported as `mountx/webdav`)
127
+
128
+
The other transport that is not a mount, and the one a kernel can mount anyway
129
+
without root or native code (`davfs2`, `mount_webdav`, the Windows redirector).
130
+
**RFC 4918 class 1** — every method but `LOCK`/`UNLOCK` — transcribed from the RFC,
131
+
with RFC 9110 for the HTTP it rides on and RFC 4331 for the quota pair.
|`constants.ts`| the errno → HTTP status table, typed **total** over `ErrnoCode` (the same shape as `s3/constants.ts`'s), the protocol's literals, and the `propstat` phrases |
136
+
|`protocol.ts`| pure: target ↔ `href` (decoded and encoded **per segment**), `Depth`/`Overwrite`/`Destination`, the two request grammars, `multistatus` and `error`|
137
+
|`session.ts`| method semantics over one driver. No handle table and no `PathLock` — HTTP carries no per-connection state, so a request resolves its own paths and is done |
138
+
|`server.ts`| the socket, and the only file here that imports `node:http`. Loopback-only without credentials; HTTP Basic with them |
139
+
140
+
The deliberate gaps, each recorded at its own definition: no locking (so the `DAV`
141
+
header says `1, 3`, never `1, 2, 3`), no dead properties (`PROPPATCH` answers `403
142
+
cannot-modify-protected-property` — a driver has nowhere to keep one), and no
143
+
conditional requests (they arrive with `If`, which exists to carry lock tokens).
144
+
125
145
## CLI (`src/cli/`, the `mountx` bin, `pnpm mountx` from source)
126
146
127
147
A demo and a test bench, not a mount tool: it mounts this package's own `README.md`
@@ -197,10 +217,24 @@ The facts no single file's header can own.
197
217
after a failure, and no probe when a transport is named. `p9ModuleRefusal` lives
198
218
there rather than in `9p/probe.ts` because it is a judgement call the no-fallback
199
219
rule forces, not a fact about the host.
200
-
-**`mountx/s3` is outside `auto` on purpose** — `auto`'s contract is a mountpoint,
201
-
and the gateway never produces one.
220
+
-**`mountx/s3` and `mountx/webdav` are outside `auto` on purpose** — `auto`'s
221
+
contract is a mountpoint, and neither serving transport produces one. WebDAV is
222
+
the one whose _client_ can produce one (`davfs2`, `mount_webdav`), which is a fact
223
+
about the host's tooling rather than something this package does.
224
+
-**`src/webdav/protocol.ts` imports `src/s3/xml.ts`.** The second deliberate
225
+
cross-transport dependency, after 9P→`fuse/flags.ts`, and the same argument: a
226
+
bounded XML encoder and a copying, DOCTYPE-refusing parser are facts about XML, not
227
+
about S3, and a second hardened parser is a second thing to get wrong. What it
228
+
costs is namespaces — the parser reports local names — which `webdav/protocol.ts`
229
+
documents in full at its header. `xml.ts` pulls in only `s3/constants.ts`, so
230
+
`mountx/webdav` does not load a signature or a chunked decoder.
231
+
-**`src/http.ts` is the HTTP the two HTTP transports share** — `formatHttpDate` /
232
+
`parseHttpDate`, `parseRange` and the `Content-Range`/`ETag` spellings, all
233
+
RFC 9110. It was `src/s3/protocol.ts`'s until WebDAV needed the same three;
234
+
`s3/protocol.ts` re-exports every symbol under its old name, so `mountx/s3`'s
235
+
surface never moved.
202
236
-**Platforms.** FUSE is Linux; 9P is Linux and root-only; NFS is Linux (root) and
203
-
macOS (no root, behind a consent gate); S3 is anywhere. macOS gets NFS by necessity
237
+
macOS (no root, behind a consent gate); S3 and WebDAV are anywhere. macOS gets NFS by necessity
204
238
— macFUSE is a third-party kext with its own dialect, so `src/fuse/` cannot serve
205
239
it.
206
240
-**`memory.ts` is the only driver with `mountx.mknod`**, which is what keeps the
Copy file name to clipboardExpand all lines: README.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -73,6 +73,7 @@ npx mountx
73
73
-[Troubleshooting](https://mountx.vercel.app/guide/troubleshooting) — the things that will bite you, and how to recover.
74
74
-[Transports](https://mountx.vercel.app/transports) — FUSE, 9P and NFS, what each costs, and how to pin one.
75
75
-[S3 Gateway](https://mountx.vercel.app/transports/s3) — serve a driver as an S3-compatible bucket for `rclone`, the AWS CLI or an SDK, no mount involved.
76
+
-[WebDAV](https://mountx.vercel.app/transports/webdav) — serve a driver over RFC 4918 to `rclone`, `curl` or `davfs2`, no mount involved either.
76
77
-[Reference](https://mountx.vercel.app/reference) — every entry point, option and type.
0 commit comments