** This file was created with an agent. **
Not yet, but it has the potential to be recommendable.
The metamodel solves a hard problem well — reactive immutable state with
referential integrity, topological dependency resolution, copy-on-write proxies,
and deep structural comparison. This is closer to a relational database engine
than a typical frontend state manager. The design is cohesive, the architecture
is clean, and it now has fully enforced type contracts (443 → 0 TypeScript
errors under strict: true).
Most state management libraries (Redux, MobX, Zustand, Jotai) don’t offer:
For applications that need these features — and TypeRoof clearly does — metamodel is a serious piece of engineering.
Apps where the data model has internal consistency rules that must hold at all times, and where changes cascade — modifying one thing implies adjusting other things.
The common thread: the state is not a bag of independent values — it’s a graph with constraints.
TypeRoof is a typographic proofing and animation tool. Its state model manages:
These aren’t flat values. A font change invalidates axis locations. An axis change invalidates glyph availability. A layout change invalidates actor configurations. This is exactly the cascading dependency problem that metamodel’s coherence functions and topological ordering solve.
Without metamodel, this logic would be ad-hoc event handlers — “when font changes, also update axes; when axes change, also update glyphs; when…” — hoping the order is right and no case is missed. Metamodel makes the dependency graph declarative and automatically resolved.
The complexity of the state management matches the complexity of the domain. Typography has deep, real interdependencies between its concepts. A simpler state manager would push that complexity into the application code — into scattered event handlers, manual dependency tracking, and “remember to also update X when Y changes” comments.
The design is not overengineered for the problem, but some implementation choices could be simplified without losing the core value proposition:
The proxy escalation system — 463 lines of _PotentialWriteProxy with
symbol-keyed trap handlers is a lot of machinery. Copy-on-write is valuable
for undo/redo and comparison, but a simpler approach (explicit
.beginDraft() / .commitDraft() without transparent proxies) would
sacrifice ergonomics but dramatically reduce complexity.
Runtime immutability enforcement — Object.defineProperty with
configurable: false plus Object.freeze was essential in pure
JavaScript — it was the only way to enforce immutability. With TypeScript’s
readonly and strict mode, the compiler now catches mutations at
write-time, making the runtime layer a second safety net rather than the
primary one. However, as long as consumers are untyped .mjs files, the
runtime enforcement still has value. Once consumers migrate to TypeScript,
the runtime layer becomes truly redundant and could be stripped or made
opt-in (e.g., a development-only FREEZE=true flag).
Baked-in serialization — Full serialization/deserialization with async resource resolution is a complete persistence layer inside the state model. Not every app needs its state manager to also be its serialization framework. For TypeRoof (saving/loading type proofs) it makes sense, but a standalone library might offer this as an optional layer.
The path to standalone should include asking “what’s essential vs. what’s accidental complexity?” for each subsystem.
There’s no README, no API guide, no usage examples. A developer encountering
createClass, metamorphoseGen, or _PotentialWriteProxy.create has to read
the source to understand the contract. The types help enormously now, but
they’re not a substitute for “here’s how to define a model, here’s how state
flows.”
You can’t recommend a library you can’t verify. Without tests, every consumer is doing faith-based engineering. The typing pass found 3 dormant bugs in ~8200 lines — there could be more in code paths not yet exercised.
It lives inside TypeRoof. To recommend it, it would need to be extractable —
its own package, its own repo or workspace, its own entry point. The barrel
(metamodel.ts) is a good start.
Object.defineProperty / as unknown as ceremony72 escape hatches and 48 declare properties signal that the runtime patterns
fight against TypeScript’s model. A library for external use should feel natural
to type, not require workarounds. Some of this could be simplified — e.g.,
readonly class fields instead of Object.defineProperty for immutable
properties.
Someone using Zustand or Redux has no on-ramp. A “metamodel in 5 minutes” guide
showing the equivalent of createStore → createClass, dispatch →
metamorphose, selector → getEntry would make it approachable.
“Metamodel” is an accurate internal name but a poor standalone identity:
The working name direction: Typecaster.
Following the typography production metaphor:
| Print shop role | Library | What it does |
|---|---|---|
| Typecaster | State management | Casts complete, immutable, typed state from class definitions |
| Compositor | Scope resolution | Assembles properties into resolved, hierarchical scopes |
Why Typecaster works:
createClass (the matrix) + metamorphose (the casting) does. The result
is a solid, immutable slug of type.OLD_STATE) still exists for
comparison.Package: @strongtypesystems/typecaster
metamorphose → castThe core operation — transforming a mutable draft into a frozen immutable
snapshot — is called metamorphose internally. For the standalone library,
cast is the better API name:
// Internal (current)
const newState = draft.metamorphose(dependencies);
// Standalone (proposed)
const newState = draft.cast(dependencies);
Why cast:
createClass typecasts a generic model into a specific
character (FontSize, AxisLocation, TypeSpec)draft.cast() reads naturallyThe full rename scope: metamorphose, metamorphoseGen,
#_metamorphoseGen → cast, castGen, #_castGen.
Timing: Do this when extracting the standalone package, not during the current typing pass. It’s a branding decision, not a technical one.
Other potential API renames for the standalone:
| Internal | Standalone | Rationale |
|---|---|---|
createClass |
define |
You define a type, the typecaster casts it |
metamorphose |
cast |
The casting operation |
metamorphoseGen |
castGen |
Generator version |
OLD_STATE |
previous |
Clearer for external users |
_PotentialWriteProxy |
DraftProxy |
Says what it is |
Start with pure, side-effect-free functions:
| Target | Why |
|---|---|
topologicalSortKahn |
Deterministic algorithm, clear inputs/outputs |
collectDependencies |
Pure data transformation |
rawCompare / compare |
Core diffing logic, many edge cases |
serialize / deserializeGen |
Round-trip property: deserialize(serialize(x)) ≡ x |
Then graduate to lifecycle integration tests:
createClass → construct → metamorphose → mutate draft → metamorphose → compare
package.json with its own name, version, exportsmetamodel.ts barrel as the public entry pointObject.defineProperty patterns (makes it idiomatic)Audit all 48 declare + Object.defineProperty sites. For each, determine:
Object.defineProperty with
configurable: falsereadonly class
field. TypeScript enforces this at the type level, and the runtime behavior is
nearly identical.WeakMap-based
approach, which is more TypeScript-friendly and avoids polluting the instance
shape.This would reduce the as unknown as count significantly and make the code
feel like idiomatic TypeScript rather than typed JavaScript.
A minimal standalone app — perhaps a todo list with categories (struct → list → enum) and referential integrity (foreign keys between categories and items). Small enough to read in 10 minutes, complete enough to demonstrate the lifecycle.
Metamodel is not a general-purpose state manager. It’s for applications that need:
This is a real niche that no mainstream library fills well. Typography tools, design systems, CAD applications, form builders with complex validation — these are the kinds of apps that would benefit.
From the Strong Type Systems purpose statement:
We create Free Libré Open Source Software (FLOSS) type tools for production, proofing, type-setting and animation.
A standalone metamodel library would be a reusable foundation for any future type tool — not just TypeRoof. Investing in its independence is investing in the ecosystem.