writeonce/docs/examples/site/content.wo
shoney.arickathil aee78c2296 feat(site): tutorial chapter for durable and resident storage modes
- new chapter 7, "Storage modes: durable and resident", covering what
  master gains with the databasev2 cherry-pick: durable: false for a
  RAM-only table, resident: keys for a table that outgrows RAM
- states the parts a reader would otherwise hit as surprises: a
  keys-resident table is REFUSED at startup without WO_DATA, an update
  appends a delta rather than rewriting the row, and the chain is
  bounded at 16 links so a hot row does not degrade reads or replay
- quotes the measured 2.55x smaller resident set, not an estimate
- actors/deps/serving shift to ord 8/9/10; seeding is ord-driven so an
  existing WO_DATA keeps its rows and only a fresh boot reseeds
- home card says a table can be RAM-only or outgrow RAM
- two gate legs pin the new chapter; site-accept 23 checks, 0 failures

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 3b503c0db50aa737dd06ef6d17151c654a42c59b)
2026-08-30 20:38:03 +02:00

115 lines
12 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- content.wo — the tutorial chapters, seeded into the Chapter table on
-- first boot (types.wo's seed_if_empty). Model CONTENT, so it sits in
-- the root module beside types.wo: it inserts rows. Bodies are HTML fragments
-- BUILT with the writeonce-view dep — prose in el(), code samples through
-- code_block() which escapes them. Editing a chapter later (the admin
-- route) overwrites body/title in place; the WAL keeps the edit across
-- restarts, which is exactly chapter 6's lesson demonstrated by the
-- site that teaches it.
use view
fn ch_hello() -> Text {
let b = el("p", "leading-relaxed mb-4",
"A writeonce program is one directory of <code>.wo</code> files and one entry: a free " .. "function named <code>main</code>. It returns the process exit code. There is no " .. "runtime to install separately and no build pipeline — <code>woc build</code> produces " .. "ONE self-contained binary with the VM and your bytecode inside.");
b = b .. code_block("fn main() -> Int {\n print(\"hello, writeonce\");\n return 0;\n}");
b = b .. el("p", "leading-relaxed mt-4",
"Run it: <code>woc build . -o hello &amp;&amp; ./hello</code>. " .. "Statements end with <code>;</code>, blocks use braces, comments start with <code>--</code>.");
return b;
}
fn ch_values() -> Text {
let b = el("p", "leading-relaxed mb-4",
"<code>let</code> binds a value; the type is inferred. Scalars: <code>Int</code> (64-bit), " .. "<code>Float</code>, <code>Bool</code>, <code>Text</code> (bytes, binary-safe). Text " .. "interpolates with <code>\${...}</code> and concatenates with <code>..</code>. " .. "Integer literals speak hex and binary, and the full bitwise set is here: " .. "<code>&amp; | ^ &lt;&lt; &gt;&gt;</code> — grouped Go-style, so a mask compare needs no parentheses.");
b = b .. code_block("let port = 8080;\nlet pi = 3.14159;\nlet name = \"writeonce\";\nlet msg = \"listening on \${port}\";\n\nlet flags = 0b1010_0001;\nlet high = flags & 0xF0; -- bitwise AND, then == compares\nlet shifted = 1 << 12; -- 4096\nif flags & 0x80 != 0 {\n print(\"top bit set\"); -- groups (flags & 0x80) != 0\n}");
return b;
}
fn ch_containers() -> Text {
let b = el("p", "leading-relaxed mb-4",
"Two containers: <code>multi T</code> (a growable list) and <code>map&lt;K, V&gt;</code>. " .. "A map read <code>m[k]</code> answers nil when the key is absent — the everyday idiom " .. "for optional lookups like HTTP headers. <code>for .. in</code> walks both.");
b = b .. code_block("let langs: multi Text = [\"c\", \"ocaml\", \"writeonce\"];\npush(langs, \"more\");\nprint(\"count \${len(langs)}\");\n\nlet ages: map<Text, Int> = {};\nages[\"ada\"] = 36;\nlet a = ages[\"grace\"]; -- ?Int: nil, no trap\nif a == nil { print(\"unknown\"); }\n\nfor l in langs {\n print(l);\n}\nfor k, v in ages {\n print(\"\${k} is \${v}\");\n}");
return b;
}
fn ch_classes() -> Text {
let b = el("p", "leading-relaxed mb-4",
"Classes hold fields and methods. There are NO function values and NO closures — a " .. "deliberate doctrine: behavior travels as a class satisfying an interface, and " .. "satisfaction is structural (same method name and shape, Go-style, no " .. "<code>implements</code>). This is how <code>porch</code>, the web framework, takes handlers.");
b = b .. code_block("interface Handler {\n fn handle(req: Req) -> Resp\n}\n\nclass Hello {\n greeting: Text\n fn handle(req: Req) -> Resp {\n return ok_text(\"\${self.greeting}, \${req.path}\");\n }\n}\n\n-- any class with a matching handle() satisfies Handler\napp.get(\"/hello\", Hello { greeting: \"hi\" });");
return b;
}
fn ch_optionals() -> Text {
let b = el("p", "leading-relaxed mb-4",
"<code>?T</code> is a value or nil, and the compiler forces the check before use. " .. "Failures are TRAPS: named, catchable, never silent. <code>try ... catch (e)</code> is " .. "an expression; <code>e</code> carries code, line, method and message. Anything " .. "uncaught ends the program with the same structured report.");
b = b .. code_block("let n = parse_int(\"42x\"); -- ?Int\nif n == nil {\n print(\"not a number\");\n}\n\nlet r = try fs.read_all(\"/etc/missing\", 4096) catch (e) e.msg;\nprint(r); -- the file's bytes, or \"No such file or directory\"\n\nlet d = 0;\nlet q = try 10 / d catch (e) 0 - 1; -- DIV0 is a trap, caught here");
return b;
}
fn ch_tables() -> Text {
let b = el("p", "leading-relaxed mb-4",
"The database is IN the language. <code>@table</code> makes a class a table; " .. "<code>insert</code> writes a row; queries are first-class expressions; an UPDATE is a " .. "plain field assignment on a query result. With <code>WO_DATA=&lt;dir&gt;</code> every " .. "commit is WAL-durable before it is acknowledged and replays on restart — this very " .. "site stores these chapters that way, and the admin form's edits survive a kill.");
b = b .. code_block("@table(name: \"notes\", index: [tag])\nclass Note {\n tag: Text @unique\n val: Int\n}\n\ninsert Note { tag: \"first\", val: 1 };\n\nfor n in from x in Note where x.val > 0 order by x.tag select x {\n print(\"\${n.tag} = \${n.val}\");\n}\n\nlet hits = from x in Note where x.tag == \"first\" take 1 select x;\nif len(hits) == 1 {\n hits[0].val = 2; -- an update: assign through the row\n}");
return b;
}
fn ch_storage() -> Text {
let b = el("p", "leading-relaxed mb-4",
"Two optional <code>@table</code> keys decide where a table's rows LIVE. Both default " .. "to today's behaviour, so every table above keeps working untouched. " .. "<code>durable: false</code> keeps a table entirely in RAM — a full table in-process, " .. "same indexes, same <code>@unique</code>, same queries — that is simply empty after a " .. "restart. That is what a session store or a rate-limit counter wants: losing it on " .. "reboot is correct, and paying to make it durable is waste.");
b = b .. code_block("@table(name: \"sessions\", durable: false)\nclass Session {\n sid: Text @unique\n hits: Int\n}\n\n@table(name: \"catalog\", resident: keys, index: [sku])\nclass Product {\n sku: Text @unique\n price: Int\n blurb: Text\n}");
b = b .. el("p", "leading-relaxed mt-4 mb-4",
"<code>resident: keys</code> is the other direction: only the KEY stays in memory. The " .. "row body lives in the write-ahead log and is read back on demand, so a table can be " .. "far larger than RAM without a page-fault cliff. Measured on a product catalogue, the " .. "resident set is <strong>2.55× smaller</strong> than the same table fully resident. " .. "Such a table has nowhere to keep its rows without a log, so declaring it and starting " .. "without <code>WO_DATA</code> is REFUSED at startup rather than silently ignored.");
b = b .. el("p", "leading-relaxed mb-4",
"Reads and writes are unchanged — the storage mode is not a different API:");
b = b .. code_block("let hits = from x in Product where x.sku == \"a-1\" take 1 select x;\nif len(hits) == 1 {\n hits[0].price = 1299; -- an ordinary assignment\n}");
b = b .. el("p", "leading-relaxed mt-4",
"Underneath, that assignment appends a <em>delta</em> — which field changed, plus a " .. "back-pointer to the row's previous record — instead of rewriting the whole row. A read " .. "walks that chain backward, newest wins. Left alone the chain would grow forever on a " .. "hot row, so it is BOUNDED: past 16 links an update writes a full row image and the " .. "chain restarts at zero. A row updated a million times still costs a bounded number of " .. "record reads, on read and on replay alike.");
return b;
}
fn ch_actors() -> Text {
let b = el("p", "leading-relaxed mb-4",
"Concurrency is actors on fibers: <code>spawn</code> makes an actor from a class with a " .. "<code>receive</code> method, <code>send</code> delivers one message at a time, and " .. "ownership MOVES with the message — no locks, no shared mutable state, no data races " .. "by construction. Blocking calls park the fiber; the shard serves others meanwhile. " .. "One VM per core by default; mailboxes are bounded (a full one is a catchable trap).");
b = b .. code_block("class Counter {\n total: Int\n fn receive(msg: Tick) {\n self.total = self.total + msg.n;\n print(\"total \${self.total}\");\n }\n}\n\nclass Tick {\n n: Int\n}\n\nfn main() -> Int {\n let c: actor Tick = spawn Counter { total: 0 };\n send(c, Tick { n: 1 });\n send(c, Tick { n: 2 });\n time.sleep(50); -- parks this fiber; the actor runs\n return 0;\n}");
return b;
}
fn ch_deps() -> Text {
let b = el("p", "leading-relaxed mb-4",
"Dependencies are git repositories pinned in <code>wo.toml</code>; <code>wo.lock</code> " .. "records the exact revision, and locked builds work offline. The [deps] KEY names the " .. "module you <code>use</code>. This site has two: <code>porch</code>, the writeonce web framework, and the writeonce-view " .. "library that rendered the page you are reading.");
b = b .. code_block(`
[deps]
porch = { git = "https://github.com/shoneyj/porch", rev = "v0.1.0" }
view = { git = "https://github.com/shoneyj/writeonce-view", rev = "v0.1.0" }`);
b = b .. code_block(`
use porch
use porch/http
use view
-- html's builders + tailwind-style utilities, zero JS, no build step:
let body = el("h1", "text-3xl font-bold", "Hello");
return ok_html(page("Hello", body));`);
return b;
}
fn ch_serving() -> Text {
let b = el("p", "leading-relaxed mb-4",
"The whole stack of this site: routes with <code>:param</code> captures, a middleware " .. "chain, handler classes, <code>@table</code> persistence, and server-rendered HTML — " .. "one binary behind a proxy. This is the site's own main, abbreviated:");
b = b .. code_block("fn main(args: multi Text) -> Int {\n seed_if_empty();\n let app = App { middleware: [], routes: [] };\n app.use_mw(Mw { m: Logging {} });\n app.get(\"/\", Home {});\n app.get(\"/ch/:slug\", ShowChapter {});\n app.post(\"/admin/ch/:slug\", AdminEdit { token: token });\n return app.serve(\"127.0.0.1\", port);\n}");
b = b .. el("p", "leading-relaxed mt-4",
"The admin route checks its bearer token in the handler — mechanism lives in the " .. "framework (<code>bearer_token</code>, constant-time <code>ct_eq</code>), POLICY stays " .. "in the app. Try editing this chapter: " .. "<code>curl -X POST -H \"authorization: Bearer ...\" -d \"title=...&amp;body=...\" /admin/ch/serving</code>.");
return b;
}
-- One seed row per chapter: (ord, slug, title, body-builder above).
pub fn seed_chapters() {
insert Chapter { slug: "hello", ord: 1, title: "Hello, writeonce", body: ch_hello() };
insert Chapter { slug: "values", ord: 2, title: "Values, Text and bitwise", body: ch_values() };
insert Chapter { slug: "containers", ord: 3, title: "multi and map", body: ch_containers() };
insert Chapter { slug: "classes", ord: 4, title: "Classes and interfaces", body: ch_classes() };
insert Chapter { slug: "optionals", ord: 5, title: "Optionals and traps", body: ch_optionals() };
insert Chapter { slug: "tables", ord: 6, title: "@table: the built-in database", body: ch_tables() };
insert Chapter { slug: "storage", ord: 7, title: "Storage modes: durable and resident", body: ch_storage() };
insert Chapter { slug: "actors", ord: 8, title: "Actors and fibers", body: ch_actors() };
insert Chapter { slug: "deps", ord: 9, title: "Dependencies", body: ch_deps() };
insert Chapter { slug: "serving", ord: 10, title: "Serving the web (this site)", body: ch_serving() };
}