Three Kinds of Data, One Server
Delta serves very different things. On every Mac it serves the account's home folder, a plain tree of files. On the workbench it serves an Eagle image library, where the address a caller asks for is an item id rather than a path, and the answer is whichever image inside that item is the original or the thumbnail. On the service host it serves the soul vault's own store, which has no folder a caller may walk at all: notes come out of a database-backed store, as committed bytes, and a write is something the store must carry out itself.
The obvious way to build that is a server full of branches: if this is the home share, open the path; if it is Eagle, parse the item id; if it is the vault, ask the store. Every new kind of data would edit the server, and every edit would risk the kinds already there. Delta does the opposite. Its server speaks to exactly one class, DataShare, and never asks which child it is holding. The module says it in its own words: a child overrides what its data needs; the server never special-cases a child.
What Every Share Owns
The root class holds the parts every share has, whatever it serves:
- A name and an owner.
nameis checked against a short alphabet (letters, digits,_,-,.);appis the repository that declared it. A share Delta builds itself has the bare name (home); an app's share is<app>/<name>, such ascwkLoomis/eagle. That pair is the share'sid, and it is the first part of every address a caller sends. - Who may.
readersandwritersare policies over Macs, andappsis the list of app names allowed to ask. Lesson 5 takes them apart. - Whether reads are cached on the receiving Mac (
cache), a humandescription, freeoptionsa child reads from its own declaration, andrepo_root, the folder of the app that shipped the child.
The Vocabulary
Then comes the vocabulary the server speaks. Each method has a default in the root, and the defaults are the point:
| Method | What it answers | Root default |
|---|---|---|
stat(rel) | one path's metadata | NotFound |
file(rel, params) | a regular file to serve, with its real path set | NotFound |
listdir(rel, recursive) | the entries under a path | NotFound |
validator(op, params) | a cheap ETag for an api answer | None (no shortcut) |
api(op, params) | a read-only operation that returns JSON | NotFound |
derive(entry, kind, params) | a recipe for a derived file | None (not derivable) |
write_path, mkdir, symlink, remove, keep | file writes | Forbidden |
call(op, params, body, …) | a write the share's owner carries out itself | Forbidden |
writable() | whether any write is possible | False |
Read the right-hand column again. A child that overrides nothing serves nothing and accepts nothing. Forgetting a method never opens a door; it closes one. That is what fail closed looks like when it is written into a type rather than remembered by a programmer.
Two Ways to Say No to a Write
The root even words its refusal by what the child is. A share that is not writable answers <id> is read-only. A share that IS writable but whose only write is the owner's call (the soul vault store is exactly that) answers <id> takes no file writes: its owner writes through a call. Both are Forbidden, and both travel to the caller as a named cause rather than a bare 403, so the person reading the error learns which door exists instead of guessing.
Entry: the One Shape Everything Returns
Every read method returns an Entry: a share-relative path, a kind (file, dir, link, other for anything else such as a socket or a FIFO, or unreadable for a folder that would not open), size, modification time in nanoseconds, mode, inode, device, a link's stored target, and an optional display name. Its etag is built from three of those numbers, size, mtime and inode, written in hexadecimal: "<size>-<mtime_ns>-<ino>". Change the file and at least one of them moves. The entry also carries path, the real file on disk, but only on the serving side: document(), the form that goes over the wire, leaves it out. A caller learns what a file is, never where it lives.
Why the Server Stays Small
When Loomis needed Eagle items served, nothing in Delta's server changed. Loomis shipped a child, EagleLibraryShare, that overrides file, stat, validator and api; the gateway's fs verb called share.file(rel, params) exactly as it does for the home folder. When Soul Vault needed its store served, it shipped two more children. The module docstring names the cost honestly: an app pays its own polymorphic tax. The tax is a class in the app's own repository; the payoff is that Delta's server is the same code for every kind of data the family will ever add.
This is inheritance and polymorphism doing what they are for: the root decides what is safe by default, each child decides only what its data needs, and the code that dispatches never learns the difference.