package memona:filesystem@0.1.0; /// Host services scoped to the current connection. No ambient filesystem, /// environment, sockets, credentials, or network permissions are inherited. interface host { /// Stable failures without backend messages or credential-bearing URLs. enum host-error { denied, unavailable, invalid-input, cancelled, internal } /// Severity of a diagnostic event. The host may discard message contents. enum log-level { debug, info, warn, error } /// Every method the host will send. A verb outside this list cannot be /// spelled, so no plugin can smuggle one past the transport rules. enum method { get, head, post, put, patch, delete, propfind, proppatch, mkcol, copy, move, lock, unlock, report, } /// An HTTP header. Host-controlled transport headers cannot be overridden. record header { name: string, value: string } /// Request head. At most 64 headers of 8 KiB each; the body is streamed. record request { method: method, url: string, headers: list
} /// Response head. The body is read from the exchange that produced it. record response { status: u16, headers: list
} /// One HTTP request and its response, streamed in bounded chunks so a /// transfer is never held whole in memory. A body of any size crosses in /// pieces, which is what multipart uploads of at least 5 MiB require. /// Dropping the handle abandons the request. resource exchange { /// Appends at most 1,048,576 bytes to the request body. Every push /// must land before `respond`. push: func(data: list) -> result<_, host-error>; /// Ends the request body and waits for the response head. Calling it /// twice is invalid-input. respond: func() -> result; /// Reads at most 1,048,576 bytes of the response body, after /// `respond`. An empty result means the body is complete. pull: func(limit: u32) -> result, host-error>; } /// Begins a request to any HTTP or HTTPS host, including LAN and loopback /// addresses. This API has no per-connection network grant. Each redirect /// and DNS result is checked, and DNS is pinned. Cross-origin redirects /// remove request headers; HTTPS cannot downgrade. The host bounds total /// bytes per exchange, not buffer size. http: func(request: request) -> result; /// One open TCP connection. Dropping the handle closes the socket, and the /// host closes every socket when the connection is disposed. resource tcp { /// Reads at most `limit` bytes, between 1 and 1,048,576. An empty /// result means the peer closed its side, not that data is pending. read: func(limit: u32) -> result, host-error>; /// Writes at most 1,048,576 bytes and returns how many were accepted. write: func(data: list) -> result; } /// Opens a socket to any host, including LAN and loopback addresses, on a /// package-declared port. This API has no per-connection network grant. /// The host resolves and pins DNS and starts TLS when asked. Ports /// negotiated at runtime, such as an FTP data channel, must be declared. connect: func(host: string, port: u16, tls: bool) -> result; /// One datagram socket connected to a single peer. resource udp { /// Sends one datagram of at most 65,507 bytes. send: func(data: list) -> result; /// Receives one datagram of at most `limit` bytes. An empty result /// means none arrived inside the host's wait, not that the peer left. receive: func(limit: u32) -> result, host-error>; } /// Connects a datagram socket under the same address and port rules as /// `connect`, for the few filesystem protocols that still need one. bind: func(host: string, port: u16) -> result; /// A configuration value, typed as the package's manifest declared it. /// A secret is never logged by the host and never leaves the connection. variant setting { text(string), secret(string), toggle(bool) } /// Reads configuration or a session-only credential for this connection. config: func(key: string) -> option; /// Emits a diagnostic event. Never include secrets or private document data. log: func(level: log-level, message: string); } /// Filesystem operations for one connection. Paths are absolute within its /// root, starting with `/`; they are never local OS paths or resource URIs. /// Colons and percent signs are literal; paths must never be URL-decoded. /// Dot segments, backslashes, and controls are invalid. /// Every invocation is bounded by host fuel, memory, time, and cancellation. interface provider { use host.{setting}; /// Stable operation failures. A mutation interrupted after dispatch can /// report outcome-unknown: inspect remote state before deciding to retry. enum error-code { not-found, denied, unsupported, conflict, unavailable, invalid-input, cancelled, outcome-unknown, internal } /// Anything that is neither a file nor a directory, such as a link, is /// reported as other rather than flattened into one of the two. enum entry-kind { file, directory, other } /// An opaque version token, at most 4096 UTF-8 bytes. type revision = string; /// How one resource is named: a path within the connection root. variant locator { path(string) } /// Optional operations. False capabilities must return unsupported. /// Conditional-write promises atomic create-only and revision comparisons. /// Virtual-directories says a directory is only ever a prefix of something /// stored, as in a flat object store: the host then never offers to create /// an empty one, warns that removing the last child removes the folder, /// and does not call a vanished folder an error. Such a provider may still /// accept mkdir by writing a marker, and must answer unsupported when /// asked to rename a directory. record capabilities { write: bool, mkdir: bool, rename: bool, remove: bool, conditional-write: bool, watch: bool, virtual-directories: bool, } /// Byte length and an opaque revision, each if known. record file-info { kind: entry-kind, size: u64, revision: option, } /// One child name, without slashes, dot segments, or control characters. record entry { name: string, info: file-info } /// At most the requested number of entries. A cursor (maximum 4096 bytes) /// identifies another page; absence means enumeration is complete. record list-page { entries: list, cursor: option } /// Atomic preconditions: absent file, exact opaque revision, or unconditional /// replacement. Providers must never simulate comparisons non-atomically. variant write-condition { create-only, if-match(revision), overwrite } /// Changes since a previous polling cursor. enum change-kind { created, modified, removed } /// A changed resource path relative to the connection root. record change { path: string, kind: change-kind } /// At most 256 changes and a cursor of at most 4096 UTF-8 bytes. record change-page { changes: list, cursor: option } /// One transfer being read. The handle survives between host calls, so a /// file of any size is read as a series of separately bounded calls. /// Dropping it releases whatever the provider held open. resource reader { /// Reads onward from the last position, at most `limit` bytes, with /// `limit` between 1 and 1,048,576. An empty result means end of file. pull: func(limit: u32) -> result, error-code>; /// The revision this transfer is reading, once the provider knows it. /// A provider that sees the revision change mid-transfer must fail the /// next pull with conflict rather than mix two versions. revision: func() -> option; } /// One transfer being written. Dropping the handle before it finishes /// abandons the transfer, and the provider must leave no partial file. resource writer { /// Appends at most 1,048,576 bytes. Oversized pushes are rejected, /// never truncated. push: func(data: list) -> result<_, error-code>; /// Commits the transfer and evaluates the write condition now, against /// the state the caller last saw. A none result means the commit needs /// another call: keep calling until it answers some, and treat an /// interruption in between as outcome-unknown. finish: func() -> result, error-code>; } /// Opens exactly one host-owned connection per component instance. The host /// passes only this connection's configuration and in-memory credentials. open: func(provider-id: string, config: list>) -> result; /// Best-effort cleanup before disposal. The host may dispose immediately /// after a trap, cancellation, permission change, or process shutdown. close: func(); /// Reads metadata without changing the resource. stat: func(target: locator) -> result; /// Enumerates 1-256 direct children per request, without recursive traversal. %list: func(target: locator, cursor: option, limit: u32) -> result; /// Begins reading at a byte offset. Streaming, rather than one capped read, /// is what lets a provider serve a file larger than a single call allows. open-read: func(target: locator, offset: u64) -> result; /// Begins replacing one complete file if the write capability is present. /// The size hint, when the caller knows it, lets a provider choose between /// a single request and a multipart upload before the first byte. open-write: func(target: locator, condition: write-condition, size-hint: option) -> result; /// Creates one directory if the mkdir capability is present. mkdir: func(target: locator) -> result; /// Renames within this connection if the rename capability is present. rename: func(target: locator, destination: string) -> result; /// Removes one file or empty directory if the remove capability is present. remove: func(target: locator) -> result<_, error-code>; /// Polls for a bounded change page if the watch capability is present. No /// background callback or persistent subscription is installed by the host. poll: func(cursor: option) -> result; } /// Memona filesystem extension ABI 0.1.0, distributed as a WebAssembly component. /// Rust guests can target wasm32-wasip2. Selected WASI 0.2 CLI, clocks, random, /// IO and filesystem interfaces are allowed for the standard library, with /// empty arguments/environment, empty preopens, and discarded stdio. WASI /// sockets and HTTP imports are not allowed; networking uses the host interface. world filesystem { import host; export provider; }