writeonce/docs/examples/pricing
shoney.arickathil 2de724df11 feat(compiler): MVS ownership pass + woc driver; drop Money/SKU/Float, reject abstract
Completes plan 2 Tasks 7-8. owner.ml: mutable-value-semantics flow analysis
producing the four plan-3 emitter tables (moves, drops incl. LIVE-MASK for trap
unwinding, rc with elision, residual borrow sites) plus WO-E301-304 two-site
diagnostics. Alias questions run over canonicalized places, so a double-mut
reached through let-bound aliases lands in the residual table like the direct
form; dump.ml's contract notes the emitter must coalesce guards per operand.
main.ml: directory discovery, cross-file programs (symbols merge before bodies
check), diagnostics ordered by (file,line,col), new WO-E214 for a name declared
in two files. New docs/plan/oop-vm/01-error-catalog.md (14 emitted + 10 reserved
codes), un-ignored so both plan tracks can cite it; justfile regains woc-*.

builtin_scalars is now the five that work: Int, Bool, Text, Timestamp, Id.
Money/SKU/Float and the abstract_types allowlist are gone — `abstract` never
lexed, and Float had no literal syntax and no wob kind, so no value could exist.
Fixtures and samples retype Money->Int, SKU->Text. The abstract newtype feature
is rejected outright (verdict row adopt->reject); haxe-parity Task 7 keeps `is`.

nullable-types-implementation.md corrected: ?T is plumbed but UNENFORCED
(E211-213 declared, never emitted; probe exits 0), handed to haxe-parity Task 6
as next work item. Records all 10 dead codes incl. E205 — interface satisfaction
is unchecked. crates/rt keeps its Money/SKU fixtures (opaque strings, Stage 2).

Gate: build warning-clean, 14 + 264 checks 0 failures, pricing golden exit 0,
docs/examples histograms unchanged (13/70, zero WO-E225).
2026-08-10 21:24:50 +02:00
..
types feat(compiler): MVS ownership pass + woc driver; drop Money/SKU/Float, reject abstract 2026-08-10 21:24:50 +02:00
ui/pricing laanguage prototype 2026-07-12 03:40:30 +02:00
main.wo laanguage prototype 2026-07-12 03:40:30 +02:00
README.md every class with @table anotation is queriable table 2026-07-15 00:44:09 +02:00
wo.toml laanguage prototype 2026-07-12 03:40:30 +02:00

pricing — class model + live pricing demo

A Price class and a Product class with methods — products have prices — and a /pricing screen that shows live price data for selected products. Data stays in RAM; one product is readable by millions of customers at once; a price update pushes live to millions of subscribers; all concurrency is Linux kernel I/O (epoll today, io_uring per the scale-out plan).

Status: 13a + 13b shipped. class declarations parse, the demo serves real REST CRUD, and methods execute over RPC — wo run docs/examples/pricing (or just pricing-demo) serves POST /api/products/:id/set_price and :id/current_price as row-scoped transactions: the whole body commits as one WAL frame, an assert … otherwise abort rolls everything back (409). subscribe is a 501 stub until 13c; the UI is design-only until 13d/plan 14. The master plan is docs/plan/13-class-model-live-pricing.md.

The class model in one paragraph

class = state + methods, no inheritance. Fields, defaults, ref/multi, service, policy, on <event> — all exactly as in type — plus fn methods with an implicit self receiver that run as row-scoped transactions (in txn [snapshot], the same machinery as the ecommerce sample's free-standing fn checkout). No extends, no override, no virtual dispatch: "is-a" is a tagged union, "has-a" is a ref/multi edge. Go-style encapsulation, not Java-style hierarchies.

Layout

The UI follows MVC (docs/plan/exploration/ui/08-mvc-structure.md), with the same screen anatomy as the v1 Angular app (reference/writeonce-app/src/app/article/) collapsed into the single binary: the model is the class itself, the view is plain .htmlx with external .scss, and the controller is a .wo file that binds the model into the view and is the only place UI may call class methods.

pricing/
├── wo.toml                   # app manifest
├── main.wo                   # entry point: insert product, LIVE subscribe, set_price
├── types/                    # MODEL — classes: schema + methods
│   ├── price.wo              #   class Price  — amount/currency/at + fn discounted(pct)
│   └── product.wo            #   class Product — prices: multi Price + fn current_price /
│                             #     fn set_price + service rest /api/products
└── ui/
    └── pricing/              # one screen = one MVC triplet
        ├── pricing.wo        #   CONTROLLER — route, model: bindings, actions → class methods
        ├── pricing.htmlx     #   VIEW — plain htmlx (Mustache + wo:live/wo:bind), logic-free
        └── pricing.scss      #   VIEW styles — external SCSS, compiled at `wo build`

What runs when

File / feature Goes live in Plan
class parses; /api/products CRUD serves 13a ✅ shipped lexer/parser/AST + spec amendments
set_price / current_price over RPC (POST /api/products/:id/set_price) 13b ✅ shipped method execution, row-scoped txn, one WAL frame per call
@table(name: "prices", index: [product, at]) + indexed DML — history() via select Price{ product == self.id }, GET /api/prices?product=1 ✅ shipped (13 follow-up) engine secondary indexes, find_by, REST filters
subscribe / LIVE select — delta on every commit, WebSocket at /api/products/live 13c subscription registry (scoped Stage 3)
/pricing screen patches price cells in open browsers 13d SSR + wo:live/wo:bind client runtime
Millions of readers + millions of live recipients 13e scale targets + load harness

The scale story (13e)

How "one product, millions of customers" works — all of it existing design, instantiated for this demo:

  • RAM-resident. The engine is the in-memory design of 03-inmemory-engine.md; the WAL/disk phases (10–12) sit behind the read path for durability, never in front of it.
  • Reads scale across cores. Thread-per-core, shared-nothing shards behind SO_REUSEPORT (09-concurrency-scaleout.md). A hot product row is owned by one shard but read-replicated to every thread, refreshed by the same per-thread broadcast that feeds subscribers — so GET /api/products/1 spreads over all cores with zero contention, while writes keep a single owner.
  • Live updates fan out in two stages. One set_price commit → one delta message per thread (not per subscriber, per plan 09d) → each thread predicate-matches its local subscription table and batches socket writes on its own ring. Kernel primitives only: edge-triggered epoll today (done/02-event-loop-epoll.md), per-thread io_uring next (exploration/linux/07-io_uring.md).

Verification targets (1 M aggregate reads/s of one product on 16 cores, 1 M live subscribers with p99 delta delivery < 250 ms, commit→first-delta p99 < 10 ms) are defined in plan 13 § 13e.

Try it today

just pricing-demo            # scripted CRUD round-trip, self-contained
# or:
cargo run --bin wo -- run docs/examples/pricing
# [wo] compiled catalog — 2 types
curl -X POST localhost:8080/api/products \
     -H "Content-Type: application/json" -d '{"sku":"WO-001","name":"writeonce mug"}'
curl localhost:8080/api/products

Methods and live push are the next sub-phases (13b/13c). For the type-based minimal program, see ../hello/; for the full workspace shape, ../../examples/ecommerce/.