# R-Machine — API Reference > A stable namespace is the contract; the implementation behind it is free to change. https://rmachine.dev https://github.com/codecarvings/r-machine/tree/RM-alpha-12 Warning: R-Machine is still in active development - API may change before stable release. --- ## Conceptual model: the namespace as a stable contract R-Machine is easier to reason about through one model than through a list of features. A codebase is a dynamic entity: it evolves sprint after sprint, refactor after refactor, generation after generation. A useful question when evaluating an architecture is not only *"can it do X?"* but *"how many files must change when X evolves?"* — production files, test files, mocks, fixtures, imports. R-Machine answers that question the way a DBMS does: | DBMS concept | R-Machine equivalent | |---|---| | Table name (`customers`) | Resource namespace (`outer/cart`, `shell/checkout`) | | Schema (column types) | TypeScript interface | | Query (`SELECT * FROM customers`) | `Plug` / `usePlug` | | Storage engine, indexes | Implementation body (gear or shell) | A database table has a stable name that consumers depend on. The storage engine can be replaced and indexes can change without forcing any consumer to update: the table name is the contract. R-Machine applies the same principle to application code. The resource namespace is the stable contract; the implementation behind it is the volatile layer. Consumers — including tests, mocks, and fixtures — depend on the namespace, not on where a value lives or how it is shaped, so a change to the implementation does not propagate to them. --- ## 1. Public API surface (complete) **Composers** (resource declaration) `InnerGear`, `BaseGear`, `OuterGear`, `Shell`, `localized` **Plug** (consumer primitive) `Plug`, `ClientPlug`, `ServerPlug` **Testing** `mockPlug`, `verifyResourceAtlas`, `createEventCollector` (from `@r-machine/testing`) **Diagnostics** `enableRMachineDevMode` (from `r-machine`) — console-traces the runtime event bus (see §15.2) `getResolveContext` (from `r-machine`) — reads the resolution attribution attached to a resolution-failure error (see §10.6) **Cursor primitives** (reactive members inside an `OuterGear`) `_.action`, `_.getter`, `_.cell`, `_.relay`, `_.cmd` **Lifecycle** `[Symbol.dispose]` convention (see §9) **Setup** `RMachine.create`, `defineLayout`, `ResourceAtlas`, `PathAtlas` **Framework strategies** `ReactStandardStrategy` (web React) `NextAppPathStrategy`, `NextAppFlatStrategy`, `NextAppOriginStrategy` (Next.js App Router, three routing models) `createNextDevImport` (Next.js dev-mode loader for HMR + `verifyResourceAtlas` activation — see §11.8) **Strategy-emitted toolsets** React: `ReactRMachine`, `VertexFrame`, `Plug` Next.js client: `NextClientRMachine`, `VertexFrame`, `ClientPlug` Next.js server (canonical, with proxy): `NextServerRMachine`, `bindLocale`, `setLocale`, `generateLocaleStaticParams`, `rMachineProxy`, `ServerPlug` Next.js server (no-proxy, path strategy only): `NextServerRMachine`, `bindLocale`, `setLocale`, `generateLocaleStaticParams`, `routeHandlers`, `ServerPlug` **Path declaration** `declarePathAtlas` (from `@r-machine/next`) — see §3.5 **Resource (`ResMatrix`)** — value produced by `.define(...)` `r.create()`, `r.plug`, `r.clone(...)` — see §4.4 and §5 **Strategy helpers** — `strategy.getHelpers()` returns `{ localeHelper }` on every strategy, plus `hrefHelper` on all Next.js strategies. See §11.3. **Type-level utilities** `RMachineLocale`, `BrandedResource` (re-exported as `RShape`) --- ## 2. Layout families Six family values are accepted in `defineLayout`. Anything else is a compile error. | Family | Composer | Stateful? | Can depend on | Consumed by | Locale-aware? | Eligible for kit? | |---|---|---|---|---|---|---| | `gear:inner` | `InnerGear` | no | `InnerGear`, `BaseGear`, `gearKit` | `InnerGear`, `BaseGear`, and `ServerPlug` (Next.js RSC only — never `Plug` or `ClientPlug`) | no | yes (`gearKit`) | | `gear:base` | `BaseGear` | no | `BaseGear`, `gearKit` | `InnerGear`, `BaseGear`, `OuterGear`, `Shell` (only if listed in `bridgeGears`) | no | yes (`gearKit`, `serverKit`/`clientKit`) | | `gear:outer` | `OuterGear` | optional | `BaseGear`, `OuterGear`, `gearKit` | other `OuterGear` and consumers via `Plug` / `ClientPlug` (not `ServerPlug`) | no | optional | | `gear:outer(vertex)` | `OuterGear` | optional | same as `gear:outer` | only consumers via `Plug` / `ClientPlug`; **cannot be a dep of any resource**; instance scoped per call (or shared via ``) | no | optional | | `shell` | `Shell` | no | `Shell`, `shell(mono)`, `BaseGear` (only if in `bridgeGears`), `shellKit` | other `Shell` and consumers | yes | yes (`shellKit`) | | `shell(mono)` | `Shell` | no | same as `shell` | other `Shell` and consumers | yes | yes (`shellKit`) | Dep-graph asymmetry: no dep path connects `gear:inner` to `gear:outer`, `gear:outer(vertex)`, or `Shell`. Enforced at the `withDeps(...)` call site by the compiler. --- ## 3. Setup A project has exactly two configuration files by convention: `resource-atlas.ts` and `setup.ts`. ### 3.1. `defineLayout` Maps folder prefixes to families. Prefix matching is **longest-match wins**. Layout keys must end in `/`; missing trailing slash is a compile error. ```ts const folders = defineLayout({ "inner/": "gear:inner", "base/": "gear:base", "outer/": "gear:outer", "vertex/": "gear:outer(vertex)", "shell/": "shell", "shell/lib/": "shell(mono)", }); ``` A file at `inner/inventory.ts` resolves to family `gear:inner`, namespace `inner/inventory`. A file at `vertex/cart.ts` resolves to `gear:outer(vertex)`, namespace `vertex/cart`. ### 3.2. `ResourceAtlas` A named class built from `defineLayout`'s return plus a type-level `ResourceMap`: ```ts type ResourceMap = { "inner/inventory": Inner_Inventory; "outer/cart": Outer_Cart; "vertex/search": Vertex_Search; "shell/product": Shell_Product; }; export class ResourceAtlas extends folders() {} ``` Keys whose namespace doesn't match any layout prefix are filtered out of the type-narrowed atlas. The error surfaces as a compile-time `RMachineTypeError` at the call site of `ResourceAtlas.getTokenBuilder()`, listing the offending keys — e.g. `RMachineTypeError<"Invalid namespaces declared in atlas shape (dropped by layout filter): *** shell_wrong/common ***">`. A TypeScript limitation prevents flagging the mistake at the atlas declaration itself. `ResourceAtlas.getTokenBuilder()` returns a factory minting typed runtime-opaque handles carrying their namespace at the type level. Tokens and string literals are interchangeable everywhere a dep handle is accepted: ```ts const token = ResourceAtlas.getTokenBuilder(); export const tasks = token("outer/tasks"); // OuterGear.withDeps(tasks).define(...) ≡ OuterGear.withDeps("outer/tasks").define(...) ``` #### Internal namespaces (`#` prefix) Prefix an atlas key with `#` to mark its namespace as **internal**. An internal namespace is visible only inside the resource network — usable as a `withDeps(...)` target by other gears/shells, and referenceable from `gearKit` / `shellKit` — but it is **filtered out of every consumer-facing surface** (`Plug`, `ClientPlug`, `ServerPlug`). ```ts type ResourceMap = { "base/config": Base_Config; // public — reachable via Plug / ClientPlug / ServerPlug "#base/jwt": Base_Jwt; // internal — only reachable as a gear→gear dep "outer/cart": Outer_Cart; }; ``` Typical use: utility resources that should never appear in UI code — JWT/crypto helpers, server-only adapters, internal caches. Attempting to consume an internal namespace from a component is a compile error (the key does not appear in the plug's accepted-namespace union). Rules: - The marker must be the **first character** of the atlas key (`"#base/jwt"`). Suffix or mid-string `#` is not recognized. - Layout classification is unchanged: `#base/jwt` still resolves to family `gear:base` via the `base/` prefix in `defineLayout`. The leading `#` is stripped before prefix matching. - `gearKit` / `shellKit` (factory-side) may reference internal namespaces. The strategy-level consumer `kit` / `clientKit` / `serverKit` may not — listing an internal namespace there is a compile error. - The `#` is a **type-level marker only** — it does not appear in filesystem paths or in module loading. The module for `#base/jwt` lives at `base/jwt.ts`, not `#base/jwt.ts`. The `load` callback receives the unmarked path. - Use the same string everywhere a handle to the resource is needed: `withDeps("#base/jwt")`, `token("#base/jwt")`, `bridgeGears: ["#base/jwt"]`. The marker is part of the namespace identity at the type level. ### 3.3. `RMachine.create` ```ts RMachine.create({ ResourceAtlas, // required locales: ["en", "it"] as const, // const tuple narrows L type defaultLocale: "en", // must be one of `locales` load: (path) => import(`./${path}.ts`),// async module loader bridgeGears: ["base/config"], // optional: base namespaces visible to shells gearKit: { log: "base/logger" }, // optional: injected as $.kit.* into every gear factory shellKit: { fmt: "shell/lib/fmt" }, // optional: injected as $.kit.* into every shell factory experimental: { outerGear: "on" }, // type-level conditional gate for OuterGear in toolset }); ``` - `bridgeGears` is type-narrowed to namespaces of family `gear:base` only. Listing an outer or inner namespace is a type error. - `gearKit` / `shellKit` are injected as `$.kit.{name}` into every factory of that family. - `experimental.outerGear`: opt-in flag, `"on"`-or-absent (there is no `"off"` value — `experimental: { outerGear: "off" }` is a type error). **Omitted is the default**: `OuterGear` does not appear on the toolset object (type-level removal, not a runtime branch). Set it to `"on"` to opt in. The `experimental` namespace gates features whose API may still evolve in non-backward-compatible ways; you enable `"on"` deliberately, accepting that, and the flag is retired once the feature stabilizes. ### 3.4. Toolset ```ts export const { InnerGear, BaseGear, OuterGear, Shell, localized } = rMachine.createToolset(); export type Locale = RMachineLocale; export type { BrandedResource as RShape } from "r-machine"; ``` ### 3.5. `PathAtlas` (Next.js only) Declares the application's localized URL paths in one place. Each canonical path key is the route as it appears in the `app/` folder; per-locale translations are siblings of nested sub-paths. Lives in its own file (`r-machine/path-atlas.ts`) and is passed to the Next.js strategy at setup time. ```ts // r-machine/path-atlas.ts import { declarePathAtlas } from "@r-machine/next"; import type { Locale } from "./setup"; export class PathAtlas extends declarePathAtlas().as({ "/example-static": { it: "/esempio-statico", // translation for locale "it" "/page-1": { it: "/pagina-1", }, "/page-2": { en: "/page-2-in-english", // explicit canonical override per locale it: "/pagina-2", }, }, "/example-dynamic": { it: "/esempio-dinamico", "/[slug]": {}, // dynamic segment: empty value, no translations }, }) {} ``` Rules: - **Locale entries** are sibling keys whose name matches a declared locale (`"it": "/..."`, `"en": "/..."`). Their values are the segment's translation in that locale, `/`-prefixed. - **Sub-path entries** are sibling keys that start with `/` (`"/page-1": {...}`). They nest recursively. - **Dynamic segments** (`"/[slug]"`, `"/[...rest]"`, `"/[[...rest]]"`) take an empty object `{}` and accept no per-locale translations and no children. - The default locale needs no translation entry — the canonical path key is used as-is unless overridden (see `/page-2`'s `en: "/page-2-in-english"`). - Validation runs at strategy-construction time. Mismatches between `PathAtlas` keys and the actual `app/` folder structure are not checked by R-Machine — that is the developer's responsibility. Pass the class (not an instance) to the strategy: ```ts export const strategy = NextAppPathStrategy.create(rMachine, { PathAtlas, // ... }); ``` `PathAtlas` is optional. When omitted, no path translations are generated; URLs are the literal `app/` folder paths in every locale. --- ## 4. Composers All composers expose a chain. Every step except `define` is optional, and `define` is always last. | Composer | Chain | |---|---| | `InnerGear` | `withDeps → withPorts → define` | | `BaseGear` | `withDeps → withPorts → define` | | `OuterGear` | `withDeps → withPorts → withState → define` | | `Shell` | `withDeps → withPorts → define` | `Shell` does not support `withState`. ### 4.1. `withDeps(...)` Two forms — list and map — yielding tuple or named-key access in the factory: ```ts // list form InnerGear.withDeps("inner/clock", "base/config").define((plugin) => { const [clock, config, $] = plugin; return { /* ... */ }; }); // map form InnerGear.withDeps({ clock: "inner/clock", config: "base/config" }) .define((plugin) => { const { clock, config, $ } = plugin; return { /* ... */ }; }); ``` The factory takes a single first argument — `plugin` — with a uniform shape across all four composers (`InnerGear`, `BaseGear`, `OuterGear`, `Shell`). Destructure it on the first line of the body: - **No deps** (or `withDeps()` with no arguments) — map form (default): `const { $ } = plugin;`. - **Map form** (`withDeps({ name: "ns" })`) — `const { name, $ } = plugin;`, with `$` alongside the named deps. - **List form** (`withDeps("ns1", "ns2")`) — `const [dep1, dep2, $] = plugin;`, with `$` always as the last tuple element. ### 4.2. `withPorts({...})` Declares external values (server actions, SDK clients, fetch wrappers, locale-aware data sources) used inside the factory. Accessed as `$.ports.{name}`. ```ts import { createPost } from "../lib/actions"; OuterGear .withDeps("base/config") .withPorts({ createPost }) .withState({ pending: false }) .define((plugin, _) => { const [config, $] = plugin; return { submit: async (title: string, body: string) => { await $.ports.createPost(title, body); }, }; }); ``` Ports are inputs to the gear/shell, not part of the consumer Surface. Available on all four composers (`InnerGear`, `BaseGear`, `OuterGear`, `Shell`). ### 4.3. `withState(initial)` Available on `OuterGear` only. Provides `$.state` and `$.defaultState` to the factory. ### 4.4. `define(factory)` Final step. The user factory receives the `plugin` context as its first argument (carrying `$`, deps, and kit) and the cursor `_` as its second for `OuterGear`, and returns the resource shape — see §6 (cursor primitives) and §8 (factory `$` context). `define(...)` itself does **not** return the resource shape. It returns a `ResMatrix` — the canonical value exported as `r` by every resource module: | Property | Type | Purpose | |---|---|---| | `r.create()` | `() => Promise` | Resolves the resource and returns its production Surface; awaits any async work in the user factory. Used directly in resource-level tests (§14.2). | | `r.plug` | typed plug | The plug handle for this resource, used as the target of `mockPlug(...)` (§10.3, §14). Carries deps/ports/locale narrowing at the type level. | | `r.clone(fn?)` | `() => ResMatrix` / `(fn: (res: R, plugin, [cursor]) => T) => ResMatrix` | Returns a fresh, independent `ResMatrix` reusing the same factory and chain, with an optional transform `fn` that overrides fields of `R` (locked to the same shape — see §5). Pair with `withPorts(...)` / `withState(...)` for ports / state overrides. | The shape is uniform across `InnerGear`, `BaseGear`, `OuterGear`, and `Shell` (including `shell(mono)`). The convention everywhere in this document is `export const r = ..define(...)`; `r` is always a `ResMatrix`. ### 4.5. `OuterGear` shorthand forms Stateless: ```ts OuterGear.define(() => ({ greet: (name: string) => `hello ${name}`, })); ``` Stateful — array shortcut. Two forms: read-write returns a `[getterName, actionName]` tuple; readonly returns a `[getterName]` single-element tuple. R-Machine synthesises a default identity getter and (read-write only) a default canonical action `(partial) => state`: ```ts // read-write OuterGear.withState({ count: 0 }).define(() => ["counter", "setCounter"]); // Surface: { counter: { count: number }; setCounter: (p: DeepPartial<{count:number}>) => {count:number} } // readonly — exposes only the synthesised getter, no action OuterGear.withState({ count: 0 }).define(() => ["counter"]); // Surface: { counter: { count: number } } ``` Stateful — full custom: see §6 cursor primitives. ### 4.6. Vertex (`gear:outer(vertex)`) Same `OuterGear` composer. There is no `VertexGear` export. What makes a resource a vertex is its **layout entry**, not the call shape: ```ts // vertex/shopping-cart.ts import { OuterGear, type RShape } from "../setup"; export const r = OuterGear.withState({ items: [] as string[] }).define((plugin, _) => { const { $ } = plugin; return { state: _.getter(), add: _.action((item: string) => ({ items: [...$.state.items, item] })), count: _.getter(() => $.state.items.length), }; }); export type Vertex_ShoppingCart = RShape; ``` Each `Plug("vertex/...").useR()` call in a component creates a fresh instance with lifecycle bound to that component. **Vertex gears cannot be a dependency of any other resource.** Reachable only through `Plug` / `ClientPlug` from a component. ### 4.7. `Shell` — multi-locale The canonical file exports `r`. R-Machine derives the shape from this file. It can be a plain object or a factory. **Plain object form:** ```ts // shell/common/en.ts import type { RShape } from "../../setup"; export const r = { greeting: "Hello", farewell: "Goodbye", }; export type Shell_Common = RShape; ``` **Factory form** (when the canonical needs `$.locale`, `$.kit`, `$.ports`, deps, async, etc.): ```ts // shell/common/en.ts import { Shell, type RShape } from "../../setup"; export const r = Shell.define((plugin) => { const { $ } = plugin; return { greeting: `Hello (${$.locale})`, }; }); export type Shell_Common = RShape; ``` **Factory form with ports** (e.g. async loading from an external source): ```ts // shell/landing/en.ts import { Shell, type RShape } from "../../setup"; import { fetchHeroCopy } from "../../lib/cms"; export const r = Shell .withPorts({ fetchHeroCopy }) .define(async (plugin) => { const { $ } = plugin; const data = await $.ports.fetchHeroCopy($.locale); return { hero: data.hero, sub: data.sub }; }); export type Shell_Landing = RShape; ``` **Variant files** use `localized(namespace, value)`: ```ts // shell/common/it.ts import { localized } from "../../setup"; export const r = localized("shell/common", { greeting: "Ciao", farewell: "Arrivederci", }); ``` `localized` performs **exact-keyed validation** against the canonical type: - Extra keys are typed as `never` (rejected). - Missing keys produce `Property '...' is missing` (rejected). For variants needing a factory (async loading, locale-aware computation), wrap `localized` inside `Shell.define`: ```ts export const r = Shell.define(async () => { await loadHeavyData(); return localized("shell/common", { greeting: "Ciao", farewell: "Arrivederci" }); }); ``` **Shell deps:** ```ts export const r = Shell.withDeps("base/config").define((plugin) => { const [config, $] = plugin; return { hero: `Welcome — API: ${config.apiBase}`, copy: $.kit.fmt.number(1000), }; }); ``` ### 4.8. `shell(mono)` A **single-file locale-aware** resource: it has access to `$.locale` like any shell, but no per-locale variant files. Use for formatters and locale-aware helpers without translation. The mono nature comes from the layout entry (`"shell/lib/": "shell(mono)"`). ```ts // shell/lib/fmt.ts import { type RShape, Shell } from "../../setup"; export const r = Shell.define((plugin) => { const { $ } = plugin; return { number: (n: number) => new Intl.NumberFormat($.locale).format(n), date: (d: Date) => new Intl.DateTimeFormat($.locale).format(d), }; }); export type Shell_Lib_Fmt = RShape; ``` #### Locale-aware formatting via the platform `Intl.*` primitives Locale-aware formatting uses the `Intl.*` family — `NumberFormat`, `DateTimeFormat`, `PluralRules`, `RelativeTimeFormat`, `ListFormat`, `Collator`, `Segmenter`, `DisplayNames`. These are already locale-aware natively, zero-bundle (built into every modern runtime — browser, Node, Deno, Bun, edge), and minimal in surface, so R-Machine does not wrap them. R-Machine provides the *wiring* — `$.locale` automatically passed, `shellKit` for cross-shell injection, `mockPlug` for test-time substitution. The primitives stay the platform's; the integration is R-Machine's. #### Pluralization in a few lines A pluralization helper is a few lines of TypeScript on top of `Intl.PluralRules`. Two variants, depending on how many CLDR plural categories the project's locales need. For locales with binary plural rules (English, Italian, German, French, Spanish, ...): ```ts // shell/lib/fmt.ts import { type RShape, Shell } from "../../setup"; type PluralForms = { one?: string; other: string }; export const r = Shell.define((plugin) => { const { $ } = plugin; const pluralRules = new Intl.PluralRules($.locale); return { number: (n: number) => new Intl.NumberFormat($.locale).format(n), date: (d: Date) => new Intl.DateTimeFormat($.locale).format(d), plural: (count: number, forms: PluralForms) => { const cat = pluralRules.select(count); const tpl = (cat === "one" ? forms.one : undefined) ?? forms.other; return tpl.replace(/#/g, String(count)); }, }; }); export type Shell_Lib_Fmt = RShape; ``` For locales with multi-category plural rules (Russian, Polish, Arabic, ...), the same pattern with a wider input type: ```ts // shell/lib/fmt.ts import { type RShape, Shell } from "../../setup"; type PluralForms = Partial> & { other: string }; export const r = Shell.define((plugin) => { const { $ } = plugin; const pluralRules = new Intl.PluralRules($.locale); return { plural: (count: number, forms: PluralForms) => { const cat = pluralRules.select(count); const tpl = forms[cat] ?? forms.other; return tpl.replace(/#/g, String(count)); }, }; }); export type Shell_Lib_Fmt = RShape; ``` Consumer-side, the call sites are typed and explicit: ```ts // en/it fmt.plural(count, { one: "# item", other: "# items" }) // ru fmt.plural(count, { one: "# элемент", few: "# элемента", many: "# элементов", other: "# элемента", }) ``` The signature `plural(count: number, forms: PluralForms)` is type-checked end-to-end: `other` is required (omitting it is a compile error), unknown keys are rejected, `count` is enforced to be a number. A consumer that forgets a required form, or that passes the wrong type, surfaces the mistake at compile time. ### 4.9. Resources are TypeScript factories R-Machine imposes a structural boundary at the resource edge — locale scoping for shells, kind classification for gears, dep-graph asymmetry between families — and **nothing else** on what the factory returns. A resource is a typed TypeScript factory; inside the boundary the full expressive power of the language is available. R-Machine introduces no DSL for content, no template syntax for strings, no wrapper layer over external libraries: there is nothing to learn beyond TypeScript itself. Two consequences follow. #### Bring your own formatter / parser / renderer If a project needs a locale-aware primitive R-Machine doesn't ship — ICU `MessageFormat`, Fluent, a Markdown renderer, an in-house format — there is no integration story. The library is imported inside a `shell` or `shell(mono)` factory; `$.locale` is the wiring. A `shell(mono)` exposing ICU `MessageFormat` via `intl-messageformat`: ```ts // shell/lib/icu.ts import { IntlMessageFormat, type PrimitiveType } from "intl-messageformat"; import { Shell, type RShape } from "../../setup"; export const r = Shell .withPorts({ IntlMessageFormat }) .define((plugin) => { const { $ } = plugin; const locale = $.locale; const cache = new Map(); const compile = (msg: string) => { let mf = cache.get(msg); if (!mf) { mf = new $.ports.IntlMessageFormat(msg, locale); cache.set(msg, mf); } return mf; }; return { format: (msg: string, values?: Record) => compile(msg).format(values) as string, }; }); export type Shell_Lib_Icu = RShape; ``` The same shape applies to any locale-aware library — date formatters (`date-fns`, `luxon`), transliterators, Markdown/MDX renderers, locale-aware collators. R-Machine's role is the boundary (`$.locale` auto-passed, kit injection, `mockPlug` for tests), not the format. This generalizes §4.8: there is no architectural commitment to any particular i18n library because there is no integration to commit to. #### Return anything TypeScript can express A resource's shape is whatever the factory returns. For a shell, that is not restricted to strings: ```tsx // shell/features/intl_demo/en.tsx export const r = Shell.define((plugin) => { const { fmt } = plugin; return { // plain string sectionTitle: "Locale-Aware Formatting", // function returning a string caption: (d: Date) => `Today's date: ${fmt.date.long(d)}`, // function returning a JSX fragment description: (amount: number) => ( <>The value {fmt.currency(amount)} is written as {fmt.number(amount)} without currency. ), // React component Items: ({ count }: { count: number }) => ( You have {fmt.plural(count, { one: "# item", other: "# items" })} in your cart. ), }; }); ``` The shape flows end-to-end to the consumer: `Plug("shell/").useR()` returns exactly what the factory returned, type-narrowed. The same freedom applies to gears — an `OuterGear` may surface state cursors next to JSX components next to instances of an external class, whatever the call site needs. #### Implication For a question of the form *"does R-Machine support X?"* — where X is a content type, formatting style, or rendering pattern — the answer is *"X is whatever the factory returns, because a resource is a TypeScript factory."* R-Machine's API surface is intentionally narrow: it provides the wiring (locale, kit, ports, plug), the boundary classification (kinds, dep rules), and the test substitution primitive (`mockPlug`). Everything expressed *inside* a factory is plain TypeScript, and the consumer sees exactly the type the factory returned. #### On architectural lock-in Adopting R-Machine couples a codebase to its consumption API: every consumer reads resources through `Plug` / `ClientPlug` / `ServerPlug`, and resources are declared with its composers. That coupling is real — it is the kind any architecture with a central consumption primitive incurs. What it does *not* reach is the substance: a resource is a plain TypeScript factory returning ordinary values, so the logic and content inside each resource is portable TypeScript. The R-Machine-specific part is the wiring around it — atlas, composer chain, plug calls — which is narrow and mechanical. Consumers depend on a string namespace and a typed shape, both framework-agnostic concepts; migrating would rewrite the wiring, not the business logic. --- ## 5. Cloning resources `clone(fn?)` is the mechanism for **declaring a second resource that reuses the logic of an existing one under a different atlas namespace**. It lives at module / atlas level, not inside a single module. The convention everywhere in R-Machine is **one module = one `export const r`**. `clone` does not break it — it is used in a *separate* resource module that imports the source `r`, calls `clone(...)` on it, and re-exports the result as its own `r`. That second module is then registered in `ResourceAtlas` under its own namespace key, just like any other resource. The matrix returned by every `.define(...)` exposes a small **fluent builder** mirroring the composer side: ``` composer.withPorts(...).withState(...).define(fn) ← create from scratch matrix.withPorts(...).withState(...).clone(fn?) ← derive from existing ``` `withPorts` / `withState` produce intermediate builders whose only terminal is `clone(fn?)`. The optional `fn` is a transform that the system runs **after** the original factory and **before** post-processing, so it receives the resource already resolved (locale, deps, state) and can override only the fields it wants — the rest pass through unchanged. The transform never widens the result shape: extra keys outside `R` are pinned to `never` at the type level (the matrix's `clone` is generic over the inferred return type, and `NoExcess` blocks excess properties at the call site). ```ts // outer/cart.ts ← source resource export const r = OuterGear .withPorts({ checkout: prodCheckout }) .withState({ items: [] as Item[] }) .define((plugin, _) => { const { $ } = plugin; return { /* ... */ }; }); export type Outer_Cart = RShape; ``` ```ts // outer/cart-secondary.ts ← derived resource, own module import { r as base } from "./cart"; export const r = base.clone(); // identical logic, new identity ``` ```ts // resource-atlas.ts type ResourceMap = { "outer/cart": Outer_Cart; "outer/cart-secondary": Outer_Cart; // ← second atlas entry }; ``` Both entries share the same factory and chain, but each carries its **own plug, its own resolved instance, and its own state** (for stateful `OuterGear`). They are independent at runtime. ### 5.1. `clone` vs `mockPlug` | | `clone` | `mockPlug` (§14) | |---|---|---| | Scope | Resource definition (atlas-level) | Plug usage (consumer-level) | | Produces | A second `r` in a second module, registered in `ResourceAtlas` under a new namespace | An ad-hoc plug substitution active in a single test or runtime context | | Lifetime | Production: lives in the deployed atlas | Test / scoped runtime override | | Identity | New plug, new resolved instance | Reuses the original resource's identity, swaps what the consumer sees | If you find yourself writing `export const draft = r.clone(...)` *next to* `export const r = ...` in the same module, you almost certainly want either `mockPlug` (for tests) or a **second resource module** (for production). `clone` is never used to create side-exports inside a resource module. ### 5.2. Builder methods by composer | Composer | Available on the matrix | |---|---| | `InnerGear`, `BaseGear` | `clone(fn?)`, `withPorts(p).clone(fn?)` | | `OuterGear` (stateless) | `clone(fn?)`, `withPorts(p).clone(fn?)` | | `OuterGear` (stateful, declared with `withState`) | `clone(fn?)`, `withPorts(p).clone(fn?)`, `withState(s).clone(fn?)`, `withPorts(p).withState(s).clone(fn?)` (commutative) | | `Shell` (incl. `shell(mono)`) | `clone(fn?)`, `withPorts(p).clone(fn?)` | `withPorts(p)` shallow-merges `p` onto the existing port map: only the keys you list are replaced. `withState(s)` accepts `DeepPartial` and deep-merges onto the original `defaultState`: only the leaves you provide are replaced, everything else is preserved. The `fn` transform is locked to the resource's `R` shape — it can override any subset of `R`'s keys but cannot add new ones (the matrix exposes a future, differently-named method for cases that genuinely need to widen the resource). ### 5.3. Use cases #### 5.3.1. Same logic, multiple atlas slots — `clone()` no-arg Use when N independent instances of the same gear must coexist under distinct namespaces. Examples: a product-comparison page rendering three cards side by side, multiple carts in a multi-tenant UI, or the same gear surfaced both globally and inside a `gear:outer(vertex)` (§12). ```ts // outer/product-card.ts ← source export const r = OuterGear .withState({ productId: "" as string, qty: 1 }) .define((plugin, _) => { const { $ } = plugin; return { /* card logic */ }; }); export type Outer_ProductCard = RShape; ``` ```ts // outer/product-card-a.ts ← slot A import { r as base } from "./product-card"; export const r = base.clone(); // outer/product-card-b.ts, outer/product-card-c.ts — same pattern ``` ```ts // resource-atlas.ts type ResourceMap = { "outer/product-card-a": Outer_ProductCard; "outer/product-card-b": Outer_ProductCard; "outer/product-card-c": Outer_ProductCard; }; ``` Each slot has its own state cursor. Mutating slot A does not touch B or C. The **global + vertex** variant follows the same shape: keep the logic in one module, then declare a no-arg clone in a `vertex/...` module so the same gear can be instantiated locally per vertex frame (§12) without sharing state with the global one. #### 5.3.2. Variant with different external bindings — `withPorts(...).clone()` Use when the same logic must run against a different external boundary — a draft writer instead of the published one, a stub fetcher instead of the CMS. ```ts // outer/post-form.ts ← source export const r = OuterGear .withPorts({ createPost: prodCreatePost }) .withState({ pending: false }) .define((plugin, _) => { const { $ } = plugin; return { /* ... */ }; }); export type Outer_PostForm = RShape; ``` ```ts // outer/post-form-draft.ts ← variant import { r as base } from "./post-form"; export const r = base.withPorts({ createPost: draftCreatePost }).clone(); ``` ```ts // resource-atlas.ts type ResourceMap = { "outer/post-form": Outer_PostForm; "outer/post-form-draft": Outer_PostForm; }; ``` The same shape applies to `Shell` variants (e.g. a `shell/landing-static` derived from `shell/landing` with a stub `fetchHeroCopy`). #### 5.3.3. Variant with different starting state — `withState(...).clone()` Available on stateful `OuterGear` only. Combine with `withPorts` when both need to change — the chain is commutative: ```ts // outer/post-form-debug.ts import { r as base } from "./post-form"; export const r = base .withPorts({ createPost: loggedCreatePost }) .withState({ pending: true }) .clone(); ``` #### 5.3.4. Sibling locale variant — `clone(fn)` for regional overrides The common case is a second locale that is almost identical to the first, with a handful of phrases that differ (US vs UK spelling, dialectal swaps, regulator-mandated wording). The factory runs in the **clone's** locale context, so anything that already depends on `$.locale` is correct in `res` — `fn` only has to override the values that genuinely differ between regions. ```ts // shell/checkout/en-US.tsx ← source variant export const r = Shell.define((plugin) => { const { $ } = plugin; return { cta: "Add to cart", colorLabel: "Color", zipPlaceholder:`ZIP for ${$.locale}`, footer: