158 lines
8.5 KiB
Markdown
158 lines
8.5 KiB
Markdown
# Public-document Worldspace
|
|
|
|
`wip-site-worldspace` is the site's in-memory Host provider: immutable documents
|
|
plus an opt-in session-local playground. It uses
|
|
`wip-host`, `wip-interface` and `wip-protocol`; it does not depend on the filesystem
|
|
server, HTTP, a browser, or a network runtime. The native and WASM callers use the
|
|
same `Worldspace` instance API.
|
|
|
|
## Generated data
|
|
|
|
The static Astro endpoint `src/pages/worldspace.json.ts` emits
|
|
`dist/worldspace.json` during the normal site build. It:
|
|
|
|
1. Reads `documents()` (the existing public-only content collection).
|
|
2. Renders each document with Astro's existing Markdown renderer, including the
|
|
configured document-link transform and syntax highlighting.
|
|
3. Renders the existing `RootPage` and `DirectoryIndex` components for `/`, `/book`
|
|
and any real public directory indexes. It does not copy their authored text.
|
|
4. Passes rendered bodies to `createWorldspaceData` in
|
|
`src/lib/worldspace-data.ts`, which uses `bookTree` and filters drafts again.
|
|
|
|
The input contract is:
|
|
|
|
```json
|
|
{"nodes":[
|
|
{"path":"/","name":"","displayPath":"/","title":"Worldspace Interface Protocol","href":"/","html":"..."},
|
|
{"path":"/book","name":"book","displayPath":"/book","parent":"/","title":"Book","href":"/book/","html":"..."},
|
|
{"path":"/book/1-concept.md","name":"1-concept.md","displayPath":"/1-concept.md","parent":"/book","title":"...","href":"/book/1-concept/","html":"..."},
|
|
{"path":"/book/1-concept.md/1.1-interaction-abstraction.md","name":"1.1-interaction-abstraction.md","displayPath":"/1.1-interaction-abstraction.md","parent":"/book/1-concept.md","title":"...","href":"/book/1.1-interaction-abstraction/","html":"..."}
|
|
]}
|
|
```
|
|
|
|
`path` is the only WIP request identity. `parent` is a WIP path and is absent only
|
|
for `/`. `displayPath` preserves the old explorer identity and selection state;
|
|
`href` preserves the existing Web route (including `SITE_BASE`). The UI can keep
|
|
its current `book/` label, but the actual Object name is `book`. No protocol name
|
|
has a trailing slash. Nodes retain `bookTree` order. The provider does not sort
|
|
children lexically. Every node, including home, book index, and a chapter that
|
|
also has sections, has a readable body.
|
|
|
|
`html` is a trusted, build-rendered fragment, not a full page or an untrusted user
|
|
submission. Consumers must retain the site's existing CSS, including home scoped
|
|
styles and code-block themes. Do not inject raw JSON into a script element without
|
|
escaping `<`; the generated endpoint is served as `application/json` instead.
|
|
There is no runtime loading by path: unpublished or guessed draft paths cannot
|
|
reach a source file. The Rust loader also rejects draft path segments, duplicate
|
|
path/displayPath/URL identities, inconsistent parent/name/path relationships,
|
|
missing parents, malformed input and unknown JSON fields.
|
|
|
|
## Rust API
|
|
|
|
```rust
|
|
use wip_site_worldspace::{Worldspace, DocumentContent};
|
|
use wip_site_worldspace::wip_protocol::{
|
|
ObserveRequest, FetchInterfaceRequest, CallOperationRequest, Target, InterfaceTarget,
|
|
};
|
|
use wip_interface::WipType;
|
|
|
|
# fn example(json: &str) {
|
|
let world = Worldspace::from_json(json).unwrap();
|
|
let observed = world.observe(ObserveRequest { path: "/".into(), depth: 4 }).unwrap();
|
|
let reference = observed.object.interfaces[0].clone();
|
|
let interface = world.fetch_interface(FetchInterfaceRequest { interface: reference.clone() }).unwrap();
|
|
let result = world.call_operation(CallOperationRequest {
|
|
target: Target { path: "/book/1-concept.md".into(), validator: None },
|
|
interface: InterfaceTarget {
|
|
reference, scope_ref: interface.scope_ref, validator: interface.validator,
|
|
},
|
|
operation: "read".into(),
|
|
arguments: Default::default(),
|
|
}).unwrap();
|
|
let document = DocumentContent::decode(result.result).unwrap();
|
|
assert_eq!(document.path, "/book/1-concept.md");
|
|
# }
|
|
```
|
|
|
|
Alternatively use `Worldspace::new(WorldspaceData { nodes })`. Construction returns
|
|
`Result<Worldspace, DataError>`. `observe(ObserveRequest)`,
|
|
`fetch_interface(FetchInterfaceRequest)` and `call_operation(CallOperationRequest)`
|
|
take requests by value and return canonical protocol response types or
|
|
`ProtocolError`. The crate re-exports `wip_protocol` for bridge consumers.
|
|
|
|
The registered root-scoped Interface is `{scope:"/",name:"document"}`. Its only
|
|
Operation, `read`, takes **no arguments** and returns the typed `DocumentContent`
|
|
record `{path,title,href,html}`. Host-generated Descriptors, scope association,
|
|
interface membership, request validation, argument/result validation and error
|
|
classification remain on the normal Host path. The provider exposes no alternate
|
|
body-fetch method. Documents are immutable, so no validators are advertised.
|
|
|
|
The recursive adapter calls `ObjectProvider::observe_object` for every node and
|
|
validates the assembled response against the request. At the requested depth,
|
|
`children` is `None`; above it, a visited leaf has `Some([])`. Unknown paths return
|
|
`NotFound` and malformed paths return `InvalidRequest`. Requests above
|
|
`MAX_DEPTH = 32` return `ResourceLimitExceeded` rather than silently truncating.
|
|
Construction allows at most `MAX_NODES = 4096`, with hierarchy depth at most 32.
|
|
`from_json` also caps input at 16 MiB. Together these bounds cap traversal work.
|
|
|
|
## Playground
|
|
|
|
A manifest document at `/playground` opts into mutable state. Manifests without
|
|
that node retain document-only behavior. Manifest descendants of `/playground`
|
|
are rejected, so mutable counters cannot shadow or delete document content.
|
|
|
|
- `/playground` keeps `/::document.read()` and also exposes `/::collection`:
|
|
`create(name: String) -> Unit` creates a zero-valued counter;
|
|
`remove(name: String) -> Unit` removes an existing counter.
|
|
- Children start as `apple = 3` and `orange = 0`, ordered lexically. Each exposes
|
|
only `/::counter`: `get() -> Integer`, `increment(amount: Integer) -> Integer`,
|
|
and `reset() -> Integer`. Increment returns the new value; reset returns zero.
|
|
- Names match `[A-Za-z0-9_-]{1,32}` and are case-sensitive. At most 32 counters
|
|
exist. Duplicate creation, missing removal, invalid names, and checked sums
|
|
outside Protocol Integer's range `[-9007199254740991, 9007199254740991]` fail
|
|
without mutation. Capacity errors are `ResourceLimitExceeded`; domain
|
|
rejections are `Internal` with an explicit not-committed Host guarantee.
|
|
Invalid typed arguments are rejected by standard Client/Host validation.
|
|
- Every counter lookup publishes a fresh Object snapshot whose `description`
|
|
is the current decimal integer and whose `name` remains the slug. All
|
|
generated interfaces bind to the same actual root ScopeObservation.
|
|
- State belongs to one Worldspace/BrowserSession. Navigation retains it; a new
|
|
session restores initial state. Documents are never mutated.
|
|
|
|
The browser's normal `tree()` includes only manifest display paths and therefore
|
|
never calls `document.read` on dynamic counters. Manual playground `observe`
|
|
uses the real Client's `refresh_observed` on every explicit retrieval (including
|
|
recovery from retained missing/stale observations); other documents retain
|
|
`ensure_observed` caching. This Client version has no public subtree-invalidation
|
|
API. A call does not observe, fetch, retry, or convert its result into an Object.
|
|
The UI explicitly observes `/playground` at depth 1, then the selected Object at
|
|
depth 1 after calls, and observes `/playground` on entry to Protocol mode.
|
|
|
|
## Validation
|
|
|
|
From the site directory, with the configured Rust toolchain and dependencies:
|
|
|
|
```sh
|
|
node --test tests/worldspace-data.test.mjs
|
|
cargo test -p wip-site-worldspace
|
|
npm run build
|
|
WIP_TEST_GENERATED=1 node --test tests/worldspace-data.test.mjs
|
|
cargo test -p wip-site-worldspace --test worldspace -- --include-ignored
|
|
npm run check:links
|
|
```
|
|
|
|
The explicit generated-data tests require `dist/worldspace.json`; they do not
|
|
silently substitute fixtures when the artifact is absent. Native fixture tests
|
|
exercise all three interactions, depth boundaries/order/empty sets, limits,
|
|
identity and constructor errors, unknown paths/interfaces/operations, invalid
|
|
arguments, validator failures, draft guesses, and JSON parsing. The generated
|
|
native test invokes all three interactions against the actual build artifact.
|
|
The JS post-build test compares every returned body and link set with the static
|
|
page's article, including home and book index. Run it again with a prefixed
|
|
`SITE_BASE` build to check deployment URLs.
|
|
|
|
Cargo dependency sourcing and the browser bridge are managed by the parent site
|
|
workspace. The initial relative Rust dependencies require the companion `wip-rs`
|
|
checkout beside `docs`; they must be replaced with the final reproducible source
|
|
configuration before a standalone site checkout can build.
|