- 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)
115 lines
12 KiB
Text
115 lines
12 KiB
Text
-- 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 && ./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>& | ^ << >></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<K, V></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=<dir></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=...&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() };
|
||
}
|